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.

has_one and after_create association bug

#2712

Summary of the bug (rails 2.3.2)...

Two simple classes (Account and Contact). The contact is created after the account is created (via after_create). The "name" field is not passed in to 'create' which the contact requires via validates_presence_of, so the contact should not get saved, but it in fact does.

class Account < ActiveRecord::Base
  has_one :contact
  def after_create
    create_contact
  end
end

class Contact < ActiveRecord::Base
  belongs_to :account
  validates_presence_of :name
end

The test...

class AccountTest < ActiveSupport::TestCase
  test "this ain't right" do
    a = Account.create
    p a.contact
    p a.contact.errors
    p a.new_record?
  end
end

produces...

#<Contact id: 996332878, name: nil, account_id: 1, created_at: "2009-05-25 05:40:40", updated_at: "2009-05-25 05:40:40">
#<ActiveRecord::Errors:0xb6e442a4 @base=#<Contact id: 996332878, name: nil, account_id: 1, created_at: "2009-05-25 09:21:38", updated_at: "2009-05-25 09:21:38">, @errors={"name"=>["can't be blank"]}>
false

The contact has errors and should not have been saved.

I tried debugging the associations code, but came up empty.

Reported by Andrew Kaspick · May 25th, 2009 @ 10:22 AM

State: resolved
Milestone: 2.x
Assigned to: nobody
Importance: none

Activity

  1. Daniel Guettler
    Daniel Guettler

    I can confirm this bug in the current gem version. However it has already been fixed by this commit: http://github.com/rails/rails/commit/a70c78177a564c2f2cd09846a5e7ab...

    May 25th, 2009 @ 03:42 PM

  2. CancelProfileIsBroken
    CancelProfileIsBroken
    • State changed from new to resolved

    May 25th, 2009 @ 03:56 PM

  3. Andrew Kaspick
    Andrew Kaspick

    Is "rake rails:freeze:edge" supposed to pull a copy of rails with this fix?

    I grabbed edge, but am still experiencing the issue.

    May 25th, 2009 @ 07:34 PM

  4. Andrew Kaspick
    Andrew Kaspick

    Looks like the rake command has the "fix", but the bug still exists because :autosave is true even if I set it to false with...

    has_one :contact, :autosave => false
    

    May 25th, 2009 @ 08:44 PM

  5. Andrew Kaspick
    Andrew Kaspick

    My error... the case outlined in this ticket is resolved.

    Reading the code/docs here I see...

    Note that the :autosave option is automatically enabled on every

    # association that accepts_nested_attributes_for is used for.
    

    This is the situation in my production code; I'm using ccepts_nested_attributes_for.

    Should :autosave => false on the association not override the automatica enabling of the :autosave option in this case?

    May 25th, 2009 @ 08:49 PM

  6. Andrew Kaspick
    Andrew Kaspick
    • Tag changed from 2-3-stable, after_create, associations, bug, has_one to 2-3-stable, accepts_nested_attributes_for, after_create, associations, bug, has_one

    Sorry for all the messages. I've removed the use of accepts_nested_attributes_for in my code and now things work.

    The issue is shown with this modification to code...

    class Account < ActiveRecord::Base
      has_one :contact
    
      accepts_nested_attributes_for :contact
    
      def after_create
        create_contact
      end
    end
    

    Note the addition of accepts_nested_attributes_for

    Should I open a new ticket for this? If I'm manually calling the assocation code with "create_contact" and not going through the accepts_nested_attributes_for avenue for creating the association, this seems to be "unexpected" behaviour.

    May 25th, 2009 @ 08:59 PM

  7. CancelProfileIsBroken
  8. Mike Breen
    Mike Breen

    Andrew,

    I believe that this has been resolved.

    May 25th, 2009 @ 09:16 PM

  9. Andrew Kaspick
    Andrew Kaspick

    It's a similar situation, but when using accepts_nested_attributes_for, line 211 in nested_attributes.rb causes the unexpected behaviour...

    reflection.options[:autosave] = true
    

    :autosave should not be true in my opinion for the example outlined. I spent a good number of hours trying to figure out why the association record was being saved even though it was invalid. Invalid records should not be saved to the DB.

    If I remove the accepts_nested_attributes_for line in my revised example, the issue goes away.

    May 25th, 2009 @ 09:27 PM

  10. Daniel Guettler
    Daniel Guettler

    Ok, this is a rather engineered example in my view. You probably want to either have accepts_nested_attributes OR a manual create_after.
    Anyway so here what's happening:

    1) All validations for Account and associated Contact run and pass, Contact is nil at this point so no validations needed.

    2) Account is created etc.

    3) Callbacks run
    3.1) after_create callback is executed and tries to create a new Contact which in this step fails since validation doesn't pass. Account.contact is NOT nil anymore after this but contains the new record with validation errors.
    3.2) after_save callback is executed which will execute the callback added by autosave. It will create the Contact which was initialized in 3.1 without any further validations since it is assumed that the validations added by autosave have been run successfully.

    To avoid this one possible solution would be to not call save(false) in autosave_association.rb in order to avoid invalid records from being saved into the database.

    May 26th, 2009 @ 04:01 AM

  11. Andrew Kaspick
    Andrew Kaspick

    I've actually modified my code to...

    def after_create
      c = build_contact
      c.save false
    end
    

    So yes, the original example given now could be looked upon as "engineered" as it doesn't check for errors anyway. I guess the "surprise" of having an object with errors being saved to the database as being the main issue here since it's what started my rat race of trying to find out why that was happening in the first place. A case of violating the principle of least surprise (POLS) I'd say.

    Thanks for the input.

    May 26th, 2009 @ 04:46 AM