开发者

How to resolve namespace clashes when including a module within a module in Ruby?

开发者 https://www.devze.com 2023-03-20 23:24 出处:网络
Here is what my code looks like: module A def foo puts \"A\" end end module B include A def bar foo end end 开发者_运维问答class MyClass

Here is what my code looks like:

module A
  def foo
    puts "A"
  end
end

module B
  include A
  def bar
    foo
  end
end

开发者_运维问答class MyClass
  include B
  def foo
    puts "X"
  end
  def self.test
    puts bar
  end
end

When I call "C.test" I get "X" instead of "A" (which is what I want) because the local definition of foo has overridden that in A. I can't change the signature of either foo's. I can only mainly edit my own class; I can edit modules A and B but lots of existing code use them and they are (so no changing foo to A.foo for instance). I am thinking of doing

class MyClass
  module MyModules
    include B
  end
  ....
    MyModules.bar
  ....
end

But this does not work.

How can I "localize" the namespace when doing include B?


From what i understand, you want to override A#foo (which is also used by B#bar) inside MyClass. However, you only want to override it inside MyClass, and not for the code in the B mixin.

The thing to understand here, is that when the code of the A and B mixins are run, they will be part of a MyClass instance. Therefore, it is impossible to override the instance methods of MyClass without affecting the mixins. It is as if the methods of the mixins were thrown into a bucket (the MyClass instance), and then run. They do not have separate scopes. Therefore, the simple answer is: no, you can not do it.

There could be several possible solutions though, many of which smell like pasta. Running into problems like this one could be an indication that the overall design decisions need some refactoring.

Going over the basics of modules:

module Numbered
  DEFAULT = "1234-AWESOME"
  def serial_number
    DEFAULT
  end
  def self.awesome?
    true
  end
end

Numbered.awesome? # => true
Numbered.serial_number # whoops! NoMethodError.

o = Object.new
o.extend(Numbered)
o.serial_number #=> "1234-AWESOME"

Numbered.extend(Numbered)
Numbered.serial_number # => "1234-AWESOME"

class Dog
  extend Numbered
end

Dog.serial_number # => "1234-AWESOME"
Dog::DEFAULT # => "1234-AWESOME"
fido = Dog.new
fido.serial_number # NoMethodError!

This is because the method was added as a class method. include to the rescue:

class Fish
  include Numbered

  def serial_number
    super + '-FSH'
  end
end

cod = Fish.new
cod.serial_number # => "1234-AWESOME-FSH"

So, include heaps all the methods together into one common instance, but we can still use super to call an included method that we were overriding. If you go for a solution on the pasta side, you might be able to use super inside the bar method, conditionally invoking the method you were overriding.

Also, depending on the case, you could set up a new module and extend it with the module containing the methods you need access too, like in the answer you added.


I found a solution for what I wanted, might not be the best answer but works exactly the way I want it to.

class MyClass
  class MyModules
    extend B
  end
  ....
    MyModules.bar
  ....
end

This effectively allows me to limit the scope of B and all other included modules in the local class MyModules. In my code it actually has a meaning because all the included modules handle expression manipulation so I can call the local class "Expr" (and they are seriously overusing the names 'eval', 'bind' and 'free' right now).

0

精彩评论

暂无评论...
验证码 换一张
取 消