Ruby metaprogramming: cannot send a method to a module

╄→尐↘猪︶ㄣ 提交于 2019-12-24 00:55:08

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!