Help needed understanding "fields_for" and resultant controller code

Hi,

I’m trying to understand some code in a book I’m reading and was hoping
someone could tell me if I have understood it correctly.

There are two models, an employee model and a department model. A
department has_many :employees and an employee belongs_to :department.

The form to edit an existing employee (form_for @employee) also
references the department model using “fields_for”:

<% fields_for( @employee.department) do |department_f| %>

<%= department_f.label :name,"Department name" %>
<%= department_f.text_field :name %>

<% end %>

The form is submitted to the update action of ‘employees_controller.rb’,
where there is the following code:

if @employee.update_attributes(params[:employee]) &&
@employee.department.update_attributes(params[:department])
… employee successfully updated …
end

My question is about the line
@employee.department.update_attributes(params[:department])”

Is it correct to say that this code updates the attributes (in this case
“name”) of the Department object that is linked (via the “has_many” and
“belongs_to” relationship) to the Employee object currently being
edited?

Thanks very much in advance

On Dec 29, 2010, at 11:33 AM, Jim B. wrote:

<% fields_for( @employee.department) do |department_f| %>
@employee.department.update_attributes(params[:department])
… employee successfully updated …
end

My question is about the line
@employee.department.update_attributes(params[:department])”

Is it correct to say that this code updates the attributes (in this case
“name”) of the Department object that is linked (via the “has_many” and
“belongs_to” relationship) to the Employee object currently being
edited?

Yes. And it only does this if the update to @employee was successful
first.

-philip

Thanks for the answer Philip. It is good to get confirmation
that I have understood things correctly.

Yes. And it only does this if the update to @employee was successful
first.

Is the order particularly significant in this case (i.e. that it
attempts to update @employee first)?

No technical reason (as this is an update, not a create), but logically
it would make sense that you’d want to update them in that order.

Obviously it would be bad to update one record and not the other, but
won’t rails throw an error and re-render the ‘edit’ view if validation
for either @employee or @employee.department fails?

Nope. update_attributes simply returns true/false depending on whether
or not it succeeded. There are ways to make it raise an error, but your
code isn’t doing that.

-philip

Thanks for the answer Philip. It is good to get confirmation
that I have understood things correctly.

Yes. And it only does this if the update to @employee was successful
first.

Is the order particularly significant in this case (i.e. that it
attempts to update @employee first)?

Obviously it would be bad to update one record and not the other, but
won’t rails throw an error and re-render the ‘edit’ view if validation
for either @employee or @employee.department fails?

No technical reason (as this is an update, not a create), but logically
it would make sense that you’d want to update them in that order.

Cool. That was exactly the conclusion I’d drawn.

won’t rails throw an error and re-render the ‘edit’ view if validation
for either @employee or @employee.department fails?

Nope. update_attributes simply returns true/false depending on whether
or not it succeeded. There are ways to make it raise an error, but your
code isn’t doing that.

Yeah, sorry, I guess I didn’t include enough code with my original
question.
I’m (the book is) using the scaffold generator to create both ‘employee’
and ‘department’ resources. The complete update method attempts to
update the attributes of @employee and @employee.department. If this
doesn’t work (as validation has failed, for example) it re-renders the
action ‘edit’.

def update
@employee = Employee.find(params[:id])
respond_to do |format|
if @employee.update_attributes(params[:employee]) &&
@employee.department.update_attributes(params[:department])
format.html { redirect_to(@employee, :notice => ‘Employee was
successfully updated.’) }
format.xml { head :ok }
else
@departments = Department.find(:all)
format.html { render :action => “edit” }
format.xml { render :xml => @employee.errors, :status =>
:unprocessable_entity }
end
end
end

Thanks very much for your help.

On 29/12/10 21:11, Jim B. wrote:

for either @employee or @employee.department fails?

You should use

accepts_nested_attributes_for :department

in your employee model.

than you can just do:

<% form_for @employee do |form| %>

<% form.fields_for @employee.department do|department_fields| %>


<%= department_fields.label :name,“Department name” %>

<%= department_fields.text_field :name %>


<% end %>

<% end %>

and with:

if @employee.update_attributes(params[:employee])
do something
else
render :edit
end

And I hope you will get, what you want to get.


best regards
Bente P.

Hi Bente,
Thanks for your reply.

You should use
accepts_nested_attributes_for :department
in your employee model.

The code you provided needed a minor tweak:

<% form.fields_for @employee.department do|department_fields| %>
was throwing an ActiveRecord::AssociationTypeMismatch -
Department(#77492292) expected, got HashWithIndifferentAccess(#37082688)

After a bit of googling I changed it to:
<% form.fields_for :department do|department_fields| %>

and everything worked fine.

Thank you once again.

P.S. In case it helps anyone else, this is described very well here:

On Dec 29, 2010, at 12:57 PM, Jim B. wrote:

code isn’t doing that.
@employee = Employee.find(params[:id])
:unprocessable_entity }
end
end
end

Still… the code above is not “throwing an error”. It’s just
processing an if/else flow.

Look at update_attributes! to see what I’m talking about.

I only bring it up because Rails/Ruby can throw errors and raise
exceptions (which are themselves different) so it can get confusing if
you play loose with the terminology :slight_smile: