views:

321

answers:

2

Attempting to be DRY, I'm trying to assign to a model's instance variable after object initialization.

class WorkNote < ActiveRecord::Base

  def after_initialize
    self[:clockin]= WorkNote.last_clockout
  end

  def self.last_clockout
    WorkNote.find(:first, :order => "clockout DESC").clockout
  end
end

However, the method call in after_initialize causes a SystemStackError:

ActiveRecord::StatementInvalid: SystemStackError: stack level too deep: SELECT * FROM "work_notes"  ORDER BY clockout DESC LIMIT 1
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/abstract_adapter.rb:212:in `log'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/sqlite_adapter.rb:157:in `execute'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/sqlite_adapter.rb:402:in `catch_schema_changes'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/sqlite_adapter.rb:157:in `execute'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/sqlite_adapter.rb:305:in `select'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/abstract/database_statements.rb:7:in `select_all_without_query_cache'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/abstract/query_cache.rb:62:in `select_all'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:661:in `find_by_sql'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:1553:in `find_every'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:1510:in `find_initial'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:613:in `find'
    from /Users/noob/jobs/app/models/work_note.rb:10:in `last_clockout'
    from /Users/noob/jobs/app/models/work_note.rb:6:in `after_initialize'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/callbacks.rb:347:in `send'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/callbacks.rb:347:in `callback'
    from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:1662:in `send'
... 5116 levels...

If I comment out after_initialize, the last_clockout method has no problems. Neither does this happen when I use a callback like before_save instead of after_initialize. Why is after_initialize causing this?

Thanks!

+2  A: 

After initialize is called whenever an object is instantiated (new'ed). Your find call in self.last_clockout is creating an object, and then recursively calling the after_initialize. Hence the infinite recursion and stack overflow.

Before save or after create be more appropriate.

See http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

ndp
+1  A: 

I've found default_value_for is a very good way of doing this.

Luke
Thanks for the helpful replies! I get what the problem is. Wouldn't `default_value_for` have the same problem, though? Seems like as long as the initial value for the attribute requires a find(), a new WorkNote object will have to be instantiated and the loop will keep happening.Other ways I can think to do this:a. assign the initial value in the controller, although that's not where it belongsb. store the default value in another model, like UsersAny other suggestions? Thanks!
rahum
default_value_for works by overriding the getter and providing a default value only if it doesn't already have one. It doesn't actually change the value in the database or underlying object. Since default_value_for doesn't automatically run when the object is created (only when the column is accessed), there's no way for a use like in the original question to recurse.
Luke