Yehuda Katz is a member of the Ember.js, Ruby on Rails and jQuery Core Teams; he spends his daytime hours at the startup he founded, Tilde Inc.. Yehuda is co-author of best-selling jQuery in Action and Rails 3 in Action. He spends most of his time hacking on open source—his main projects, like Thor, Handlebars and Janus—or traveling the world doing evangelism work. He can be found on Twitter as @wycats and on Github.

alias_method_chain in models

As people know, there’s been a fair bit of back-and-forth between me, the apparent foe of alias_method_chain, and folks who feel that alias_method_chain is a perfectly reasonable API that people should not blindly hate.

There are basically two use-cases for alias_method_chain:

  1. Organizing internal code
  2. Modifying existing code

Using alias_method_chain to organize internal code is an interesting discussion that I will hopefully continue to have into the future. Today, I want to address uses of alias_method_chain to override methods in ActionController::Base or ActiveRecord::Base. People who do this are blindly using the technique that has been most evangelized as the solution to all their problems when Ruby comes with a perfectly good solution.

Consider this post from vaporbase, which I am decidedly not picking on. It represents a common idiom that people have been trying to use in Rails. First, the usage in a model:

class Foo < ActiveRecord::Base
  include FooBar
end

Second, the code implementation:

module FooBar
  module ClassMethods
    def find_with_bar( *args ) 
      find_without_bar( *args )
      #...or whatever 
    end
  end
 
  def self.included(base)
    base.class_eval do
      extend ClassMethods
      class << self
        alias_method_chain :find, :bar
      end
    end
  end  
end

This is exactly equivalent to:

module FooBar
  def self.included(base)
    base.extend(ClassMethods)
  end
 
  module ClassMethods
    def find(*args)
      super
      #...or whatever
    end
  end
end

That’s right… if you’re looking to modify subclasses of ActiveRecord::Base or ActionController::Base, keep in mind that you’re (gasp) in an OO language with inheritance and super.

If you want to modify all of the models in your application, create your own custom ActiveRecord::Base subclass, and inherit from that throughout your application. That’s what inheritance is there for!

Another example

Another example, by a very, very smart person, goes even further overboard.

First, he started with:

class Post < ActiveRecord::Base
  class << self
    def find_with_tags(*args)
      options = extract_options_from_args!(args)
      if tag = options.delete(:tags)
        options[:select] ||= 'posts.*'
        options[:joins]  ||= ''
        options[:joins]  << <<-END
          INNER JOIN posts_tags AS inner_posts_tags ON posts.id = inner_posts_tags.post_id
          INNER JOIN tags AS inner_tags ON inner_tags.id = inner_posts_tags.tag_id
        END
        add_to_conditions(options, tags.map { 'inner_tags.name = ?' }.join(' OR '), *tags)
      end
      find_without_tags(*(args + [options]))
    end
    alias_method_chain :find, :tags
 
    def find_with_query(*args)
      options = extract_options_from_args!(args)
      if query = options.delete(:query)
        if query.empty?
          add_to_conditions(options, 'false')
        else
          term = "%#{query}%" 
          add_to_conditions(options, "posts.content LIKE ? OR posts.title LIKE ?", term, term)
        end
      end
      find_without_query(*(args + [options]))
    end
    alias_method_chain :find, :query
 
    protected
 
    def add_to_conditions(options, condition, *args)
      condition = args.empty? ? condition : [condition, *args]
      if options[:conditions].nil?
        options[:conditions] = condition
      else
        options[:conditions] = sanitize_sql(options[:conditions]) + " AND (#{sanitize_sql(condition)})" 
      end
    end
  end
end

Noticing it wasn’t very DRY, he resorted to metaprogramming:

class Post < ActiveRecord::Base
  class << self
    def handle_find_option(name, &block)
      eigenclass = class << self; self; end
      eigenclass.send :define_method, "find_with_#{name}_handled" do |*args|
        options = extract_options_from_args!(args)
        if option = options.delete(name)
          block[options, option]
        end
        send("find_without_#{name}_handled", *(args + [options]))
      end
      eigenclass.send :alias_method_chain, :find, "#{name}_handled" 
    end
  end
end
 
class Post < ActiveRecord::Base
  handle_find_option(:tags) do |options, tags|
    options[:select] ||= 'posts.*'
    options[:joins]  ||= ''
    options[:joins]  << <<-END
      INNER JOIN posts_tags AS inner_posts_tags ON posts.id = inner_posts_tags.post_id
      INNER JOIN tags AS inner_tags ON inner_tags.id = inner_posts_tags.tag_id
    END
    add_to_conditions(options, tags.map { 'inner_tags.name = ?' }.join(' OR '), *tags)
  end
 
  handle_find_option(:query) do |options, query|
    if query.empty?
      add_to_conditions(options, 'false')
    else
      term = "%#{query}%" 
      add_to_conditions(options, "posts.content LIKE ? OR posts.title LIKE ?", term, term)
    end
  end
end

An alternative, using super:

class Post < ActiveRecord::Base
  class << self
    def find(*args)
      options = args.last.is_a?(Hash) ? args.last : {}
      add_tag_conditions(options)
      add_query_conditions(options)
      super
    end
 
    private
    def add_tag_conditions(options)
      if tag = options.delete(:tags)
        options[:select] ||= 'posts.*'
        options[:joins]  ||= ''
        options[:joins]  << <<-END
          INNER JOIN posts_tags AS inner_posts_tags ON posts.id = inner_posts_tags.post_id
          INNER JOIN tags AS inner_tags ON inner_tags.id = inner_posts_tags.tag_id
        END
        add_to_conditions(options, tags.map { 'inner_tags.name = ?' }.join(' OR '), *tags)
      end
    end
 
    def add_query_conditions(options)
      if query = options.delete(:query)
        if query.empty?
          add_to_conditions(options, 'false')
        else
          term = "%#{query}%" 
          add_to_conditions(options, "posts.content LIKE ? OR posts.title LIKE ?", term, term)
        end
      end      
    end
 
    def add_to_conditions(options, condition, *args)
      condition = args.empty? ? condition : [condition, *args]
      if options[:conditions].nil?
        options[:conditions] = condition
      else
        options[:conditions] = sanitize_sql(options[:conditions]) + " AND (#{sanitize_sql(condition)})" 
      end
    end
  end
end

We just override find, have it modify the options as appropriate, and call super. This same technique works fine for your own applications’ ActionController modifications, and in any case where the framework API involves subclassing. Folks: this is what subclassing is FOR!

30 Responses to “alias_method_chain in models”

I think you should mention that this requires your classes to be structured a certain way and you need control over the class you are intending to change.
ForExample assume you have:

class Foo < ActiveRecord::Base
def self.ordered(*args)
# some stuff here
end
end

and you want to change the arguments passed to ordered. If you create a module as in your first example which should wrap around ordered method this won’t work. You would need the original method ordered to be included into Foo to be defined already in a module so you get an ancestor chain of: Foo, ModuleWithNewOrderedMethod, ModuleWithOriginalOrderedMethod

@daniel: that is the usual argument against my generic alias_method_chain position. This is much more limited to things that are defined on AR::Base or AC::Base and where you want to make app-specific changes.

In that case, no caveats are necessary, and that was my point.

*HEADDESK*

Holy crap. So obvious. Why on earth did I get into the habit of using alias? I should have just used super.

God. I. Am. An. Idiot.

I agree. super is definitely an under used keyword in ruby, which is an object-oriented language.

Here, here. Couldn’t have said it better myself.

The voice of reason… Thanks!

Hmm but you’d still have to define a “find_without_options” method to access the original find? Could also be that I’m missing something here. Not sure. Can someone clue me in?

Nah. You’d just use super to access the original.

Could you provide an example. Sorry I don’t understand how you would access the original find.

Like you define a new find in the FooBar module and inlclude it into the Foo class. Now Foo inherited find from ActiveRecord, but with the module inclusion, its got its own find now which first calls super and does some custom stuff. So far so good. Now when I want to call the original Foo.find method, the one it has inherited, I would call Foo.super.find ?

Sorry again for my cluelessness ;) I like your examples but I want to fully understand them ;)

So I played a bit with that concept, but its in the morning and I still can’t figure it out. This is what I have:

http://pastie.textmate.org/private/dgmvvqvygqwlqoprkrtftg

How could I call the orginal .say_hello here which only puts “say hello to the real world” ?

Thanks in advance!

The only way I found was to call Foo.superclass.say_hello or Foo.superclass.find to stick with your example which would be totally okay i guess.

def original_find
self.superclass.find
end

Is that the way then ?

You wouldn’t! The idea here is to modify the “save” functionality to modify it with some changes. If you *really* need access to the old functionality alone, you could do what you suggested (def original_find(*args, &blk) self.superclass.find(*args, &blk))

But honestly, if you wanted the original around, you should probably alias it off to the side for continued use, and then redefine the original method and use super.

Alright thank you!

Heh… I give bad replies when I’m half-asleep. Probably a better approach:

def find(*args)
  options = args.last.is_a?(Hash) ? args.last : {}
  unless options.delete(:skip_my_new_feature)
    # perform new feature
  end
  super
end

This effectively provides a flag to turn off the new feature through the existing API. This sort of API is already in use by Rails (via a_m_c) in AR::Base#save, and it works better than trying to access an incidental byproduct of a_m_c.

I was GOING to use alias_method in a custom form builder that I was writing, so that the form could either use (for example) the text_field method if it wanted the customizations the custom form builder introduced, or resort to (for example) original_text_field if it did not (the aliased-away old method).

But then I found something. The excellent Ruby Facets library introduces a method called ‘Kernel#as’ (description: “Returns a As-functor that allows one to call any ancestor‘s method directly of the given object.”)

So I now have a method in the form builder called ‘original’, which is simply:

def original; return self.as(ancestor); end

So the form can choose to have the customizations provided by my form builder if it wants, by simply using text_field, or it can do this if I have a unique requirement that I don’t want to hack into the form builder (because it’s only going to be used once):

Another reason to use alias_method instead of super gone!

Note on usage: ‘form.original.send(:text_field, …)’ doesn’t work. Kernel#as can’t be used like this, you have to use Kernel#send_as

I have a feeling that many people learn these techniques by looking at gems and plugins, and end up using them in their own applications where inheritance would be more appropriate.

But in the case of gems and plugins, alias_method_chain is exactly what you would want to use to sprinkle additional functionality on top of standard Rails methods without forcing users to adopt a different API. A good example of this is will_paginate, which allows users to call paginate() on standard AR::Base collections without modifying their models to inherit from something like WillPaginate::PaginatedRecord.

@Nathan: which, to be honest, totally sucks from a maintainability standpoint in larger projects.

I would rather the model class DID tell you which bits of plugins it was using which change the behaviour of the standard api.

Isn’t it generally held that calling super is an anti-pattern? I’ve always found it to be a fairly brittle approach to anything I do, and replace it with IOC/DI anywhere I can, especially when coding in a statically typed language. In Rails and Ruby, I thought the method aliasing approach was a far healthier approach so long as everyone is cautious about what the extend. Aliasing gives us multiple-inheritance, which isn’t hideous, so long as you’re clearly not out to hang yourself. Rubyists idiomatically avoid hanging themselves, and so welcome multiple inheritance in this way.

Or have I missed something obvious.

But how can we “override” attributes readers and writers in ActiveRecord without alias_method_chain? There inheritance really cannot help and using attributes directly don’t work for some conversions and dirty relation states.
Does it was removed from Rails 3?

Rasheed: Why would calling super be an antipattern? I’ve never heard this said of any language. It seems to me that calling super is a very *good* thing to do in most cases. What problems have you had with it? It’s a much better solution than circumventing the object model with alias_method_chain, I think.

Agree with Marnen here. I don’t see how super can be more brittle than alias_method_chain. If you *do* call foo_without_bar, you are still depending on the implementation of the original (possibly moreso since you don’t get the equivalent automagical argumentless super). If you *don’t* call :foo_without_bar then you wouldn’t need to call super anyway.

I think the crux of the issue here is do you have a place in the method lookup hierarchy to insert your new method. In the case of ActiveRecord models you always do because you are creating an explicit subclass. In fact, thanks to the metaclass, you can go far with this.

I found your post via chadfowler.com while trying to figure out a solution for overriding the attribute readers/writers in multiple (sibling) subclasses of AR::Base. Naturally I first tried mixing in a module HasFoo, with foo() defined in it, and naturally it didn’t work.

Lailson is wondering about this too; could you perhaps say a bit more about how you’d deal with that example?

This post has some fantastic discussion in the comments about the exceptions to the rule, but just yesterday I ran into a case where someone didn’t understand the means by which super can replace alias_method_chain in many/most cases. As such, I wrote up a quick post that hopefully explains a situation in which it becomes necessary to resort to alias_method_chain in more detail.

Anyway, the post can be found over at http://metautonomo.us/2011/02/03/when-to-use-alias_method_chain/, and it basically just expands on the situation that Daniel outlined in his very first comment some time ago, for those who didn’t read it or had trouble following.

Seemed like, since discussion is still sputtering along on this (nearly two year old!) post, it might be worth linking from here. Thanks for reading!

Extending rails functionality without alias_method_chain

module ActiveRecordUuid
module AssociationMethods
def has_many(name, options = {}, &extension)
options = uuid_assoc_options(:has_many, name, options)
super
end

……..
end
end

ActiveRecord::Base.send(:extend, ActiveRecordUuid::AssociationMethods)

Leave a Reply

Archives

Categories

Meta