Better Ruby Idioms
Carl and I have been working on the plugins system over the past few days. As part of that process, we read through the Rails Plugin Guide. While reading through the guide, we noticed a number of idioms presented in the guide that are serious overkill for the task at hand.
I don’t blame the author of the guide; the idioms presented are roughly the same that have been used since the early days of Rails. However, looking at them brought back memories of my early days using Rails, when the code made me feel as though Ruby was full of magic incantations and ceremony to accomplish relatively simple things.
Here’s an example:
module Yaffle def self.included(base) base.send :extend, ClassMethods end module ClassMethods # any method placed here will apply to classes, like Hickwall def acts_as_something send :include, InstanceMethods end end module InstanceMethods # any method placed here will apply to instaces, like @hickwall end end
To begin with, the send is completely unneeded. The acts_as_something method will be run on the Class itself, giving the method access to include, a private method.
This code intended to be used as follows:
class ActiveRecord::Base include Yaffle end class Article < ActiveRecord::Base acts_as_yaffle end
What the code does is:
- Register a hook so that when the module is included, the ClassMethods are extended onto the class
- In that module, define a method that includes the InstanceMethods
- So that you can say
acts_as_somethingin your code
The crazy thing about all of this is that it’s completely reinventing the module system that Ruby already has. This would be exactly identical:
module Yaffle # any method placed here will apply to classes, like Hickwall def acts_as_something send :include, InstanceMethods end module InstanceMethods # any method placed here will apply to instances, like @hickwall end end
To be used via:
class ActiveRecord::Base extend Yaffle end class Article < ActiveRecord::Base acts_as_yaffle end
In a nutshell, there’s no point in overriding include to behave like extend when Ruby provides both!
To take this a bit further, you could do:
module Yaffle # any method placed here will apply to instances, like @hickwall, # because that's how modules work! end
To be used via:
class Article < ActiveRecord::Base include Yaffle end
In effect, the initial code (override included hook to extend a method on, which then includes a module) is two layers of abstraction around a simple Ruby include!
Let’s look at a few more examples:
module Yaffle def self.included(base) base.send :extend, ClassMethods end module ClassMethods def acts_as_yaffle(options = {}) cattr_accessor :yaffle_text_field self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s end end end ActiveRecord::Base.send :include, Yaffle
Again, we have the idiom of overriding include to behave like extend (instead of just using extend!).
A better solution:
module Yaffle def acts_as_yaffle(options = {}) cattr_accessor :yaffle_text_field self.yaffle_text_field = options[:yaffle_text_field].to_s || "last_squawk" end end ActiveRecord::Base.extend Yaffle
In this case, it’s appropriate to use an acts_as_yaffle, since you’re providing additional options which could not be encapsulated using the normal Ruby extend.
Another “more advanced” case:
module Yaffle def self.included(base) base.send :extend, ClassMethods end module ClassMethods def acts_as_yaffle(options = {}) cattr_accessor :yaffle_text_field self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s send :include, InstanceMethods end end module InstanceMethods def squawk(string) write_attribute(self.class.yaffle_text_field, string.to_squawk) end end end ActiveRecord::Base.send :include, Yaffle
Again, we have the idiom of overriding include to pretend to be an extend, and a send where it is not needed. Identical functionality:
module Yaffle def acts_as_yaffle(options = {}) cattr_accessor :yaffle_text_field self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s include InstanceMethods end module InstanceMethods def squawk(string) write_attribute(self.class.yaffle_text_field, string.to_squawk) end end end ActiveRecord::Base.extend Yaffle
Of course, it is also possible to do:
module Yaffle def squawk(string) write_attribute(self.class.yaffle_text_field, string.to_squawk) end end class ActiveRecord::Base def self.acts_as_yaffle(options = {}) cattr_accessor :yaffle_text_field self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s include Yaffle end end
Since the module is always included in ActiveRecord::Base, there is no reason that the earlier code, with its additional modules and use of extend, is superior to simply reopening the class and adding the acts_as_yaffle method directly. Now we can put the squawk method directly inside the Yaffle module, where it can be included cleanly.
It may not seem like a huge deal, but it significantly reduces the amount of apparent magic in the plugin pattern, making it more accessible for new users. Additionally, it exposes the new user to include and extend quickly, instead of making them feel as though they were magic incantations requiring the use of send and special modules named ClassMethods in order to get them to work.
To be clear, I’m not saying that these idioms aren’t sometimes needed in special, advanced cases. However, I am saying that in the most common cases, they’re huge overkill that obscures the real functionality and confuses users.


Recent comments
1 year 23 weeks ago
1 year 23 weeks ago
1 year 25 weeks ago
1 year 27 weeks ago
1 year 42 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 46 weeks ago
1 year 48 weeks ago