PandaRaid
PandaRaid

Reputation: 628

Dynamically Modify Ruby Instance Variable in Array

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

Answers (2)

Wayne Conrad
Wayne Conrad

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.

"Box" a reference in an array

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

Encapsulate the number in a mutable object.

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.

Why these two solutions work

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

seph
seph

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

Related Questions