Reputation: 6484
module A; def a; end; end
module B; def b; end; end
class C; include A; end
module A; include B; end
class D; include A; end
C.new.b # undefined method error
D.new.b # nil
C.ancestors # [C, A, Object...]
D.ancestors # [D, A, B, Object...]
How to include module B inside of A, so that classes that already include module A will also get methods from module B?
Upvotes: 7
Views: 2384
Reputation: 369458
You can't.
When you include
a mixin M
into a class C
, Ruby creates a new class ⟦M′⟧
whose method table points to the method table of the mixin M
and whose superclass is the superclass of C
, then makes this class the new superclass of C
. This is repeated for every mixin that was mixed into M
.
Note that this algorithm is run only once, when you mix M
into C
. Modules that get include
d later, will not get considered.
Upvotes: 2
Reputation: 499
As has already been stated, Ruby doesn't work like this - when a class includes a module, it doesn't maintain any reference to the instance of that module so if the module includes other modules, the classes that have already included it won't know about the change.
You could get around this by storing the classes that include the module in the module itself - that way you can update them whenever the module includes another module, i.e. something like this:
module A
class<<self
attr_accessor :observers
end
self.observers = []
def a
"#{self} successfully called a"
end
def self.included(base)
self.observers << base unless self.observers.include?(base)
end
def self.include(mod)
observers.each {|o| o.send(:include,mod) }
super
end
end
module B
def b
"#{self} successfully called b"
end
end
class C
include A
end
module A
include B
end
class D
include A
end
module E
def e
"#{self} successfully called e"
end
end
module A
include E
end
puts C.new.b
puts D.new.b
puts C.new.e
puts D.new.e
Not sure this is the direction you want to take, but shows that it can be done in principle.
Upvotes: 3
Reputation: 3739
You should Include B in A before class C; include A; end.
module A; def a; end; end
module B; def b; end; end
module A; include B; end
class C; include A; end
class D; include A; end
p C.new.b # nil
p D.new.b # nil
p C.ancestors # [C, A, B, Object, Kernel, BasicObject]
p D.ancestors # [D, A, B, Object, Kernel, BasicObject]
Edit
module A; def a; end; end
module B; def b; end; end
class C; include A; end
module A; include B; end
class D; include A; end
C.send(:include, A)
p C.new.b # nil
p D.new.b # nil
p C.ancestors # [C, A, Object...]
p D.ancestors # [D, A, B, Object...]
Upvotes: 1