2

I've found an oddness in Ruby. Using method_missing/respond_to_missing? to dynamically handle method calls initiated via __send__ fails when Kernel defines the same method:

class Testerize

  def method_missing(method, *args, &block)
    if method == :system
      puts 'yay'
    else
      super
    end
  end

  def respond_to_missing?(method, internal)
    return true if method == :system
  end

end

t = Testerize.new
puts t.respond_to?(:system)
# Prints true
t.system
# Prints 'yay'
t.__send__(:system)
# Exception: wrong number of arguments (ArgumentError)

Kernel.system is somehow getting in the mix. Does anyone know what's going on here? I would have expected the :system "message" to get posted to the Testerize instance, hit method_missing, and voila. Why isn't my method_missing getting called when using __send__ when it is with direct invocation?

I'm using Ruby 1.9.3, if that is relevant.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Irongaze.com
  • 1,639
  • 16
  • 22

2 Answers2

3
with '__send__' or 'send' we can even call private methods of the object.

In your script do:

t.private_methods.grep /system/

You will see system method, while with

t.methods.grep /system/

you will see an empty array.

__send__ tries to call the private method inherited from Kernel in the inheritance chain, hence instead of using __send__ use Ruby's public_send method.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Sachin Singh
  • 7,107
  • 6
  • 40
  • 80
  • Thanks Sachin. That's it exactly. I'd like to give Zach the accept, since he beat you to it by a minute or two, but yours is an excellent answer. – Irongaze.com Jul 11 '13 at 20:52
1

If you look up the ancestor chain of your class (Testerize.ancestors), you will find:

[Testerize, Object, Kernel, BasicObject]

Since new classes inherit from Object by default, and Object inherits from Kernel, all of the instance methods available to Kernel are available in your instances.

Zach Kemp
  • 11,736
  • 1
  • 32
  • 46
  • This was my first thought as well, but: ```Object.new.respond_to?(:system)``` is false, and the call to t.system does hit my dynamic code. Kernel.system is a class-level method... – Irongaze.com Jul 11 '13 at 20:10
  • 2
    Ah, I see what you're saying. This is indeed strange. If you use `send` or `__send__`, you get the `Kernel.system` private method, but `public_send` will trigger the method missing call. – Zach Kemp Jul 11 '13 at 20:23
  • Also, deriving from BasicObject fixes things, all else remaining equal. How the heck are class-level Kernel methods ending up in my call chain? – Irongaze.com Jul 11 '13 at 20:24
  • Kernel is a Module rather than a Class, so it might cause some behavior I'm not aware of. You can verify the existence of the private method in your call chain: `Testerize.new.private_methods`. – Zach Kemp Jul 11 '13 at 20:27
  • Yeah, that seems to be the key. There's a private method :system lurking in the inheritance chain. When I call ```t.system``` or ```t.public_send```, the call is confined to public methods, and thus works correctly. When I use ```__send__``` it also checks for private methods, finds the internal #system, and so misses my method_missing entirely. Can you update your answer so I can accept? Thanks for the help! – Irongaze.com Jul 11 '13 at 20:29