How to iterate over an Enumerable, returning the first truthy result of a block ("map-find")

Updated . Posted . Visible to the public. Repeats.

Ruby has Enumerable.find(&block), which returns the first item in the collection for which the block evaluates to true.

first_post_with_image = posts.find do |post|
  post.image
end

However, sometimes it's not the item you're interested in, but some value depening on it – e.g. the value the block evaluated to. You could first map the collection and then take the first truthy value, but this way you need to process the whole collection twice:

first_image_url = posts.map(&:image).find(&:present?).url

If the mapping is a costly operation or has undesirable side effects, you need to do it in a single iteration instead.

Single iteration solution with break

As you know, #find loops through a collection, yielding the block with each item. Calling break with an argument will exit the whole block evaluation, returning whatever argument was given to break:

first_image_url = posts.find do |post|
  break post.image.url if post.image.present?
end

While the condition (post.image.present?) is false, the block returns nil and #find will try the next item in the collection. If the condition is never true, #find will return nil.

Single iteration solution with lazy

Ruby can iterate enumerables lazily, processing a chain for each entry instead of mapping a whole collection in multiple steps. With lazy, a solution would be:

first_image_url = posts.lazy.filter_map do |post|
  post.image.url if post.image.present?
end.first

Proposals for built-in Ruby method

There is no one-liner in Ruby or ActiveSupport with the behavior described above.

There have been discussions about adding a method like Enumerable#find_map:

Dominik Schöler
Last edit
Dominik Schöler
License
Source code in this card is licensed under the MIT License.
Posted by Dominik Schöler to makandra dev (2015-09-04 08:29)