2

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.

Trần Kim Dự
  • 5,872
  • 12
  • 55
  • 107
  • `SimpleModule.new.send(hello_world)` won't work because you just can't have an instance of a module. – Ed de Almeida Feb 04 '17 at 05:53
  • @EddeAlmeida thanks. So Ruby implement keyword "include" by its language or I can do a similar things by using metaprogramming ? If metaprogramming possible, should have a way for call method from module. – Trần Kim Dự Feb 04 '17 at 06:00

4 Answers4

5

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
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • thanks. I understand the problem I have met now is because I cannot initialize a module. I tried to proof-of-concept about implement again Ruby keyword **include**. So if I cannot call method on module like this, can I do by some way ? – Trần Kim Dự Feb 04 '17 at 06:02
  • thanks for **prepend** keyword. I don't know this before :D but anyway, they still little like **include** in term of language-based keyword. ( so I cannot implement this on my own) :D – Trần Kim Dự Feb 04 '17 at 06:35
  • Didn't know about `prepend`, I could see that causing a lot of headaches if it was misused :D. Thanks for the writeup! – trueinViso Feb 04 '17 at 06:37
  • 1
    "I could see how ___ could cause a lot of headaches if it were misused." Ruby--any programming language--provides many, many ways to fill in that blank. – Cary Swoveland Feb 04 '17 at 06:44
  • Please refer to this [link](http://stackoverflow.com/questions/42039474/ruby-metaprogramming-how-can-make-module-method-see-classs-variable/42045992?noredirect=1#comment71272550_42045992) as how to implement custom include/extend. I have learnt many thing from this post :D – Trần Kim Dự Feb 05 '17 at 09:27
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.

trueinViso
  • 1,354
  • 3
  • 18
  • 30
  • thanks. I know "include" keyword. Can I implement "include" on my own ? no real world application, just I want to demonstrate concept but I stuck at call method from module. thanks – Trần Kim Dự Feb 04 '17 at 05:59
  • I added an example of how you could include the module in a class and then call send on the class to invoke the module method. – trueinViso Feb 04 '17 at 06:17
2

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

akuhn
  • 27,477
  • 2
  • 76
  • 91
  • Thanks for linking to source code. it's great. About append, I see I looks like "include", just a little different in behavior compare to include. So I don't think using append is a way for rewrite include :D – Trần Kim Dự Feb 04 '17 at 11:40
  • 1
    about link to source code. How can you find this ? Are you a contributor of Ruby? I am just little curious. So I can know how can I do same thing in the future. thanks – Trần Kim Dự Feb 04 '17 at 11:42
  • I use `pry` and `pry-docs` gem to locate source code, the `$` command shows the source code. And then dig further using search at https://github.com/ruby/ruby – akuhn Feb 07 '17 at 04:35
0

You also can use constantize:

def call_method(method)
  "SimpleModule::#{method}".constantize
end
Jon Maciel
  • 106
  • 1
  • 3