Rails 3: alias_method_chain still used?
I was just reading about Gems/Plugin development for Rails 3 and ran across this post that says that alias_method_chain is no longer used. I can see the method is still there in activesupport-3.0.0/lib/active_support/core_ext/module/aliasing.rb.
Should I still be using alias_method_chain in Rails 3?
Is this still reflective of the best practices for gems/plugins in Rails 3 that want to modify ActiveRecord?
No, it has been replaced by a clever use of method overriding in modules and the super
keyword.
Basically, you define the original function in an included module, and override it in another included module. When you call super
in the overriding function, it calls the original function. But there is one catch. You have to include the extending modules after including the base module, and in the order you want the chaining to occur.
class Something
module Base
def my_method
# (A) original functionality
end
end
module PreExtension
def my_method
# (B) before the original
super # calls whatever was my_method before this definition was made
end
end
module PostExtension
def my_method
super # calls whatever was my_method before this definition was made
# (C) after the original
end
end
include Base # this is needed to place the base methods in the inheritance stack
include PreExtension # this will override the original my_method
include PostExtension # this will override my_method defined in PreExtension
end
s = Something.new
s.my_method
#=> this is a twice extended method call that will execute code in this order:
#=> (B) before the original
#=> (A) the original
#=> (C) after the original
Ryan Bates of Railscasts talks about how this is used in the Rails Routing code. I'd recommend watching it, and his other screencasts. They have the power to transform a knitting grandmother into a Rails guru.
PS: Credit goes to Peeja for correcting a fundamental error in my original answer. Thanks.
In general, a module can never override a method in the class it's included in. This is because module inclusion works just like subclassing. A superclass can't override its subclasses' methods either, nor would you expect it to.
When a module is included in a class, the module is inserted
just after the class in the class's ancestor chain. Calling
super
from the class will call the module's implementation.
class Something
module PreExtension; end
module PostExtension; end
include PreExtension
include PostExtension
end
Something.ancestors # => [Something, Something::PostExtension, Something::PreExtension, Object, Kernel]
Whenever a method is called on a Something
, Ruby looks through
this list in order and calls the first implementation it finds.
If the implementation calls super
, it keeps looking and finds
the next one.
This means that modules included later take precedence over
modules included earlier, and can call super
to get the earlier
modules' implementations. This is because included modules are
inserted in the ancestor chain directly after the class. This
is how the routing code edgerunner mentioned works. That code
puts everything in modules, like so:
class SomethingNew
module Base
def my_method
puts "(A)"
end
end
module Extension
def my_method
puts "(B)"
super
end
end
include Base
include Extension
end
SomethingNew.new.my_method
# Output:
# >> (B)
# >> (A)
SomethingNew.ancestors # => [SomethingNew, SomethingNew::Extension, SomethingNew::Base, Object, Kernel]
This is why alias_method_chain
existed in the first place. If putting the base code in a module is not an option, I'm not sure how to accomplish the equivalent of alias_method_chain
.