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

nested_attributes is applying _destroys immediately when applying attributes, despite validations.

    • Type: Icon: Task Task
    • Resolution: Done
    • Priority: Icon: Major - P3 Major - P3
    • 5.0.2
    • Affects Version/s: None
    • Component/s: None
    • Labels:
      None

      We've been experiencing a very troublesome issues for many, many months now. We were noticing that :has_many relationships that were being updated via nested_attributes were getting emptied to [], despite having validations in place which ensure there's always something present. Records were getting created successfully, but the association was being emptied later.

      Today, I finally tracked it down. It seems that passing nested_attributes with a _destroy: true to a model via attributes= immediately deletes from the database.

      In summary, here's a quick summary of the problem:

      Unable to find source-code formatter for language: `. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      Person.validates_presence_of :ordered_posts
      person = Person.new;
      person.ordered_posts.build(rating: 1);
      person.valid?
      => true
      
      person.attributes = {:username => 'adammeghji', :ordered_posts_attributes => {:'0' => {:_destroy => true, :_id => person.ordered_posts[0]._id.to_s}}}
      person.valid?
      => true
      person.reload;
      person.valid?
      => false
      

      `

      In our app, we pass these attributes in via update_attributes(), but since this is immediately issuing DELETEs in mongo, we're losing data despite the server-side validations in place. Instead, I would expect it to instead flag for deletion, and have the records get flushed when the save actually occurs, and only if the document is valid.

      Here's a more elaborate example, incl. MOPED queries, so you can see how nested_attributes are immediately issuing DELETEs:

      NOTE: this is against 3.1.0-stable .. we're on 3.1.4 and our results are consistent with this.

      Unable to find source-code formatter for language: `. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      require "spec_helper"
      
      describe Mongoid::NestedAttributes do
        it 'should not atomically destroy during attributes=' do
          Mongoid.logger = Logger.new("log/mongo_test.log")
          Moped.logger = Logger.new("log/mongo_test.log")
          Mongoid.logger.level = Logger::DEBUG
          Moped.logger.level = Logger::DEBUG
      
          Person.accepts_nested_attributes_for :ordered_posts, allow_destroy: true
          Person.validates_presence_of :username
      
          person = Person.new
          person.save!
          # `-> MOPED: 127.0.0.1:27017 INSERT       database=mongoid_test collection=people documents=[{"_id"=>"52a906e82636d5fdaf000001", "age"=>100, "map_with_default"=>{}, "_type"=>"Person", "username"=>"arthurnn1", "pets"=>false, "blood_alcohol_content"=>0.0, "last_drink_taken_at"=>2013-12-10 00:00:00 UTC}] flags=[] (0.2320ms)
      
          #
          # here, we confirm that attributes= doesn't persist changes:
          #
          orig_username = person.username
      
          person.attributes = {username: 'adam'}
          # `-> MOPED: <no commands>
      
          person.username.should == 'adam' # not persisted yet though ..
          person.reload
          # `-> MOPED: 127.0.0.1:27017 QUERY        database=mongoid_test collection=people selector={:_id=>"52a906e82636d5fdaf000001"} flags=[] limit=-1 skip=0 batch_size=nil fields=nil (1.2488ms)
      
          person.username.should == orig_username # it didn't persist.
      
          person.ordered_posts.create(rating: 1)
          # `-> MOPED: 127.0.0.1:27017 INSERT       database=mongoid_test collection=ordered_posts documents=[{"_id"=>"52a907272636d5fdaf000002", "rating"=>1, "person_id"=>"52a906e82636d5fdaf000001"}] flags=[] (0.3881ms)
      
          Person.validates_presence_of :ordered_posts
          person.valid?.should == true
      
          #
          # here, we add nested attributes with _destroy => true.
          # I would expect the behaviour of attributes= to be consistent, i.e. ordered_posts shouldn't be mutated.
          #
          # However, it seems that ordered_posts gets destroyed immediately, instead of flagged_for_deletion.
          #
          person.attributes = {:username => 'adammeghji', :ordered_posts_attributes => {:'0' => {:_destroy => true, :_id => person.ordered_posts[0]._id.to_s}}}
          # `-> MOPED: 127.0.0.1:27017 QUERY        database=mongoid_test collection=ordered_posts selector={"$query"=>{"person_id"=>"52a906e82636d5fdaf000001"}, "$orderby"=>{"rating"=>-1}} flags=[:slave_ok] limit=0 skip=0 batch_size=nil fields=nil (1.0748ms)
          #     MOPED: 127.0.0.1:27017 QUERY        database=mongoid_test collection=ordered_posts selector={"$query"=>{"person_id"=>"52a906e82636d5fdaf000001", "_id"=>"52a907272636d5fdaf000002"}, "$orderby"=>{"rating"=>-1}} flags=[:slave_ok] limit=0 skip=0 batch_size=nil fields=nil (1.1992ms)
          #     MOPED: 127.0.0.1:27017 DELETE       database=mongoid_test collection=ordered_posts selector={"_id"=>"52a907272636d5fdaf000002"} flags=[:remove_first] (0.2630ms)
      
          person.username.should == 'adammeghji' # not persisted either
          person.reload
          # `-> MOPED: 127.0.0.1:27017 QUERY        database=mongoid_test collection=people selector={:_id=>"52a906e82636d5fdaf000001"} flags=[] limit=-1 skip=0 batch_size=nil fields=nil (1.1172ms)
      
          person.username.should == orig_username # it didn't persist.
      
          person.ordered_posts.should be_present  # BUG: person.ordered_posts is now []!!
          # `-> MOPED: 127.0.0.1:27017 COMMAND      database=mongoid_test command={:count=>"ordered_posts", :query=>{"person_id"=>"52a9092a2636d5941c000001"}} (0.9630ms)
      
          person.valid?.should == true            # BUG: person is now invalid!!
          # `-> MOPED: 127.0.0.1:27017 COMMAND      database=mongoid_test command={:count=>"ordered_posts", :query=>{"person_id"=>"52a9092a2636d5941c000001"}} (0.9630ms)
        end
      end
      

      `

      Any assistance would be hugely appreciated been tracking this down for months!

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

              Created:
              Updated:
              Resolved: