Home > Mobile >  Include a Ruby module but change the root module reference
Include a Ruby module but change the root module reference

Time:11-04

Given a Module Foo, I would like to extend this module, but not preserve the module name Foo.

# sscce (irb)
module Foo
  class Foo; end
end

module Bar
  include Foo
end

> Foo::Foo
Foo::Foo
> Bar.ancestors
[Bar, Foo]
> Bar::Foo
Foo::Foo

How can I get Bar::Foo to be Bar::Foo (not Foo::Foo) without redefining the class?

I've attempted include and prepend as well as include Foo.clone with the same result. I think it's safe to assume that it's referencing the original class definition, fine, but I'd still like to have the class Foo actually belong to Bar.

CodePudding user response:

maybe something like this could work?

module Foo
  class Foo
    def foo
      puts "inside: #{self.class}"
    end
  end
end

module Bar
  class Foo < ::Foo::Foo; end
end

now when you do:

a = Foo::Foo.new
a.foo # inside: Foo::Foo

and if you do

b = Bar::Foo.new
b.foo # inside: Bar::Foo

instead of trying to include/extend Foo in a tricky way just create a new class inside the Bar module and rely on inheritance?

CodePudding user response:

You cannot make a class belong to a module by inclusion. Even more - a class doesn't belong to a module even without inclusion (only a reference to a class is available as a module constant). When you import Foo to Bar, you bring there the constants and instance methods from Foo, that's why you have access to Foo::Foo by using the Bar::Foo constant and it points to the uniquely identified class Foo::Foo.

If you want to have the class Bar::Foo, it will be a different class uniquely identified by this "address". And if you want to have it identical to Foo::Foo, you'll have to use the included hook in module Foo and then do some heavy metaprogramming stuff to create a new class from the old one and assign it to a constant with the same name from the new module.

Or, maybe it could be enough just to clone the class:

module Foo
  class Foo; end

  def included(mod)
    mod.const_set('Foo', self::Foo.clone)
  end
end

module Bar
  include ::Foo
end

obj1 = Foo::Foo.new
obj2 = Bar::Foo.new
Foo::Foo == Bar::Foo  # false

But I'm not sure if cloning is enough for all the cases.

  • Related