Reputation: 13667
An API I’m using returns different content depending on circumstances.
Here’s a snippet of what the API might return:
pages = [
{
'id' => 100,
'content' => {
'score' => 100
},
},
{
'id' => 101,
'content' => {
'total' => 50
},
},
{
'id' => 102,
},
]
content
is optional, and can contain different items.
I would like to return a list of pages where the score
is more than 75.
So far this is as small as I can make it:
pages.select! do |page|
page['content']
end
pages.select! do |page|
page['content']['score']
end
pages.select! do |page|
page['content']['score'] > 75
end
If I was using JavaScript, I would do this:
pages.select(page => {
if (!page['content'] || !page['content']['score']) {
return false
}
return page['content']['score'] > 75
})
Or perhaps if I was using PHP, I would array_filter
in a similar way.
However, trying to do the same thing in Ruby throws this error:
LocalJumpError: unexpected return
I understand why you cannot do that. I just want to find a simple way to achieve this in Ruby.
Here’s my make-believe Ruby I want to magically work:
pages = pages.select do |page|
return unless page['content']
return unless page['content']['score']
page['content']['score'] > 75
end
Upvotes: 4
Views: 1020
Reputation: 33420
You can use dig
, which can access to nested keys in a hash, it returns nil if it can't access to at least one of them, then use the safe operator to do the comparison:
p pages.select { |page| page.dig('content', 'score')&.> 75 }
# [{"id"=>100, "content"=>{"score"=>100}}]
Notice this "filter" is done in one step, so you don't need to mutate your object.
For your make-belive approach, you need to replace the return
s with next
:
pages = pages.select do |page|
next unless page['content']
next unless page['content']['score']
page['content']['score'] > 75
end
p pages # [{"id"=>100, "content"=>{"score"=>100}}]
There next
is used to skip the rest of the current iteration.
You can save one line in that example, if you use fetch and pass as the default value an empty hash:
pages.select do |page|
next unless page.fetch('content', {})['score']
page['content']['score'] > 75
end
Same way you can do just page.fetch('content', {}).fetch('score', 0) > 75
(but better don't).
Upvotes: 5
Reputation: 23327
You can use logical "and" (&&
) to concatenate conditions
pages.select! do |page|
page['content'] && page['content']['score'] && page['content']['score'] > 75
end
Upvotes: 1
Reputation: 51151
You can actually achieve this using lambda
, where return
means what you expect it to mean:
l = lambda do |page|
return false if !page['content'] || !page['content']['score']
page['content']['score'] > 75
end
pages.select(&l)
# => [{"id"=>100, "content"=>{"score"=>100}}]
but I guess it's not very practical.
Upvotes: 1