rubymetaprogrammingalias-methodclass-eval

ruby method_alias in inherited class


I'm deeping into ruby metaprogramming and have next question. Example:

module ExampleAliaser
  def do_example_alias(prefix=:origin)

    class_eval  <<-EOS
       class << self 
           alias_method :#{prefix}_example, :example
           def example
              puts "in aliase will call :#{prefix}_example"
              #{prefix}_example
           end  
        end
    EOS

  end   
end  

class Example1
 def self.example
    puts "Example"
 end 
end


Example1.extend(ExampleAliaser)

class Example1 
 do_example_alias(:origin)
end
class Example2 <  Example1
 do_example_alias(:origin)
end



     Example1.example
    in aliase will call :origin_example
    Example
     => nil 

     Example2.example
in aliase will call :origin_example
in aliase will call :origin_example
in aliase will call :origin_example
    SystemStackError: stack level too deep
        from /Users/igorfedoronchuk/.rvm/rubies/ruby-1.9.2-p180/lib/ruby/1.9.1/irb/workspace.rb:80
    Maybe IRB bug!!

So when mixin used 2 times it causes error. What is the best way to fix such things? How to determine that mixing exists and remove it before new mixing


Solution

  • Follow the definition of methods to see why this is happening.

    You first define Example1::example in the class definition of Example1. It writes a string to the console.

    Then you extend ExampleAliaser. When you call Example1::do_example_alias, you then alias the method example to origin_example and redefine the method example to write a different string to the console and call origin_example.

    Then you define the class Example2 to inherit from Example1, which now has two methods defined on it: origin_example and example. When you call Example2::do_example_alias, you alias the method example to origin_example. But remember that example was already redefined to call origin_example. So effectively, Example2::example will call itself until you run out of room on the stack.


    If you want to avoid double-aliasing, you could include some kind of guard in do_example_alias:

    def do_example_alias(prefix = :origin)
      unless methods.include?("#{prefix}_example")
        # do the aliasing
      end
    end
    

    You can also undef :method_name in subclasses to remove methods that you no longer want defined.