Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby metaprogramming: cannot send a method to a module

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.

like image 946
Trần Kim Dự Avatar asked Feb 17 '26 06:02

Trần Kim Dự


1 Answers

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
like image 151
Cary Swoveland Avatar answered Feb 19 '26 20:02

Cary Swoveland