Does adding a block to Object.send pass it to the called method?
TL;DR: Yes, the send
method will pass the closure to the method it calls.
Under the hood, a closure supplied to a method call implicitly gets passed to the method as an additional argument (albeit a special one). You can use the closure explicitly with call
...
> def do_it(&block)
> block.call
> end
=> :do_it
> do_it { puts 'apples' }
apples
=> nil
... or implicitly with yield
.
> def do_it_with_yield
> yield if block_given?
> end
=> :do_it_with_yield
> do_it_with_yield { puts 'bananas' }
bananas
=> nil
You can pass closures to any method, including :send
, :public_send
, etc., though it is up to the method called to use the closure or not.
In the case of send
et al, they dutifully pass the closure along as we would hope and expect.
> self.public_send(:do_it) { puts 'cherries' }
cherries
=> nil
> self.send(:do_it_with_yield) { puts 'dates' }
dates
=> nil
This is important when implementing the method_missing
method, for example.
def method_missing(method_name, *args, &block)
puts "Delegating #{method_name} to api handler"
my_api_handler.send(method_name, *args, &block)
end
References:
- https://scotch.io/tutorials/understanding-ruby-closures
- https://medium.com/rubycademy/the-yield-keyword-603a850b8921
- https://www.leighhalliday.com/ruby-metaprogramming-method-missing
Yes, it will. Inside the method you can check it with block_given?
and call the block with yield
Code
class Foo
def bar
puts "before yield"
yield if block_given?
puts "after yield"
end
end
f = Foo.new
f.send(:bar)
puts ""
f.send(:bar) { puts "yield"}
Output
before yield
after yield
before yield
yield
after yield
The documentation is a bit unclear on this:
send(symbol [, args...]) → obj
Invokes the method identified by symbol, passing it any arguments specified.
But note the any arguments specified part. The block that you give to a method is really a funny type of implicit argument so that you can do things like:
def m(&b)
@a.each(&b)
end
m { |e| puts e }
to pass the block around as a Proc instance. However, you can also do this:
def m
yield
end
m { puts 'pancakes' }
so the block is special as far as the argument list is concerned but the block still behaves as an argument even if it is sometimes implicit.
Given the above "block is sort of an argument" rambling and the importance of blocks in Ruby, it would be reasonable for send
to pass the block through. You can also try it but you have to careful about accidental and undocumented behavior with the "try it" approach:
class C
def m
yield
end
end
o = C.new
o.send(:m) { puts 'pancakes' }
# "pancakes" comes out
Yes. Consider the following:
class A
def explicit(&b); b; end
def implicit; yield "hello"; end
end
>> A.new.send(:explicit) { }
=> #<Proc:0x0000000000000000@(irb):19>
>> A.new.send(:implicit) { |greeting| puts greeting }
hello
=> nil
Hope this helps!