See some examples at: http://svn.tylerrick.com/public/examples/ruby/method_aliasing/
This is especially a problem with Rails[Rails (category)], which sometimes has as many as 5 or so methods in an alias chain!
Method chains are made possible with alias_method_chain, which is part of (and is listed on this page) Facets.
Probably my best example to show how it works is this: http://svn.tylerrick.com/public/examples/ruby/method_aliasing/alias_method_chain-best.rb
Yes!
class Foo
def a; 'a'; end
alias_method :alias_for_a, :a
def self.b; 'b'; end
class <<self
alias_method :alias_for_b, :b
end
end
puts Foo.new.a
puts Foo.new.alias_for_a
puts Foo.b
puts Foo.alias_for_b
If you want to include your module...
http://www.redhillconsulting.com.au/blogs/simon/archives/000326.html
module ForeignKeyMigrations::Schema
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
class << self
alias_method :define_without_fk, :define unless method_defined?(:define_without_fk)
alias_method :define, :define_with_fk
end
end
end
module ClassMethods
def define_with_fk(info={}, &block)
...
define_without_fk(info, &block)
end
end
end
module M
def self.included(base)
base.class_eval do; class << self
def class_method_with_M
'From M'
end
alias_method_chain :class_method, :M
end; end
end
end
If you want to extend your module (which I ordinarily don't)...
http://snippets.dzone.com/posts/show/2133
module Foo
def self.extended(object)
class << object
alias_method :to_s_without_foo, :to_s unless method_defined?(:to_s_without_foo)
alias_method :to_s, :to_s_with_foo
end
end
def to_s_with_foo
"#{to_s_without_foo} - got foo!"
end
end
class X
end
X.extend(Foo)
puts X.to_s
alias_method_chain for class methods
require 'rubygems'
require 'facets/core/module/alias_method_chain'
class Person
def self.hello
'Hello'
end
end
module Fuddiness
def self.included(base)
base.extend ClassMethods
base.class_eval do
class << self
alias_method_chain :hello, :fuddiness
end
end
end
module ClassMethods
def hello_with_fuddiness
hello_without_fuddiness.gsub('ll', 'w')
end
end
end
Person.send :include, Fuddiness
puts Person.hello
class String
def self.foo(*args)
'foo'
end
def self.foo_with_stuff(*args)
'foo_with_stuff'
end
class << self
alias_method_chain :foo, :stuff
end
end
puts String.foo
# This method is just a tiny bit more concise.
require 'rubygems'
require 'qualitysmith_extensions/module/malias_method_chain'
class String
def self.shoe(*args)
'shoe'
end
def self.shoe_with_stuff(*args)
'shoe_with_stuff'
end
malias_method_chain :shoe, :stuff
end
puts String.shoe
It happened to me. I've reproduced it in this test case if you want to try it or see how:
But I'm not the only one it's happened to...
http://www.redhillconsulting.com.au/blogs/simon/archives/000326.html
(As a side note, the use of unless method_defined?(:to_s_without_quotes) is to work-around a bug in Ruby 1.8.4 that causes an infinite recursion when using alias_method. I never detected it under Mac OS X but apparently it affects Windows machines with monotonous regularity. D'oh!)
(It's happened my GNU/Linux workstation as well. So I don't think it's a Windows-only issue...)
A fail-safe method...
http://svn.tylerrick.com/public/examples/ruby/method_aliasing_can_lead_to_infinite_recursion-5.rb
# This is the simplest way to prevent the infinite recursion from happening...
# Just use qualitysmith_extensions/module/alias_method_chain, which will check if the '_without' method is already defined and, if so, will *not* attempt to
require 'rubygems'
require 'quality_extensions/module/alias_method_chain'
class Object
def foo
'foo'
end
def foo_with_stuff
foo_without_stuff + '_with_stuff'
end
alias_method_chain :foo, :stuff
alias_method_chain :foo, :stuff
alias_method_chain :foo, :stuff
end
puts String.foo
This was my initial question...
So I see this reference to perform_action_without_benchmark here:
def perform_action_with_benchmark
...
runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
...
end
How do I know which method that's actually referring to?
It could be any of these...
> cgrep def.*perform_action ./vendor/rails/actionpack/lib/action_controller/benchmarking.rb:63: def perform_action_with_benchmark ./vendor/rails/actionpack/lib/action_controller/filters.rb:618: def perform_action_with_filters ./vendor/rails/actionpack/lib/action_controller/rescue.rb:81: def perform_action_with_rescue #:nodoc: ./vendor/rails/actionpack/lib/action_controller/base.rb:1093: def perform_action
We can assume that the perform_action defined here: ./vendor/rails/actionpack/lib/action_controller/base.rb will be invoked by the chain eventually. But we can't assume that perform_action_without_benchmark will end up being equivalent to the original perform_action (in base.rb) by the time perform_action_with_benchmark calls it!
This is because there are a bunch of members in the method chain. Any one of them could be next in line at the time we call __ ...
module ActionController #:nodoc:
module Benchmarking #:nodoc:
def self.included(base)
base.class_eval do
alias_method_chain :perform_action, :benchmark
end
end
> cgrep method_chain|grep perform_action
rails/actionpack/lib/action_controller/filters.rb
alias_method_chain :perform_action, :filters
rails/actionpack/lib/action_controller/benchmarking.rb
alias_method_chain :perform_action, :benchmark
rails/actionpack/lib/action_controller/rescue.rb
alias_method_chain :perform_action, :rescue
The order that those alias_method_chain statements are called does matter (as this example, http://svn.tylerrick.com/public/examples/ruby/method_aliasing/alias_method_chain-order_of_aliasing_matters.rb , shows), so let's take a look at the order in which they were included/called:
rails/actionpack/lib/action_controller.rb
ActionController::Base.class_eval do include ActionController::Filters include ActionController::Benchmarking include ActionController::Rescue ... end
Following the pattern discovered in http://svn.tylerrick.com/public/examples/ruby/method_aliasing/alias_method_chain-best.rb , I can guess that this results in the following method aliases being created...
:perform_action => what was originally :perform_action_with_rescue (and still is) (called *1st* since Rescue was the *last* to be included) :perform_action_without_rescue => what was originally :perform_action_with_benchmark (and still is) :perform_action_without_benchmark => what was originally :perform_action_with_filters (and still is) :perform_action_without_filters => what was originally :perform_action (but is no longer)
Let's see if we can test that theory. I put this in each of the _with_ methods, which caused a backtrace to be printed out for each of them.
def perform_action
pp caller(0)
...
This an excerpt from the backtrace in perform_action (base.rb) that listed all 4 methods (the other two methods only listed 3, 2, or 1 respectively):
["./script/../config/../vendor/rails/actionpack/lib/action_controller/base.rb:1094:in `perform_action_without_filters'", "./script/../config/../vendor/rails/actionpack/lib/action_controller/filters.rb:632:in `call_filter'", "./script/../config/../vendor/rails/actionpack/lib/action_controller/filters.rb:619:in `perform_action_without_benchmark'", "./script/../config/../vendor/rails/actionpack/lib/action_controller/benchmarking.rb:67:in `perform_action_without_rescue'", "/usr/lib/ruby/1.8/benchmark.rb:293:in `measure'", "./script/../config/../vendor/rails/actionpack/lib/action_controller/benchmarking.rb:67:in `perform_action_without_rescue'", "./script/../config/../vendor/rails/actionpack/lib/action_controller/rescue.rb:83:in `perform_action'", "./script/../config/../vendor/rails/actionpack/lib/action_controller/base.rb:430:in `send'", "./script/../config/../vendor/rails/actionpack/lib/action_controller/base.rb:430:in `process_without_filters'", ...
Cleaned up a little bit...
"rails/actionpack/lib/action_controller/base.rb:1094: in `perform_action_without_filters'" (called 4th) ... "rails/actionpack/lib/action_controller/filters.rb:620: in `perform_action_without_benchmark'", (called 3rd) ... "rails/actionpack/lib/action_controller/benchmarking.rb:69: in `perform_action_without_rescue'", (called 2nd) ... "rails/actionpack/lib/action_controller/rescue.rb:85: in `perform_action'", (called 1st) ...
(Didn't see the perform_action that is defined in base.rb listed in the backtrace (it should have been called 4th). Why not?
Fortunately, even though the method names reported by the backtrace don't line up with reality (the names we gave the methods in the source code) (see below), we can just look at the respective line in the source code to see what it says. This is what I discovered when I did that:
base.rb:1094: in `perform_action_without_filters'" [is actually perform_action] filters.rb:620: in `perform_action_without_benchmark' [is actually perform_action_with_filters] benchmarking.rb:69: in `perform_action_without_rescue'" [is actually perform_action_with_benchmark] rescue.rb:85: in `perform_action'" [is actually perform_action_with_rescue]
For example, even if the method was defined as perform_action_with_filters, the backtrace may report it as "perform_action_without_benchmark" (or some other "_without_/code>" method).
"rails/actionpack/lib/action_controller/filters.rb:620:in `perform_action_without_benchmark'"
Fortunately, we can always just look at the respective line it gives us (line 620 in filters.rb) in the source code to discover the real name of the method (in this case it was perform_action_with_filters).
It can be helpful to put each feature in its own file so that one can figure out the name of the method (even if it has been renamed) by looking at the filename). This is the convention Rails uses. So if the backtrace says filters.rb:620:in `perform_action_without_benchmark', we don't even have to open the file and go to line 620. We can just drop the _without_benchmark part from the method name and add _with_filters (getting the filters part straight from the filename, filters.rb).
alias_method_chain can accept a blockCurrently (2007-04-05 10:15), only the ActiveSupport version accepts blocks, not the Facets version.
Here is an example of how you might use that...
./activesupport/lib/active_support/deprecation.rb:85
# Declare that a method has been deprecated.
def deprecate(*method_names)
options = method_names.last.is_a?(Hash) ? method_names.pop : {}
method_names = method_names + options.keys
method_names.each do |method_name|
alias_method_chain(method_name, :deprecation) do |target, punctuation|
class_eval(<<-EOS, __FILE__, __LINE__)
def #{target}_with_deprecation#{punctuation}(*args, &block)
::ActiveSupport::Deprecation.warn(self.class.deprecated_method_warning(:#{method_name}, #{options[method_name].inspect}), caller)
#{target}_without_deprecation#{punctuation}(*args, &block)
end
EOS
end
end
end