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

Positionally method does not update the first match

    • Type: Icon: Bug Bug
    • Resolution: Done
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: 3.1.6, 4.0.0 final, 5.1.1
    • Component/s: None
    • Labels:
      None
    • Environment:
      Ubuntu/OSX, Ruby 2.0 - 2.3

      I'm having this issue after updating from MongoDB 2.4 to 2.6. I'm still running Mongoid 3.1 in this project however after extensive testing this issue still seems to be relevant all the way through Mongoid 5 (haven't tested 6). Its related to the positionally method when updating deeply embedded documents.

      From the MongoDB documentation https://docs.mongodb.com/manual/reference/operator/update/positional/

      Remember that the positional $ operator acts as a placeholder for the first match of the update query document.

      Given the following atomic selector (of an embedded document nested within another embedded):

      {"_id"=>"576ad5a2eea25fa26f000001", "items._id"=>"576ad5a2eea25fa26f000002", "items.0.items._id"=>"576ad5a3eea25fa26f000004"}
      

      And given the following updates:

      {"$set"=>{"items.0.items.1.name"=>"c"}}
      

      The following line within positionally

      keys = keys.sort_by { |s| s.length*-1 }
      

      Will cause the most specific index to be positionally updated. Resulting in the following:

      {"$set"=>{"items.0.items.$.name"=>"c"}}
      

      However this is not the first element, this is the 2nd element being updated, which violates the MongoDB requirement. Instead if switching to:

      keys = keys.sort_by { |s| s.length }
      

      will result in the expected result of

      {"$set"=>{"items.$.items.1.name"=>"c"}
      

      Repo steps

      require 'spec_helper'
      
      class BasicParent
        include Mongoid::Document
      
        field :name
        embeds_many :items, class_name: 'BasicChild'
      end
      
      class BasicChild
        include Mongoid::Document
      
        embedded_in :basic
      
        field :name
        embeds_many :items, class_name: 'BasicSubChild'
      end
      
      class BasicSubChild
        include Mongoid::Document
      
        embedded_in :basic
        field :name
      end
      
      describe BasicParent do
        let(:parent) { BasicParent.create(name: 'a') }
        let(:child) { parent.items.create(name: 'a') }
        let!(:nested_a) { child.items.create(name: 'a') }
        let!(:nested_b) { child.items.create(name: 'b') }
      
        # this spec fails in multiple projects of mine across many versions. One 
        # project does work (Mongoid 5.1.2) however it works only becuase for 
        # some reason it doesnt update any position with $
        it 'should properly support updating nested items' do
          nested_b.name = 'c'
          nested_b.save
      
          child.reload
      
          expect(child.items.map(&:name)).to eq ['a', 'c']
        end
      
        it 'should properly support updating nested items via parent' do
          nested_b.name = 'c'
          child.save
          child.reload
      
          expect(child.items.map(&:name)).to eq ['a', 'c']
        end
      end
      
      

      I can monkey patch but please help me understand the ramifications of this change and if it will have other consequences. There clearly was an intention behind sorting the keys by most specific first.

            Assignee:
            emily.stolfo Emily Stolfo
            Reporter:
            jhoffner Jake Hoffner
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: