Anti-Pattern: Iteratively Building a Collection

Anti-Pattern: Iteratively Building a Collection

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:

Mike Burns Developer

Sharpen your programming skills by completing coding exercises that are reviewed by other developers at Upcase today.