estani
estani

Reputation: 26547

module extending other modules in Ruby

I have read quite a few answers here in this regards, but I still cant figure out why the following doesn't work

module A
  def a
    puts 'hi'
  end
end

module B
  extend A
end

class C
  extend B
  def b
    a
  end
end
C.new.b # undefined local variable or method `a' for #<C:...

I've also tried with:

module B
  def self.included(recipient)
    recipient.extend A
  end
end

Hoping C will get extended (but I guess the hierarchy is then wrong)

Important: The Problem is that I have a module that requires to be extended memoist, and I want to add some functionality to it.

How can I achieve that and why is the first example not working?

Upvotes: 4

Views: 1068

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110755

Recall that if a module M1 is included or extended (or prepended) by a module M2, M1's instance methods are brought into M2, either as instance methods (if including) or as module methods (if extending). If M1 contains module methods (methods invoked on M1) they are skipped over by both include and extend. As classes are modules, this applies when M2 is a class.

module M1
  def i() end
  def self.m() end
end

M1.instance_methods
  #=> [:i] 
M1.methods(false)
  #=> [:m] 

module M2; end

M2.extend M1
M2.instance_methods
  #=> []
M2.methods & [:i, :m]
  #=> [:i]

module M3; end

M3.include M1
M3.instance_methods
  #=> [:i]
M3.methods & [:i, :m]
  #=> []

We see that neither M2 nor M3 contains a method :m. Now consider the following.

module A
  def a
    puts 'hi'
  end
end

module B
  extend A
end

B.instance_methods
  #=> []
B.methods & [:a]
  #=> [:a]

As expected, B contains no instance methods and its module methods include :a. From the earlier discussion it follows that including or extending B into another module (or class) C brings no instance methods or methods into C.

To add to the functionality of A, B must include (or prepend) A.

module B
  include A
  def b
    puts 'ho'
  end
end

B.instance_methods
  #=> [:b, :a] 
B.methods & [:a, :b]
  #=> []

C may then include or extend B, depending on requirements.

One may ask whether there is any reason to define module methods if they are disregarded when the module is included or extended by another module. The answer is that they are simply helper methods that behave as functions in non-OOP languages. An example is the module Math, which contains module methods only. They are therefore invoked on the module; for example,

Math.sqrt(2)
  #=> 1.4142135623730951  

Upvotes: 2

3limin4t0r
3limin4t0r

Reputation: 21160

First of all your example is not entirely valid. A module that is extended doesn't add instance methods by default. Your A module should look something like this:

module Memoist
  def self.extended(mod)
    mod.include InstanceMethods
  end

  module InstanceMethods
    def a
      puts 'hi'
    end
  end
end

You can extend Memoist in an extended, included or excluded callback. This works as long as you're adding to the interface. The callbacks are triggered after your module is extended, included or prepended. This means that if you want to extend an existing method using super this won't work since the original is called before your version.

module YourMemoist
  # Depending on how you want to incorporate your module into the class
  # you can use one of the following: 
  def self.extended(mod)
    mod.extend Memoist
  end

  # def self.included(mod)
  #   mod.extend Memoist
  # end

  # def self.prepended(mod)
  #   mod.extend Memoist
  # end

  # ...
end

class C
  extend MemoistHelper # or include, prepend
end

C.new.a #=> prints hi
C.singleton_class.ancesotrs #=> [#<Class:C>, Memoist, YourMemoist, ...]

If you do want to use the super keyword things are going to be a bit more difficult. Since the Memoist module needs to be extended first. One way of doing this by including the module into your own, so you can override them. Pretty simple so far. However Memoist might have an extended callback defined (to add instance methods), which is not triggered when we include the module in our own. So we need to call this manually.

module YourMemoist
  include Memoist

  def self.extended(mod)
    Memoist.send(:extended, mod) # using #send to call private method
    # If you want to add your own instance methods add them after the
    # above call (as shown in the Memoist module).
  end

  # ...
end

class C
  extend MemoistHelper
end

C.new.a #=> prints hi
C.singleton_class.ancesotrs #=> [#<Class:C>, YourMemoist, Memoist, ...]

Upvotes: 1

Marek Lipka
Marek Lipka

Reputation: 51181

extends adds the methods from the module passed as argument as 'class methods'. What you're looking for is include, which adds methods from (in this case) A module as instance methods, just like you want it to:

module A
  def a
    puts 'hi'
  end
end

module B
  include A
end

class C
  include B
  def b
    a
  end
end
C.new.b
# hi

Upvotes: 4

Related Questions