There has been a great deal of debate on how to extend Rails Associations.
Ryan
discusses the "block" method:
class Organization < ActiveRecord::Base
has_many :people do
def find_active
find(:all, :conditions => ["active = ?", true])
end
end
end
This is identical to the "extend" method; in fact Rails converts a "block" into an "extend":
module FindActiveExtension
def find_active
find(:all, :conditions => ["active = ?", true])
end
end
class Organization < ActiveRecord::Base
has_many :people, :extend => FindActiveExtension
end
The commenters then debate the merits of the "class method" method:
class Person < ActiveRecord::Base
def self.find_active
find(:all, :conditions => ["active = ?", true])
end
end
class Organization < ActiveRecord::Base
has_many :people
end
In all three cases (really two), you can call something like:
organization.people.find_active
There are differences, though!
I ran some experiments to find out how each works, and found that unless
you have some particular reason not to, you should always use the "block"
or "extend" version, for two reasons:
- The "class method" version does not allow manual caching.
class Person < ActiveRecord::Base
def self.find_active
@active ||= find(:all, :conditions => ["active = ?", true])
end
end
Will break, as Adam T. mentioned in the comments.
-
The association version is slightly faster then the class version.
I created the same has_many relationship to two pairs of classes
(Person has_many Things; User has_many Widgets).
I then added 6 sub-association selectors (red, green, blue,
small, medium, large) in different ways:
Thing had class methods; User had the same methods embedded in
the has_many association. I then ran benchmarks on 100 users/people
and 5000 things/widgets (with identical respective associations).
The x1 times are for finding all of 6 sub-association lookups on
every Person (Class) and User (Association). The x2 times are
for finding and reloading all 6 (that is, to gain the benefit of
any automatic caching done by Rails or the database).
In absolute times:
user system total real
Class (x1): 4.650000 0.680000 5.330000 ( 11.320903)
Association (x1): 4.530000 0.670000 5.200000 ( 10.861711)
Class (x2): 9.240000 1.360000 10.600000 ( 21.934539)
Association (x2): 9.000000 1.330000 10.330000 ( 21.066327)
Dividing the bottom two rows in half:
user system total real
Class (x1): 4.650000 0.680000 5.330000 ( 11.320903)
Association (x1): 4.530000 0.670000 5.200000 ( 10.861711)
Class (x2)/2: 4.620000 0.680000 5.300000 ( 10.967270)
Association (x2)/2: 4.500000 0.665000 5.165000 ( 10.533164)
Dividing all rows by the number of queries performed (per/query time):
user system total real
Class (x1)/600: 0.007750 0.001133 0.008883 ( 0.018868)
Association (x1)/600: 0.007550 0.001117 0.008667 ( 0.018103)
Class (x2)/1200: 0.007700 0.001133 0.008833 ( 0.018279)
Association (x2)/1200: 0.007500 0.001108 0.008608 ( 0.017555)
The association version is a little faster, and gains more from the
automatic caching Rails does.
You can check out the experiment at https://svn.u-presence.com/svn/experiments/association_vs_class/
(username guest, no password).