strapyourself.in and flouri.sh
Extending has_many associations correctly
How to build custom methods on associations that aren't slow
The "has_many do" syntax has been widely adopted in rails, but I often see it going wrong. Consider the following association extension:
has_many :versions do def primary find(:first, :conditions => {:primary => true}) end end
What's wrong with this code is that the finder has to execute a database request every time it's invoked. True, rails has a query cache, but it results in more database requests then one should need. We can make it better by caching the result in an instance variable attached to the association:
has_many :versions do def primary @primary ||= find(:first, :conditions => {:primary => true}) end end
But what if you already have the association loaded? Inside the block, you can access various methods of the AssociationCollection and AssociationProxy classes. Notable methods are the following:
- proxy_owner - the module that contains the association
- proxy_reflection - the Reflection object that contains the association options (FK, :dependent, etc)
- proxy_target - the cached association data, if the association has been loaded
- loaded? - returns true if the association has been loaded
- reset - delete the cached association data and forget it has been loaded
Using these methods, we can rewrite our method to be the most efficient possible, by making use of loaded association data when present:
has_many :versions do def primary if loaded? @primary ||= proxy_target.detect { |ver| ver.primary? } else @primary ||= find(:first, :conditions => {:primary => true}) end end end
Now our method is guarenteed to make only 1 database call, no matter how many times invoked. It also will make zero database calls if the entire association has already been loaded. The only downside is that "how to determine if primary" logic has to be written once in rails_sql and once in pure ruby.
Sorry, comments are closed for this article.