Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Creating Multiple Models in One Action

One of the issues in my previous post The Controller Action that sparked some interest is the handling of the creation of multiple Models in one Action. In this post I shall elaborate on this problem in some detail, first considering the cases where in constructing the Dependent Object no data are needed from the querystring/params, and secondly where such data are necessary (and where we have a complicated nested Form). Let’s head right in to an example.

With No Extra Params to Worry About

Your site has Groups and Memberships. Our Business Rule is: when a user creates a Group, she should also be a Member of that Group. Following the Controller Formula, we know the solution in advance:

class GroupsController < ActionController::Base
  def create
    group = logged_in_user.groups.build(params[:group])
    raise SecurityTransgressionError.new unless logged_in_user.can_create?(group)
    if group.save
      ...
     end
  end
end

This leaves unresolved where to put our Business Rule…

Let’s just put it in the Model as a before_create:

class Group < ActiveRecord::Base
  belongs_to :creator, :class_name => 'User'
  has_many :memberships
  has_many :members, :through => :memberships

  before_create :create_first_member

  private
  def create_first_member
    memberships.build(:member => creator)
  end
end

Building the Membership as a before_create relies on the fact that objects built in a Proxy will be cascaded (i.e., saved) on creation.

Some Extra Params to Worry About

A more complicated example is where we have a Form that prompts the User for data for the creation of two Models, one Dependent upon the other. Let’s use the example where our Model is a Cyclops and the Dependent Model is an Eyeball. First we need a new Action:

def new
  @cyclops = Cyclops.new
end

Pretty Skinny, eh? Very Formulaic too. The corresponding View will look something like this:

<% form_for :cyclops do |c| %>
  ...
  <% fields_for 'cyclops[eyeball_attributes]', @cyclops.eyeball do |e| %>
    ...
  <% end %>
<% end %>

When the User submits this form, params come into our create Action looking like this:

{
  'cyclops' => {
    'name' => 'Polyphemus',
    'eyeball_attributes' => {'color' => 'grey'}
  }
}

So what should the create Action look like? Well, we don’t have to think about it, because we’re following the Controller Formula:

def create
  cyclops = Cyclops.new(params[:cyclops])
  ...
  if cyclops.save
    ...
  end
end

Two things now need to be implemented in the model. First, @cyclops.eyeball should not be nil even if the Cyclops is brand new. This is because we assume an Eyeball exists when we draw the new Form. A simple way of accomplishing this is the override the getter for eyeball so that it will build an eyeball if none exists:

class Cyclops < ...
  has_one :eyeball

  def eyeball
     @eyeball || build_eyeball
  end
end

This still leaves unresolved how to deal with setting the Dependent Model, the Eyeball. Given the way we drew the form above, we need merely implement eyeball_attributes=:

class Cyclops < ...
  ...
  def eyeball_attributes=(attrs)
      eyeball.attributes = attrs
  end
end

The eyeball_attribues= method will get called automatically when the create action passes in params[:cyclops] to the Cyclops initializer. (I wish we didn’t have to call this Attribute eyeball_attributes, but calling it just eyeball would require too much fancy footwork for my taste)

At this point, the only issue outstanding is how to deal with Validation. An invalid Dependent Model (which cannot be saved) will not make the Parent Model invalid by default when has_one is used (but it will validate by default when using has_many–there’s your Principle of Least Surprise for ya!). So it’s easy to imagine a scenario where the User inputs bad data for the Eyeball, good data for the Cyclops, and therefore Rails would save the Cyclops but not the Eyeball, and we’d have a sightless Cyclops with nary an error in sight. The only logical thing to do is add the following Business Rule: a Cyclops is invalid on creation if its Eyeball is invalid. This is simple enough:

class Cyclops < ...
  validates_associated :eyeball, :on => :create
end

I love Rails.

Comments
  1. Nick Kallen says:

    Two people have asked me about `build_eyeball`. You get this for free with the `has_one` :eyeball declaration. You also get create_eyeball. For a `has_many` :eyeballs declaration, you get eyeballs.build and eyeballs.create (and so much more). Know your proxy objects!! Proxy = Power!!

  2. Jon Walker says:

    Nick,
    Thanks for the two great articles. I also heard the “Rails Way” guys say something similar at railsconf. One thing I don’t get is why this preferred method of creating dependent objects in Rails goes against what I learned in good old OO design. What if I want to use Cyclops in the future without an Eyeball?

    It seems like better design to not create the Eyeball in Cyclops. I can see where this adds one more line in every place where you need to create a Cyclops and an Eyeball but even though this is not DRY, it has some serious advantages in my view:

    1) Cyclops is not tied to Eyeball so I can use Cyclops independently of Eyeball in the future. With the design above if I ever did need to decouple the two to reuse Cyclops differently I would have to change all of the lines where I create a Cyclops (an alternative is to have some method like buildcyclopswith_eyeball which has a non OO smell to me).

    2) Creation of an eyeball is unlikely to change so it doesn’t matter that I have multiple lines in multiple places for object creation. It is probably just as likely that I will want to use Cyclops decoupled from Eyeball as it is that I would change the way Eyeballs are created and have to update my non-DRY code.

    3) In terms of readability creating the dependent object in the controller is much more obvious. No hidden side effects!

    Just curious on your thoughts on this. Also I have to admit that I am not a highly experienced Rails programmer so I may be missing some finer points in my argument.

  3. Nick Kallen says:

    Jon — thanks for the great comment. In response I can only say that it all depends upon whether in your domain it makes sense for a Cyclops to not have an Eyeball. I’ve tried to construct this example such that it doesn’t make sense–a Cyclops, by definition has one Eyeball. It follows from this that you cannot create a Cyclops without creating its Eyeball; and similarly a Cyclops is not Valid if it does not have one Eyeball.

    But, again, it depends on your domain. The design of your Forms can tell you something about the Domain. Can you create a Book without at least one Page? Maybe… Or is it by creating a Page that you create a Book? … I think a lot depends on what your UI engineer ends up deciding. But it also depends on how you as the Architect think about the world. I like to think that half of my job is Ontology: I think about Being of Things. Decide what is the Essence of the Thing and that can tell you whose responsibility is what. OTOH, there are concrete engineering concerns about what can be concisely and maintainably implemented in code. These last two articles have focused on those: What can be expressed tersely in Rails, and what is idiomatic such that other Rails programmers can follow your code easily.

    Anyway, back to Reality…. If you are typically creating Cyclopses independently of Eyeballs, and it’s important that in some cases the Eyeball of a Cyclops be nil, my proposed implementation is just wrong. But, I suspect, you’re not likely to have a Form of the kind presupposed in this example.

    So I would say on balance that your points #1 and #2 I agree with, depending on Context. With #3 however, I take issue. One man’s side-effect is another man’s encapsulation. Again, it depends on your Domain, but if if you typically don’t interact directly with Eyeballs it is absolutely the right thing to do to shield them with the Cyclops.

  4. Tammer Saleh says:

    Great article.

    I was wondering about the SecurityTransgressionError exception that you had raised… Does that get caught by anything in order to redirect the user and display a message?

  5. Nick Kallen says:

    Tammer — Next week I’m going to write an article about how I typically handle Access Control. As indicated in the post, I like to Raise exceptions (I’ll provide more motivation for this technique later). I usually catch the exception in ApplicationController… There’s a special method called rescue_action. I dispatch on the type of exception and render a special error message.

  6. Tim Case says:

    Another nice post Nick and I find there is still one weird issue that irks me about validates associated. It does me no good to send an error message to the user that says “Eyeball is invalid”, imo validates associated is useless as it’s written and I’ve thought about hacking it to roll the child’s error messages into the parent. How did you handle child error messages?

  7. Davis says:

    Great explanation!
    Thanks man, save my life today! ;-)

  8. Davis says:

    But, how do you edit this record with the same form??

  9. As far as I can tell, your use of a method like association_attributes breaks after_create.. Wrestled with this one for a bit and found an (obvious, once you see it) alternative:

    def association_attributes(attr)
      assocation.update_attributes(attr)
    end

    That should fix it. At least it does for me.

  10. should this be ||= instead of || ?

    def eyeball
      @eyeball ||= build_eyeball
    end
    
  11. should this be ||= instead of || ?

    def eyeball
      @eyeball ||= build_eyeball
    end
    
  12. Nick Kallen says:

    build eyeball has side-effects, so same thing

  13. I tried something like this yesterday and there appears to be a small problem. When you reload the cyclop and try to access its eyeball, you instead get a new eyeball.

  14. nick says:

    JFC: good point:
    def eyeballwithinitialization
    eyeballwithoutinitialization
    end
    aliasmethodchain :eyeball, :initialization

  15. nick says:

    JFC: good point:

    def eyeball_with_initialization
      eyeball_without_initialization
    end
    alias_method_chain :eyeball, :initialization
    
  16. nick says:

    JFC: good point:

    def eyeball_with_initialization
      eyeball_without_initialization
    end
    alias_method_chain :eyeball, :initialization
    
  17. Tom says:

    If you’re going to create an eyeball_attributes=() method to assign, which you really are only going to use during the create action, instead of doing funky stuff for the eyeball() method, you can also just define an eyeball_attributes() method instead, and use this fields in the fields_for call. This way, you’re guaranteed not to trample on things that are happening under the hood.

    Also, instead of using eyeball.attributes() or eyeball.update_attribute(), I use build_eyeball(attr) to keep all the validation functionality.

  18. nick says:

    Tom —

    Interesting suggestions. I would probably not use your technique, because having an eyeball built allows you to prepopulate the form with default values a la:

    class Eyeball
      def color
        self[:color] || 'red'
      end
    end
    

    Also, these days, the way I would solve this problem is without using fields_for:

    class Cyclops
      delegate :color=, :color, :to => :eyeball
    
      def eyeball_with_initialization
        eyeball_without_initialization || build_eyeball
      end
      alias_method_chain :eyeball, :initialization
    end
    

    Nothing wrong with fields_for, but recently I’ve been following a design principal that the view should only interact with one object.

  19. tom says:

    What if you have a form where you submit multiple cyclops on one page?
    Anyone coming across this scenario, not using AJAX.
    All the cyclops are added at the same time.

    cyclops.each do |cyclop|
    fields_for ‘cyclop[]’
    fields_for ‘cyclop[][eyeball_attributes][]’
    end

    This confuses the param builder in the action.
    I get cyclop=>[]
    but eyeball attributes aren’t populated properly.

  20. nick says:

    tom — easiest solution is to make a bulk_cyclops object, have the controller interact with that. The bulk cyclops should act just like an active record, etc.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *