Uploaded image for project: 'Mongoid'
  1. Mongoid
  2. MONGOID-2601

update callbacks called while document is in an inconsistent state

    • Type: Icon: Task Task
    • Resolution: Done
    • 3.0.16
    • Affects Version/s: None
    • Component/s: None

      It makes unexpected results when creating a document with devise :confirmable and has_many associations with autosave.

      I made a Rails project at https://github.com/clee704/testapp to demonstrate this issue. To compare results with ActiveRecord, there is a branch activerecord which does the same job as the master branch, but with ActiveRecord instead of Mongoid. There is also a branch without-autosave.

      Please see the following listings:

      app/models/user.rb

      class User
        include Mongoid::Document
      
        ## Devise
        devise :database_authenticatable, :confirmable
      
        ## Database authenticatable
        field :email,              type: String, default: ""
        field :encrypted_password, type: String, default: ""
      
        validates_presence_of :email
      
        ## Confirmable
        field :confirmation_token,   type: String
        field :confirmed_at,         type: Time
        field :confirmation_sent_at, type: Time
        field :unconfirmed_email,    type: String # Only if using reconfirmable
      
        has_many :authentications, autosave: true, inverse_of: :user
      end
      

      app/models/authentication.rb

      class Authentication
        include Mongoid::Document
        field :provider, type: String
        field :uid,      type: String
        belongs_to :user, inverse_of: :authentications
        validates :user, presence: true
      end
      

      spec/models/user_spec.rb

      require 'spec_helper'
      
      describe User do
        describe "creation" do
          before do
            @user = User.new
            @user.email = "user@example.com"
            @user.password = "foobar"
            @user.password_confirmation = "foobar"
            @user.authentications.build(provider: "facebook", uid: "12345678")
            @user.skip_confirmation!
            @user.save
          end
          specify { @user.should be_persisted }
          specify { @user.should be_valid }
          specify { @user.email.should == "user@example.com" }
        end
      end
      

      The above test passes with ActiveRecord, but fails with Mongoid. This is because Devise::Models::Confirmable#postpone_email_change?, a before_update callback on User model, is called when the user document is in an inconsistent state. (persisted? is true but changes are not moved because post_persist is not called yet.)

      I traced relevant method calls and it was like this:

      begin Mongoid::Persistence::Insert#persist (User)
        begin Mongoid::Persistence::Insert#persist (Authentication)
          begin Mongoid::Persistence#update (User)
            call Devise::Models::Confirmable#postpone_email_change?`
            begin Mongoid::Persistence#update (User)
              call Devise::Models::Confirmable#postpone_email_change?`
            end Mongoid::Persistence#update (User)
          end Mongoid::Persistence#update (User)
          call Mongoid::Dirty#post_persist (Authentication)
        end Mongoid::Persistence::Insert#persist (Authentication)
        call Mongoid::Dirty#post_persist (User)
      end Mongoid::Persistence::Insert#persist (User)
      

      I'm not sure whether it's a bug or not.

            Assignee:
            durran Durran Jordan
            Reporter:
            clee704 clee704
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved: