Summary: Use the “Auto-generated methods” on the new parent object to build new associated child objects to gain the benefits of less code, encapsulation and cascading saves.
For example, do this:
account = Account.new(:name => "Nicholas") address = account.build_address(:street => "Laurier Avenue") account.save! address.new_record? # => false
Rather than this:
account = Account.new(:name => "Nicholas") address = Address.new(:street => "Laurier Avenue") address.account = account address.save! account.new_record? # => false
Details
With all the automagic and flexibility (i.e. multiple ways to do the same thing) Ruby on Rails provides, it’s easy to get confused with how the framework behaves and what the best practices are. But it is just as easily solved when you have a few rules of thumbs to follow. This is very true for ActiveRecord behaviour, specifically when saving associated records.
There is even a section dedicated to it in the Agile Web Development Book with Rails (Chapter 18, Section 7). The book looks at an existing object at one end of the association, and a new object at the other. But it does not discuss dealing with two new objects.
So let’s look at some examples how associations work. Consider the following models:
class CreateAccounts < ActiveRecord::Migration
def self.up
create_table :accounts do |t|
t.string :name
t.timestamps
end
end
def self.down
drop_table :accounts
end
end
class CreateAddresses < ActiveRecord::Migration
def self.up
create_table :addresses do |t|
t.integer :account_id
t.string :street
t.string :city
t.string :state_province
t.string :zip_postal_code
t.timestamps
end
end
def self.down
drop_table :addresses
end
end
class Account < ActiveRecord::Base
validates_presence_of :name
has_one :address
end
class Address < ActiveRecord::Base
validates_presence_of :street
belongs_to :account
end
Simple stuff. For clarity sake, we will refer to the Account as the parent object, and the Address as the child object. We can consider the Address as the child object as it stores the id of the parent object. In this case the field account_id to identify the Account. So let’s create a an Account with an associated Address.
Example 1 (Recommended)
account = Account.new(:name => "Nicholas") address = account.build_address(:street => "Laurier Avenue") account.save! address.new_record? # => false
When we add a has_one relationship, a number of new methods are defined on the receiver (e.g. Account#build_address). This is true for all relationships, although the methods differ. See the ROR documentation for a complete list of Auto-generated methods.
The key observation here is, when you build a new child object via the parent object using the build method you do not need to explicitly save the child object, as the save cascades. An alternative way of doing this is:
Example 2
account = Account.new(:name => "Nicholas") address = Address.new(:street => "Laurier Avenue") account.address = address account.save! address.new_record? # => false
The first example, is preferable since it’s less code due to encapsulation.
If you assign a parent object to a child object:
Example 3
account = Account.new(:name => "Nicholas") address = Address.new(:street => "Laurier Avenue") address.account = account address.save! account.new_record? # => false
You will need to explicitly save the child object. But don’t save the parent object, because you will end up with this:
account = Account.new(:name => "Nicholas") address = Address.new(:street => "Laurier Avenue") address.account = account account.save! address.new_record? # => true
It’s always best to standardize the way you write code. When possible, when creating new objects that have an association use the following pattern:
parent = Parent.new(...params...) child = parent.build_child(...params...) parent.save! # or parent.save
The benefits are:
- encapsulation, hiding the implementation of how an address is created
- less code
- cascading saves
Although I looked at the has_one/belongs_to association, this also works for other associations such as has_many.
For my next post, I will discuss how validating associations works in this context.