Yehuda Katz is a member of the Ember.js, Ruby on Rails and jQuery Core Teams; his 9-to-5 home is at the startup he founded, Tilde Inc.. There he works on Skylight, the smart profiler for Rails, and does Ember.js consulting. He is best known for his open source work, which also includes Thor and Handlebars. He travels the world doing open source evangelism and web standards work.

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:

  1. Register a hook so that when the module is included, the ClassMethods are extended onto the class
  2. In that module, define a method that includes the InstanceMethods
  3. So that you can say acts_as_something in 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.

46 Responses to “Better Ruby Idioms”

Couldn’t agree more. Well said.

You are right.

For a less convoluted Ruby & Rails world!

I recently wrote an AR tree gem (github.com/stefankroes/ancestry) using exactly this pattern from the guide. Your solution is much more straight forward. Is there any way changing the implementation could break the gem for some users?

Thanks for writing this. I’ve been following the Rails Guides pattern for a while now… all the while being suspicious that some of the “magic” was unnecessary. This post is really helpful for clearing up how it all works.
Next up… the plugin guide should be revised!

Great writeup, but if anyone releases one more acts_as plugin, I’m going to scream

Thanks for this post. Turns out I was using this code without really bothering to check for a better idiom (read: without really understanding). All I cared about was “it works”.

Yes! That pattern was the first thing confusing me when I started on my first rails plugin.

.. Still I just replicated it since I assumed there was some well thought out reason behind it :).

You are absolutely right. I am currently studying the rails plugin patterns, and was wondering myself why is it so … thx for the simplication.

And the life of Rails IDE developers would be so much simpler if we didn’t have to unravel all these unneeded layers of metamagic. :)

Please do feel free to fix the guide :)

In the last case where you opened up the classs. I really do not like that. What about the ability to reflect on how a class is extended? For instance ActiveRecord::Base.included_modules and/or ActiveRecord::Base.extended_by.

http://pastie.org/695381

I love the article and agree. But I think thought should go out to how those familiar with meta programming and reflection. Looking at the examples in that link… you can see what I’m talking about. What are your thoughts? For instance, it may be just pure habbit that I ask the class what was “included” vs “extended”.

Talking about unnecessary sends, base.send :extend, ClassMethods is another one. Mainly Object#extend isn’t private method at all, try:

Class.new.extend(Module.new) # with class
ARGV.extend(ArgvParseMixin)  # with an object

Preach on, brother!

Here is another snippet of code to expand on my point. I just found out that the class method extended_by comes from ActiveSupport (I think) but it’s not vanilla ruby. So maybe this is where the pattern evolved. IE, by using included and letting include do the extend, we are able to reflect on how us/others have extended the class. Which is funny, because the english of it should be allowed. Hence is there a default Ruby way to reflect on extended?

http://pastie.org/695405

All of what your saying might make sense for one’s own lib and code organization. Like for instance, the author of ActiveRecord might just follow your simple pattern at the top and by the time I load up ActiveRecord::Base and asks for it’s included modules, I get a clean slate of [Kernel] which makes the default idiom (which yes does not match english) work. Thoughts?

Great article!

Regarding the very last example. Where is the best place to save the file in a rails application. Do I need to require the file somewhere?

I’ve always wondered that myself. Thanks for formalizing it in a blog post.

Of course… this perpetuates one of the things rails started that I hate most… just a little less than alias_method_chain. acts_as_* as was a horrible API for including functionality. IMO, it should have looked like this:

class Article < ActiveRecord::Base
include Yaffle # Just use the module directly!
end

This is much more Ruby and must less DSL. If you need to pass options you can either call option setters on the class now like `self.yaffle_field = 42` or specialize the module during inclusion:

class Article 42) # There are many ways to do this via clone or new modules
end

Why do this? Because it preserves Ruby’s concept of mixins more carefully. Consider:

article = Article.new.extend Yaffle

This allows it to come into play on the instance as well as class level. You could also use the module specialization to simplify sharing of settings:

MyYaffle = Yaffle(:foo => :bar)
class ….
include MyYaffle
end

Pushing things away from include hurst the overall Rubyness of a lot of things. I’d highly recommend that API’s always strive to allow inclusion and extension even if there are alternative methods.

Agree. I’m waiting for your new guide after reading this. I think a guide written like you just explained would be a great help to people who aren’t too familiar with Ruby or writing Rails plugins. I know when I read the guide for the first time, there was a lot of ‘magic’. Explaining it like this removes some of the magic.

I agree with you, there are some cases when that method seems to overcomplicate the things. But I guess the author of that Guide mentioned the universal way to create plugins. Actually I would rather follow that guide then and have the module’s code in the module itself and then extend AR::Base then writing the code directly in the AR::Base class like you shown at the final snippet.

You are very right.

Completely agree, excellent post!

For the last example, I think it’s easier to unit test using the ‘extend’ pattern. The Yaffle could be tested using any object as a container rather than the explicit coupling to ActiveRecord::Base of the final alternative.

Good points indeed. By the way, more I use ruby more I don’t like the trade-off where I cannot use super in AR models.

Thanks for speaking up about this. My eyes are bleeding less.

@Ken Collins – extend essentially includes modules into the metaclass, IIRC. If that is the case, you could do:

def extended_by
  (class << self; self; end).included_modules
end

@priit Can you post more on this? Or link me to a post that explains this in detail?

Bingo! Nice post!

Well I feel alot better that this was all cleared up. Thanks Katz!

On a somewhat related note, working against ActiveRecord::Base as in

class ActiveRecord::Base
extend Yaffle
end

seems a little wrong to me. I’ve started experimenting with inheriting all app models from an ApplicationModel, analogous to ApplicationController.

Let’s remember that the guide isn’t really at fault here, since it simply reflects the standard (poor) way that plugins have been written for years. The include/extend class/instance method issue is pretty confusing, particularly when it comes to Rails plugins. Most plugins I’ve seen use the techniques critiqued here and it makes plugin development intimidating since it’s easy to assume that there must be a reason if everyone is doing it.

Seems pretty obvious to me…

I’ve been unhappy with the acts_as pattern since my first encounter with it and have been wanting to publicly point out how complicated it is compared to simply mixing in a module, but you beat me to it.

Though it may make things more DSL’ish. This is a case of overkill indeed where the acts_as vs include does not affect thinking about things that much.

Great post.

class Article < ActiveRecord::Base
include Yaffle
end

^^^ module include FTW!

I agree with Ken, adding a method by reopenning has the disadvantage of losing some information.

Perhaps that information is not important for a specific plugin (in this case, x.included_modules.include?(Yaffle) could be replaced with x.respond_to?(:acts_as_yaffle) ), there are more complex ones which justify it, and having an uniform mechanism makes them easier to read (it should be grear if it could be made a little easier, though).

– nachokb

Yehuda,

I thoroughly enjoyed reading your analysis and deconstruction of a common rails idiom. Thank you!

Ryan

@Sean Cribbs… agreed! I just think that perhaps a path to Ruby should be made (no time here personally) that would expose the simple extended_by or some other way that would yield a common pattern of reflection. Like I was saying, it is just habit for me now to think of using included_modules since I am so used to it.

So ingeneral, I strongly AGREE with this but I love the idea of framing it more around (1) teaching developers of large libraries to not include/extend in such a way that when the library meets my code I can reflect on an empty included/exended array. I’m sure there are pros/cons to this as I adore the type of code organization that say ActiveRecord::Base does and lays out the discrete logic into modules. Perhaps that topic is just academic and food for thought. Number (2) being that opening up a class should be discouraged. I know the article was not championing that, but just wanted to state my opinion. I very seldom have made libraries that have class method calls like “acts_as_foo” that use the reflection of seeing if the module is already included in the parent class before doing some other meta work. I have yet to examine if that was a good idea or not, but wanted to put out there that that reflection via “included_modules” is lost and is where most like myself go out of habbit. Cheers!

Referring to the legacy ‘plugin api’ observed by OP…
This is similar to the sort of nonsense the late eighties was cluttered with in the name of ’4th generation languages’.
Macros producing macros to rid the world of programmers.
Drivel producing krud.
The damage of which sometimes could only be reversed by writing a macro to ripe the crap out of the entire code base.

Further ranting withheld.

Holy Cow!

Balls, my friend: clear-thinking BALLS is what you have.

Yeah, I REALLY had a hard time learning how to open classes/extend stuff for the first 2 years of doing Rails work because of this crap. Glad to see someone is as fed up with it as I am!

Hallelujah!

Amen

Speaking of Rails Plugin Idioms…There should be a better, more consistent approach to including plugins.

class Post
include ActsAsPlugin
end

class Post
acts_as_plugin :configuration => true
end

Compare the 2 approaches: #1 is much less “magical” and also has the advantage of not including the acts_as_plugin method into every ActiveRecord class. But obviously, #2 allows for configuration options.

Wouldn’t it be cool if there was a specific API for including plugins that would have the advantages of both approaches. Something like:

class Post
use ActsAsPlugin, :configuration => true
end

Nice post, it can clear up this include/extend magic indeed.

But what bothers me is why this overkill pattern with additional module layers came up in the first place. I mean – are there some really good reasons to use the classic act_as_overkill way?

I must agree whole heartedly. When I first started with Rails, though I had worked with Ruby prior, I didn’t have a grasp on some concepts. I remember slugging through AuthLogic code trying to interpret it. While Ben Johnson did a great job on the library, it is wrought with the kind of patterns that you describe above, making it tough to review.

Hopefully this clears it up for some newer folks.

A bit late, but here’s my take:

http://gist.github.com/281080

If someone is still reading this, can you tell me if it makes sense? :-)

@wycats: I think one good overview about AR anti-”super”: http://stackoverflow.com/questions/373731/override-activerecord-attribute-methods and the same thing goes for initialize method as we know. Anyhow, it’s very welcome to see rails 3 become better ruby citizens with optional defaults. Keep up good work about promoting cleaner ruby style.

@wycats: Rather than “overriding include” I might prefer to call it sidestepping or bypassing extend(). In fact I don’t think I like the term include() at all. Is instance_extend() better? Either that or include is kept and the compliment becomes class_include. Generally, though, the usefulness of having everything modular, such that doing the mixin by loading everything dynamically rather than by simply opening a class, is dependent upon the reversability of mixins in Ruby. If it were possible to drop a module in and rip it back out of a class (or another module) again with equal ease, then sure, don’t go opening classes. However, since Module#uninclude/exclude and Module#unextend/retract (And, assuming instance_extend, then we should have #instance_unextend/instance_retract, or alternately we might have Module#class_uninclude/class_exclude.) don’t exist (I ought to know, since I’m writing it in POR (Plain Old Ruby) right now and hence how I got here for the second time when looking for a pre-existing solution.), and since there doesn’t exist a way to easily remove functionality on a per module basis from the ancestors chain, there is insufficient reason to have everything modular and to load it dynamically at this time. The trick to making Module#exclude or Module#retract work properly is to ensure that by removing one module that the functionality provided by another included/extending module isn’t becoming some portion of useless because of a dependency on the one removed. Short of that, it is possible to do this with a little jiggery-pokery. I’m nearly there by way of introspecting on the module and trashing all the methods on the target class or module… Apart from a dependency issue amongst modules, there remains a problem with it remaining in the ancestors chain though, since a reference to the included module remains doing it this way. The alternative to actually banging on the target class or module, and a feature of Ruby I have wanted for about half a decade now is to be able to create context-specific instances of a class where the desired additional (or reduced functionality), such that the functionality exists either actually within or appears to reside within the class of concern, but then outside the context returns to ‘normal’. Something like the eye-popping lambda-lambda-lambda stuff Raganwald was working on a couple of years ago suffices, but what a pain to have to go to all that trouble, when it isn’t unreasonably included into a language with the sort of dynamism which we expect of Ruby. I was pleased to see that _why also took this approach with Poison. I’m working on something pretty horrible in Ruby to accomplish this as well just now… If it isn’t worse than ghastly I’ll eventually get it onto github. @Peter: The syntax I have at the moment is very much like what you suggest. In your case it would be Post.uses(ActsAsPlugin, :configuration => true) do; stuff; end. In that way Post could optionally act as if it had a plugin. I’m not presently worried about the machine efficiency of doing this because it should not be my concern at this level as to how quicly this operation works. It should be the internals of the language which worries about realising that a particular module has or has not been included already and to cache it somehow.

What defining about an include and extend method?

class Class
def include_and_extend(the_module)
include the_module
extend the_module::ClassMethods
end
end

class Article < ActiveRecord::Base
include_and_extend Yaffle
end

http://glosoli.blogspot.com/2010/11/includeandextend-method-for-ruby.html

Yehuda, you *can’t* do it like this. acts_as_something with ClassMethods and InstanceMethods and abstraction layer is a well established programming patter. It’s enterprise, you see :P

Leave a Reply

Archives

Categories

Meta