Question

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?

Was it helpful?

Solution

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

OTHER TIPS

The splat in the method definition means "take all unmatched arguments and put them in an array" (in ruby 1.8 this was always the last arguments, but in 1.9 splats can occur in the middle).

Using it in a method call is the reverse: it means take this array and use its contents as the arguments

foo(a,b) #call foo with 2 arguments: a and b
foo([a,b]) #call foo with a single array argument 
foo(*[a,b]) # call foo with 2 arguments: a and b

& is similar: in a method definition it captures the block and turns it into a proc, but in a method call it turns a proc (or proc like object - anything responding to to_proc will do) into the block for that method

You need both of these for method_missing because (in general) you want to pass along all the arguments and the block from the original method call.

To my knowledge, anytime you pass a block directly, it is with the syntax &block_name.

Also, the method signature for Object#send takes endless arguments, not an array. So by passing the splatted values *args, it is the same as if you had passed the comma-delimited args.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top