3

I have the following code:

default = {:id => 0, :detail =>{:name=>"Default", :id => ""}}
employees = {}

nr = (0..3).to_a 
nr.each do |n|
    employee = default
    employee[:id] = n
    employee[:detail][:name] = "Default #{n}"
    employee[:detail][:id] = "KEY-#{n}"
    employees[n] = employee
end
puts employees

I expect the values for the key :id in :detail hash to be KEY-0, KEY-1, KEY-2.

potashin
  • 44,205
  • 11
  • 83
  • 107

3 Answers3

1

You will need marshall your default in order to copy

default = {id: 0, detail: {name: "Default", id:""}}
employees = {}
4.times do |n|
  employees[n] = Marshal.load(Marshal.dump(default))
  employees[n][:id] = n
  employees[n][:detail][:name] = "Default #{n}"
  employees[n][:detail][:id] = "KEY-#{n}"
end
puts employees

The output is

{0=>{:id=>0, :detail=>{:name=>"Default 0", :id=>"KEY-0"}}, 1=>{:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}}, 2=>{:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}}, 3=>{:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}}

You can read this post Cloning an array with its content

ADDED

And here you have an reduce version and should be faster if you want.

employees = {}
4.times { |n| employees[n]={id: n, detail: {name: "Default #{n}", id:"KEY-#{n}"}} }
puts employees
Community
  • 1
  • 1
Horacio
  • 2,865
  • 1
  • 14
  • 24
1

You need only change:

default = { :id=>0, :detail=>{ :name=>"Default", :id=>"" } }

to

def default
  {}.merge(:id=>0, :detail=>({}.merge(:name=>"Default", :id=>"")))
end

but, hey, while we're at it we may as well Ruby-ize the rest:

employees = (0..3).map do |n|
    employee = default
    employee[:id] = n
    employee[:detail][:name] = "Default #{n}"
    employee[:detail][:id] = "KEY-#{n}"
    employee
end
  #=> [{:id=>0, :detail=>{:name=>"Default 0", :id=>"KEY-0"}},
  #    {:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}},
  #    {:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}},
  #    {:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}] 

Let's confirm we are making deep copies of default:

employees[0][:detail][:id] = "cat"
employees
  #=> [{:id=>0, :detail=>{:name=>"Default 0", :id=>"cat"}},
  #    {:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}},
  #    {:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}},
  #    {:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}] 

You'd more commonly see this written:

employees = (0..3).map do |n|
  default.merge(:id=>n, :detail=>{:name=>"Default #{n}", :id=>"KEY-#{n}"})
end
  #=> [{:id=>0, :detail=>{:name=>"Default 0", :id=>"cat"}},
  #    {:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}},
  #    {:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}},
  #    {:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}] 

As suggested by other answers, you could to this:

class Object
  def deep_copy
    Marshal.load(Marshal.dump(self))
  end
end

Then you could write:

default = { :id=>0, :detail=>{ :name=>"Default", :id=>"" } }
employees = (0..3).map do |n|
  default.deep_copy.merge(:id=>n, :detail=>{:name=>"Default #{n}",
    :id=>"KEY-#{n}"})
end
  #=> [{:id=>0, :detail=>{:name=>"Default 0", :id=>"KEY-0"}},
  #    {:id=>1, :detail=>{:name=>"Default 1", :id=>"KEY-1"}},
  #    {:id=>2, :detail=>{:name=>"Default 2", :id=>"KEY-2"}},
  #    {:id=>3, :detail=>{:name=>"Default 3", :id=>"KEY-3"}}] 

This has the advantage that, if you change default, no other changes are needed.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

You are making a shallow copy in each iteration, i.e. each time each copy is overridden with the values calculated in the last iteration. You can try the following for you hash-within-hash default pattern to make a deep copy:

employee = Marshal.load(Marshal.dump(default))

Demonstration

potashin
  • 44,205
  • 11
  • 83
  • 107