Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

after_initialize may lead to missing attribute when used with uniqueness validation

#6127

Under some special circumstances an after_initialize callback may lead to a missing attribute exception when combined with a uniqueness validation.

A not so uncommon scenario:

class MyModel < ActiveRecord::Base

  after_initialize :init_token

  validates :token, :uniqueness => true

  def init_token
    if self.token.blank?
      self.token = rand(999999) # just for demo
    end
  end
end

MyModel has only a token column (String) in the database.

Now when doing the following:

m1 = MyModel.create
m2 = MyModel.new
m2.token = m1.token
m2.valid? # calls init_token again and throws the below exception.

missing attribute: token is because of the last find_by_sql that does a

SELECT grids.id FROM grids WHERE (grids.token = BINARY 'mkp8ae2qbq') LIMIT 1
where token is not selected.

I am not sure if it is that good at all that a valid? call ends up in a instantiation of a new record and calling the initialize callbacks.
Could it not just check the row without a new instantiation?

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

Reported by Kai Schlamp · December 7th, 2010 @ 10:18 PM

State: resolved
Milestone: none
Assigned to: nobody
Importance: none

Activity

  1. Carl Youngblood
    Carl Youngblood

    I'm also experiencing this bug in rails 2.3.11.

    March 2nd, 2011 @ 12:50 AM

  2. Carl Youngblood
    Carl Youngblood

    Oops, I meant 2.3.8. In my case, activerecord/lib/active_record/base.rb is attempting to call after_initialize on line 1687 for a record that only has an id. The after_initialize method it is calling attempts to set some attributes that aren't present in the record and throws a missing attribute error.

    March 2nd, 2011 @ 01:39 AM

  3. John Berry
    John Berry

    +1 on this one. Also seeing it in Rails 3.0.5 with a situation very similar to the original poster (validating a column in an after_initialize callback.)

    March 25th, 2011 @ 04:07 PM

  4. Corey Ward
    Corey Ward

    Ditto on what John Berry said. Rails 3.0.5.

    March 26th, 2011 @ 03:52 AM

  5. Corey Ward
    Corey Ward

    Until this is fixed properly, I'm using the following pattern to work around the issue:

    after_initialize :set_defaults
    
    def set_defaults
      self.foo ||= 'bar' unless attributes["foo"].nil?
    end
    

    Unfortunately this isn't the only case where this error arises when setting defaults. If you use this, try to keep note that you did so that an obscure bug you encounter in a month doesn't waste hours of your time. ;)

    March 26th, 2011 @ 04:05 AM

  6. Repository
    Repository
    • State changed from new to resolved

    (from [827e5de60f27c6b559640eda7b79857050a72d9e]) Change exists? so that it doesn't instantiate records [#6127 state:resolved] https://github.com/rails/rails/commit/827e5de60f27c6b559640eda7b798...

    March 29th, 2011 @ 01:12 PM

  7. Repository
  8. John Berry
    John Berry

    I can confirm that that patch fixes our problem with validates_uniqueness_of. Thanks.

    April 4th, 2011 @ 10:29 PM