66

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...

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
gauth
  • 908
  • 1
  • 7
  • 11

3 Answers3

139

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
ryenus
  • 15,711
  • 5
  • 56
  • 63
Avdi
  • 18,340
  • 6
  • 53
  • 62
  • 1
    That's a great answer, Avdi, and it clears up some other questions that I'd had. Thank you. – gauth Oct 09 '08 at 11:40
  • 13
    As a general rule, this shows why you should always a) use a whitelist in `method_missing`, so that you *only* handle those methods that you are *actually* interested in and b) forward everything you do *not* want to handle "up the food chain", using `super`. – Jörg W Mittag Dec 04 '09 at 06:46
  • 2
    @JörgWMittag may i have some more words on the line *b) forward everything you do not want to handle "up the food chain", using super. ?* – Arup Rakshit Aug 25 '13 at 11:50
4

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
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
4
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.

Craig Walker
  • 49,871
  • 54
  • 152
  • 212
Andrew
  • 41
  • 1