Common Hash issue
Did you get RuntimeError: can't add a new key into hash during iteration
error?
In this post we will check together a common pattern and how to solve it easily.
The error message is self descriptive, we can’t update a hash during iteration and it can be reproduced with the following snippet:
hash = {title: "basch the hash", pages: 404}
hash.each do |k,v|
hash["#{k}-dup"] = v
end
So here is a quick question, what will be the output of entry
variable after executing track(entry)
entry = {title: "Funny memes", pages: 404}
def track(hash)
hash[:title] = "#{hash[:title].upcase}-TITLE-DATA}"
# send hash to a tracking service
fake_service.send(hash)
end
track(entry)
# output of entry var:
p entry
Surprise, the original hash was modified, and we can confirm that we are using the same object by printing the object id.
entry = {title: "Funny memes", pages: 404}
p "entry object id: #{entry.object_id}" # entry object id: 32144
def track(hash)
p "hash object id: #{hash.object_id}" # hash object id: 32144
hash["title"] = "#{hash[:title].upcase}-TITLE-DATA}"
# send hash to a tracking service
fake_service.send(hash)
end
So what are our options in this case?
We can simply use a copy of the hash instead of modifying the original one. If this snippet is used in a Rails project we can take advantage of ActiveSupport library by using deep_dup method.
And if I’m not using ActiveSupport? No worries there is another option also which is Marshalling:
entry = {title: "Funny memes", pages: 404}
p "entry object id: #{entry.object_id}" # entry object id: 32144
entry_copy = Marshal.load(Marshal.dump(entry))
p "entry_copy object id: #{entry.object_id}" # entry object id: 56333
It’s better to wrap in a utility like this:
module Utility
# Avoid using deep_dup name here, it will be confusing trust me ;)
def self.deep_duplicate(collection)
Marshal.load(Marshal.dump(collection))
end
end
A quick note, as you will see in the example below, I’m creating a duplicate just before updating the hash. The reason for this is to make it easier for us to understand that we are updating a copy and not the original hash.
Hmmm and what if I pass a copy to the method? In theory, it’s a good suggestion but keep in mind that your application will be harder to understand as you will be always be asking: “Am I passing a copy or the original Hash here? :D”
entry = {title: "Funny memes", pages: 404}
def track(hash)
# hash_copy = Utility.deep_duplicate(hash)
hash_copy = hash.deep_dup
hash_copy["title"] = "#{hash[:title].upcase}-TITLE-DATA}"
# send hash to a tracking service
fake_service.send(hash_copy)
end
That’s it, so have fun and keep coding 🙂