This example perfectly links several Ruby concepts. Because of this, I will try to explain them all.
Methods in Ruby can take blocks (code snippets) in elegant material:
def run_code yield end run_code { puts 42 }
- Procs are similar to blocks, but they are relevant addressable objects:
deep_thought = proc { puts 42 } deep_thought.call
- You can include proc in a block when calling a method with the
& operator:
def run_code yield end deep_thought = proc { puts 42 } run_code(&deep_thought)
- Procs and blocks can take arguments:
def reveal_answer yield 5_000 end deep_thought = proc do |years_elapsed| years_elapsed >= 7_500_000 ? 42 : 'Still processing' end reveal_answer(&deep_thought) # => 'Still processing'
- You can turn the block into proc using
& in the method signature:
def inspector(&block) puts block.is_a?(Proc) puts block.call end inspector { puts 42 }
Symbol#to_proc creates a proc that calls methods with the same name on the object:
class Dummy def talk 'wooooot' end end :talk.to_proc.call(Dummy.new) # => "wooooot"
In other words,
:bar.to_proc.call(foo)
pretty much equivalent
foo.bar
BasicObject#method_missing :
When you try to call a method on an object, Ruby traverses the chain of ancestors looking for a method with that name. How the chain is built is another topic, long enough for another day, it is important that if the method is not found to the very bottom ( BasicObject ), the second search is performed in the same chain - this time for a method called method_missing . It takes as arguments the name of the original method plus any argument it receives:
class MindlessParrot def method_missing(method_name, *args) "You caldt #{method_name} with #{args} on me, argh!" end end MindlessParrot.new.foo
So what does all this mean in our particular case? Suppose there was no protected for a second.
translator.speak(&:spanish)
calls the Translator#speak method with :spanish converted to a block.
Translator#speak takes this block and converts it to proc with the name language and calls it, passing self as an argument.
self is an instance of Translator , so it has the methods speak , french , spanish , turkey and method_missing .
So:
Translator.new.speak(&:spanish)
is equivalent to:
:spanish.to_proc.call(Translator.new)
which is equivalent to:
Translator.new.spanish
giving us "hola" .
Now, returning protected back, all the language methods of our Translator object are still present, but outsiders cannot directly access them.
Just like you can't call
Translator.new.spanish
and expect a "hola" back, you cannot call
Translator.new.speak(&:spanish)
And since the method is not directly accessible, it is considered not found and method_missing is method_missing , which gives us "awkward silence" .