szimek
szimek

Reputation: 6484

Including a module in other module

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

Answers (3)

Jörg W Mittag
Jörg W Mittag

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 included later, will not get considered.

Upvotes: 2

griswoldbar
griswoldbar

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

Shamith c
Shamith c

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

Related Questions