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
Why Am I Getting Objects Printed Twice
Error Installing Libv8: Error: Failed to Build Gem Native Extension
Difference Between Map and Collect in Ruby
Difference Between Datetime and Time in Ruby
You Have Already Activated X, But Your Gemfile Requires Y
Rails 3: Alias_Method_Chain Still Used
How to Get the Match Data For All Occurrences of a Ruby Regular Expression in a String
Understanding the "||" or Operator in If Conditionals in Ruby
Ruby 2.4 and Rails 4 Stack Level Too Deep (Systemstackerror)
When Is 'Eval' in Ruby Justified
Rescue_From Actioncontroller::Routingerror in Rails 4
How to Uninstall Ruby Installed by Ruby-Install