-
Type: Task
-
Resolution: Done
-
Priority: Major - P3
-
Affects Version/s: None
-
Component/s: None
-
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:
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.
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!