2

Is this the way that attr_reader is supposed to work with arrays ?

class User
  def initialize
    @interests = []
  end
  attr_reader :interests
end

u = User.new

=> #User:0x1d2cb60

u.interests << "music"

=> ["music"]

u.interests[1] = "travelling"

=> ["travelling"]

u.interests = nil

NoMethodError: undefined method `interests=' for u:User

I am just checking if my own explanation is correct, if not please correct me:

Is attr_reader not stopping me from assigning "@interests" values because you are not directly modifying the instance variable itself (which still holds a reference to the array object) but you are just operating on the values that that array object references ?

If that's correct, is there a quick and nice way to avoid attr_reader giving me write access to the array values, but letting me read them ?

Thanks

2
  • possible duplicate of Ruby attr_reader allows one to modify string variable if using << Commented Mar 1, 2013 at 3:16
  • Yes indeed the questions are on exactly the same topic but I'd like to point out that mine is a bit different in the fact that other than reporting the behaviour I am also explicitly asking for ways to modify it.. also I found it a bit hard to find the correct wording to look for other answers about this, but.. up to you... Commented Mar 1, 2013 at 3:35

2 Answers 2

3

What you describe is correct with regards to "not directly modifying the instance variable". @interests is unchanged, however since it's a mutable object, the caller can sneak in behind you.

If you want to disallow edits of the :interests field, just freeze it. The timing is tricky since you want to prevent writes before handing the object back to the user, so you'd probably need to do this at object creation time:

def initialize(whatever)
   @foo = bar
   @interests = %w(a b c).freeze
end

Just be careful should your object want to make changes to @interests. You'd need to rebuild the array entirely, since you cannot unfreeze an object.

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

2 Comments

Thanks for your answer, i'll wait a bit more to see if other answers are coming, and if not I'll flag it right. ..So I'd need to do data serialization because #dup will not deep copy it... right? deep copying on objects would have made for a nice new little feature for Ruby 2.0 :) oh well..
Correct, you generally want to write your own #dup methods if you're going to be doing that a lot. OTOH, this is a rare case where you can reuse the same object since it's frozen - ergo nobody can change it! :) And if you write your own #dup you can take advantage of that fact... but better write a comment to save yourself a headache down the road.
2

Code your own getter, and use dup to supply a copy of the original array:

def interests
  @interests.dup
end

Full code:

class User
  def initialize
    @interests = []
  end

  def interests
    @interests.dup #
  end
end

# Without dup:
u.interests # => []
u.interests << "music"
u.interests # => ["music"]

# With dup:
u.interests # => []
u.interests << "music"
u.interests # => []

(Naturally, you'd need a slightly more advanced solution for multidimensional arrays and similar layered data structures)

1 Comment

Welcome to SO :)

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.