How do I declare a method dynamically with method_missing?
Asked Answered
J

3

5

I have a ruby program, and I want to accept the user's made up method, and make a new method out of that name. I have tried this:

def method_missing(meth,*args,&block)
  name = meth.to_s
  class << self
    define_method(name) do
      puts "hello " + name
    end
  end
end

And I get the following error:

`define_method': interning empty string (ArgumentError) in 'method_missing'

Any ideas? Thanks.

Edit:

I got it working a different way, but I'm still curious how to do it this way. Here is my code:

def method_missing(meth,*args,&block)
  Adder.class_eval do
    define_method(meth) do
      puts "hello " + meth
    end
  end
  send("#{meth}")
end
Jorry answered 10/11, 2011 at 5:12 Comment(0)
P
13

The variable name is not available inside the class definition (class << self) scope. It isn't throwing a NameError because you've overridden method_missing.

To do what you're trying to do, you need to keep the scope with name. In order to do that, you have to only use block-based methods (e.g. class_eval) instead of directly opening the class, so something like this:

def method_missing(meth,*args,&block)
  name = meth.to_s
  eigenclass = class << self; self; end
  eigenclass.class_eval do
    define_method(name) do
      puts "hello " + name
    end
  end
end

But actually, the symbol in meth is quite sufficient — you don't need name at all. (Though you'll still need the above technique either way.) On top of that, though, you want to execute the method immediately. The simplest way would just be to resend the message:

def method_missing(meth,*args,&block)
  eigenclass = class << self; self; end
  eigenclass.class_eval do
    define_method(meth) do
      puts "hello #{meth}"
    end
  end
  send(meth, *args, &block)
end
Pellucid answered 10/11, 2011 at 5:59 Comment(2)
@Aditya Sanghi: Thanks for the correction. But I actually meant to switch to interpolation, which is just all-around better (faster, less garbage, reads cleaner and works with symbols!).Pellucid
instead of eigenclass = class << self; self; end you can simply do - self.class.class_evalWanton
S
1

The problem is that class << self doesn't act as a closure, which means the variable name won't be available inside of the method definiton.

On the other hand, when you use class_eval, you're passing a block (Proc), which is a closure, which means all of the local variables from the current binding will be available inside the block body.

Sidra answered 10/11, 2011 at 16:2 Comment(0)
S
0

Another way to define methods dynamically when method_missing is called is to call define_singleton_method.

class Person
  def method_missing(method_name, *args, &block)
    define_singleton_method(method_name) do
      puts "I'm a new method called #{__method__}"
    end
    # Add this is you want to automatically call the new method
    public_send(method_name)
  end

  # Add this for proper introspection.
  def respond_to_missing?(method_name, include_private = false)
    true
  end
end

This will give you

Person.new.test_method # => "I'm a new method called test_method"

Note: The solution is slightly different to the other solutions proposed, in that it only defines the method on the current instance and not the class. New instances of the same class will not have the newly defined method; but this is usually what people are aiming to do when defining new methods within method_missing.

Stubble answered 31/12, 2021 at 10:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.