Remember_me
Remember_me

Reputation: 283

iterate over nested hash and modify values in place

I am trying to 'monkey patch' some code we have deployed without breaking the API consumer, basically I want to find param with a key value pair, with value equaling certain value('\u0000') and set it to nil, here is what I have :

def traverse_param(key, value)
  if value.is_a? Hash
    value.each do |k, v|
      return traverse_param(k, v)
    end
  end
  if value == '\u0000'
     value = nil
  end
end

In rails 5 I cannot pass nil to the controller as it will be cast to '' and I need to pass the nil in param onto the service doing the work below.

Now since I can have nested params this is why I check if the param value is a Hash. I worked with this example :

params = {:filters=>{:user=>{:disabled_at=>"\u0000"}, :importan_param=>1}}
params.each { |k, v| traverse_param(k,v)  }

I m trying to modify the value in place, but it does not seem to work? The code is entering the right places when I debug, but it's not producing desired outcome. Any ideas?

Upvotes: 3

Views: 2738

Answers (2)

SRack
SRack

Reputation: 12203

So, the problem you're facing is that you're modifying a local variable value. Were you to pass the entire params hash (or sub-hash) to traverse, you can then update the values as so:

def traverse(params)
  params.each do |k, v|
    traverse(v) if v.is_a? Hash        
    params[k] = nil if v == '\u0000'
  end
end

# => {:filters=>{:user=>{:disabled_at=>nil}, :importan_param=>1}}

You can tidy this a little using transform_values if you're Ruby > 2.4:

def traverse(params)
  params.dup.transform_values { |v| v == "\\u0000" ? nil : v.is_a?(Hash) ? traverse(v) : v }
end

traverse(params)
# => {:filters=>{:user=>{:disabled_at=>nil}, :importan_param=>1}}
params
# => {:filters=>{:user=>{:disabled_at=>"\\u0000"}, :importan_param=>1}}

Or, far more readable:

def traverse(params)
  params.dup.transform_values do |v| 
    next traverse(v) if v.is_a?(Hash)
    v == "\\u0000" ? nil : v
  end
end

Hope that's useful - let me know how you get on or if you have any questions.

Upvotes: 3

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

You are abusing each for mapping. Mapping is to be done with map, each is for iterating.

def fix(hash)
  hash.map do |k, v|
    [k, case v
        when Hash then fix(v)
        when '\u0000' then nil
        else v
        end]
  end.to_h
end

Upvotes: 1

Related Questions