attr_accessor_with_default

Posted by Andre Foeken Wed, 05 Mar 2008 08:53:39 GMT

What a marvelous feature, but be wary! It can cause some unexpected behavior if you don't know what you are doing.

This morning we found a rather suspicious bug that lead to unexpected things. Here is an example:

class Person ; attr_accessor_with_default :things, {} ; end
john = Person.new
john.things[:table] = true
...
jim = Person.new
jim.things => {:table => true} # huh??

As it would seem attr_accessor_with_default has some problems with collections. Since we are sharing the instance over the entire class. Peter Williams noted this problem several month ago in his article (which we didn't read until it was too late), however the solution he provided still left us with some very undesireable behaviour.

class Person ; attr_accessor_with_default :things, {{}} ; end # note the extra brackets!


john = Person.new
john.things[:table] = true

...

jim = Person.new
jim.things => {} # okay!
john.things => {} # uhm... not okay!

The variable would only stick if we actually assigned it.

john.things = {:table => true}
john.things => {:table => true} # yay!

But doing this every time is not only a pain but also introduces very hard to debug errors, since the assignment does not fail...it just doesn't work!

We solved it by going old-school. Back to the normal accessor for collections.

class Person
attr_accessor :things

 def initialize attributes=nil
  super
  self.things = {}
 end

end

Now the code works as expected and we can use all operators (like >>, []) from the get go.

Update: It seems this had no effect on ActiveRecord objects that were created using finders so here is the fix for any those:

class Person < ActiveRecord::Base
attr_accessor :things

 def after_initialize
  self.things = {}
 end

end

Posted in ,  | Tags , ,  | no comments