Ruby private attr_accessor and unexpected nil

ruby

Solution

A form of `foo = bar` assigns to a local variable called `foo`. If you want to call an `attr_writer`, you need to use an explicit receiver: `self.sneaky += 1`.

This has nothing to do with `private`, it's just basic Ruby syntax for local variable assignments.

Problem

Back when I used Ruby regularly, I had a bad habit of leaving everything public and ignoring privacy. Unfortunately, that ignorance is coming back to haunt me. Here's a simpler version of my problem: ``` class Something private attr_accessor :sneaky public def initialize @sneaky = 0 end def test while sneaky < 10 puts "#{sneaky}" sneaky = (sneaky + 1) end end end test = Something.new test.test ``` This prints the correct value of sneaky (`0`), then errors out at `sneaky = (sneaky + 1)`, saying that `sneaky` is `nil`: ``` 0 test.rb:13:in `test': undefined method `+' for nil:NilClass (NoMethodError) from test.rb:19:in `<main>' ``` What's up with that? `@sneaky`'s been set to 0 in the constructor. And if it really were nil, shouldn't that `puts` print a blank line and not a `0`? EDIT: Yep, replacing `sneaky = (sneaky + 1)` with `self.sneaky = sneaky + 1` solves the problem, even though `self.sneaky=` looks like a privacy violation, since it has an explicit receiver. Apparently an exception is made for setters. But the weird interaction with privacy means you can't say `self.sneaky += 1` (you end up getting `test.rb:14:in 'test': private method 'sneaky' called for #<Something:0x000001019004c8 @sneaky=0> (NoMethodError)`). Fortunately I'm not the only one who thinks that's weird.

Original source