I am working on Ruby Koans about_message_passing.rb and got the code working for method_missing as follows:
def method_missing(method_name, *args, &block)
@messages << method_name
@object.__send__(method_name, *args, &block)
end
This code seems to work, but I do not quite understand why the splat in needed in *args and the & is needed with the block.
If I were defining a method, I understand that the * and & are used to denote an array argument and block argument respectively, but what does it mean when they are used with the send method to invoke a method on an object?
I'll take these one at a time. take method_missing
out of this completely, since it just makes what's going on confusing. It's actually completely unrelated to that.
The splat *
does 2 things. In the arguments of a method definition, it soaks up multiple arguments into an array. When used in method invocation it splats out an array into individual arguments. Using both allows you to forward any number of arguments to another method.
def foo(*args)
bar(*args)
end
def bar(a, b, c)
puts a
puts b
puts c
end
foo(1,2,3) # prints 1, 2 and then 3
Since you are basically forwarding all arguments, this is the same pattern.
The &
is for the block argument. There can be exactly one of these per method invocation, it's the block that hangs off the end. It's a special argument, in that it doesn't go in the arguments directly. You can capture the block to a variable by capturing add &someblock
as the last argument in a method definition.
Then you can pass a block in a method invocation using the same syntax.
def foo(&block)
bar(&block)
end
def bar
yield
end
foo { puts 'hello' } # prints hello
This allows you pass the hanging block to another method, without invoking it. It's not always required because you usually just use yield
to execute whatever block was passed. But if you want to do something besides just execute it, you need to capture a reference to the block itself.
So if you combine these 2 things, you get the ultimate method forwarder. You capture all of any number of arguments, and any block that was hanging off the end, and send those to another method.
# forwards everything to the method `bar`
def foo(*args, &block)
bar(*args, &block)
end
Lastly, send
is just a method. It expects a name of a method, followed by any number of arguments (not an array), and can optionally handle a hanging block.
In other words:
foo.send methodName, *args, &block