Friday, 28 March 2008

Rails: Don't override initialize on ActiveRecord objects

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.

22 comments:

Casper Fabricius said...

Thanks - this information was just what I needed :)

Sxadvi said...

Thank you Thank you Thank you

Livet är en kod. said...
This comment has been removed by the author.
kulpae said...

Thank you!

Abhijit Dixit said...

Thank you!! One year old post still benefiting Rails users :)

SoccerShoutPhil said...

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.

Ian Moody said...

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

Dale said...

@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.

Take -IT- Easy said...

Thanks a lot!!!!!!

Oliver Haag said...

You saved the day (looked 4 hours for the error ..)

Stephen said...

Thanks for the great description. This saved me a ton of time.

steven said...

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.

Daniel Rosenstark said...

Thanks, that really helps. I mean, I actually needed to do what you said not to do, but the code helped :)

lessless said...

thnaks! thats w0rk3d for me, but
DEPRECATION WARNING: Base#after_initialize has been deprecated, please use Base.after_initialize :method instead.

arkadi.kagan said...

You have saved me a day. Thanks.

stz said...

Thanks -- exactly what I wanted to do and exactly the problem I ran into.

Rob said...

Excellent. That's just what was confusing me, thanks.

Unknown said...

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.

Dale said...

@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.

Blog-Name said...

Seems in Rails 3.2 the syntax has changed:

class User < ActiveRecord::Base
after_initialize do
@my_cache = {}
end
end

worked for me

curtis foster said...

As "Blog-Name" stated, this is also the case for Rails 3.1

den be said...

Dịch vụ chuyển phát nhanh ở tphcm ngày ngày càng phát triển góp phần nâng cao dịch vụ mua bán đặc biệt mua bán online. Nên các chuyển phát nhanh tphcm luôn được phát triển nâng cao nhu cầu phục vụ công ty doanh nghiệp. Dịch vụ chuyển phát nhanh của Công ty Proship đã được nhiều tổ chức, doanh nghiệp, cá nhân yêu mến, tin cậy và hợp tác lâu dài. Trong quá trình xây dựng và phát triển, chúng tôi công ty chuyển phát nhanh tại tphcm là hiện thân cho tinh thần trách nhiệm và trung thực, dịch vụ chu đáo và nhanh chóng. Tại thị trường Tphcm, Proship được đánh giá là một trong những Công ty Chuyển phát nhanh làm ăn có hiệu quả, uy tín, có sức phát triển và dành được nhiều cảm mến. Chuyên cung cấp các dịch vụ gửi hàng vào sài gòn .Ngoài dịch vụ nhanh chóng, chu đáo và giá cả hợp lý thì Proship cũng có nhiều chương trình khuyến mãi cho các khách hàng tại Sài Gòn. Nên nếu bạn là cá nhân hay công ty doanh nghiệp đang có nhu cầu sử dụng giá chuyển phát nhanh vào sài gòn hãy liên hệ với chúng tôi công ty Proship. Với những uy tín vốn có của doanh nghiệp, chuyển phát Proship nhận vận chuyển các loại hàng hóa và bưu phẩm theo yêu cầu của khách hàng. Với thời gian 1 ngày duy nhất cho quá trình vận chuyển hàng từ các tỉnh thành trên cả nước và chuyển phát nhanh uy tín tại sài gòn , chúng tôi sẽ nhận hàng của quý khách tại văn phòng hoặc tại nhà riêng, công ty sau đó sẽ kết nối vào Đà Nẵng và phát tận tay người nhận theo địa chỉ ghi trên bưu kiện. Nếu bạn có nhu cầu sử dụng dịch vụ ship hàng Nhật chúng tôi đáp ứng cho bạn với chi phí và giá phải chăn nhất.Hay tham quan bruno xem nhiều mặt hàng thời trang khác.