missing attribute during initialization

Hello.

In one of my ActiveRecord models I provide an after initialization callback after_initialize :foobar

In that callback I check some attribute def foobar   if my_attr.blank? ..... end

Sometimes when I check the validation of an instance of that model my_model.valid? this foobar method (together with all my other after_initialize methods) are called (why that? ... the instance already exist.). The main problem is that this leads to "missing attribute" errors. It seems that the valid? method fetches and initializes objects from the database without loading all attributes ... is that true? How to avoid those missing attribute errors? Simply ignore them?

Regards, Kai

That seems very strange. Are you absolutely sure that this what is happening? valid? cannot be fetching the object from the db as it must exist before you call valid? Or are you talking about fetching associated objects that are checked within valid? Do you have a repeatable situation where you see it? If so what happens for example if you use ruby-debug to break in just before the call of valid? and then inspect the object and call valid? from the console.

Colin

Colin

Sorry for the late answer ... I had to simplyfy my model somehow to really test that is not an other interaction that causes that problem.

That seems very strange. Are you absolutely sure that this what is happening? valid? cannot be fetching the object from the db as it must exist before you call valid? Or are you talking about fetching associated objects that are checked within valid? Do you have a repeatable situation where you see it?

Yes, I guess so.

My Model:

class MyModel < ActiveRecord::Base

  after_initialize :init_token

  validates :token, :uniqueness => true

  def init_token     puts "init token"     if self.token.blank?       self.token = ActiveSupport::SecureRandom.hex(10)     end   end end

And a simple case where it happens: m1 = MyModel.create m1.token.should_not be_empty m2 = MyModel.new m2.token.should_not be_empty m2.token = m1.token m2.valid? # where the exception is thrown (also "init token" is called)

Full trace below.

If so what happens for example if you use ruby-debug to break in just before the call of valid? and then inspect the object and call valid? from the console.

I never used the debugger yet, but I will try to. But maybe you already know of the above info what is going on here?

Kai

Error trace: ActiveModel::MissingAttributeError: missing attribute: token   from /home/zeus/projects/myproject/app/models/my_model.rb:9:in `init_token'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activesupport-3.0.3/lib/active_support/callbacks.rb:415:in `_run_initialize_callbacks'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/base.rb:1453:in `init_with'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/base.rb:909:in `instantiate'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/base.rb:467:in `find_by_sql'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/base.rb:467:in `collect!'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/base.rb:467:in `find_by_sql'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/relation.rb:64:in `to_a'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/relation/finder_methods.rb:333:in `find_first'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/relation/finder_methods.rb:122:in `first'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/relation/finder_methods.rb:180:in `exists?'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/validations/uniqueness.rb:39:in `validate_each'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activemodel-3.0.3/lib/active_model/validator.rb:154:in `validate'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activemodel-3.0.3/lib/active_model/validator.rb:151:in `each'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activemodel-3.0.3/lib/active_model/validator.rb:151:in `validate'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activesupport-3.0.3/lib/active_support/callbacks.rb:314:in `send'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activesupport-3.0.3/lib/active_support/callbacks.rb:314:in `_callback_before_1265'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activesupport-3.0.3/lib/active_support/callbacks.rb:414:in `_run_validate_callbacks'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activemodel-3.0.3/lib/active_model/validations.rb:212:in `run_validations!'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activemodel-3.0.3/lib/active_model/validations/callbacks.rb:67:in `run_validations!'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activesupport-3.0.3/lib/active_support/callbacks.rb:413:in `_run_validation_callbacks'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activemodel-3.0.3/lib/active_model/validations/callbacks.rb:67:in `run_validations!'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activemodel-3.0.3/lib/active_model/validations.rb:179:in `valid?'   from /home/zeus/.rvm/gems/ruby-1.8.7-p302@rails/gems/ activerecord-3.0.3/lib/active_record/validations.rb:55:in `valid?'   from (irb):103

I am not fully confident, but here is a suggestion. The call of valid? must check for the uniqueness of token. To do that I think it may be doing a find with a condition of the token value of the object being validated, so that is the find_by_sql in the stack. Since the token is not unique this does actually find a record. Possibly then the found record is being instantiated, hence the call to init_token. But why it should fail I do not know. I presume the table has got a field called token? As I said I am not convinced. You may learn more by studying the log to see what the query being run is. Also try the debugger and you can find out what is being instantiated. Is it only in tests that you have the problem or is the test throwing up a real error in your app (such as the missing field in the table for example).

Colin

I am not fully confident, but here is a suggestion. The call of valid? must check for the uniqueness of token. To do that I think it may be doing a find with a condition of the token value of the object being validated, so that is the find_by_sql in the stack. Since the token is not unique this does actually find a record. Possibly then the found record is being instantiated, hence the call to init_token.

Yes, that is what I also guessed and the debugger seems to bring up. But in my opinion it is not good behavior to instantiate a record in that case. I wonder why that can't be avoided by simply checking the data without creating the record (but I am no Rails pro).

But why it should fail I do not know.

The last SQL statement is: SELECT `grids`.`id` FROM `grids` WHERE (`grids`.`token` = BINARY 'mkp8ae2qbq') LIMIT 1

And thats the cause of the problem as token is not selected here. I am not sure if this is good behavior, that why opened a ticket to discuss it further: https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/6127-after_initialize-may-lead-to-missing-attribute-when-used-with-uniqueness-validation

Kai

There is nothing wrong with rails running that query to check for an existing token. It must do something similar. Note that it is only picking up grids.id (not sure why it is grids as that does not seem to bear any relationship to your model names but presumably you understand that). The question is whether it goes on to attempt to instantiate an object from that, which the query does not prove. You could go a bit further by debugging into the failing method and seeing what is there and how it got there.

I don't think you answered my question as to whether the failure is only in the tests and whether the app itself basically works.

Colin

I’m experiencing this error and it is happening during normal site operation, not just in tests. Haven’t debugged it yet to see if it’s attempting to instantiate an object. Will try…

In fact, it appears that activerecord/lib/active_record/base.rb (rails 2.3.8) is calling after_initialize on line 1687 on a record that only has an id set.