Rustam Gasanov
Rustam Gasanov

Reputation: 15791

Hash overriding a hash

I got some yml config files with a deep, different structures and I want to extract passwords(that are located on different levels) and store them in a yml file outside a git repo. Let me show an example

config1.yml

a:
  b:
    c: 1
    password: secret
    ...
  d: 2
  ...

I wish to replace secret with '' and extract a pass to a different yml file that will look like:

config1_passwords.yml

a:
  b:
    password: secret

Is there any way to merge config1.yml without passwords with config1_passwords.yml to get a correct config structure?

So basically in terms of hashes(parsed ymls) I want to do following:

{ :a => { :b => { :c => 1, :password => '' }, :d => 2 } }
{ :a => { :b => { :password => 'secret' } } } 
# =>
{ :a => { :b => { :c => 1, :password => 'secret' }, :d => 2 } }

Thanks for suggestions

EDITED another example

{ :a => { :b => { :c => 1, :d => { :password1 => '' }, :password2 => '' } } }
{ :a => { :b => { :d => { :password => 'secret' }, :password2 => 'secret2' } } }
# =>
{ :a => { :b => { :c => 1, :d => { :password => 'secret' }, :password2 => 'secret2' } } }

Upvotes: 3

Views: 461

Answers (3)

tessi
tessi

Reputation: 13574

Rails 3 has a deep_merge, which does exactly what you want.

a = { :a => { :b => { :c => 1, :d => { :password1 => '' }, :password2 => '' } } }
b = { :a => { :b => { :d => { :password1 => 'secret' }, :password2 => 'secret2' } } }
a.deep_merge(b)
# -> {:a=> {:b=> {:c=>1, :d=>{:password1=>"secret"}, :password2=>"secret2"}}}

Note: I changed a[:a][:b][:d] to contain :password1 instead of :password.

Upvotes: 4

molf
molf

Reputation: 74985

It appears you want some sort of Hash deep merge. It's available in ActiveSupport (part of Rails):

# You can omit this require statement if you're running Rails.
require "active_support/core_ext/hash/deep_merge"

a = { a: { b: { c: 1, d: { password1: "" }, password2: "" } } }
b = { a: { b: { d: { password1: "secret" }, password2: "secret2" } } }

a.deep_merge(b)
#=> { a: { b: { c: 1, d: { password1: "secret"}, password2: "secret2" } } }

If you don't want to depend on ActiveSupport, take a look at the implementation.

Upvotes: 2

Neil Slater
Neil Slater

Reputation: 27207

Don't think this can be done using Ruby one-liners. But a simple recursive function might do

def recurse_merge_password! conf_hash, pw_hash
  pw_hash.keys.each do |k|
    next unless conf_hash.has_key?(k)
    case
      when k == :password
        conf_hash[:password] = pw_hash[:password]
      when conf_hash[k].is_a?(Hash) && pw_hash[k].is_a?(Hash)
        recurse_merge_password! conf_hash[k], pw_hash[k]
    end
  end
end

h1 = { :a => { :b => { :c => 1, :password => '' }, :d => 2 } }

h2 = { :a => { :b => { :password => "secret" } } }

recurse_merge_password! h1, h2

puts h1.inspect

=> {:a=>{:b=>{:c=>1, :password=>"secret"}, :d=>2}}

If you have arrays and other structures that you may need to traverse, it is up to you to improve on this. Note I made it modify the config in place.

Upvotes: 2

Related Questions