Rails 3: Alias_Method_Chain Still Used

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.

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.

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.

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.

undefined method `alias_method_chain' updating Spree from 3.2 to 3.3

For anyone else bumping into this, the multi_fetch_fragments gem has been merged into rails 5 itself and so the line

gem 'multi_fetch_fragments'

in my Gemfile was the culprit. Sources:
https://github.com/n8/multi_fetch_fragments/issues/34 and https://github.com/rails/rails/pull/18948

The error was gone after removing this gem from my Gemfile.

Rails 3: alias_method_chain to set specific attribute first

In your controller, why not just do this:

def create
@post = Post.new :user_id => params[:post][:user_id]
@post.update_attributes params[:post]
...
end

But it seems to me that it would be much better to create the artist records after you've done validation on the post rather than when you first assign the attribute.

EDIT

I would change this to a callback like this:

class Post < ActiveRecord::Base
attr_accessor :author_tokens
def artist_tokens=(tokens)
@artist_tokens = tokens.split(',')
end

after_save :create_artists
def create_artists
@artist_tokens.each do |token|
...
end
end
end

How to refactor code for deprecated alias_method_chain

prepend is basically like importing a module, but it ends up "in front" of other code (so the module can call super to run the code it's in front of).

This is a runnable example with something close to your situation.

module MyModule
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end

class Example
prepend MyModule

def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end

Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo

I defined read_attribute directly on Example, but it could just as well have been a method inherited from a superclass (such as ActiveRecord::Base).

This is a shorter but more cryptic version that uses an anonymous module:

class Example
prepend(Module.new do
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end)

def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end

Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo

UPDATE:

Just for fun and to address a question below, here's how it could be done without having to explicitly make any modules yourself. I don't think I'd choose to do it this way myself, since it obscures a common pattern.

# You'd do this once somewhere, e.g. config/initializers/prepend_block.rb in a Rails app.
class Module
def prepend_block(&block)
prepend Module.new.tap { |m| m.module_eval(&block) }
end
end

# Now you can do:
class Example
prepend_block do
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end

def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end

Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo


Related Topics



Leave a reply



Submit