0

I have a hash like this:

entity = {1=> nil, 2 => {3 => nil, 4 => 1}}

I wrote a function which can remove the null values of the given entity using recursion.

def clear_null_values(entity)
   entity.each do |key, value|
     if value == nil || value.blank?
       entity.delete(key)
     elsif value.is_a? Hash
       clear_null_values(value)
       entity.delete(key) if value.blank?
     end
   end
end 

And also I need the original entity as well for other purposes. So I duplicated the hash variable and then cleared the null values.

final_entity = entity.dup
clear_null_values(entity)
puts entity
puts final_entity

Result:

{2 => {4 => 1}}
{1=> nil, 2 => {4 => 1}} # the nested values are overwritten.

Ideally, the final_entity should be the same as original entity.

Question1: Why is the entity.dup copying only outerhash?

Question2: How to make final_entity the exactly copy of original entity i.e., even if we modify entity then also final_entity shouldn't change?

Sk. Irfan
  • 299
  • 3
  • 15
  • 2
    I understand that you want to compute `{2 => {4 => 1}}`, but do not understand how `{1=> nil, 2 => {4 => 1}}` is determined, considering that it contains only one of the `nil` values in `entity`.I believe that your question needs a Rails tag as `blank` is not a Ruby method. – Cary Swoveland May 11 '18 at 05:07
  • What is your question? – sawa May 11 '18 at 05:08
  • Updated the question with tags and some more explanation. – Sk. Irfan May 11 '18 at 05:11
  • I understand that you want to compute `{2 => {4 => 1}}`, but do not understand the criteria for the construction of `{1=> nil, 2 => {4 => 1}}`, considering that it contains only one of the `nil` values in `entity`. – Cary Swoveland May 11 '18 at 05:13
  • 1
    @CarySwoveland There's no criteria for constructing `{1=> nil, 2 => {4 => 1}}` since the badly worded question is how to avoid this result and have final_entity == original entity, a problem arising from `entity.dup` only applying to the outermost hash. – Michael Kohl May 11 '18 at 05:17
  • 1
    @Michael, I see. The second line after "result" is the OP's incorrect result, not the desired result. – Cary Swoveland May 11 '18 at 05:20

2 Answers2

5

Try using deep_dup instead, your original code only dup-ed the outermost hash.

final_entity = entity.deep_dup
clear_null_values(entity)
puts entity
puts final_entity

Outputs:

{2=>{4=>1}}
{1=>nil, 2=>{3=>nil, 4=>1}}

Note: Rails also adds Hash#compact, which you could use to simplify clear_null_values.

potashin
  • 44,205
  • 11
  • 83
  • 107
Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • `deep_dup` worked like a charm. Thanks for the other suggestion suggestion, but I am using Rails 3.2, so Hash#compact won't work. – Sk. Irfan May 11 '18 at 05:26
0

It would be cleaner, in my opinion, to compute the hash stripped of nil values by operating on entities directly, rather than on a copy of it.

def clear_null_values(entity)
  entity.each_with_object({}) do |(k,v),h|
    next if v.nil?
    h[k] = Hash === v ? clear_null_values(v) : v
  end
end

entities = { 1=>nil, 2=>{ 3=>nil, 4=>1 } }

clear_null_values entities
  #=> {2=>{4=>1}}

We can confirm entities was not mutated.

entities
  #=> {1=>nil, 2=>{3=>nil, 4=>1}}
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    I agree, would be better to return a new value instead of having to `dup` and modifying in place. Made a different version of your approach for fun: https://gist.github.com/citizen428/03006bc4dd40ce5a8323275b7c1fe0cc – Michael Kohl May 11 '18 at 06:24
  • @MichaelKohl, your refinement is an improvement, so I've edited my answer to use it. Thanks. – Cary Swoveland May 11 '18 at 15:40