问题
For example I have following custom class and module:
module SimpleModule
def hello_world
puts 'i am a SimpleModule method'
end
def self.class_hello_world
puts 'i am a SimpleModule class method'
end
end
class SimpleClass
def hello_world
puts 'i am SimpleClass method'
end
def self.class_hello_world
puts 'i am a SimpleClass class method'
end
end
I tried to called those methods inside class and module by using method send
SimpleClass.send(class_hello_world) # work
SimpleClass.new.send(hello_world) # work
SimpleModule.send(class_hello_world) # work
SimpleModule.new.send(hello_world) # not work
SimpleModule.send(hello_world) # not work
In other word, I don't know how to invoke hello_world
from SimpleModule
. It is possible if that method is defined with self before.
I need to do this because I want to implement a "custom-include": that include all methods from module to another class.
Please tell me how to do this.
回答1:
The five statements
Let's consider those five statements one at a time (but in a different order than as presented). Note that send
's argument must be the name of the method expressed as a string or symbol.
SimpleModule.send("class_hello_world")
# i am a SimpleModule class method
This is normal, though such methods are normally called module methods. Some common built-in modules, such as Math, contain module methods only.
SimpleClass.send(:class_hello_world)
# i am a SimpleClass class method
Since a class is a module, the behaviour is the same as above. class_hello_world
is usually referred to as a class method.
SimpleClass.new.send(:hello_world)
# i am SimpleClass method
This is the normal invocation of an instance method.
SimpleModule.send("hello_world")
#=> NoMethodError: undefined method `hello_world' for SimpleModule:Module
There is no module method hello_world
.
SimpleModule.new.send(hello_world)
#=> NoMethodError: undefined method `new' for SimpleModule:Module
One cannot create an instance of a module.
include
vs prepend
Suppose one wrote
SimpleClass.include SimpleModule
#=> SimpleClass
SimpleClass.new.hello_world
# i am SimpleClass method
so SimpleClass
' original method hello_world
is not overwritten by the module's method by the same name. Consider SimpleClass
' ancestors.
SimpleClass.ancestors
#=> [SimpleClass, SimpleModule, Object, Kernel, BasicObject]
Ruby will look for hello_world
in SimpleClass
--and find it--before considering SimpleModule
.
One can, however, use Module#prepend to put SimpleModule#hello_world
before SimpleClass#hello_world
.
SimpleClass.prepend SimpleModule
#=> SimpleClass
SimpleClass.new.hello_world
# i am a SimpleModule method
SimpleClass.ancestors
#=> [SimpleModule, SimpleClass, Object, Kernel, BasicObject]
Binding unbound methods
There is one other thing you do. SimpleModule
's instance methods (here just one) are unbound. You could use UnboundMethod#bind to bind each to an instance of SimpleClass
and then execute it using call
or send
.
sc = SimpleClass.new
#=> #<SimpleClass:0x007fcbc2046010>
um = SimpleModule.instance_method(:hello_world)
#=> #<UnboundMethod: SimpleModule#hello_world>
bm = um.bind(sc)
#=> #<Method: SimpleModule#hello_world>
bm.call
#=> i am a SimpleModule method
sc.send(:hello_world)
#=> i am a SimpleModule method
回答2:
Module's can't have instance methods because they aren't classes. If you define an instance method in a module it is called a mixin. These "mixins" can be included in another class and are then available for use.
The full explanation is here in the docs
Edit:
For example, you could do something like this:
module SimpleModule
def hello_world
p 'hello world'
end
end
class Example
include SimpleModule
end
Example.new.send(:hello_world)
This is one way to call the mixin of a module.
回答3:
Since you seem to want to create your own version of include.
Take a look at Module.append_features
documentation
When this module is included in another, Ruby calls append_features in this module, passing it the receiving module in mod. Ruby's default implementation is to add the constants, methods, and module variables of this module to mod if this module has not already been added to mod or one of its ancestors. See also Module#include.
So this is what you have to do if you want to rewrite include
yourself.
And since you're adventurous, maybe you will enjoy reading Ruby's source code to see how exactly this is implemented internally. See https://github.com/ruby/ruby...class.c#L853:L934
回答4:
You also can use constantize
:
def call_method(method)
"SimpleModule::#{method}".constantize
end
来源:https://stackoverflow.com/questions/42036886/ruby-metaprogramming-cannot-send-a-method-to-a-module