4

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?

4
  • Also see railstips.org/blog/archives/2009/05/15/… Commented Sep 4, 2019 at 11:14
  • @Kris yes I did read it already, but first that is a case of a module define within another (not sure it's important) and more than that, it's not about extending (in any way) a method that has to be imported as "extended" (requirement for memoist) Commented Sep 4, 2019 at 12:52
  • 1
    I like that you've created a standalone example. It may be too far from reality, however. I think you may need to give us an example that uses Memoist, so that we can see the actual problem you are having. Commented Sep 4, 2019 at 14:23
  • A module which is included in a class or another module is usually called a mixin. When you use extend it will create class, not instance, methods. Although you can also create class methods via include too using a bit of extra magic by the way of the included method which can then extend the target class with a nested module by convention called ClassMethods. Commented Sep 4, 2019 at 14:45

3 Answers 3

4

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
Sign up to request clarification or add additional context in comments.

3 Comments

Sorry but class C needs to be extended by module A as I put in the question, this is important. (Is a requirement of memoist, I don't want to extend on why this is so, but it is).
This does answer the question.
@estani please clarify then. You want to have methods from module A as instance methods as well as class methods? Maybe you should describe what you're trying to achieve using Memoist?
2

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  

Comments

1

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, ...]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.