Symbol to proc
We've already used "blocks" quite a bit in previous reasons. They're a fundamental building block (yes, literally) of Ruby. They're roughly equivalent to anonymous functions or lambdas in other languages. Here's a comparison with JavaScript in case you're unfamiliar with Ruby syntax:
numbers = [1, 2, 3, 4, 5]
# one-line blocks are typically defined with { |args| ... } syntax
squared = numbers.map { |n| n * n }
# => [1, 4, 9, 16, 25]
# multi-line blocks are typically defined with do |args| ... end syntax
squared = numbers.map do |n|
n * n
end
# => [1, 4, 9, 16, 25]
const numbers = [1, 2, 3, 4, 5];
// one-line functions typically use a minimal arrow syntax
const squared = numbers.map(n => n * n);
// => [1, 4, 9, 16, 25]
// multi-line functions typically use curly braces syntax
const squared = numbers.map(n => {
const square = n * n;
return square;
});
// => [1, 4, 9, 16, 25]
Just like functions in JavaScript, we can store blocks in variables for later use. In Ruby, these are instances of Proc:
square = Proc.new { |n| n * n }
# we can also declare procs using the "stabby" operator
square = ->(n) { n * n }
# as well as the lambda method
square = lambda do |n|
n * n
end
# we can pass any proc as a block to any method using the & operator
numbers = [1, 2, 3, 4, 5]
squared = numbers.map(&square)
# => [1, 4, 9, 16, 25]
const square = n => n * n;
// JavaScript also has a history of different ways to define functions:
const square = function(n) {
return n * n;
};
function square(n) {
return n * n;
}
const numbers = [1, 2, 3, 4, 5];
const squared = numbers.map(square);
// => [1, 4, 9, 16, 25]
Now for the magic of "symbol to proc". As mentioned above, we can pass a Proc as a block by prefixing it with &. However, if we prefix any non-Proc object with &, Ruby will attempt to convert it to a proc by invoking the to_proc method on that object.
Now back in November 2005, Rails core team member Marcel Molina introduced Symbol#to_proc:
class Symbol
def to_proc
Proc.new { |obj, *args| obj.send(self, *args) }
end
end
This allowed any symbol (like :upcase or :even?) to be converted to a proc that invokes the method named by that symbol on its first argument:
numbers = 1..5
even_numbers = numbers.select(&:even?)
# => [2, 4]
names = %w[alice bob charlie]
uppercased_names = names.map(&:upcase)
# => ["ALICE", "BOB", "CHARLIE"]
const numbers = Array.from({ length: 5 }, (_, i) => i + 1);
const evenNumbers = numbers.filter(n => n % 2 === 0);
// => [2, 4]
const names = ['alice', 'bob', 'charlie'];
const uppercasedNames = names.map(name => name.toUpperCase());
// => ['ALICE', 'BOB', 'CHARLIE']
It turned out there were endless use cases for this pattern, and it quickly became a beloved idiom in the community. So much so that it was added to Ruby itself and released as part of version 1.8.7 in 2008.
I'll end this celebration of Symbol#to_proc with an example from the Rails codebase for this very website. It runs whenever you visit the "latest" reason URL:
class ReasonsController < ApplicationController
# GET /reasons/latest
def latest
latest_reason = Reason.all.select(&:published?).max_by(&:id)
redirect_to reason_path(latest_reason)
end
end
Here we use select(&:published?) to filter only published reasons, then find the one with the highest ID using max_by(&:id) (ID being equivalent to the reason's number).
Rails veterans may be confused about why select and max_by are used instead of where, order and first. That's because Reason is not an ActiveRecord model but a simple Ruby class wrapping static content files. Don't panic!