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)