my situation is the current:
module Base
module A
include Base::B
end
end
module Base
module B
module_function
def my_method
...
end
end
end
And then my test:
Rspec.describe Base::A do
describe '.my_method' do
it 'does something'
...
end
end
end
Problem:
NoMethodError. undefined method `my_method' for Base::A:Module
What am I missing? Thanks in advance!
CodePudding user response:
module Base
module B
module_function
def my_method
"you've found me!"
end
end
end
module Base
module A
include Base::B
end
end
module_function
lets us call our method on the module by defining a singleton method:
>> Base::B.singleton_methods
=> [:my_method]
>> Base::B.my_method
=> "you've found me!"
it also turns my_method
into a private method, this is the method you get when "including" or "extending":
>> Base::B.private_instance_methods
=> [:my_method]
# NOTE: we can call this version of `my_method` by including or
# extending the module in a class
class Included
include Base::B
end
>> Included.private_instance_methods
=> [:my_method, ...]
>> Included.new.send(:my_method)
=> "you've found me!"
class Extended
extend Base::B
end
# this would be a private class method
>> Extended.private_methods
=>[:my_method, ...]
>> Extended.send(:my_method)
=> "you've found me!"
Same rules apply if Base::B
is included in Base::A
module:
>> Base::A.private_instance_methods
=> [:my_method]
# NOTE: we cannot call `my_method` on the module, because we do not get a
# singleton method
>> Base::A.singleton_methods
=> []
# there is the expected instance method, made private by using `module_function`
>> Base::A.private_instance_methods
=> [:my_method]
# only we can't call `.new` on a module.
>> Base::A.new.send(:my_method)
`<main>': undefined method `new' ...
Since we can't initialize an instance out of the module, we can bump the instance method to a class method by extending Base::A
with self
.
module Base
module A
include Base::B
extend self
end
end
>> Base::A.private_methods
=> [:my_method, ...]
>> Base::A.send(:my_method)
=> "you've found me!"
or extend Base::B
from the start:
module Base
module A
extend Base::B
end
end
>> Base::A.send(:my_method)
=> "you've found me!"
module_function
does the equivalent of this:
module Base
module C
# NOTE: singleton method stays in this module
def self.my_method
"singleton my_method"
end
private
# NOTE: instance method gets included/extended
def my_method
"private instance my_method"
end
end
end
Base::C.my_method # => "singleton my_method"
Module.new.extend(Base::C).send(:my_method) # => "private instance my_method"
Class.new.extend(Base::C).send(:my_method) # => "private instance my_method"
Class.new.include(Base::C).new.send(:my_method) # => "private instance my_method"
https://ruby-doc.org/core-3.1.2/Module.html#method-i-module_function
https://ruby-doc.org/core-3.1.2/Module.html#method-i-include