Ruby: ignoring nil? - ruby

I am trying to parse a bunch of json attributes and assign them to an hash. The json has a ton of levels to it:
player[:position]=player["positions"]["primary_position"]["position"]["name"]
The problem I'm encountering is that any one of these levels may be nil, which means that I need to write four separate nil? checks just to extract the value.
I don't care about the nil values -- it would be fine if the array just recorded them as " " or something similar.
Is there any way I can turn off nil errors for this particular type of thing? Or should I run the whole thing thru a rescue method that returns the value I want in the event of nil?

You could use the andand gem, which is similar to the Maybe monad.
player[:position] = player.andand["positions"].andand["primary_position"].andand["position"].andand["name"]
This is a guarded method invocation. It only continues to send the method to the object if the result is not nil otherwise it returns nil. This way the chained invocation is stopped the moment one of the them returns nil.

player[:position]=player["positions"]["primary_position"]["position"]["name"] rescue " "
This would return the desired value, or " " if something is nil.

The usual approach is to use inject thusly:
path = %w[positions primary_position position name]
value = path.inject(player) { |h, k| h && h[k] }
You'd adjust the h && h[k] test to suite your specific needs, that one assumes that you won't have to worry about non-Hash values inside h. You make it as elaborate as you need.
You could even patch that into Hash if you were doing this sort of thing all the time:
class Hash
def follow_path(*path)
# You could also path.flatten if you're not expecting Array keys
path.inject(self) { |h, k| h && h[k] }
end
end
value = person.follow_path(*%w[positions primary_position position name])
or toss it into a private utility method:
private
def follow_path(h, *path)
# Assuming you're not using Array keys...
path.flatten.inject(h) { |h, k| h && h[k] }
end
and then:
value = follow_path(person, %w[positions primary_position position name])

Related

Ruby Enumerable: get the first which is not nil, but the result must be what's inside the block

The Enumerable#find method works by evaluating until the it finds an element which matches the condition in the block. Is there something similar for returning the first time that the block is not evaluated to nil? imagining one would have a collection of hashes:
value = nil
options.each do |o|
break if value = o[:desired]
end
value ||= DEFAULT
isn't there a method which already accomplishes this?
No point in making a lot of transformations to the collection, i'd like to minimize the number of allocations, so any solution which allocates a new Array will not be good for me.
find method will work for finding first element which has :desired key with minimum iterations.
I think you wish to get the value of desired key from the block instead of element itself - there is no method in Enumerable that behaves like a mixture of find and map - you will have to use the outer variable to which value is assigned inside the block as shown below.
options = [{foo: 1}, {desired: 2}, {bar: 3}]
value = nil
options.find do |o|
value = o[:desired]
break if value
end
p value
#=> 2
It more or less looks like your code, which should also work just fine.
Below is one way which you can use if you want to use Enumerable methods, but it will iterate over all elements.
p value = options.map { |o| value = o[:desired] }.compact.first
You can use reduce:
value = options.reduce(nil){|memo, entry| memo || entry[:desired] } || DEFAULT
As of Ruby 2.0, this can be accomplished by combining #map and #find with lazy enumerables:
value = options.lazy.map { |o| o[:desired] }.find { |x| !x.nil? } # or find(&:present?) with ActiveSupport
value ||= DEFAULT
This came up for me today: I think we can use break with a value from reduce
treasure = [1,2,3].reduce(nil) do |memo, value|
break memo if memo
foo(value)
end
How about
options.reduce{ |_,o|
break o if o[:desired]
DEFAULT
}
or
catch do |tag|
options.each{ |_,o| o[:desired] and throw tag, o }
DEFAULT
end
The latter allows for recursion.

Method to get value from a hash from array of keys in Ruby

I am trying to write a function which returns the value of a hash key, when provided with an array of keys (and 'nil' if the key doesn't exist).
Consider the hash:
my_hash = {
font_size: 10,
font_family: "Arial",
boo: {
name: 'blah'
}
}
A stub of the method might be:
def get_value(hash, array_of_keys)
...
end
The reason being that I can access different keys in the hash which may not actually exist. So for example, I want 'blah', normally I would call my_hash[:boo][:name] however it may not already exist or it may be very deep. What I would like to do is have my function, which I could call with get_value(my_hash, [:boo, :name]) so that I can test that each key exists (so I don't get exceptions if any of them do not exist, and where it doesn't matter how 'deep' the value might be).
In my case, its not feasible to check the existence of each value I require (I might need to test existence of 10 consecutive keys) I simply need a function that I can say which value I need and get the value if it exists, and nil if it doesn't (so an exception isn't thrown trying to retrieve it).
I gave it a shot, trying to use a recursive function that 'pop's the first element in the array each time it loops but i couldn't workout how to return my value.
def get_value(val, arr)
if arr.count > 1
arr.each do |a|
get_value val[a], arr.pop
end
else
val[a]
end
end
get_value s, [:boo, :name]
This is my attempt (which doesn't work obviously) - can anyone help me solve this problem, or have an alternate solution that might be more elegant? A couple of points:
The keys are not guaranteed to exist, and I'd like to return nil in
those cases, and
The hash could potentially be very deep so it needs to handle any hash and any number of keys in the array (which is why I thought recursion might be the answer, but now I'm not so sure).
[:font_size].inject(my_hash){|h, k| h.to_h[k]} #=> 10
[:boo, :name].inject(my_hash){|h, k| h.to_h[k]} #=> "blah"
[:boo, :foo].inject(my_hash){|h, k| h.to_h[k]} #=> nil
class Hash
def get_value(array_of_keys)
return nil unless array_of_keys.is_a? Array
return nil if array_of_keys.empty?
return nil unless self.has_key? array_of_keys.first
if self[array_of_keys.first].is_a? Hash
self[array_of_keys.first].get_value(array_of_keys[1..-1])
else
self[array_of_keys.first]
end
end
end
my_hash = {
font_size: 10,
font_family: "Arial",
boo: {
name: 'blah'
} ,
radley: {
one: {
more: {
time: 'now'
}
}
}
}
p my_hash.get_value [:boo, :name]
p my_hash.get_value [:radley, :one, :more, :time]
p my_hash.get_value [:radley, :one, :other, :time]
Explanation
I'm adding the method directly to the Hash class so that you can call it as an instance method on existing hashes (makes for a nicer usage).
The method takes your array, returns nil if the argument is not actually an array, if the array is empty, of if the Hash doesn't contain a key matching the first element of the array.
Next, check to see if the value of that key is itself a Hash.
If so, call this very method on that Hash, with the rest of the array!
If the value is anything but a hash, return that value.
My stab at it, using recursion:
def get_value(hash, keys_array)
key = keys_array.shift
if hash.has_key? key
if hash[key].is_a?(Hash) && keys_array.size >= 1
get_value(hash[key], keys_array)
else
hash[key]
end
else
nil
end
end
I remove the first key from the keys_array
I check if that key exists in the Hash
If yes I check if the value for that key is a Hash and that we still have keys left in the array, if that's the case I call the method with that value and the array of keys left
If it's not a Hash, I just return the value
If the key does not exist, return nil
Nice problem by the way :)

Return to Each Iterator from For Loop

I was wondering if it is possible to return to an Each iterator in Ruby from within a for-loop placed within the block passed to Each.
def find member = ""
productHash = {}
##entries is a hash, with both the keys and values being strings
#the member parameter is a string
#entries.each do |key, value|
for i in 0...member.size
if(key[i] != member[i])
next #the next keyword doesn't work...all it does is return to the for iterator. I'm looking for a keyword that would return to the each iterator, and allow each to pass back in the next key-value pair.
end
end
productHash[key] = value
end
productHash
end
What I'm trying to accomplish is this: the moment I see that a character in the member parameter doesn't match the corresponding character in a given key, I move on to the next key-value pair.
It looks like you're trying to do some kind of comparison where if the key matches a particular prefix specified by member then you would make an assignment.
This code should be functionally similar:
def find(member = "")
hash = { }
#entries.each do |key, value|
# Unless key begins with prefix, skip it.
next unless (key[0, prefix.length] == prefix)
hash[key] = value
end
hash
end
There's no official goto statement in Ruby, and many would argue this is a good thing, but it means that busting out of nested blocks can be a bit tricky.
Still, if you approach the problem in the right way, there's almost always a solution that's elegant enough.
Update:
To break out of nested loops, an approach might be:
list.each do |i|
broken = false
inner_list.each do |j|
if (j > 10)
broken = true
break
end
end
break if (broken)
end

How do I see if a multi-dimensional hash has a value in ruby?

Currently I am doing the following, but I am sure there must be a better way:
def birthday_defined?(map)
map && map[:extra] && map[:extra][:raw_info] && map[:extra][:raw_info][:birthday]
end
There may be cases where only map[:extra] is defined, and then I will end up getting Nil exception errors cause map[:extra][:raw_info] doesn't exist if I dont use my checked code above.
If you're using Rails, then you can use try (and NilClass#try):
value = map.try(:[], :extra).try(:[], :raw_info).try(:[], :birthday)
That looks a bit repetitive: it is just doing the same thing over and over again while feeding the result of one step into the next step. That code pattern means that we have a hidden injection:
value = [:extra, :raw_info, :birthday].inject(map) { |h, k| h.try(:[], k) }
This approach nicely generalizes to any path into map that you have in mind:
path = [ :some, :path, :of, :keys, :we, :care, :about ]
value = path.inject(map) { |h, k| h.try(:[], k) }
Then you can look at value.nil?.
Of course, if you're not using Rails then you'll need a replacement for try but that's not difficult.
I have two ways. Both have the same code but subtly different:
# Method 1
def birthday_defined?(map)
map[:extra][:raw_info][:birthday] rescue nil # rescues current line
end
# Method 2
def birthday_defined?(map)
map[:extra][:raw_info][:birthday]
rescue # rescues whole method
nil
end
Use a begin/rescue block.
begin
map[:extra][:raw_info][:birthday]
rescue Exception => e
'No birthday! =('
end
That's idiomatic why to do it. And yes it can be a little cumbersome.
If you want to extend Hash a bit though, you can do some cool stuff with something like a key path. See Access Ruby Hash Using Dotted Path Key String
def birthday_defined?
map.dig('extra.raw_info.birthday')
end
This is a little hacky but it will work:
def birthday_defined?(map)
map.to_s[":birthday"]
end
If map contains :birthday then it will return the string which will evaluate to true in a conditional statement while if it doesn't contain :birthday, it will return nil.
Note: This assumes the key :birthday does not appear at potentially multiple locations in map.
This should work for you:
def birthday_defined?(map)
map
.tap{|x| (x[:extra] if x)
.tap{|x| (x[:raw_info] if x)
.tap{|x| (x[:birthday] if x)
.tap{|x| return x}}}}
end

Search ruby hash for empty value

I have a ruby hash like this
h = {"a" => "1", "b" => "", "c" => "2"}
Now I have a ruby function which evaluates this hash and returns true if it finds a key with an empty value. I have the following function which always returns true even if all keys in the hash are not empty
def hash_has_blank(hsh)
hsh.each do |k,v|
if v.empty?
return true
end
end
return false
end
What am I doing wrong here?
Try this:
def hash_has_blank hsh
hsh.values.any? &:empty?
end
Or:
def hash_has_blank hsh
hsh.values.any?{|i|i.empty?}
end
If you are using an old 1.8.x Ruby
I hope you're ready to learn some ruby magic here. I wouldn't define such a function globally like you did. If it's an operation on a hash, than it should be an instance method on the Hash class you can do it like this:
class Hash
def has_blank?
self.reject{|k,v| !v.nil? || v.length > 0}.size > 0
end
end
reject will return a new hash with all the empty strings, and than it will be checked how big this new hash is.
a possibly more efficient way (it shouldn't traverse the whole array):
class Hash
def has_blank?
self.values.any?{|v| v.nil? || v.length == 0}
end
end
But this will still traverse the whole hash, if there is no empty value
I've changed the empty? to !nil? || length >0 because I don't know how your empty method works.
If you just want to check if any of the values is an empty string you could do
h.has_value?('')
but your function seems to work fine.
I'd consider refactoring your model domain. Obviously the hash represents something tangible. Why not make it an object? If the item can be completely represented by a hash, you may wish to subclass Hash. If it's more complicated, the hash can be an attribute.
Secondly, the reason for which you are checking blanks can be named to better reflect your domain. You haven't told us the "why", but let's assume that your Item is only valid if it doesn't have any blank values.
class MyItem < Hash
def valid?
!invalid?
end
def invalid?
values.any?{|i| i.empty?}
end
end
The point is, if you can establish a vocabulary that makes sense in your domain, your code will be cleaner and more understandable. Using a Hash is just a means to an end and you'd be better off using more descriptive, domain-specific terms.
Using the example above, you'd be able to do:
my_item = MyItem["a" => "1", "b" => "", "c" => "2"]
my_item.valid? #=> false

Resources