Reputation: 650
I have a module:
module MM::NN
def test_method
puts "module method called"
end
end
I am trying to include this module in a class A
per instance depending on a parameter passed to the initializer of the instance:
class A
def initialize(add_module)
self.class.send(:include, MM::NN) if add_module
end
end
I expected:
A.new(true).test_method # >> module method called
A.new(false).test_method # >> NoMethodError
But test_method
is defined on all instances. Calling the class with argument true
adds the module to the instances created later as well. I got:
A.new(true).test_method # >> module method called
A.new(false).test_method # >> NoMethodError
Initializing the class with false
argument on first initializer call and true
in the second will give the desired result, as test_method
method is added later:
A.new(false).test_method # >> NoMethodError
A.new(true).test_method # >> module method called
because the module is attached to the class itself.
How can I make the method available on specific cases like above?
How do i solve an another case:
where class A
inherit properties and methods of another class B
and class B
as well contains test_method
. How is it possible to use the module TestModule
s test_method
instead of method inside the class B
.
And what are the differences(or)side-effects in calling methods by including a module inside a class:
1) include module outside initialize.
2) Include module inside initialize.
Upvotes: 3
Views: 1015
Reputation: 26768
When you call self.class.send(:include, MM::NN)
, that's making a irreversible change to the class (the blueprint for all instances).
If you want a module to only be included for an instance, you have to use the singleton class (What exactly is the singleton class in ruby?):
module TestModule
def test_method; "ok"; end
end
class A
def initialize(add_module)
singleton_class.include(TestModule) if add_module
end
end
A.new(true).test_method # module method called
A.new(false).test_method # NoMethodError
Note that I changed display
to test_method
, because display
is a built in Kernel method so it won't raise NoMethodError even if you don't include the module on the instance.
Note, Ozer's answer is the same thing but using a different syntax.
-- edit in response to comment --
Adding methods on the singleton class takes priority over those added to the regular class, and if you include multiple modules onto the singleton class, the last on will take priority:
module A; def fn; 1; end; end
module B; def fn; 2; end; end
module C; def fn; 3; end; end
class D
include A
def initialize(add_b, add_c)
singleton_class.include(B) if add_b
singleton_class.include(C) if add_c
end
end
puts D.new(false, false).fn # => 1
puts D.new(true, true).fn # => 3
Upvotes: 7
Reputation: 79733
include
adds all the methods from the module to any instances of the class that includes the module. To add the methods to an individual object you can use extend
:
class A
def initialize(add_module)
extend(MM::NN) if add_module
end
end
Upvotes: 5
Reputation: 4657
You could add it to the singleton class as others suggest above. But I would suggest that the fact that OOP constructs are making your task hard is because you are approaching the problem from a bad angle. I think having instances of class A
behave so differently by having different methods and ancestors is going to cause a lot more confusion than it solves.
What I'd suggest instead is that you try to accomplish what you are doing using composition instead of inheritance. That is, have an instance variable inside class A
that contains the behavior in theMM:NN
. This depends on the specifics of the behavior in that module, but could roughly look like this:
# This class is here because I'm assuming you can't make `MM::NN` a class in its own right. However it probably needs additional data or methods to actually work with the MM::NN methods
class NN_implementation
include MM::NN
end
class A
def initialize(use_module)
# if this class needs access to data or methods from A, you could add `self` as an input arg to the constructor of `NN_implementation`
@nn = NN_implmenetation.new if use_module
end
def has_nn?
[email protected]?
end
end
Then you can either (1) expose the specific NN
methods you want as methods on class A
, or you can expose the nn
instance var using attr_reader
and call something like my_a_instnace.nn.some_nn_method if my_a_instance.has_nn?
. Again, largely depends on the specifics of A
and MM::NN
how I would ultimately code this up for prod-ready code, but something along these lines keeps class definitions clean and makes it not confusing where specific behavior comes from.
Upvotes: 1
Reputation: 31
This will add it to just the instance that is being initialized:
class A
def initialize(add_module)
if add_module
(class <<self; include MM::NN; end)
end
end
end
Upvotes: 2