posts by tag

forms (2)
Updating multi-model forms in Rails

The other day I received an email about the post concerning multi-model forms in Rails. This person had a similar situation, but was having difficulty saving the records on an update. I didn’t make that part very clear, so I figured I’d complete the puzzle.

If you recall, there was a check in the set_model_attributes method that looks for an id (attributes[:id].blank?) so it’d know whether it was supposed to update or create. If there’s an id present, it must be an update.

Based on these two lines,

_model = send(model).detect {|m| m.id == attributes[:id].to_i}
_model.attributes = attributes

there’s a misconception that the model’s attributes are getting saved. Setting the attributes like this does not save them, but it’s easy to do. I’m using an after_update callback which looks like this:

class Course < ActiveRecord::Base

  has_many :holes, :dependent => :destroy
  has_many :tees,  :dependent => :destroy  

  after_update { |c| c.save_associated :holes, :tees }

  # ...

end

And the save_associated method looks like this:

def save_associated(*models)
  models.each do |model|
    send(model).each do |attributes|
      # handle validations before this point
      # and pass false to the save method to
      # ensure that the attributes get saved
      attributes.save(false)
    end
  end
end

If you’re only dealing with one association you can eliminate the parameters and the nested loop, as well as provide a more meaningful method name for the callback (such as save_holes). All in all, this seems to work pretty good.

Chris mentioned conductors in an earlier comment, which also seems like a viable option.

Handling multi-model forms in Rails

Sometimes (a lot of times, actually) I need to post a form that deals with several different models. For instance, in Golf Trac, fields_for "course[course_holes][]" passes an array of hole attributes that relate back to a course, where a course has_many holes. A course also has_many tees, so there’s a fields_for "course[course_tees][]", as well. This post isn’t about how to setup the forms, but more of what to do in the model.

Of course, I’m sure there are a lot of different ways to do this, but here’s my approach:

class Course < ActiveRecord::Base

  has_many :holes, :dependent => :destroy
  has_many :tees,  :dependent => :destroy

  def course_holes=(course_holes)
    set_model_attributes(:holes, course_holes)     
  end

  def course_tees=(course_tees)
    set_model_attributes(:tees, course_tees)
  end

  private

  def set_model_attributes(model, model_attributes)
    model_attributes.each_with_index do |attributes, index|
      if attributes[:id].blank?
        send(model).build(attributes)
      else
        _model = send(model).detect {|m| m.id == attributes[:id].to_i}
        _model.attributes = attributes
      end
    end
  end

end

I’m going to need the set_model_attributes method in a few other places, so it’ll eventually end up in a module.

2008 by Ryan Heath | Get In Touch

flickr

DesolateInfinityLooking upDazedBlurred