ActiveRecord::Base doesn't always use
new
to create objects, so
initialize
might not be called.
I wanted to use a Hash on an
ActiveRecord::Base
subclass to store some calculated values, so I naively did this:
class User < ActiveRecord::Base
def initialize(args = nil)
super
@my_cache = {}
end
end
However I quickly ran into some "
You have a nil object when you didn't expect it!
" issues. Some debugger investigation revealed that the
@my_cache
variable wasn't being set when I called
find_or_create_
if the object already existed in the database.
Digging in the source revealed that the
instantiate
method in active_record/base.rb uses
allocate
to create classes rather than
new
. This means the
initialize
method is being neatly sidestepped when creating objects from the database.
The solution is to use the '
after_initialize
' callback:
class User < ActiveRecord::Base
def after_initialize
@my_cache = {}
end
end
One further note of caution, When passing parameters into a
new
or
create
method the
after_initialize
is called after the parameters have been set. So you can't rely on the initialization being done before overridden accessors are called.
20 comments:
Thanks - this information was just what I needed :)
Thank you Thank you Thank you
Thank you!! One year old post still benefiting Rails users :)
Thanks for this post.
Two things:
1) I had to use 'self.myattribute' instead of '@myattribute' - not sure why (I'm pretty new)
2) Remember to check whether 'myattribute' is null, otherwise you'll overwrite what is in your db when you recall the object.
Thanks - That's just fixed a very long standing bug of mine - I was using "before_create" but it wasn't getting called early enough.. xxx
@SoccerShoutPhil 1) Not sure, I've not done any Rails for a while. 2) I used this method for calculated values that would not be stored in the database.
Thanks a lot!!!!!!
You saved the day (looked 4 hours for the error ..)
Thanks for the great description. This saved me a ton of time.
I've found that I need to use both.
I have an AR object that handles a few different files. I got sick of defining setter methods to handle these files so I created a method to automatically generate the setters using class_eval. Unfortunately, I have to call the method twice. Once in initalize so it works for new objects and once again in after_initalize so it works for updates. In after_initalize I also have to check that the file setter methods haven't been created already so that's additional overhead.
It all works but I worry that the code is much less clear than I originally intended.
Thanks, that really helps. I mean, I actually needed to do what you said not to do, but the code helped :)
thnaks! thats w0rk3d for me, but
DEPRECATION WARNING: Base#after_initialize has been deprecated, please use Base.after_initialize :method instead.
You have saved me a day. Thanks.
Thanks -- exactly what I wanted to do and exactly the problem I ran into.
Excellent. That's just what was confusing me, thanks.
This is nice, but you still might need to overwrite ActiveRecord's initialize function. I think your error is due to the fact that the initializer takes a Hash as an argument, which explains you're "You have a nil object when you didn't expect it!". The proper call to override it would be this if I'm not mistaken:
class User < ActiveRecord::Base
def initialize(args = {})
super
@my_cache = {}
end
end
Then you'll see those error messages go away.
@Unknown Thanks for your comment, I've not looked at the code for a while but the issue I had was that initialize wasn't called at all when active record loaded the object back from the DB.
Seems in Rails 3.2 the syntax has changed:
class User < ActiveRecord::Base
after_initialize do
@my_cache = {}
end
end
worked for me
As "Blog-Name" stated, this is also the case for Rails 3.1
Post a Comment