-
Type: Task
-
Resolution: Done
-
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.