-
Type: Bug
-
Resolution: Done
-
Priority: Major - P3
-
Affects Version/s: None
-
Component/s: None
-
None
> ruby -v ruby 1.9.3p545 (2014-02-24 revision 45159) [x86_64-darwin13.1.0] > mongo --version MongoDB shell version: 2.6.5 > bundle show mongoid /Users/gottfrois/.rvm/gems/ruby-1.9.3-p545/gems/mongoid-4.0.0
When safe mode is set to true (w: 1 in mongoid.yml), using the followings models:
require 'mongoid' require 'mongoid/support/query_counter' require 'minitest/autorun' # Ensure backward compatibility with Minitest 4 Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) # Change the w value to 0 to make the spec pass Mongoid.configure.connect_to("mongoid_test", { read: :primary, write: { w: 1 } }) class Post include Mongoid::Document embeds_many :comments has_many :tags end class Comment include Mongoid::Document embedded_in :post before_create :create_tag def create_tag Tag.create(post: post) end end class Tag include Mongoid::Document belongs_to :post validates :post, presence: true end class BugTest < Minitest::Test def test_comment_creation p = Post.create! c = p.comments.create! assert_equal true, c.valid? end end
After digging in mongoid code base, here is what I found. First, the logs will show the following:
p = Post.create!
# INSERT database=foo_development collection=posts documents=[{"_id"=>BSON::ObjectId('547c652f4d616314ed000000')}] flags=[]
c = p.comments.create!
# INSERT database=foo_development collection=tags documents=[{"_id"=>BSON::ObjectId('547c65504d616314ed010000'), "post_id"=>BSON::ObjectId('547c652f4d616314ed000000')}] flags=[] # UPDATE database=foo_development collection=posts selector={"_id"=>BSON::ObjectId('547c652f4d616314ed000000')} update={"$pushAll"=>{"comments"=>[{"_id"=>BSON::ObjectId('547c65504d616314ed020000')}]}} flags=[] # UPDATE database=foo_development collection=posts selector={"_id"=>BSON::ObjectId('547c652f4d616314ed000000')} update={"$push"=>{"comments.0"=>{"_id"=>BSON::ObjectId('547c65504d616314ed020000')}}} flags=[]
The issue comes from the last UPDATE where it tries to $push a comment in comments.0. It should be either a $set or {{$push =>
{ 'comments' }}}.
This is due to this line https://github.com/mongoid/mongoid/blob/v4.0.0/lib/mongoid/atomic/paths/embedded/many.rb#L37
document.new_record? respond false because the comment has already been persisted due to the validates :post, presence: true statement in the Tag model. When that code ran, the post instance contained in memory a "not yet persisted" comment document. The Tag validation tried to persist the post and its comment, generating update={"$pushAll"=>{"comments" command.
Finally, since we called p.comments.create!, it tries to persist the comment document thinking it should be a new record, which is not anymore, resulting in the following exception (when safe mode is true):
Moped::Errors::OperationFailure: The operation: #<Moped::Protocol::Command @length=86 @request_id=9 @response_to=0 @op_code=2004 @flags=[] @full_collection_name="foo_development.$cmd" @skip=0 @limit=-1 @selector={:getlasterror=>1, "w"=>1} @fields=nil> failed with error 16837: "The field 'comments.0' must be an array but is of type Object in document {_id: ObjectId('547c652f4d616314ed000000')}" See https://github.com/mongodb/mongo/blob/master/docs/errors.md for details about this error. from /Users/gottfrois/.rvm/gems/ruby-1.9.3-p545/gems/moped-2.0.2/lib/moped/operation/read.rb:50:in `block in execute'