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.

Here's a snippet of code from the form to edit an existing employee:

<% fields_for( @employee.department) do |department_f| %>
  <p>
    <%= department_f.label :name,"Department name" %><br />
    <%= department_f.text_field :name %>
  </p>
<% 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?

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)?

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?

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

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 fields %>
<p>
<%= department_fields.label :name,"Department name" %><br />
<%= department_fields.text_field :name %>
</p>
<% 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.

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.

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:
http://guides.rubyonrails.org/2_3_release_notes.html#nested-object-forms

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.

http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_attributes!

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: