Using set_association_target to Avoid Reloading ActiveRecord Associations
Being smart with ActiveRecord means making sure your code is at least passably efficient with the database. Consider the following code:
class Thing < ActiveRecord::Base has_many :photos end class Photo < ActiveRecord::Base belongs_to :thing def public_filename File.join("images",thing.name,filename) end end @thing = Thing.find(:first, :include => :photos) @thing.photos.public_filename #results in an extra query
You might assume that all of of @@thing@’s photos would automatically have their belongs_to association set when they are accessed through
@thing.photos. After all,
@thing is right there, there wouldn’t be any extra cost. Unfortunately that does not happen. I’ve dug through the ActiveRecord association code a bit to understand this issue. I’m thinking a fix for this at the core level might be computationally costly, which would explain this behavior. However I’m not yet familiar enough with the ActiveRecord code to quickly analyze the whole situation, much less create a patch.
set_association_target to the rescue
Fortunately I did discover an undocumented public method that allows you to solve the problem easily—if not prettily—in practice. The method is
association is replaced by the name of your association. It’s actually pretty similar to
association= except that it doesn’t modify anything, it just attaches the object directly to the association, which means that you better know what you are doing when you use it. But since when has Ruby ever assumed you don’t know what you’re doing?
So the fix is:
@thing = Thing.find(:first, :include => :photos) @thing.photos.set_thing_target(@thing) @thing.photos.public_filename #no extra query.
It seems like ActiveRecord ought to be able to figure this out for itself, but for whatever reason, it doesn’t. Sure you can work around the limitation by defining the
public_filename method on
Thing instead of
Photo, but that feels like it negatively affects the whole architecture rather than just uglifying a small snippet. At least that’s my opinion.