Skip to content

Instantly share code, notes, and snippets.

@thoughtpunch
Created December 12, 2011 18:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thoughtpunch/1468577 to your computer and use it in GitHub Desktop.
Save thoughtpunch/1468577 to your computer and use it in GitHub Desktop.
A Better Way to iterate through arrays/hashes with nils?
#Let say I'm getting back a JSON nested hash (or array of hashes) from an API
@example = {"results" = > {{"poop" => "shoop"},{"foo" => {"shizz" => "fizz", "nizzle"=>"bizzle"}}}
# YAML VIEW OF ABOVE
#- poop: shoop
#- foo:
# shizz: fizz
# nizzle: bizzle
#Now lets go make a db entry with ActiveRecord from the hash. This should work fine.
Thing.create!(:poop => @example["results"]["poop"],
:shizz => @example["results"]["foo"]["shizz"],
:nizzle=> @example["results"]["foo"]["nizzle"])
# But what if 'foo' is empty or nil? For example, if an API result has a "person" hash with "first name","last name" # etc, the "person" hash will usually be empty if there is no data, which means the hashes inside it don't exist.
# such as this example:
@example = {"results" = > {{"poop" => "shoop"},{"foo" => nil }}
Thing.create!(:poop => @example["results"]["poop"],
:shizz => @example["results"]["foo"]["shizz"],
:nizzle=> @example["results"]["foo"]["nizzle"])
#NoMethodError: You have a nil object when you didn't expect it!
#You might have expected an instance of Array.
#The error occurred while evaluating nil.[]
#What's the best way to handle this?
@brandonweiss
Copy link

Does the hash have to be re-mapped because the keys are different from the attribute names? Or are they the same?

@brandonweiss
Copy link

What I was getting at is, if the hash returned was a one-to-one representation of the model, I think you could literally just pass the whole thing in, nils and all:

Thing.create!(@results)

But if you need to re-map it, and or cherry-pick keys out, then that won't work. Another solution might be to use the try method. You pass in a symbol, and it will attempt to call that method. But if there's a NoMethodError error it will just return nil. To do this with a hash would look something like this:

a = {}
=> {}
a.try(:'[]', :stuff)
=> nil
a[:stuff] = "awesome"
=> "awesome"
a.try(:'[]', :stuff)
=> "awesome"

@thoughtpunch
Copy link
Author

I also asked this question on SO and got the same answer, as well as a monkey patch for 'Hash' that should do the trick.

http://stackoverflow.com/questions/8479476/iterating-through-a-ruby-nested-hash-with-nils

class Hash
def get(key, default=nil)
key.split(".").inject(self){|memo, key_part| memo[key_part] if memo.is_a?(Hash)} || default
end
end

h = { 'a' => { 'b' => { 'c' => 1 }}}
puts h.get "a.b.c" #=> 1
puts h.get "a.b.c.d" #=> nil
puts h.get "not.here" #=> nil

@brandonweiss
Copy link

Doh, that's right, try is Rails-only. I personally wouldn't monkeypatch Hash like that. Better to create a special method that handles it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment