www.flickr.com
|
Monday, May 01, 2006
Rails realities part 12 (Active record, the relationship breaker, create em and leave em)
I recently was trying to activate optimistic locking on my rails model classes but have been receiving optimistic concurrency errors that aren't readily reproduceable. After looking into my code a little further I realized that now might be a good time to explore polymorphic relations to address some ugly design decisions that I'd made last October while learning ruby/rails and just trying to get an application off the ground. Very simply I have an Address class which has need to be involved in "has_one" relations to more than one class. So in order to not pollute my Addresses table with more than one FK I decided to have the other sides of these relationships to hold the FK columns. So my tables look like this:
So to model these relationships active record requires the the side holding the FK column is the "belongs_to" side of the relationship. So I have the following code:
So the fallout here is that I need to actively manage this address information. A save from Loan or LoanRole won't cascade the save operation to the related address classes. And since I was having problems with optimistic concurrency I thought that perhaps cutting down on the number of save operations that I'm doing can only help. I recently upgrated to rails 1.1.2 (not voluntarily as I blogged about before) and I thought now would be a fine time to cleanup this earlier mess and migrate my schema to take advantage of the new polymorphic relations in active record. So my relations can change from belongs_to to the proper has_one by making the following change to my database schema:
What this allows me to do is to have one FK column in addresses and have a relation to more than one class by including a "type" column. There are drawbacks to this approach (like not being able to share object instances between classes, that is I can't have Loan.mailing_address be the same value as LoanRole.address). That aside I thought that this would be a fantastic way to clean up my code and not have to manage the cascading of save operations.
My model objects now become:
OK so with that backdrop (not really necessary but I thought someone out there might appreciate a brief overview of polymorphic relations), I get into the topic of today's post:
I was surprised to find this out but after thinking about it for a minute it makes sense. AR doesn't do any dirty field checking and instead always saves all attributes to the DB. In order to cascade save operations ons collection based relations you'd have to implement a dirty flag. But what's kind of sucky is that you get lulled into believing that AR does manage your relations for you because it does cascade the initial save operation when you build up a hierarchy of related objects. But once that first save is over AR does its best Sam Malone impression and gets the f**k out of dodge for any of your has_many relations. It will continue to cascade save operations to any has_one relations you may have but your has_manies are out of luck.
So for those that are visual like me here's a synopsis:
Where this became an issue in my object model was Loan has_many -> Lenders (LoanRoles) and LoanRole has_one address. In my current code I was always saving things explicitly so when I made the switch to polymorphic relations I assumed a top level save would do all the work for me and after it didn't I spiraled down the rat hole that is now this blog post.
After discussing it further with one of the rails core team members he suggested that adding dirty field checking would make a good plugin for AR and suggested I get to work :-). Unfortunately I don't have the time right now but I can't imagine I'd be the only one that would be happy to see dirty flags and cascading save operations work on all parent -> child relation types as well as from child -> parent if you declare that you'd like that on the relation definition.
addresses
:id
loan_roles
:address_id
loan
:mailing_address_id
So to model these relationships active record requires the the side holding the FK column is the "belongs_to" side of the relationship. So I have the following code:
class LoanRole
belongs_to :address
end
class Loan
belongs_to :mailing_address,
:class_name => "Address",
:foreign_key => 'mailing_address_id'
end
So the fallout here is that I need to actively manage this address information. A save from Loan or LoanRole won't cascade the save operation to the related address classes. And since I was having problems with optimistic concurrency I thought that perhaps cutting down on the number of save operations that I'm doing can only help. I recently upgrated to rails 1.1.2 (not voluntarily as I blogged about before) and I thought now would be a fine time to cleanup this earlier mess and migrate my schema to take advantage of the new polymorphic relations in active record. So my relations can change from belongs_to to the proper has_one by making the following change to my database schema:
addresses
:addressable_type
:addressable_id
What this allows me to do is to have one FK column in addresses and have a relation to more than one class by including a "type" column. There are drawbacks to this approach (like not being able to share object instances between classes, that is I can't have Loan.mailing_address be the same value as LoanRole.address). That aside I thought that this would be a fantastic way to clean up my code and not have to manage the cascading of save operations.
My model objects now become:
class LoanRole
has_one :address, :as => :addressable
end
class Loan
has_one :mailing_address,
:class_name => 'Address',
:as => :addressable
end
OK so with that backdrop (not really necessary but I thought someone out there might appreciate a brief overview of polymorphic relations), I get into the topic of today's post:
Active Record does not cascade save operations in has_many relations after the initial save
I was surprised to find this out but after thinking about it for a minute it makes sense. AR doesn't do any dirty field checking and instead always saves all attributes to the DB. In order to cascade save operations ons collection based relations you'd have to implement a dirty flag. But what's kind of sucky is that you get lulled into believing that AR does manage your relations for you because it does cascade the initial save operation when you build up a hierarchy of related objects. But once that first save is over AR does its best Sam Malone impression and gets the f**k out of dodge for any of your has_many relations. It will continue to cascade save operations to any has_one relations you may have but your has_manies are out of luck.
So for those that are visual like me here's a synopsis:
class Parent
has_many :foos <-- AR will calls save on foo objects on the first parent.save calls but will not call save of foo objects on subsequent save calls on parent
has_one :bar <-- AR will call save all the time
end
Where this became an issue in my object model was Loan has_many -> Lenders (LoanRoles) and LoanRole has_one address. In my current code I was always saving things explicitly so when I made the switch to polymorphic relations I assumed a top level save would do all the work for me and after it didn't I spiraled down the rat hole that is now this blog post.
After discussing it further with one of the rails core team members he suggested that adding dirty field checking would make a good plugin for AR and suggested I get to work :-). Unfortunately I don't have the time right now but I can't imagine I'd be the only one that would be happy to see dirty flags and cascading save operations work on all parent -> child relation types as well as from child -> parent if you declare that you'd like that on the relation definition.
Post a Comment