Reputation: 628
I have an array ["moniker", @moniker]
where the moniker can be any one of around 100 instance variables and its string representation. I want to change what the instance variable located at index 1 is referencing (not that data itself, which may very well be immutable). Just doing array[1] = newData
doesn't work because it just changes whats in the array. I know this would be simple in C, but I'm struggling to find a way to do this in Ruby.
Upvotes: 1
Views: 333
Reputation: 107969
Your struggle is because you are thinking like a C programmer, where you have access to the underlying pointers, and where everything is mutable. In C, The array would store a pointer to a mutable integer, and you could change the integer whenever you want. In Ruby, every variable is a reference to an object, and numbers are immutable objects. So, @moniker is a reference to an object, the integer 4. When you create the array, you copy that reference into the array, so now the integer 4 has two references: One from @moniker, and one from the array. As you have found, changing the reference in the array does not change the reference named @moniker--it still refers to the object 4.
This is not really a Ruby way of doing things. I'm showing it because it might help to illustrate how Ruby works with references.
You can box a reference in an array:
@moniker = [4]
a = ["moniker", @moniker]
This requires you to deference the array when you want access to the underlying object:
@moniker.first
a[1].first
But now you can change the underlying integer in @moniker and the array will see the change:
@moniker[0] = 42
p a[1].first # => 42
Being an object oriented language, you might encapsulate that number in a mutable object.
class Moniker
attr_accessor :value
def initialize(value)
@value = value
end
end
(attr_accessor :value
builds reader and writer methods for the instance variable @value
).
@moniker = Moniker.new(4)
a = ["monikier", @moniker]
@moniker.value = 42
p a[1].value # => 42
You would obviously chose a better name than "value." I couldn't because I don't know what the value represents.
This was a comment by Jörg W Mittag, but it deserves to be part of the answer:
It may seem obvious, but I wanted to mention it explicitly: the two solutions are the same solution. The first uses an already existing class with generic semantics, the the second defines a new class with precise semantics for the specific encapsulated value. But in both cases, it's about wrapping the immutable value in a mutable value and mutating the "outer" value.
Upvotes: 1
Reputation: 6076
@moniker never got into the array but its value did.
In IRB:
@moniker = 4
a = ["moniker", @moniker]
=> ["moniker", 4]
You're just working with the value in the array anyway so just change it and you're good to go:
a[1] = 5
a
=> ["moniker", 5]
You might want to consider a hash:
h = {:moniker => @moniker}
=> {:moniker=>4}
h[:moniker] = 5
h
=> {:moniker=>5}
Upvotes: 1