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

Can't update embedded document if parent is previously saved

    • Type: Icon: Bug Bug
    • Resolution: Done
    • Priority: Icon: Major - P3 Major - P3
    • 6.1.1
    • 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'
      

            Assignee:
            emily.stolfo Emily Stolfo
            Reporter:
            gottfrois gottfrois
            Votes:
            1 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: