Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pick the method to call when there is mixin method name conflict?

Tags:

ruby

When you include module in class with method name conflict, it would use method defined by the class. Is there a way to choose which one I want to run?

module B
  def self.hello
    "hello B"
  end
end

class A
  include B
  def self.hello
     "hello A"
  end
end

A.hello #=> this prints "hello A", what if I want "hello B"?
like image 858
benzhang Avatar asked Feb 01 '12 19:02

benzhang


3 Answers

Ben, when you call a method (say it's hello) in Ruby, this is what happens:

  1. If the receiver's eigenclass has a method called hello, it will be called. If not:
  2. If the receiver's class has an instance method called hello, it will be called. If not:
  3. If any module included by the receiver's class has an instance method called hello, it will be called. If there are more than one, the most recently included module will "win". Otherwise:
  4. If the superclass of the receiver's class has an instance method called hello, it will be called. If not:
  5. If any module included by the superclass of the receiver's class...
  6. (And so on for the superclass of the superclass, all the way to BasicObject...)
  7. If no method called hello is found, the same process is repeated with method_missing, starting from the eigenclass, then the class, then included modules, then the superclass, then modules included by the superclass... this search always succeeds, because Kernel defines a default implementation of method_missing.

So to answer your question, if there is more than one method called hello, you can't choose which one will be invoked. The method lookup rules described above decide which one will be invoked.

HOWEVER: Ruby is a very flexible language, and if you post more details about what you want to do and why, I can probably help you think of a way to simulate the desired effect.

Another point: if you want to add class methods from a module to a class, you can do this:

module B; def hello; "hello B"; end end
A.extend(B)
A.hello
=> "hello B"

Do you see? When a class includes a module, the module's instance methods become instance methods on the class. When a class extends a module, the module's instance methods become class methods on the class.

like image 98
Alex D Avatar answered Sep 24 '22 01:09

Alex D


The way it's set up it'd never call the included module's method anyway. Try leaving out the hello method in A and see what happens when you call A.hello.

This will include the method from B. There's probably a more succinct way to do this. I just cribbed it from my codebase:

module B
  def self.included(base)
    base.extend Greetings
  end

  module Greetings
    def hello
      "hello B"
    end
  end
end

The module's method will always override the included module's method. That's the way it's supposed to work. However, you could conditionally return A's hello value like this:

module A
  include B

  def self.hello
    if show_hello_a?
      "hello A"
    else
      super
    end
  end

 def self.show_hello_a?
   false # insert an actual condition statement here
 end

end
like image 35
Tom L Avatar answered Sep 23 '22 01:09

Tom L


When you call A.Hello all that is happening is the message "hello" is being passed to the A object. The A object then must determine how to handle that message. It will first look at it's own methods to determine if it has one called "hello" before looking to it's parents and included modules for a "hello" method.

While you could technically use A.ancestors to see that B is an ancestor of A, and call it's hello method, this would be violating the abstraction of A as an object.

The correct way to allow both methods to be called would be to create another method in A that calls B.hello, or to name A.hello something else so that it wouldn't override the functionality of B.hello.

Edit: Because you have included B in A, creating a method that calls B's hello within A is as simple as adding a method that calls B.hello

def self.hello2
  B.hello
end
like image 36
Andrew B Avatar answered Sep 26 '22 01:09

Andrew B