Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

:dependent specifies behavior for parent destruction _and_ associatio…

…n replacement for both one and many associations (default is nullify in both cases)
  • Loading branch information...
commit 6caa8a6fcd6a713b36771e1e683f7a6f69774dab 1 parent 2e8b4c7
@brianhempel brianhempel authored
View
2  UPGRADES
@@ -1,6 +1,8 @@
0.9 => 1.0
* Using String IDs are no longer supported. If you are declaring your own ID, ensure it is an ObjectId, and set the default
key :_id, ObjectId, :default => lambda { BSON::ObjectId.new }
+ * The :dependent association option now applies to both when the parent is destroyed and when the association is reassigned (one and many associations)
+ * The default :dependent option is now :nullify for both when the parent is destroyed and when the association is reassigned
0.8.6 => 0.9
* [attribute]_before_typecast should become [attribute]_before_type_cast (note the extra _)
View
8 lib/mongo_mapper/plugins/associations/many_association.rb
@@ -41,11 +41,11 @@ def #{name}=(value)
end
end_eval
- if options[:dependent] && !embeddable?
- association = self
- options = self.options
+ association = self
+ options = self.options
- model.after_destroy do
+ model.after_destroy do
+ if !association.embeddable?
case options[:dependent]
when :destroy
self.get_proxy(association).destroy_all
View
10 lib/mongo_mapper/plugins/associations/many_documents_proxy.rb
@@ -8,7 +8,15 @@ class ManyDocumentsProxy < Collection
def replace(docs)
load_target
- target.each { |t| t.destroy }
+
+ (target - docs).each do |t|
+ case options[:dependent]
+ when :destroy then t.destroy
+ when :delete_all then t.delete
+ else t.update_attributes(self.foreign_key => nil)
+ end
+ end
+
docs.each { |doc| prepare(doc).save }
reset
end
View
21 lib/mongo_mapper/plugins/associations/one_association.rb
@@ -10,6 +10,27 @@ def embeddable?
def proxy_class
@proxy_class ||= klass.embeddable? ? OneEmbeddedProxy : OneProxy
end
+
+ def setup(model)
+ super
+
+ association = self
+ options = self.options
+
+ model.after_destroy do
+ if !association.embeddable?
+ proxy = self.get_proxy(association)
+
+ unless proxy.nil?
+ case options[:dependent]
+ when :destroy then proxy.destroy
+ when :delete then proxy.delete
+ else proxy.nullify
+ end
+ end
+ end
+ end
+ end
def autosave?
options.fetch(:autosave, embeddable?)
View
36 lib/mongo_mapper/plugins/associations/one_proxy.rb
@@ -19,29 +19,41 @@ def replace(doc)
load_target
if !target.nil? && target != doc
- if options[:dependent] && target.persisted?
+ if target.persisted?
case options[:dependent]
- when :delete
- target.delete
- when :destroy
- target.destroy
- when :nullify
+ when :delete then target.delete
+ when :destroy then target.destroy
+ else
target[foreign_key] = nil
target.save
end
end
end
-
- if doc.nil?
- target.update_attributes(foreign_key => nil) unless target.nil?
- else
+
+ unless doc.nil?
proxy_owner.save unless proxy_owner.persisted?
doc = klass.new(doc) unless doc.is_a?(klass)
doc[foreign_key] = proxy_owner.id
doc.save unless doc.persisted?
- loaded
- @target = doc
end
+
+ loaded
+ @target = doc
+ end
+
+ def destroy
+ target.destroy
+ reset
+ end
+
+ def delete
+ target.delete
+ reset
+ end
+
+ def nullify
+ target.update_attributes(foreign_key => nil)
+ reset
end
protected
View
161 test/functional/associations/test_many_documents_proxy.rb
@@ -93,6 +93,144 @@ def pets
project.statuses[0].name.should == "ready"
end
end
+
+ context "with :dependent" do
+ setup do
+ @broker_class = Doc('Broker')
+ @property_class = Doc('Property') do
+ key :broker_id, ObjectId
+ belongs_to :broker
+ end
+ end
+
+ context "=> destroy" do
+ setup do
+ @broker_class.many :properties, :class => @property_class, :dependent => :destroy
+
+ @broker = @broker_class.create(:name => "Bob")
+ @property1 = @property_class.create
+ @property2 = @property_class.create
+ @property3 = @property_class.create
+ @broker.properties << @property1
+ @broker.properties << @property2
+ @broker.properties << @property3
+ end
+
+ should "call destroy the existing documents" do
+ @broker.properties[0].expects(:destroy).once
+ @broker.properties[1].expects(:destroy).once
+ @broker.properties[2].expects(:destroy).once
+ @broker.properties = [@property_class.new]
+ end
+
+ should "remove the existing document from the database" do
+ @property_class.count.should == 3
+ @broker.properties = []
+ @property_class.count.should == 0
+ end
+
+ should "skip over documents that are the same" do
+ @broker.properties[0].expects(:destroy).never
+ @broker.properties[1].expects(:destroy).once
+ @broker.properties[2].expects(:destroy).never
+ @broker.properties = [@property3, @property1]
+ end
+ end
+
+ context "=> delete_all" do
+ setup do
+ @broker_class.many :properties, :class => @property_class, :dependent => :delete_all
+
+ @broker = @broker_class.create(:name => "Bob")
+ @property1 = @property_class.create
+ @property2 = @property_class.create
+ @property3 = @property_class.create
+ @broker.properties << @property1
+ @broker.properties << @property2
+ @broker.properties << @property3
+ end
+
+ should "call delete the existing documents" do
+ @broker.properties[0].expects(:delete).once
+ @broker.properties[1].expects(:delete).once
+ @broker.properties[2].expects(:delete).once
+ @broker.properties = [@property_class.new]
+ end
+
+ should "remove the existing document from the database" do
+ @property_class.count.should == 3
+ @broker.properties = []
+ @property_class.count.should == 0
+ end
+
+ should "skip over documents that are the same" do
+ @broker.properties[0].expects(:delete).never
+ @broker.properties[1].expects(:delete).once
+ @broker.properties[2].expects(:delete).never
+ @broker.properties = [@property3, @property1]
+ end
+ end
+
+ context "=> nullify" do
+ setup do
+ @broker_class.many :properties, :class => @property_class, :dependent => :nullify
+
+ @broker = @broker_class.create(:name => "Bob")
+ @property1 = @property_class.create
+ @property2 = @property_class.create
+ @property3 = @property_class.create
+ @broker.properties << @property1
+ @broker.properties << @property2
+ @broker.properties << @property3
+ end
+
+ should "nullify the existing documents" do
+ @property1.reload.broker_id.should == @broker.id
+ @property2.reload.broker_id.should == @broker.id
+ @property3.reload.broker_id.should == @broker.id
+
+ @broker.properties = [@property_class.new]
+
+ @property1.reload.broker_id.should be_nil
+ @property2.reload.broker_id.should be_nil
+ @property3.reload.broker_id.should be_nil
+ end
+
+ should "skip over documents that are the same" do
+ @broker.properties = [@property3, @property1]
+
+ @property1.reload.broker_id.should == @broker.id
+ @property2.reload.broker_id.should be_nil
+ @property3.reload.broker_id.should == @broker.id
+ end
+
+ should "work" do
+ old_properties = @broker.properties
+ @broker.properties = [@property1, @property2, @property3]
+ old_properties.should == @broker.properties
+ end
+ end
+
+ context "unspecified" do
+ should "nullify the existing documents" do
+ @broker_class.many :properties, :class => @property_class
+
+ @broker = @broker_class.create(:name => "Bob")
+ @property1 = @property_class.create
+ @property2 = @property_class.create
+ @property3 = @property_class.create
+ @broker.properties << @property1
+ @broker.properties << @property2
+ @broker.properties << @property3
+
+ @broker.properties = [@property_class.new]
+
+ @property1.reload.broker_id.should be_nil
+ @property2.reload.broker_id.should be_nil
+ @property3.reload.broker_id.should be_nil
+ end
+ end
+ end
end
context "using <<, push and concat" do
@@ -624,6 +762,29 @@ class ::Thing
Property.count.should == 3
end
end
+
+ context "unspecified" do
+ setup do
+ Property.key :thing_id, ObjectId
+ Property.belongs_to :thing
+ Thing.has_many :properties, :dependent => :nullify
+
+ @thing = Thing.create(:name => "Tree")
+ @property1 = Property.create
+ @property2 = Property.create
+ @property3 = Property.create
+ @thing.properties << @property1
+ @thing.properties << @property2
+ @thing.properties << @property3
+ end
+
+ should "should nullify relationship but not destroy associated documents" do
+ @thing.properties.count.should == 3
+ @thing.destroy
+ @thing.properties.count.should == 0
+ Property.count.should == 3
+ end
+ end
end
context "namespaced foreign keys" do
View
215 test/functional/associations/test_one_proxy.rb
@@ -85,6 +85,122 @@ def setup
post.author.name.should == 'Emily'
end
end
+
+ context "with :dependent" do
+ context "=> delete" do
+ setup do
+ @post_class.one :author, :class => @author_class, :dependent => :delete
+
+ @post = @post_class.create
+ @author = @author_class.new
+ @post.author = @author
+ end
+
+ should "call delete on the existing document" do
+ @author_class.any_instance.expects(:delete).once
+ @post.author = @author_class.new
+ end
+
+ should "remove the existing document from the database" do
+ @post.author = @author_class.new
+ lambda { @author.reload }.should raise_error(MongoMapper::DocumentNotFound)
+ end
+
+ should "do nothing if it's the same document" do
+ @author_class.any_instance.expects(:delete).never
+ @post.author = @author
+ end
+ end
+
+ context "=> destory" do
+ setup do
+ @post_class.one :author, :class => @author_class, :dependent => :destroy
+
+ @post = @post_class.create
+ @author = @author_class.new
+ @post.author = @author
+ end
+
+ should "call destroy the existing document" do
+ @author_class.any_instance.expects(:destroy).once
+ @post.author = @author_class.new
+ end
+
+ should "remove the existing document from the database" do
+ @post.author = @author_class.new
+ lambda { @author.reload }.should raise_error(MongoMapper::DocumentNotFound)
+ end
+
+ should "do nothing if it's the same document" do
+ @author_class.any_instance.expects(:destroy).never
+ @post.author = @author
+ end
+ end
+
+ context "=> nullify" do
+ setup do
+ @post_class.one :author, :class => @author_class, :dependent => :nullify
+
+ @post = @post_class.create
+ @author = @author_class.new
+ @post.author = @author
+ end
+
+ should "nullify the existing document" do
+ @author.reload
+ @author.post_id.should == @post.id
+
+ @post.author = @author_class.new
+
+ @author.reload
+ @author.post_id.should be_nil
+ end
+
+ should "work when it's the same document" do
+ old_author = @post.author
+ @post.author = @author
+ old_author.should == @post.author
+ end
+ end
+
+ context "unspecified" do
+ should "nullify the existing document" do
+ @post_class.one :author, :class => @author_class
+
+ post = @post_class.create
+ author = @author_class.new
+ post.author = author
+ author.reload
+ author.post_id.should == post.id
+
+ post.author = @author_class.new
+
+ author.reload
+ author.post_id.should be_nil
+ end
+ end
+ end
+
+ context "with nil" do
+ setup do
+ @post_class.one :author, :class => @author_class
+
+ @post = @post_class.new
+ @author = @author_class.new(:name => 'Frank')
+ @post.author = @author
+ end
+
+ should "nullify the existing document" do
+ @post.author = nil
+ @author.reload
+ @author.post_id.should be_nil
+ end
+
+ should "set the target to nil" do
+ @post.author = nil
+ @post.author.should == nil
+ end
+ end
end
should "have boolean method for testing presence" do
@@ -118,44 +234,89 @@ def setup
post.author = nil
post.author.nil?.should be_true
end
+
+ context "destroying parent with :dependent" do
+ context "=> destroy" do
+ setup do
+ @post_class.one :author, :class => @author_class, :dependent => :destroy
+
+ @post = @post_class.create
+ @author = @author_class.new
+ @post.author = @author
+ end
- should "work with :dependent delete" do
- @post_class.one :author, :class => @author_class, :dependent => :delete
+ should "should call destroy on the associated documents" do
+ @author_class.any_instance.expects(:destroy).once
+ @post.destroy
+ end
+
+ should "should remove the associated documents" do
+ @author_class.count.should == 1
+ @post.destroy
+ @post.author.should == nil
+ @author_class.count.should == 0
+ end
+ end
- post = @post_class.create
- author = @author_class.new
- post.author = author
- post.reload
+ context "=> delete" do
+ setup do
+ @post_class.one :author, :class => @author_class, :dependent => :delete
- @author_class.any_instance.expects(:delete).once
- post.author = @author_class.new
- end
+ @post = @post_class.create
+ @author = @author_class.new
+ @post.author = @author
+ end
- should "work with :dependent destroy" do
- @post_class.one :author, :class => @author_class, :dependent => :destroy
+ should "should call delete the associated documents" do
+ @author_class.any_instance.expects(:delete).once
+ @post.destroy
+ end
- post = @post_class.create
- author = @author_class.new
- post.author = author
- post.reload
+ should "remove the associated documents" do
+ @author_class.count.should == 1
+ @post.destroy
+ @post.author.should == nil
+ @author_class.count.should == 0
+ end
+ end
- @author_class.any_instance.expects(:destroy).once
- post.author = @author_class.new
- end
+ context "=> nullify" do
+ should "should nullify the relationship but not destroy the associated document" do
+ @post_class.one :author, :class => @author_class, :dependent => :nullify
- should "work with :dependent nullify" do
- @post_class.one :author, :class => @author_class, :dependent => :nullify
+ post = @post_class.create
+ author = @author_class.new
+ post.author = author
- post = @post_class.create
- author = @author_class.new
- post.author = author
- post.reload
+ @author_class.count.should == 1
+ post.destroy
+ post.author.should == nil
+ @author_class.count.should == 1
- post.author = @author_class.new
+ @author_class.first.should == author
+ author.post_id.should == nil
+ end
+ end
+
+ context "unspecified" do
+ should "should nullify the relationship but not destroy the associated document" do
+ @post_class.one :author, :class => @author_class
- author.reload
- author.post_id.should be_nil
+ post = @post_class.create
+ author = @author_class.new
+ post.author = author
+
+ @author_class.count.should == 1
+ post.destroy
+ post.author.should == nil
+ @author_class.count.should == 1
+
+ @author_class.first.should == author
+ author.post_id.should == nil
+ end
+ end
end
+
should "be able to build" do
@post_class.one :author, :class => @author_class
Please sign in to comment.
Something went wrong with that request. Please try again.