How to "New" an array of Hashes

I am creatinig a "constant" array containing hashes. Then, in the code, I create a new array based on the constant array. The hashes in the new array are then manipulated in the code. Here is some psudo- code showing what I am doing.

TAB_SET = [ {<hash1>}, {<hash2>}, ... {<hashn>} ]

def process_tabset   tabs = Array.new(TAB_SET)   <Process the tabs array here, modifying the hash values as needed> end

Apparently what happens is that the new array contains REFERENCES to the original hashes instead of separate copies. Whatever changes are made to hashes in the "tabs" array, also shows up in the hashes in the TAB_SET array. In development mode (with config.cache_classes = false) this doesn't matter, as the whole thing gets re-initialized on the next call anyway. But in Production mode, this causes all sorts of problems.

How do I create the tabs array with copies of the hashes, instead of references back to the originals?

Thanks, sjf

sjf_control wrote:

TAB_SET = [ {<hash1>}, {<hash2>}, ... {<hashn>} ]

def process_tabset   tabs = Array.new(TAB_SET)   <Process the tabs array here, modifying the hash values as needed> end

Apparently what happens is that the new array contains REFERENCES to the original hashes instead of separate copies.

According to the pickaxe book it should be a copy. Perhaps try

tabs = Array.new.concat(TAB_SET)

TAB_SET.dup

I won't ask why you would actually want to do this.

Rein

Nope -- doesn't work.

irb(main):001:0> TAB_SET = [{:one=>"uno"},{:two=>"dos"}] => [{:one=>"uno"}, {:two=>"dos"}] irb(main):002:0> tabs = Array.new.concat(TAB_SET) => [{:one=>"uno"}, {:two=>"dos"}] irb(main):003:0> tabs[1][:two] += "(2)" => "dos(2)" irb(main):004:0> tabs => [{:one=>"uno"}, {:two=>"dos(2)"}] irb(main):005:0> TAB_SET => [{:one=>"uno"}, {:two=>"dos(2)"}] irb(main):006:0>

Well, why I would want to do this isn't really relevant, unless you have a better way.

In short, each array entry contains information about a folder-tab on a page. One of the hashes contains the tab's label, and I want to be able to dynamically update (append to) the label based on the contents of that page. In the example below, though, I would get "dos(2)" the first time (correct), and "dos(2)(2)" the second, and so forth.

But in any case, "dup" doesn't work either...

irb(main):001:0> TAB_SET = [{:one=>"uno"},{:two=>"dos"}] => [{:one=>"uno"}, {:two=>"dos"}] irb(main):002:0> tabs = TAB_SET.dup => [{:one=>"uno"}, {:two=>"dos"}] irb(main):003:0> tabs[1][:two] += "(2)" => "dos(2)" irb(main):004:0> tabs => [{:one=>"uno"}, {:two=>"dos(2)"}] irb(main):005:0> TAB_SET => [{:one=>"uno"}, {:two=>"dos(2)"}]

I believe the problem is NOT that the array is a reference. Using either "new" or "dup" should get me a fresh array. The problem is that the array ELEMENTS (the hashes) are references back to the original hashes. So I'm guessing I need something like:

tabs = Array.new( Hash.new(hash1), Hash.new(hash2), ... ) But that sure isn't very pretty!

Anybody know an easier way?

sjf

In a situation like this, where you are using basic data structures and defining all this business logic external to them to control their use, the ruby way would be to encapsulate all this logic in a class and use it like:

@tabs = TabSet.new( options )

This way your TabSet class encapsulates all the logic of initializing and controlling a set of tabs (whatever that may be). You can use intention-revealing methods on that class and its instances to achieve its goals.

Otherwise, and I hesitate to suggest this for fear that you might actually do it, you would need a deep copy:

@tabs = Marshal.load(Marshal.dump(TAB_SET))

But please please please please don't actually do that. It's ugly as all hell.

Rein

Rein

Thanks for sticking with this subject. I am sure I am missing your point here. The problem exists whether the code is in a separate class or not. As a test I added the file tab_set.rb to my models. It contains...

class TabSet

  TAB_SET = [{:label=>"one"}, {:label=>"two"}]

  def initialize     @a = Array.new(TAB_SET)   end

  def process     @a[1][:label] = TAB_SET[1][:label] + "(2)" # grab the original value and append a string     @a   end end

I then run it from the console...

a = TabSet.new

=> #<TabSet:0x36caf40 @a=[{:label=>"one"}, {:label=>"two"}]>

b = a.process

=> [{:label=>"one"}, {:label=>"two(2)"}]

b = a.process

=> [{:label=>"one"}, {:label=>"two(2)(2)"}]

in fact, its even worse -- since if I now try to NEW another tabset...

c=TabSet.new

=> #<TabSet:0x36c7048 @a=[{:label=>"one"}, {:label=>"two(2)(2)"}]>

What did I miss?

Thanks, sjf

The issue that you are running into here is one of scope. Since TAB_SET is the same across all instances of the class, modifying it will modify all variables that point to it. The point of using a class is that you can change the implementation to be more robust, not just to wrap your current implementation inside a class.

This isn't that pretty either but you could possibly do:

tabs = TAB_SET.dup.map(&:dup)