HSM744
HSM744

Reputation: 11

What is causing the issue here?

when I run this code in ruby:

updated_users = []
users = [{:name => "sam" , :number => 001 }]
ids = ["aa" , "bb" , "cc"]
 
users.each do |user|
  ids.each do |id|
    user[:guid] = id
    updated_users << user
  end       
end

p updated_users

I get:

[{:name=>"sam", :number=>1, :guid=>"cc"},
 {:name=>"sam", :number=>1, :guid=>"cc"},
 {:name=>"sam", :number=>1, :guid=>"cc"}]

I expected to get:

[{:name=>"sam", :number=>1, :guid=>"aa"},
 {:name=>"sam", :number=>1, :guid=>"bb"},
 {:name=>"sam", :number=>1, :guid=>"cc"}]

What is happening and how should I get the desired output?

Upvotes: 1

Views: 86

Answers (1)

Cary Swoveland
Cary Swoveland

Reputation: 110755

Your question reflects a common misunderstanding held by budding Rubyists. You begin with the following:

updated_users = []
users = [{:name => "sam" , :number => 001 }]
ids = ["aa" , "bb" , "cc"]

users is an array containing a single hash. Let's note the id of that hash object:

users.first.object_id
  #=> 1440

Now let's execute your code with some puts statements added to see what is going on.

users.each do |user|
  puts "user = #{user}"
  puts "user.object_id = #{user.object_id}"
  ids.each do |id|
    puts "\nid = #{id}"
    user[:guid] = id
    puts "user = #{user}"
    puts "user.object_id = #{user.object_id}"
    updated_users << user
    puts "updated_users = #{updated_users}"
    updated_users.size.times do |i|
      puts "updated_users[#{i}].object_id = #{updated_users[i].object_id}"
    end
  end
end

This displays the following.

user = {:name=>"sam", :number=>1}
user.object_id = 1440

So far, so good. Now begin the ids.each loop:

id = aa
user = {:name=>"sam", :number=>1, :guid=>"aa"}
user.object_id = 1440

As user did not previously have a key :guid, the key-value pair :guid=>"aa" was added to user. Note that user's id has not changed. user is then appended to the (empty) array updated_users:

updated_users = [{:name=>"sam", :number=>1, :guid=>"aa"}]
updated_users[0].object_id = 1440

Again this is what we should expect. The next element of ids is then processed:

id = bb
user = {:name=>"sam", :number=>1, :guid=>"bb"}
user.object_id = 1440

Since user has a key :guid this merely changes the value of that key from "aa" to "bb". user is then appended to updated_users:

updated_users = [{:name=>"sam", :number=>1, :guid=>"bb"},
                 {:name=>"sam", :number=>1, :guid=>"bb"}]
updated_users[0].object_id = 1440
updated_users[1].object_id = 1440

The first and second elements of this array are seen to be the same object user, so changing the value of :user's key :guid affected both elements in the same way.

The same thing happens when the third and last element of ids is processed:

id = cc
user = {:name=>"sam", :number=>1, :guid=>"cc"}
user.object_id = 1440
updated_users = [{:name=>"sam", :number=>1, :guid=>"cc"},
                 {:name=>"sam", :number=>1, :guid=>"cc"},
                 {:name=>"sam", :number=>1, :guid=>"cc"}]
updated_users[0].object_id = 1440
updated_users[1].object_id = 1440
updated_users[2].object_id = 1440

Got it?


To obtain the result you want you need to append updated_users with distinct hashes derived from user:

users.each do |user|
  ids.each do |id|
    updated_users << user.merge(guid: id)
  end
end

updated_users
  #=> [{:name=>"sam", :number=>1, :guid=>"aa"},
  #    {:name=>"sam", :number=>1, :guid=>"bb"},
  #    {:name=>"sam", :number=>1, :guid=>"cc"}]

Note that users has not been mutated (changed):

users
  #=> [{:name=>"sam", :number=>1}]

See Hash#merge. Note that user.merge(guid: id) is shorthand for user.merge({ guid: id }) or, equivalently, user.merge({ :guid => id }).

Upvotes: 5

Related Questions