Activerecord Plurals - A shot at the rails source code
In a nutshell:
gem install activerecord_plurals
Usage: https://github.com/gurdotan/activerecord_plurals
In detail:
I find myself many times in need of fetching all IDs or all names of an ActiveRecord query result. To generalize, I’d like to get all [attribute]s of a certain ActiveRecord model. Instead of this:
>> Beer.select(:id).map{&id}
=> [1, 2, 3, 4, 5]
I’d like to do something more semantic:
>> Beer.ids
=> [1, 2, 3, 4, 5]
>> Beer.names
=> ["Miller", "Coors", "Heineken", "Tuborg", "Guinness"]
There comes a time in every Rails programmer’s life when he \ she have to open the Rails source code and start digging in the mud. Since I found myself repeating the same pattern of ActiveRecord queries followed by map() calls over and over again, I figured this was my queue. ActiveRecord uses a lot of meta-programming techniques. Time to code!
# activerecord_plurals.rb
#
class << ActiveRecord::Base
alias_method :singular_method_missing, :method_missing
def method_missing(method_id, *args, &block)
singular_method_id = method_id.to_s.singularize.to_sym
if method_id == singular_method_id
singular_method_missing(method_id, *args, &block)
else
self.select(singular_method_id).map(&singular_method_id)
end
end
end
What’s going on here? First, we’re reopening the ActiveRecord::Base meta-class (also called an eigenclass by some books) with the class << syntax. Then we provide a new implementation for method_missing along with an around alias. This is necessary since ActiveRecord::Base already implements method_missing and we want to wrap the original implementation. The new implementation’s logic just checks if the invoked method is in singular or plural form. In case of a singular form, we delegate the method call to the singular_method_missing which is a reference to ActiveRecord’s original implementation. Otherwise we assume a plural method invocation - we select the requested field and map the query result into an array.
This is not enough though. method_missing should always be implemented with respond_to?. If you can call it, you should be able to recognize it! Add the following to the above class:
alias_method :singular_respond_to?, :respond_to?
def respond_to?(*args)
return true if singular_respond_to?(*args)
singular_method_id = args[0].to_s.singularize.to_sym
instance_methods.include?(singular_method_id)
end
All of the meta-programming techniques demonstrated in this post - Ghost methods (method_missing), method aliases(alias_method), and meta classes - are also heavily used in ActiveRecord’s source code, so I highly recommend you to have a look. If you’re completely new to Ruby meta-programming concepts you can read more here.
The full source code is available on Github. Or, you can just install it as a gem
gem install activerecord_plurals
or as a rails plugin
rails plugin install git://github.com/gurdotan/activerecord_plurals.git
This is my first gem so any feedback, good or bad, is very welcome :-)
