1

I have the following code:

session={"apple"=>["bp", "80APPM", "donald"]}
@like_list = session
@like_list.each do |key, value|
  value.each_with_index do |v, i|
    @like_list[key][i] = 2
  end
end
session    # => {"apple"=>[2, 2, 2]}
@like_list # => {"apple"=>[2, 2, 2]}

The object assigned to the variables session and @like_list are both changed. Is there any way to perform the above code without changing the value of session?

I tried using clone and dup but no change

Eric Luo
  • 118
  • 2
  • 11

3 Answers3

1

You need to make a copy of the original hash, to prevent two variables reference the same object.

@like_list = session.map { |key, value| [key, value.clone] }.to_h
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • 1
    This method works for a single-level hash, but if there are any nested levels, those below the top level will still be shallow copied. To make this work, you need to handle arbitrary levels of nesting. – Michael Gaskill May 16 '16 at 00:57
  • 1
    @Michael, the question did not specify multiple levels of nesting, and the specified value of `session` has but one, so this answer is fine, preferable to methods that permit arbitrary levels of nesting. – Cary Swoveland May 16 '16 at 19:16
  • My comment was simply intended as a warning to anyone that tries to use this in the general case. Questions and answers have a long lifetime on SO. – Michael Gaskill May 16 '16 at 20:01
1

This works:

session={"apple"=>["bp", "80APPM", "donald"]}
@like_list = Marshal.load(Marshal.dump(session))
@like_list.each do |key, value|
  value.each_with_index do |v, i|
    @like_list[key][i] = 2
  end
end

It uses the Marshal.load(Marshal.dump(session)) solution from this SO question: Ruby dup/clone recursively

Community
  • 1
  • 1
Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
  • `Marshal.load(Marshal.dump(session)).each_with_object({}) { |(k,v),h| h[k] = [2]*v.size }` or `@like_list = Marshal.load(Marshal.dump(session)).tap { |h| h.each { |k,v| h[k] = [2]*v.size } }`, but as I've indicated in other comments, I don't think the question calls for a deep dup. – Cary Swoveland May 16 '16 at 19:47
0

Use Hash.deep_dup.

@like_list = session.deep_dup

Here is the documentation for the method.

EDIT:

This method comes from activesupport library, which is included with Rails. As suggested in the comments, this method used to be problematic, but as of Rails 4, it behaves more expectedly. I tested the above example and it works with activesupport 4.2.3.

If you are not using Rails, you need to load activesupport:

require 'active_support'
Petr Gazarov
  • 3,602
  • 2
  • 20
  • 37
  • 2
    Hash#deep_dup doesn't work with contained arrays. Try it out and see. – Michael Gaskill May 16 '16 at 00:48
  • @MichaelGaskill Which ruby version are you using? I'm running 2.3.0 and it works – Petr Gazarov May 16 '16 at 00:52
  • 1
    Ruby 2.2.5, 2.1.9, and 1.9.3, and none of them work. It must be fixed in the new one. That bug has been around for ages and ages... – Michael Gaskill May 16 '16 at 00:53
  • 1
    I am using Ruby 2.3 and Rails 4. deep_dup method works for me. Both variable values are kept. Thanks! – Eric Luo May 16 '16 at 01:32
  • You have, imo, answered a question that wasn't asked. In the absence of a specification that `session["apple"]` can have multiple levels of nesting, and the fact that the given value does not have nesting, we should assume there is no nesting, in which case loading `activesupport` (note no Rails tag) is overkill. It's fine to show how nesting could be dealt with, but only as an aside. – Cary Swoveland May 16 '16 at 19:28