How to work through name collisions in ruby

1.2k Views Asked by At

Two modules Foo and Baa respectively define a method with the same name name, and I did include Foo and include Baa in a particular context.

When I call name, how can I disambiguate whether to call the name method of Foo or Baa?

4

There are 4 best solutions below

0
On

Only the order of modules inclusion decides which one will get called. Can't have both with the same name - the latter will override the former.

Of course, you can do any tricks, just from the top of my head:

module A
  def foo
    :foo_from_A
  end
end

module B
  def foo
    :foo_from_B
  end
end

class C
  def initialize(from)
    @from = from
  end

  def foo
    from.instance_method(__method__).bind(self).call
  end

  private

  attr_reader :from
end

C.new(A).foo #=> :a_from_A
C.new(B).foo #=> :a_from_B

But that's no good for real life use cases :)

8
On

Provided none of the methods of Foo or Baa call name (which seems a reasonable assumption), one can simply create aliases.

module Foo
  def name; "Foo#name"; end
end

module Baa
  def name; "Baa#name"; end
end

class C
  include Foo
  alias :foo_name :name
  include Baa
  alias :baa_name :name
  undef_method :name
end

c = C.new
c.foo_name
  #=> "Foo#name"
c.baa_name
  #=> "Baa#name"

C.instance_methods & [:foo_name, :baa_name, :name]
  #=> [:foo_name, :baa_name]

The keyword alias is documented here. One may alternatively use the method #alias_method. See this blog for a comparison of the two.

Module#undef_method is not strictly necessary. It's just to ensure that an exception is raised if name is called.

0
On

Technically, there is no name collision because the method foo is redefined.

In the following exemple, A.foo is redefined and is never called

module A
  def foo
    raise "I'm never called"
  end
end

module B
  def foo
    puts :foo_from_B
  end
end

class C
  include A
  include B
end

C.new.foo
# =>
# foo_from_B

If you write A and B module, you can use super to call previous definition of foo. As if it where an inherited method.

module A
  def foo
    puts :foo_from_A
  end
end

module B
  def foo
    super
    puts :foo_from_B
  end
end

class C
  include A
  include B
end

C.new.foo
# =>
# foo_from_A
# foo_from_B

There are side effects and I would not use this but this is doing the trick :

module A
  def foo
    puts :foo_from_A
  end
end

module B
  def foo
    puts :foo_from_B
  end
end


class C
  def self.include_with_suffix(m, suffix)
    m.instance_methods.each do |method_name|
      define_method("#{method_name}#{suffix}", m.instance_method(method_name))
    end
  end
  include_with_suffix A, "_from_A"
  include_with_suffix B, "_from_B"
end

c= C.new
c.foo_from_A
c.foo_from_B
begin
  c.foo
rescue NoMethodError
  puts "foo is not defined"
end
# =>
# foo_from_A
# foo_from_B
# foo is not defined
2
On

You should definetely read about method lookups.

Anyway, I would do it this way:

module Foo
  def name
    :foo
  end
end

module Bar
  def name
    :bar
  end
end

class MyClass
  include Foo
  include Bar

  def foo_name
    Foo.instance_method(:name).bind(self).call
  end

  def bar_name
    Bar.instance_method(:name).bind(self).call
  end

  #
  # or even like this: obj.name(Foo)
  #

  def name(mod)
    mod.instance_method(:name).bind(self).call
  end
end

BTW if you are using Module#instance_method and UnboundMethod#bind you don't really need to include specific module. This code works:

Foo.instance_method(:name).bind('any object (e.g. string)').call