Ruby: define_method vs. def

RubyMetaprogramming

Ruby Problem Overview


As a programming exercise, I've written a Ruby snippet that creates a class, instantiates two objects from that class, monkeypatches one object, and relies on method_missing to monkeypatch the other one.

Here's the deal. This works as intended:

class Monkey

  def chatter
    puts "I am a chattering monkey!"
  end

  def method_missing(m)
    puts "No #{m}, so I'll make one..."
    def screech
      puts "This is the new screech."
    end
  end
end

m1 = Monkey.new
m2 = Monkey.new

m1.chatter
m2.chatter

def m1.screech
  puts "Aaaaaargh!"
end

m1.screech
m2.screech
m2.screech
m1.screech
m2.screech

You'll notice that I have a parameter for method_missing. I did this because I was hoping to use define_method to dynamically create missing methods with the appropriate name. However, it doesn't work. In fact, even using define_method with a static name like so:

def method_missing(m)
  puts "No #{m}, so I'll make one..."
  define_method(:screech) do
    puts "This is the new screech."
  end
end

Ends with the following result:

ArgumentError: wrong number of arguments (2 for 1)

method method_missing	in untitled document at line 9
method method_missing	in untitled document at line 9
at top level	in untitled document at line 26
Program exited.

What makes the error message more bewildering is that I only have one argument for method_missing...

Ruby Solutions


Solution 1 - Ruby

define_method is a (private) method of the object Class. You are calling it from an instance. There is no instance method called define_method, so it recurses to your method_missing, this time with :define_method (the name of the missing method), and :screech (the sole argument you passed to define_method).

Try this instead (to define the new method on all Monkey objects):

def method_missing(m)
    puts "No #{m}, so I'll make one..."
    self.class.send(:define_method, :screech) do
      puts "This is the new screech."
    end
end

Or this (to define it only on the object it is called upon, using the object's "eigenclass"):

def method_missing(m)
    puts "No #{m}, so I'll make one..."
    class << self
      define_method(:screech) do
        puts "This is the new screech."
      end
    end
end

Solution 2 - Ruby

self.class.define_method(:screech) doesn't work,because define_method is private method you can do that

class << self
    public :define_method
end
def method_missing(m)
puts "No #{m}, so I'll make one..."
Monkey.define_method(:screech) do
  puts "This is the new screech."
end

Solution 3 - Ruby

def method_missing(m)
    self.class.class_exec do
       define_method(:screech) {puts "This is the new screech."}
    end 
end

screech method will be available for all Monkey objects.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongauthView Question on Stackoverflow
Solution 1 - RubyAvdiView Answer on Stackoverflow
Solution 2 - RubyclfView Answer on Stackoverflow
Solution 3 - RubyAndrewView Answer on Stackoverflow