The maze book for programmers!
mazesforprogrammers.com

Algorithms, circle mazes, hex grids, masking, weaving, braiding, 3D and 4D grids, spheres, and more!

DRM-Free Ebook

The Buckblog

assorted ramblings by Jamis Buck

Moving associated creations to the model

11 January 2007 — 2-minute read

Have you ever done something like this in a controller?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# The corresponding view looks something like this:
#
#    Name: <input type="text" name="person[name]" /><br />
#    Email: <input type="text" name="email_address[address]" /><br />
#    Phone: <input type="text" name="phone_number[number]" /><br />
#
def create
  Person.transaction do
    @person = current_account.people.create(params[:person])
    @person.create_email_address params[:email_address]
    @person.create_phone_number params[:phone_number]
  end
  redirect_to person_url(@person)
end

Harkening back to the Skinny Controller, Fat Model idea, I’ve lately been converting the above, into the following:

1
2
3
4
5
6
7
8
9
10
11

# The corresponding view looks something like this:
#
#    Name: <input type="text" name="person[name]" /><br />
#    Email: <input type="text" name="person[data][email_address][address]" /><br />
#    Phone: <input type="text" name="person[data][phone_number][number]" /><br />
#
def create
  @person = current_account.people.create(params[:person])
  redirect_to person_url(@person)
end

This works by moving all of the creation logic to the models. Consider the Person model for the above:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

class Person < ActiveRecord::Base
  has_one :email_address
  has_one :phone_number

  attr_writer :data
  after_create :update_data

  private

    def update_data
      return if @data.nil?
      create_email_address(@data[:email_address]) if @data[:email_address]
      create_phone_number(@data[:phone_number]) if @data[:phone_number]
      @data = nil
    end
end

So, when the params[:person] hash gets used to create the new Person, the data subhash, if it exists, gets assigned to the data writer on the Person model. Then, after the create happens, the update_data callback is invoked, which examines that @data variable to see what needs to be done.

It keeps your controllers slim, and puts all the creation logic in one place, so if you ever want to create the email address and phone number from a different action, it’s all ready for you!

With a little more effort, this can work for updates and deletes, too, but I’ll leave that as an exercise for the reader. ;)

Reader Comments

Ah, great example. Suits my needs perfectly. Gonna implement it tomorrow ;]

Awesome! Thanks Jamis.

So does that function the same as the transaction? If the after_create fails does the whole save fail?

Matt, ActiveRecord wraps every #save or #create call in a transaction, if one is not already active, so yah, you get the transaction for free this way, too. :)

How do you keep users from updating fields you they shouldn’t be allowed to change like foreign keys or record versions or audit fields (created/updated_on)?

Eric, in practice, I sanitize the hash before passing it to the create method. I didn’t demonstrate that here, simply to keep the example clearer.

I am really enjoying your “tip a day”-series. It’s a great inspiration and the choice of topics is spot-on. Keep up the great work!

Maybe it’s the insane purist in me getting out but, with this approach it really looks like you’re teaching your models a bit too much about the view realm. In the original version of the controller, it was very clear how the values were being set. Now it’s not so clear if you’re just running across this code.

Eric: You can also use attr_protected to guard against a user setting user_id or somesuch.

Chris, thanks for pointing that out. I always forget about attr_protected.

Rich, I disagree. The Person model above still has zero knowledge of the view in it. All I’ve done is made it so that if a particular bit of data is given when creating a person, the person record knows how to create it’s associated records simultaneously. It’s a data issue, not a view issue, and belongs (IMO) in the model.

This is particularly useful with has_many associations, like where a person might have multiple email_addresses, because you can manage the deletion/update/creation transactionally with the creation/update of the associated person.

I haven’t tried this yet, but how will validations work in this case? Say the @data[:email_address] contains an invalid email address that will fail a validation inside of the EmailAddress model. The view may have to go through some hoops to get access to this failed validation.

That looks like code from Sunrise :) I love the syntax highlighting, is that achieved with the Syntax gem?

Jamis,

I’ve thought about doing something like this in the past for Substruct (and other code), but decided against it.

I didn’t like the idea of models creating instances of other models from within. Seemed “dirty” to me.

My solution was to create a module which contained all of the logic for CRUD actions that needed transactions. I just include that module from my controller, and use the methods declared within.

What’s the general thought on creating AR objects from within AR objects? Am I over complicating the problem?

Curtis, validations definitely make it tricker, though not impossible. You’d need to use build instead of create, and set up a custom validation callback in which to test whether the associated objects are all valid. If not, you would then need to set up a way to report that back to the client. Not trivial, but not impossible, either. Fortunately, I’ve not had to deal with that particular scenario yet. :)

Ryan, let’s just say it is inspired by Sunrise. :) The actual code in Sunrise is significantly different, and quite a bit more robust. The syntax highlighting is achieved via a Mephisto plugin, which uses Coderay.

Seth, I think having model objects create other model objects is far from dirty, and in fact is one of the reasons for having models. Models are what you use to talk to the database, and creating records is one of the fundamental things you do when you talk to databases.

Validation is definitely one reason I went with my method of modularizing the code for order creation…

Maybe for your next tip of the day you could provide an insight as to just how you’d deal with such a situation :)

As I know, that is not need persondata[address], just name your field as personemail_address and associated object will be created by default

same for has_many <%= text_field_tag(“web_links[][name]”) %> <%= text_field_tag(“web_links[][url]”) %>

I found that it not works only for composed_of, if I want to allow component init itself in this case i need @event = Event.new(params[:event]) @event.date = @date = DatePattern.new(params[:date]) but thanks for that article, I will try to change that

Very intresting ! Thank you Jamis

Is there any particular reason in Rails why your Active Record classes have to be accessed directly from the controller?

It would seem to me that there is a layer of logic between the controller and the database representation.

What concerns me is that you have created a data structure container within a class. The @data hash is a classic old fashioned Struct (or Record if you remember pascal), that is only there because your controller is collecting the params as a hash.

The data hash and the break out procedure look like they ought to be an object/class to me and really shouldn’t belong to Person. It is the transformation linkage between the screen/controller representation and the Person representation.

Can’t it be decoupled from Person?

Neil, sounds like a whole lot of complexity for very little benefit, if you ask me. The hash going in is simply information about how to construct the object. If you feel strongly about it, you could have a separate object that is solely responsible for building Person instances and their associations, but then you have to add additional code to support the same elegance as “account.people.create(...)”. My recommendation: if you really need that extra layer, go for it. If you don’t, avoid it, because more code equals higher development and maintenance cost.

Great tip Jamis … but I wasn’t clear on where (or what) sets @data? Is that in the ActiveRecord’s create method or the Person’s new method?

Bill, when the form gets submitted, the params hash that comes in looks something like this:

  { :person => {
      :name => "...",
      :data => {
        :email_address => {
          :address => "..." 
        },
        :phone_number => {
          :number => "..." 
        }
      }
    }
  }

When we pass params[:person] to the create method, ActiveRecord automatically calls the name= and data= methods on Person to set those attributes, Since we defined attr_writer :data on Person, that automatically sets the @data instance variable.

It’s a bit of magic, but once you get the idea that AR uses the setter methods to assign incoming attributes, you can do a lot of cool tricks with attr_writer.

Thanks for the clarification, Jamis … it was the “view” part that I was missing … I see it now in the code snippet. By the way, do you normally use the ”# The corresponding view …” comments in your controllers as you do here? That appears to be quite an excellent practice.

Bill, I don’t normally do that, since it requires a lot of work and attention to make sure it stays in sync. I only did it here to demonstrate the relationship between what the action expects and what the view sends.

Funny, I had just started using that pattern a little while ago because it makes creating associated objects so easy, it’s criminal. Nice post!

That is good technique, but standard text_field :object, :method f.text_field :method Don’t allows create such nested keys

Sergey, as of last night, edge rails (NOT 1.2) works for that. You just do form_for(some_object), and then use the yielded form builder to do “f.fields_for(:subobject, some_object.subobject)”, and the fields will be named correctly (e.g. “some_objectsubobject”, etc.).

Thanks, I’m seen that changeset http://dev.rubyonrails.org/changeset/5965

I’m added following http://pastie.caboo.se/34206 may be that can usefull for someone

If a person should always be created along with their address and phone number, I’d be more inclined to make create private, and add a new factory-type method that creates the person, address and phone number from its args. That eliminates the need for stuffing temporary data into the “data” instance variable.

Alternatively, if AR lets you override create, I’d do that.

Nice Post Jamis.

I’ve done something similar, and did need to validate the created objects. In fact I needed the save to return false (rather that raise an exception) and have some errors (for ActiveResource – you’ll get a nice errors xml rather than 500 Server Error).

The first step is to validate associated objects, as you point out.

Then, you need to make sure you use save! to create your objects (to raise invalid exceptions)

The next step is to catch those exceptions and return false

  1. catch any problems with the after_create def save(args) super(args) rescue ActiveRecord::RecordInvalid => invalid false end

If you want to transmit those errors, you’d just iterate through invalid.record.errors and add them to the main object.

Jamis or Sergey,

I am having trouble with the nested form builders mentioned in comment 25.

Sergey, as of last night, edge rails (NOT 1.2) works for that. You just do form_for(some_object), and then use the yielded form builder to do “f.fields_for(:subobject, some_object.subobject)”, and the fields will be named correctly (e.g. “some_objectsubobject”, etc.).

Here is a pastie of what I have tried. http://pastie.caboo.se/37394 I get an nil object error where it is trying to do nil.merge.

watkyn, are you sure you’re using edge rails? Sounds like maybe not, judging from the error.

Jamis, I guess I was thinking I would not need to use edge rails this early after the release of 1.2.1 :) Thanks.