Reputation: 11
I'm currently learning the basics of Ruby and OOP in general. From what I've read so far, I can use attr_reader to grab the value of an instance variable but not give it access to be overwritten. However, given the code block below, the end result is not what I intended and the instance variable was completely changed from outside of the class. What would be the best way where I can simply read the value and return the intended changes into another variable instead of overwriting the instance variable itself?
class X
def initialize
@y = [1,2,3,4]
end
attr_reader :y
end
class Z
def initialize
end
def self.change(arr)
arr[1] = 0
arr[2] = 0
return arr
end
end
x = X.new
z = Z.change(x.y)
p z
p x.y
Upvotes: 1
Views: 412
Reputation: 114218
From what I've read so far, I can use
attr_reader
to grab the value of an instance variable but not give it access to be overwritten.
Yes and no.
attr_reader :y
creates a so-called getter which is equivalent to:
def y
@y
end
attr_writer :y
creates the corresponding setter:
def y=(value)
@y = value
end
And attr_accessor
creates both.
The getter allows you to conveniently access @y
from the outside. The setter allows you to re-assign @y
.
But even with just a getter, you can still send messages to the object. And if the object is mutable, like your array, you can modify it that way:
x = X.new
x.y #=> [1, 2, 3, 4]
x.y.push(5)
x.y #=> [1, 2, 3, 4, 5]
In the above example, @y
is not re-assigned, it still refers the same object. But the message push
caused the object to change itself.
What would be the best way where I can simply read the value [...]
There are several options. If you want to prevent modification form the outside, you could return a copy of the original array:
class X
def initialize
@y = [1, 2, 3, 4]
end
def y
@y.dup
end
end
dup
creates a shallow copy of the array and returns it. "shallow" means that a new array is created containing the same elements. Any modification from the outside to the array will only affect the copy.
But you could still modify its elements (via messages) and that change would be reflected by both, original and copy.
Fortunately, your array contains integers which are immutable.
Upvotes: 2
Reputation: 369536
However, given the code block below, the end result is not what I intended and the instance variable was completely changed from outside of the class.
No, it wasn't. The object that the instance variable references was changed. That is to be expected: arrays can be changed, and you handed the caller an array, so the caller can obviously change the array.
But the instance variable was not changed: it still points to the exact same object as before.
My boss and my mother call me by different names. But if I shave my beard, both of them will see my clean-shaven face.
Understanding the difference between a thing and the name of the thing is fundamental in programming.
What would be the best way where I can simply read the value and return the intended changes into another variable instead of overwriting the instance variable itself?
The best way is to not expose internal representation in the first place. It's not quite clear from your question how a client is expected to use X
(the naming is pretty terrible). So, for example, if clients are expected to iterate over the contents of @y
, then you offer them a way to do exactly that and only that.
class X
def initialize
self.y = [1, 2, 3, 4]
end
def each_y(...)
return enum_for(__callee__) unless block_given?
y.each(...)
self
end
private attr_accessor :y
end
See Overriding the << method for instance variables for another example of the same problem and how to solve it.
Upvotes: 1
Reputation: 161
by using clone
: https://ruby-doc.org/core-2.7.2/Object.html#method-i-clone
Produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference.
class X
def initialize
@y = [1,2,3,4]
end
attr_reader :y
end
class Z
def self.change(arr)
arr2 = arr.clone
arr2[1] = 0
arr2[2] = 0
return arr2
end
end
x = X.new
z = Z.change(x.y)
p z
p x.y
Upvotes: 0