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
: