Ruby attr_reader allows one to modify string variable if using <<
You are not really writing the val attribute. You are reading it and invoking a method on it (the '<<' method).
If you want an accessor that prevents the kind of modification you describe then you might want to implement a method that returns a copy of @val instead of using attr_reader.
Just a little modification of your example:
test = TestClass.new([])
Now you should get (replace puts with p to get the internal view):
[]
['hello']
It's the same thing. You 'read' val, and now you can do something with it. In my example, you add something into the Array, in your example you add something to your String.
Read-access reads the object (which can be modified), write-access change the attribute (it replaces it).
Perhaps you look for freeze
:
class TestClass
attr_reader :val
def initialize(value)
@val = value
@val.freeze
end
end
test = TestClass.new('hello')
puts test.val
test.val << ' world'
puts test.val
This ends in:
__temp.rb:12:in `<main>': can't modify frozen string (RuntimeError)
hello
To avoid side effect with a frozen variable, you may duplicate value
:
class TestClass
attr_reader :val
def initialize(value)
@val = value.dup.freeze #dup to avoid to freeze the variable "value"
end
end
hello = 'hello'
test = TestClass.new(hello)
puts test.val
hello << ' world' #this is possible
puts test.val #this value is unchanged
test.val << ' world' #this is not possible
puts test.val
Assigning is different to modifying, and variables are different to objects.
test.val = "hello world"
would be a case of assignment to the @val
instance variable (which would not work), whereas
test.val << " world"
would be a modification of the object referred to by @val
.
Why does the absence of the assignment operator permit me to modify a Ruby constant with no compiler warning? is a similar question, but talking about constants rather than instance variables.