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
first_image_url = posts.find do |post|
break post.image.url if post.image.present?
end
Explanation
As you know, #find
loops through the 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
.
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
.