Quirks of Assignment

Jon Yurek

If you’ve been using Ruby for any length of time, then you should know that you can override assignment on object attributes, right? It’s what you get from attr_accessor (by way of attr_writer), and it’s what you get when you define a something= method (which is what attr_writer really does).

>> class Foo
     def bar= val
       @bar = val
     end
   end
>> f = Foo.new
>> f.bar = :none
>> p f
#<Foo:0x319afc @bar=:none>

The setter is a method, completely normal, and you can call it with send like any other. Trouble is that when you assign stuff in Ruby (that is, a simple a = b statement), your setter doesn’t act like a normal method. First off, all assignments return the right-hand-side of the assignment, instead of what the return value of the setter would be.

>> def test= val
     @test = val.to_f
   end
>> self.test = "12.34"
=> "12.34"
>> self.send(:test=, "12.34")
=> 12.34

Secondly, if you use a getter method bare (like, say, foo) without having defined it first, Ruby will try to access the local variable foo first. Seeing it isn’t there, it’ll try to access the method on self. And then if it isn’t there, it’ll go to method_missing. If you try this with a setter, though, you’ll find it won’t get past the local variable… Ruby will just set it and continue on. You need to explicitly call it through self to get it to work.

>> def foo= val
     puts val
     @foo = val
   end
>> foo = 1
=> 1
>> self.foo = 1
1
=> 1

Both of these quirks have caught me before in rather unexpected ways, most annoyingly the first one, because I fully expected the return value of the statement to be the return value of the setter. Getters work fine without being referenced through an object, just remember to add self before setters, or you may find yourself wondering wtf is going on, since everything looks right.