Df2e97f18b5802e199d4920552a52d34

What's the best way to save multiple models in one action. Generally this is a case where you use form_for to update a model and fields_for to edit an associated model as well. The trick is to not save one of the models if the other has errors, and to generally handle all of the errors properly. This is how I have done it, but it seems simple, and in some cases there are more than just two models to deal with and I don't want to just keep using more and expressions.

1
2
3
4
5
    if @foo.update_attributes(params[:foo]) and @foo.bar.update_attributes(params[:bar])
      redirect_to foo_url(@parent)
    else
      render :action => "edit"
    end

Refactorings

No refactoring yet !

Be1e3ee645d23c95ba650c21bc885927

Fabien Jakimowicz

July 29, 2008, July 29, 2008 22:21, permalink

No rating. Login to rate!

there is a lot of way to clean this, but without any other information (relation type between @foo and bar, form, ...) i can just propose something using exceptions and transactions.

A transaction will roll back any modifications if there is any failure. In your case, you need to save both instances only if both of them are valid. If one fail, the entire transaction will fail and nothing will be saved

1
2
3
4
5
6
7
def update_foo_and_bar
  # You can also use @foo.class.transaction and @foo.bar.class.transaction if you have multiple database at the same time
  ActiveRecord::Base.transaction {@foo.update_attributes!(params[:foo]) and @foo.bar.update_attributes!(params[:bar])}
  redirect_to foo_url(@parent)
rescue ActiveRecord::RecordNotSaved
  render :action => 'edit'
end
8484df61a1434e91cb088f53cc089f18

Adam

August 11, 2008, August 11, 2008 18:06, permalink

No rating. Login to rate!

Set accessible => true in your model and use a nested fields_for block.

app/models/foo.rb

1
2
3
class Foo < ActiveRecord::Base
  has_one :bar, :accessible => true
end

app/controllers/foos_controller.rb

1
2
3
4
5
6
7
8
9
def update
  @foo = Foo.find(params[:id])

  if @foo.update_attributes(params[:foo])
     redirect_to foo_url(@parent)
  else
     render :action => "edit"
  end
end

app/views/foos/edit.html.erb

1
2
3
4
5
6
7
8
9
<% form_for @foo do |f| %>
	<%= f.text_field :name %>
	
	<% f.fields_for :bar, @foo.bar do |b| %>
		<%= b.text_field :name %>
	<% end %>
	
	<%= submit_tag "Submit" %>
<% end %>

Your refactoring





Format Copy from initial code

or Cancel