8

Ran into some weird behaviour and wondering if anyone else can confirm what I am seeing.

Suppose you create a class with a member variable, and allow it to be read with attr_reader.

class TestClass
  attr_reader :val

  def initialize(value)
   @val = value
  end
end

Now when I do the following, it seems to modify the value of @val, even though I have only granted it read privileges.

test = TestClass.new('hello')
puts test.val
test.val << ' world'
puts test.val

This returns

hello
hello world

This is just the result from some testing I did in irb so not sure if this is always the case

1
  • attr_reader means that you can't set the value, i.e., no method value= is defined. It certainly doesn't mean that you can't cal a method on the object Commented Nov 6, 2011 at 23:48

4 Answers 4

8

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Rather than implementing another method, you could also duplicate and freeze the value in initialize. I.e., @val = value.dup.freeze. (If you know value is a string, you can accomplish the same thing with @val = -value)
4

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.

Comments

4

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

2 Comments

Be careful with this. You'll want to duplicate the value, so you don't end up freezing somebody else's variable that they passed in.
@JayMitchellI extended my answer to catch this side effect. Thanks for the hint.
1

While this seems unexpected, this is exactly right. Let me explain.

The attr_reader and attr_writer class macro methods define "getter" and "setter" methods for instance variables.

Without a "getter" method, you don't have access to an object's instance variables simply because you're not in the context of that object. The "setter" method is essentially this:

def variable=(value)
  @variable = value
end

Since the instance variable points to a mutable object with a set of methods itself, if you "get" it and manipulate it, it stands to reason that those changes will take. You do not need to use the above setter method to call variable.<<(value).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.