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.








Two people have asked me about
build_eyeball. You get this for free with thehas_one:eyeball declaration. You also get create_eyeball. For ahas_many:eyeballs declaration, you get eyeballs.build and eyeballs.create (and so much more). Know your proxy objects!! Proxy = Power!!remove
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.
remove
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.
remove
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?
remove
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.
remove
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?
remove
Great explanation! Thanks man, save my life today! ;-)
remove
But, how do you edit this record with the same form??
remove
As far as I can tell, your use of a method like
association_attributesbreaksafter_create.. Wrestled with this one for a bit and found an (obvious, once you see it) alternative:That should fix it. At least it does for me.
remove
should this be ||= instead of || ?
remove
should this be ||= instead of || ?
remove
build eyeball has side-effects, so same thing
remove
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.
remove
JFC: good point: def eyeballwithinitialization eyeballwithoutinitialization end aliasmethodchain :eyeball, :initialization
remove
JFC: good point:
remove
JFC: good point:
remove
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.
remove
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:
<code>class Eyeball def color self[:color] || 'red' end end </code>Also, these days, the way I would solve this problem is without using fields_for:
<code>class Cyclops delegate :color=, :color, :to => :eyeball def eyeball_with_initialization eyeball_without_initialization || build_eyeball end alias_method_chain :eyeball, :initialization end </code>Nothing wrong with fields_for, but recently I've been following a design principal that the view should only interact with one object.
remove
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.
remove
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.
remove