I may just be missing something simple, but I am relatively inexperienced so it is likely. I've searched extensively for a solution without success.
I am using the fields_for function to build a nested form using the accepts_nested_attributes_for function. If the submit on the form fails the params are passed to the render of the new template only for the parent model. How do I pass the nested params for the child model so that fields that have been filled out previously remain filled. Note that I am using simple_form and HAML but I assume this shouldn't impact the solution greatly.
My models:
class Account < ActiveRecord::Base
attr_accessible :name
has_many :users, :dependent => :destroy
accepts_nested_attributes_for :users, :reject_if => proc { |a| a[:email].blank? }, :allow_destroy => true
end
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
belongs_to :account
end
My accounts controller:
def new
@account = Account.new
@account.users.build
end
def create
@account = Account.new(params[:account])
if @account.save
flash[:success] = "Welcome."
redirect_to @account
else
@account.users.build
<- I suspect I need something here but unsure what
render :new
end
end
The key part of the accounts/new view:
= simple_form_for @account do |f|
= f.input :name
= f.simple_fields_for :users do |u|
= u.input :email
= u.input :password
= u.input :password_confirmation
= f.button :submit, :value => "Sign up"
My params on a failed save are:
:account {"name"=>"In", "users_attributes"=>{"0"=>{"email"=>"u@e.com", "password"=>"pass", "password_confirmation"=>"pass"}}}
As you can see, the key information, in the users_attributes section, is stored but I can't seem to have the email address default i开发者_如何学编程nto the new form. Account name on the other hand is filled automatically as per Rails standard. I'm not sure if the solution should live in the accounts controller or in the accounts/new view, and have not had any luck with either.
Answers with .erb are, of course, fine.
I'm fairly new to Ruby and Rails so any assistance would be much appreciated.
The problem lies with attr_accessible
, which designates the only attributes allowed for mass assignment.
I feel a bit silly in that I actually stated the problem in a comment last night and failed to notice:
accepts_nested_attributes_for :users will add a users_attributes= writer to the account to update the account's users.
This is true, but with attr_accessible :name
, you've precluded every attribute but name being mass-assigned, users_attributes=
included. So when you build a new account via Account.new(params[:account])
, the users_attributes
passed along in params are thrown away.
If you check the log you might note this warning:
WARNING: Can't mass-assign protected attributes: users_attributes
You can solve your original problem by adding :users_attributes
to the attr_accessible
call in the account class, allowing it to be mass-assigned.
Amazingly, after reading a blog post this evening, and some more trial and error, I worked this out myself.
You need to assign an @user
variable in the 'new' action so that the user params are available for use in the 'create' action. You then need to use both the @account
and @user
variables in the view.
The changes look like this.
Accounts Controller:
def new
@account = Account.new
@user = @account.users.build
end
def create
@account = Account.new(params[:account])
@user = @account.users.build(params[:account][:user]
if @account.save
flash[:success] = "Welcome."
redirect_to @account
else
render :new
end
end
The accounts/new view changes to:
= simple_form_for @account do |f|
= f.input :name
= f.simple_fields_for [@account, @user] do |u|
= u.input :email
= u.input :password
= u.input :password_confirmation
= f.button :submit, :value => "Sign up"
In this case the params remain nested but have the user
component explicitly defined:
:account {"name"=>"In", "user"=>{"email"=>"user@example.com", "password"=>"pass", "password_confirmation"=>"pass"}}
It has the additional side effect of removing the @account.users.build
from within the else
path as @numbers1311407 suggested
I am not certain whether their are other implications of this solution, I will need to work through it in the next few days, but for now I get the information I want defaulted into the view in the case of a failed create
action.
@Beerlington and @numbers1311407 I appreciate the help in guiding me to the solution.
精彩评论