I have a hash yaml, which is sometimes like:
{"foo" => {"bar" => 10}}
and sometimes like:
{"foo" => nil}
I want to do a certain action depending on whether "bar" is present.
I write this code:
if yaml["foo"] && yaml["foo"].key?["bar"]
...
I'd like to know if there's an idiomatic way to deal with that conditional, especially the first part where I have to check the existence of the parent key.
Hash#dig comes in very handy for cases like yours:
hash = {"foo" => {"bar" => { "baz" => 10}}}
hash.dig('foo', 'bar', 'baz')
#=> 10
Note, that if at any point of digging it returns nil, the method won't blow up but just return nil as result:
hash.dig('foo', 'baz')
#=> nil
Related
My understanding is that a single splat on a non-array object calls to_a and then dissociates the elements apart. And since nil.to_a is defined to be [], the following conversion happens:
[:foo, *nil, :bar]
# => [:foo, *nil.to_a, :bar]
# => [:foo, *[], :bar]
# => [:foo, :bar]
By analogy, I thought that a double splat on a non-hash object calls to_h and then dissociates the key-value pairs apart. And since nil.to_h is defined to be {}, I expected the following conversion to happen:
{"foo" => 1, **nil, "bar" => 2}
# => {"foo" => 1, **nil.to_h, "bar" => 2}
# => {"foo" => 1, **{}, "bar" => 2}
# => {"foo" => 1, "bar" => 2}
But actually, it raises an error: no implicit conversion of nil into Hash. Why does it behave like that?
Edit I am not asking about the reasoning behind the design. I am asking where my thinking is wrong regarding double splat.
Well it's our human being super power to recognize patterns and predict things. However it's not always true. This is one example. Ruby is not consistent in splat and double splat. Your way of thinking is a good way to "remember" but it's not exactly the way Ruby works on splats.
See this bug report for more detail. In this bug report, Ruby's author Matz rather to remove the feature of being able to splat nil than add double splat to nil.
The reason *nil works is because the splat operator works on anything that responds to to_a, and nil.to_a returns []. The reason **nil doesn't work is that nil doesn't respond to to_hash, which is to_a's double-splat counterpart.
If you wanted this behavior, you could monkey-patch NilClass:
class NilClass
def to_hash
{}
end
end
{ "foo" => 1, **nil, "bar" => 2 }
# => { "foo" => 1, "bar" => 2 }
I'm running ruby 2.2.2:
$ ruby -v
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Here I am initializing a hash with one key :b that has a value of Hash.new({})
irb(main):001:0> a = { b: Hash.new({}) }
=> {:b=>{}}
Now, I'm going to attempt to auto-vivify another hash at a[:b][:c] with a key 'foo' and a value 'bar'
irb(main):002:0> a[:b][:c]['foo'] = 'bar'
=> "bar"
At this point, I expected that a would contain something like:
{ :b => { :c => { 'foo' => 'bar' } } }
However, that is not what I'm seeing:
irb(main):003:0> a
=> {:b=>{}}
irb(main):004:0> a[:b]
=> {}
irb(main):005:0> a[:b][:c]
=> {"foo"=>"bar"}
This differs from the following:
irb(main):048:0> a = { :b => { :c => { "foo" => "bar" } } }
=> {:b=>{:c=>{"foo"=>"bar"}}}
irb(main):049:0> a
=> {:b=>{:c=>{"foo"=>"bar"}}}
So what is going on here?
I suspect this is something to do with Hash.new({}) returning a default value of {}, but I'm not exactly sure how to explain the end result...
Apologies for answering my own question, but I figured out what is happening.
The answer here is that we are assigning into the default hash being returned by a[:b], NOT a[:b] directly.
As before, we're going to create a hash with a single key of b and a value of Hash.new({})
irb(main):068:0> a = { b: Hash.new({}) }
=> {:b=>{}}
As you might expect, this should make things like a[:b][:unknown_key] return an empty hash {}, like so:
irb(main):070:0> a[:b].default
=> {}
irb(main):071:0> a[:b][:unknown_key]
=> {}
irb(main):072:0> a[:b].object_id
=> 70127981905400
irb(main):073:0> a[:b].default.object_id
=> 70127981905420
Notice that the object_id for a[:b] is ...5400 while the object_id for a[:b].default is ...5420
So what happens when we do the assignment from the original question?
a[:b][:c]["foo"] = "bar"
First, a[:b][:c] is resolved:
irb(main):075:0> a[:b][:c].object_id
=> 70127981905420
That's the same object_id as the .default object, because :c is treated the same as :unknown_key from above!
Then, we assign a new key 'foo' with a value 'bar' into that hash.
Indeed, check it out, we've effectively altered the default instead of a[:b]:
irb(main):081:0> a[:b].default
=> {"foo"=>"bar"}
Oops!
The answer is probably not as esoteric as it might seem at the onset, but this is just the way Ruby is handling that Hash.
If your initial Hash is the:
a = { b: Hash.new({}) }
b[:b][:c]['foo'] = 'bar'
Then seeing that each 'layer' of the Hash is just referencing the next element, such that:
a # {:b=>{}}
a[:b] # {}
a[:b][:c] # {"foo"=>"bar"}
a[:b][:c]["foo"] # "bar"
Your idea of:
{ :b => { :c => { 'foo' => 'bar' } } }
Is somewhat already accurate, so it makes me think that you already understand what's happening, but felt unsure of what was happening due to the way IRB was perhaps displaying it.
If I'm missing some element of your question though, feel free to comment and I'll revise my answer. But I feel like you understand Hashes better than you're giving yourself credit for in this case.
Sometimes while dealing with API responses, I'll end up writing something like:
what_i_need = response["key"]["another key"]["another key 2"]
The problem with that is, it'll throw an error if, say, "another key" is missing. I don't like that. I'd be a lot happier if what_i_need turned up a nil if something along the process broke.
Is there a more elegant solution than:
what_i_need = nil
begin
what_i_need = response["key"]["another key"]["another key 2"]
rescue Exception => e
end
I also thought about monkey patching NilClass you try to access nil["something"] it would return nil, but I'm not sure if that's the best way to go about it either of if it's possible even.
Use Hash#fetch with default value.
h = {:a => 2}
h.fetch(:b,"not present")
# => "not present"
h.fetch(:a,"not present")
# => 2
Without default value it will throw KeyError.
h = {:a => 2}
h.fetch(:b)
# ~> -:2:in `fetch': key not found: :b (KeyError)
But with nested Hash like your one you can use :
h = {:a => {:b => 3}}
val = h[:a][:b] rescue nil # => 3
val = h[:a][:c] rescue nil # => nil
val = h[:c][:b] rescue nil # => nil
Ruby 2.0 has NilClass#to_h.
what_i_need = response["key"].to_h["another key"].to_h["another key 2"]
Taking some inspiration from Objective-C's key-value coding system, you can do this with a lightweight DSL to walk a series of keys in an arbitrarily-nested data structure:
module KeyValue
class << self
def lookup(obj, *path)
path.inject(obj, &:[]) rescue nil
end
end
end
h = { a: { b: { c: 42, d: [ 1, 2 ] } }, e: "test"}
KeyValue.lookup(h, :a, :b, :d, 1) # => 2
KeyValue.lookup(h, :a, :x) # => nil
Or if you just want the one-liner:
(["key", "another key", "another key 2"].inject(h, &:[]) rescue nil)
To expand on #Priti’s answer a bit, you can use a chain of Hash#fetch instead of Hash#[] to return empty hashes till you get to the last in the chain, and then return nil:
what_i_need = response.fetch('key', {})
.fetch('another key', {})
.fetch('another key 2', nil)
or rely on the KeyError exception being raised (maybe not the best, as exceptions as control flow should be avoided):
what_i_need = begin
response.fetch('key').fetch('another key').fetch('another key 2')
rescue KeyError
nil
end
I have 2 Ruby objects that I am converting to hashes: one from XML and another from JSON. When I puts the variable name I get hash, so it appears that I'm doing that correctly.
The format is several records in the format below.
Format of hash one (smithjj being a unique username):
{ smithjj => {office => 331, buidling => 1} }
Format of hash 2:
{"Data"=>{"xmlns:dmd"=>"http://www.xyz.com/schema/data-metadata",
"dmd:date"=>"2012-03-06", "Record"=>{"PCI"=>{"DPHONE3"=>nil, "OPHONE3"=>"111",
"DTY_DOB"=>"1956", "TEACHING_INTERESTS"=>nil, "FAX1"=>"123", "ROOMNUM"=>"111",
"DTD_DOB"=>"5", "DTM_DOB"=>"11", "WEBSITE"=>"www.test.edu", "FAX2"=>"324",
"ENDPOS"=>"Director", "LNAME"=>"Smith", "FAX3"=>"4891", "MNAME"=>"Thomas",
"GENDER"=>"Male", "ALT_NAME"=>nil, "PFNAME"=>"TG", "id"=>"14101823488",
"RESEARCH_INTERESTS"=>nil, "BIO"=>"", "CITIZEN"=>"Yes", "EMAIL"=>"test#email",
"SUFFIX"=>nil, "DPHONE1"=>nil}, "termId"=>"234", "IndexEntry"=>{"text"=>"Other",
"indexKey"=>"DEPARTMENT", "entryKey"=>"Other"}, "dmd:surveyId"=>"23424",
"username"=>"smithers", "userId"=>"23324"}, "xmlns"=>"http://www.adsfda.com/"}}
I want to iterate over each unique username in the first hash and compare values from the PCI section of the second hash to the values in the first hash. The keys are different names so I planned on pairing them up.
I've tried several ways of doing it, but I keep getting a string integer error, so I must not be iterating correctly. I'm doing an .each do block, but all the examples I see show a simple hash, not a key => key => value, key => value.
Any direction is much appreciated.
Here's how you should be able to do the iteration properly for hashes, if you're not already using this:
h = {:foo => 42, :bar => 43, 44 => :baz}
h.each {|key, val| puts "The value at #{key} is #{val}."}
This produces:
The value at foo is 42.
The value at bar is 43.
The value at 44 is baz.
As for the last part of your question, could you please make it a little clearer? e.g., what error are you getting exactly? This could help us understand the issue more.
EDIT: If what you'd like to do is compare certain values against others, try something like this:
h1 = {:foo => 42, :bar => 43, 44 => :baz, :not_used => nil}
h2 = {:life_universe => 42, :fourty_three => 45, :another_num => 44, :unused => :pancakes}
# Very easy to add more
comparisons = {:foo => :life_universe, :bar => :fourty_three, :baz => :another_num}
def all_match_up?(hash1, hash2, comps)
comparisons.each {|key, val|
if key != val
# They don't match!
puts "#{key} doesnt match #{val}!"
return false
end
}
# They all match!
true
end
all_match_up?(h1, h2, comparisons)
Try this to see what happens ;)
what is the best way to make such deep check:
{:a => 1, :b => {:c => 2, :f => 3, :d => 4}}.include?({:b => {:c => 2, :f => 3}}) #=> true
thanks
I think I see what you mean from that one example (somehow). We check to see if each key in the subhash is in the superhash, and then check if the corresponding values of these keys match in some way: if the values are hashes, perform another deep check, otherwise, check if the values are equal:
class Hash
def deep_include?(sub_hash)
sub_hash.keys.all? do |key|
self.has_key?(key) && if sub_hash[key].is_a?(Hash)
self[key].is_a?(Hash) && self[key].deep_include?(sub_hash[key])
else
self[key] == sub_hash[key]
end
end
end
end
You can see how this works because the if statement returns a value: the last statement evaluated (I did not use the ternary conditional operator because that would make this far uglier and harder to read).
I like this one:
class Hash
def include_hash?(other)
other.all? do |other_key_value|
any? { |own_key_value| own_key_value == other_key_value }
end
end
end