Using belongs_to in Rails model validations when the parent is unsaved
Can’t get there from here
In one of my Ruby on Rails projects, I have a model validation which is dependent on attributes from a belongs_to parent. Normally, you can refer to a parent model as child.parent and access the parent’s attributes as child.parent.attr. The difficulty arises when the parent object is unsaved. Let’s look at the code:
class Item < ActiveRecord::Base
has_many :quantity_limits
has_many :locations, :through => :quantity_limits
has_many :line_items
has_many :orders, :through => :line_items
end
class Location < ActiveRecord::Base
has_many :orders
has_many :quantity_limits
has_many :items, :through => :quantity_limits
end
class QuantityLimit < ActiveRecord::Base
belongs_to :item
belongs_to :location
#Attribute quantity is the max quantity of an item which may be ordered for a location
end
class Order < ActiveRecord::Base
has_many :line_items, :dependent => :destroy
belongs_to :location
end
class LineItem < ActiveRecord::Base
belongs_to :order
belongs_to :item
def quantity_limit
if self.order
QuantityLimit.find(:first, :conditions => {:location_id => self.order.location_id, :item_id => self.item_id})
else
nil
end
end
def validate
if self.quantity_requested
if self.quantity_limit
limit = self.quantity_limit.quantity
else
limit = 0
end
errors.add(:quantity_requested,"above maximum of " + limit.to_s)if self.quantity_requested > limit
end
end
end
The key element is LineItem#quantity_limit, which retrieves the relevant QuantityLimit object. You might expect self.order.location_id to be valid once the LineItem has been added to the Order#line_items collection (order.line_items << new LineItem). If the parent has been saved (i.e. Order#new_record? is false) then LineItem[:order_id] is updated with Order[:id] and everything is copacetic. However, if the parent is unsaved, the child is added to the has_many collection Order#line_items but the LineItem object is not changed. The underlying reason is the has_many collection Order#line_items is more or less an array and doesn’t need a primary key to describe the relationship. The belongs_to reference, on the other hand, stores only the primary key of the parent and can’t be referenced if the primary key hasn’t been established.
OK, I know the smokers are ready for a cigarette after all that, but bear with me. The upshot of all this is a line for a saved Order is properly validated. But if the Order is unsaved, limit is always 0 because self.order is nil and the relevant QuantityLimit cannot be found. But there is a solution…
Ruby to the Rescue
The Ruby core language includes a module called ObjectSpace which allows direct interaction with the garbage collector. Using this, we’re able to determine which Order object contains our LineItem. Let’s look at the new code:
def quantity_limit
parent_order = self.order
if !parent_order
ObjectSpace.each_object(Order) {|o| parent_order = o if o.line_items.include?(self)}
end
return nil if !parent_order
QuantityLimit.find(:first, :conditions => {:location_id => parent_order.location_id, :item_id => self.item_id})
end
This time we’re still checking the normal primary key based reference first, but if it’s nil we take another approach. We iterate through all the live objects of the Order class to find the one that references our LineItem. We’re assuming that only one Order will contain our LineItem, which will be true unless we’ve re-parented a LineItem. Once we’ve located the correct Order, the validation can proceed as before.
While we’re spending a few extra cycles and lines of code to use this method, I think the maintainability benefits of a DRY solution make it the right choice. Please let me know how it works for you!

June 20th, 2008 at 3:50 am
Heyy…this was definitely a great blog post! ’twas exactly what I needed when I thought I was really creating something rather screwy.
September 1st, 2008 at 5:25 am
Thanks, very useful!
September 26th, 2008 at 3:50 pm
Do you still consider this the best way to do this? I’m in a strikingly similar situation…
September 28th, 2008 at 6:08 pm
I haven’t needed to revisit this, so I really can’t say.
November 1st, 2008 at 6:27 pm
Mack,
Was just googling you and it’s interesting that we are doing the same thing!
How long have you been working with rails?
Do you have any open source projects?
Are still around Indy? Actually I now remember seeing BW3s on your twitter, so I guess so. Have you been to any of the iRug meetings? They’re excellent!
- Peter Boling