Reputation: 26547
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
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
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
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