Ruby on Rails: Alias_Method_Chain, What Exactly Does It Do

Ruby on Rails: alias_method_chain, what exactly does it do?

1 - is it still used at all?

Apparently yes, alias_method_chain() is still used in Rails (as of version 3.0.0).

2 - when would you use
alias_method_chain and why?

(Note: the following is largely based on the discussion of alias_method_chain() in Metaprogramming Ruby by Paolo Perrotta, which is an excellent book that you should get your hands on.)

Let's start with a basic example:

class Klass
def salute
puts "Aloha!"
end
end

Klass.new.salute # => Aloha!

Now suppose that we want to surround Klass#salute() with logging behavior. We can do that what Perrotta calls an around alias:

class Klass
def salute_with_log
puts "Calling method..."
salute_without_log
puts "...Method called"
end

alias_method :salute_without_log, :salute
alias_method :salute, :salute_with_log
end

Klass.new.salute
# Prints the following:
# Calling method...
# Aloha!
# ...Method called

We defined a new method called salute_with_log() and aliased it to salute(). The code that used to call salute() still works, but it gets the new logging behavior as well. We also defined an alias to the original salute(), so we can still salute without logging:

Klass.new.salute_without_log # => Aloha!

So, salute() is now called salute_without_log(). If we want logging, we can call either salute_with_log() or salute(), which are aliases of the same method. Confused? Good!

According to Perrotta, this kind of around alias is very common in Rails:

Look at another example of Rails
solving a problem its own way. A few
versions ago, the Rails code contained
many instances of the same idiom: an
Around Alias (155) was used to add a
feature to a method, and the old
version of the method was renamed to
something like
method_without_feature(). Apart from
the method names, which changed every
time, the code that did this was
always the same, duplicated all over
the place. In most languages, you
cannot avoid that kind of duplication.
In Ruby, you can sprinkle some
metaprogramming magic over your
pattern and extract it into its own
method... and thus was born
alias_method_chain().

In other words, you provide the original method, foo(), and the enhanced method, foo_with_feature(), and you end up with three methods: foo(), foo_with_feature(), and foo_without_feature(). The first two include the feature, while the third doesn't. Instead of duplicating these aliases all around, alias_method_chain() provided by ActiveSupport does all the aliasing for you.

Rails - alias_method_chain with a 'attribute=' method

alias_method_chain is a simple, two-line method:

def alias_method_chain( target, feature )
alias_method "#{target}_without_#{feature}", target
alias_method target, "#{target}_with_#{feature}"
end

I think the answer you want is to simply make the two alias_method calls yourself in this case:

alias_method :foo_without_bar=, :foo=
alias_method :foo=, :foo_with_bar=

And you would define your method like so:

def foo_with_bar=(value)
...
end

Ruby symbols process the trailing = and ? of method names without a problem.

alias_method, alias_method_chain, and self.included

When you monkey patch something you must redefine the method, because there's no super to inherit from (so you can't use the second code excerpt).

Copying the current method implementation and adding your own is just asking for trouble, so this is where alias_method comes in.

alias_method :form_for_without_cherries, :form_for

It actually creates a clone to the original method, which you can use instead of super. The fact that you can't chain monkey patches is not a bug, it's a feature.

The rails alias_method_chain method was indeed deprecated, but only because it was a poor idea from the start. However alias_method is a pure ruby method, and when used correctly can provide an elegant way of monkey patching your code, that's why I'm pretty sure it's not going away any time soon.

Rails 3: alias_method_chain still used?

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.

Alias method chain in ruby calling itself

you could at line 2 pass some unique value to options hash, and then detect it in your code and remove

def render_with_xhr(options = {}, extra_options = {}, xhr_check = true, &block)
if xhr_check && request.xhr? && !options.delete(:bacon)
template = render_to_string(options.merge(:bacon => true))
render_without_xhr(:update) {|page| page.replace_html("#popup .dialog", template)}
else
render_without_xhr(options, extra_options, &block)
end
end

alias_method_chain :render, :xhr

like that :)

ActiveSupport::Concern and alias_method_chain

It's old question, but I found an answer and I write for someone.

module LogStartEngine
extend ActiveSupport::Concern

define_method :start_engine_with_logging do
Rails.logger.info("Starting engine!")
start_engine_without_logging
Rails.logger.info("Engine started!")
end

included do
alias_method_chain :start_engine, :logging
end
end

define_method is point of this approach, it defines method dynamically on included (before alias_method_chain)

To extend rails' `link_to`, should I use `alias_method_chain` or mixins + inheritance?

I'd simply do:

# app/helpers/my_helper.rb

module MyHelper
def link_to(text, path, options={})
options = options.clone
icon = options.delete(:icon)
text = "<i class='#{icon}'></i> #{text}" if icon.present?
super(text, path, options)
end
end

But watch out if ever you use link_to with block.



Related Topics



Leave a reply



Submit