Ruby comes with many fantastic Enumerable methods, but the two most
useful ones come through Smalltalk via LISP: #map
and #inject
. What
follows are some lengthy method definitions followed by rewrites that
are not only more concise but also more clear in their intentions.
Building an array
Requirement:
As a user with a PGP key I want to see the list of key ids for all my signers so I can quickly import them from the keyserver.
The initial implementation is a little lengthy and overly explicit:
def signer_key_ids
result = []
signers.each do |signer|
result << signer.key_id
end
result
end
But a simple use of #map
more clearly illuminates what this method
does:
def signer_key_ids
signers.map { |signer| signer.key_id }
end
Building an array from multiple arrays
Another requirement comes in:
As a user with a PGP key I want to see the list of all UIDs for all my signers so I can see their names and where they work.
We can write this in a structured way using #each
and #flatten
:
def signer_uids
result = []
signers.each do |signer|
result << signer.uids
end
result.flatten
end
But a #map
makes it more clear. Note the use of Symbol#to_proc
here:
def signer_uids
signers.map(&:uids).flatten
end
An #inject
combined with Array#+
removes the need to call #flatten
at the end:
def signer_uids
signers.inject([]) do |result, signer|
result + signer.uids
end
end
Though in this case using #inject
is the long way; instead try
Enumerable#flat_map
:
def signer_uids
signers.flat_map(&:uids)
end
Build a hash from an array
Another requirement comes in from above:
As a user with a PGP key I want to see a mapping of all key ids to their UIDs for each signer so I can build my own keyserver.
Well we need to build a hash, and we need to build it from each element in an array. At least, that’s one way to phrase it:
def signer_keys_and_uids
result = {}
signers.each do |signer|
result[signer.key_id] = signer.uids
end
result
end
But another way to phrase it is: given an empty hash, #inject
a hash
from key id to UIDs for each element in the array of signers:
def signer_keys_and_uids
signers.inject({}) do |result, signer|
result.merge(signer.key_id => signer.uids)
end
end
Build a Boolean from an array
One last requirement, they swear:
As a user with a PGP key I want to confirm that all my signers are signed by me so I can always feel mutually complete.
With the hash above we were dealing with another Enumerable. Here it’s a Boolean, so let’s try it the long way:
def mutually_signed?
result = true
signers.each do |signer|
result = result && signer.signed_by?(self)
end
result
end
Though, now that we’ve seen that, it looks a bit familiar:
def mutually_signed?
signers.inject(true) do |result, signer|
result && signer.signed_by?(self)
end
end
But if that’s too obtuse, we can always think of it as an array of Booleans that must all be true:
def mutually_signed?
signers.map(&:signed_by?).inject(:&)
end
As Rubyists we also know that we have other fantastic
abstractions up
our Enumerable
sleeve:
def mutually_signed?
signers.all?(&:signed_by?)
end
What’s next
To get a comfortable intuition with #map
, #inject
, and other
Enumerable
methods, I recommend going outside of Ruby for a bit. Some
amazing books on the topic of functional programming are:
If you want to read more about #inject
in Ruby, check out these
articles: