Symbol#to_proc can be Tricky
In Ruby, when you have a block that calls a method on an object with no
arguments, you can replace the block syntax with a more convenient, shorter
syntax provided by Symbol#to_proc:
# This
['these', 'are', 'words'].map { |word| word.reverse } # ['eseht', 'era', 'sdrow']
# Will produce the same result as this
['these', 'are', 'words'].map(&:reverse) # ['eseht', 'era', 'sdrow']
You can generally treat these the same, but since the call passes through
Symbol#to_proc, there are some differences to watch out for. Today I’ll show
an example of a fun one. Imagine you have a class like this:
class Something
def test1
[Something.new, Something.new].each { |thing| thing.method_to_use }
end
def test2
[Something.new, Something.new].each(&:method_to_use)
end
protected
def method_to_use
puts 'used!'
end
end
These should be identical, but Something.new.test1 will work perfectly fine,
while Something.new.test2 will raise a NoMethodError! The reason for the
error is that although method_to_use being protected is fine from the block in
#test1, passing through Symbol#to_proc in #test2 changes the context of the
call. Since Symbol doesn’t have access to protected methods on Something,
you get an error.
We can prove this to ourselves by running the same test against Symbol, like
so:
class Symbol
def test1
[:one, :two].each { |s| s.method_to_use }
end
def test2
[:one, :two].each(&:method_to_use)
end
protected
def method_to_use
puts 'used!'
end
end
No errors from (:sym).test1 or (:sym).test2 :)
Another fun thing to look at here, which is pretty confusing is the values for
caller in method_to_use.
In #test1, here’s caller (as you’d expect):
["do.rb:3:in `block in test1'", "do.rb:3:in `each'",
"do.rb:3:in `test1'", "do.rb:15:in `<main>'"]
But caller in #test2 tells a different story (one devoid of Symbol#to_proc):
["do.rb:6:in `each'", "do.rb:6:in `test2'", "do.rb:16:in `<main>'"]
And that’s a preview for a follow-up post!