Browse Source

Fix errors when updating belongsToMany associations.

* Fix removing all linked records.
* Fix removing some linked records, but not adding new ones.

Refs #3485
mark_story 12 years ago
parent
commit
e16a34a2e6

+ 16 - 7
src/ORM/Association/BelongsToMany.php

@@ -385,10 +385,14 @@ class BelongsToMany extends Association {
 		$targetEntity = $entity->get($this->property());
 		$strategy = $this->saveStrategy();
 
-		if (!$targetEntity) {
+		if ($targetEntity === null) {
 			return false;
 		}
 
+		if ($targetEntity === [] && $entity->isNew()) {
+			return $entity;
+		}
+
 		if ($strategy === self::SAVE_APPEND) {
 			return $this->_saveTarget($entity, $targetEntity, $options);
 		}
@@ -516,7 +520,7 @@ class BelongsToMany extends Association {
  *
  * This method does not check link uniqueness.
  *
- * ###Example:
+ * ### Example:
  *
  * {{{
  * $newTags = $tags->find('relevant')->execute();
@@ -682,12 +686,17 @@ class BelongsToMany extends Association {
 					return false;
 				}
 
+				$inserted = [];
 				$property = $this->property();
-				$inserted = array_combine(
-					array_keys($inserts),
-					(array)$sourceEntity->get($property)
-				);
-				$targetEntities = $inserted + $targetEntities;
+
+				if (count($inserts)) {
+					$inserted = array_combine(
+						array_keys($inserts),
+						(array)$sourceEntity->get($property)
+					);
+					$targetEntities = $inserted + $targetEntities;
+				}
+
 				ksort($targetEntities);
 				$sourceEntity->set($property, array_values($targetEntities));
 				$sourceEntity->dirty($property, false);

+ 101 - 1
tests/TestCase/ORM/Association/BelongsToManyTest.php

@@ -1240,7 +1240,83 @@ class BelongsToManyTest extends TestCase {
 	}
 
 /**
- * Tests that replaceLinks will delete entities not present in the passed
+ * Test that replaceLinks() can save an empty set, removing all rows.
+ *
+ * @return void
+ */
+	public function testReplaceLinksUpdateToEmptySet() {
+		$connection = ConnectionManager::get('test');
+		$joint = $this->getMock(
+			'\Cake\ORM\Table',
+			['delete', 'find'],
+			[['alias' => 'ArticlesTags', 'connection' => $connection]]
+		);
+		$config = [
+			'sourceTable' => $this->article,
+			'targetTable' => $this->tag,
+			'through' => $joint,
+			'joinTable' => 'tags_articles'
+		];
+		$assoc = $this->getMock(
+			'\Cake\ORM\Association\BelongsToMany',
+			['_collectJointEntities', '_saveTarget'],
+			['tags', $config]
+		);
+		$assoc->junction();
+
+		$this->article
+			->association('ArticlesTags')
+			->conditions(['foo' => 1]);
+
+		$query1 = $this->getMock(
+			'\Cake\ORM\Query',
+			['where', 'andWhere', 'addDefaultTypes'],
+			[$connection, $joint]
+		);
+
+		$joint->expects($this->at(0))->method('find')
+			->with('all')
+			->will($this->returnValue($query1));
+
+		$query1->expects($this->at(0))
+			->method('where')
+			->with(['foo' => 1])
+			->will($this->returnSelf());
+		$query1->expects($this->at(1))
+			->method('where')
+			->with(['article_id' => 1])
+			->will($this->returnSelf());
+
+		$existing = [
+			new Entity(['article_id' => 1, 'tag_id' => 2]),
+			new Entity(['article_id' => 1, 'tag_id' => 4]),
+		];
+		$query1->setResult(new \ArrayIterator($existing));
+
+		$tags = [];
+		$entity = new Entity(['id' => 1, 'test' => $tags]);
+
+		$assoc->expects($this->once())->method('_collectJointEntities')
+			->with($entity, $tags)
+			->will($this->returnValue([]));
+
+		$joint->expects($this->at(1))
+			->method('delete')
+			->with($existing[0]);
+		$joint->expects($this->at(2))
+			->method('delete')
+			->with($existing[1]);
+
+		$assoc->expects($this->never())
+			->method('_saveTarget');
+
+		$assoc->replaceLinks($entity, $tags);
+		$this->assertSame([], $entity->tags);
+		$this->assertFalse($entity->dirty('tags'));
+	}
+
+/**
+ * Tests that replaceLinks will delete entities not present in the passed,
  * array, maintain those are already persisted and were passed and also
  * insert the rest.
  *
@@ -1335,6 +1411,30 @@ class BelongsToManyTest extends TestCase {
 	}
 
 /**
+ * Test that saving an empty set on create works.
+ *
+ * @return void
+ */
+	public function testSaveEmptySetSuccess() {
+		$assoc = $this->getMock(
+			'\Cake\ORM\Association\BelongsToMany',
+			['_saveTarget', 'replaceLinks'],
+			['tags']
+		);
+		$entity = new Entity([
+			'id' => 1,
+			'tags' => []
+		], ['markNew' => true]);
+
+		$assoc->saveStrategy(BelongsToMany::SAVE_REPLACE);
+		$assoc->expects($this->never())
+			->method('replaceLinks');
+		$assoc->expects($this->never())
+			->method('_saveTarget');
+		$this->assertSame($entity, $assoc->save($entity));
+	}
+
+/**
  * Tests saving with replace strategy returning true
  *
  * @return void

+ 55 - 2
tests/TestCase/ORM/TableTest.php

@@ -1503,7 +1503,7 @@ class TableTest extends \Cake\TestSuite\TestCase {
  * @group save
  * @return void
  */
-	public function testsASavedEntityIsClean() {
+	public function testASavedEntityIsClean() {
 		$entity = new \Cake\ORM\Entity([
 			'username' => 'superuser',
 			'password' => 'root',
@@ -1524,7 +1524,7 @@ class TableTest extends \Cake\TestSuite\TestCase {
  * @group save
  * @return void
  */
-	public function testsASavedEntityIsNotNew() {
+	public function testASavedEntityIsNotNew() {
 		$entity = new \Cake\ORM\Entity([
 			'username' => 'superuser',
 			'password' => 'root',
@@ -2594,6 +2594,59 @@ class TableTest extends \Cake\TestSuite\TestCase {
 	}
 
 /**
+ * Tests saving belongsToMany records can delete all links.
+ *
+ * @group save
+ * @return void
+ */
+	public function testSaveBelongsToManyDeleteAllLinks() {
+		$table = TableRegistry::get('articles');
+		$table->belongsToMany('tags', [
+			'saveStrategy' => 'replace',
+		]);
+
+		$entity = $table->get(1, ['contain' => 'Tags']);
+		$this->assertCount(2, $entity->tags, 'Fixture data did not change.');
+
+		$entity->tags = [];
+		$result = $table->save($entity);
+		$this->assertSame($result, $entity);
+		$this->assertSame([], $entity->tags, 'No tags on the entity.');
+
+		$entity = $table->get(1, ['contain' => 'Tags']);
+		$this->assertSame([], $entity->tags, 'No tags in the db either.');
+	}
+
+/**
+ * Tests saving belongsToMany records can delete some links.
+ *
+ * @group save
+ * @return void
+ */
+	public function testSaveBelongsToManyDeleteSomeLinks() {
+		$table = TableRegistry::get('articles');
+		$table->belongsToMany('tags', [
+			'saveStrategy' => 'replace',
+		]);
+
+		$entity = $table->get(1, ['contain' => 'Tags']);
+		$this->assertCount(2, $entity->tags, 'Fixture data did not change.');
+
+		$tag = new \Cake\ORM\Entity([
+			'id' => 2,
+		]);
+		$entity->tags = [$tag];
+		$result = $table->save($entity);
+		$this->assertSame($result, $entity);
+		$this->assertCount(1, $entity->tags, 'Only one tag left.');
+		$this->assertEquals($tag, $entity->tags[0]);
+
+		$entity = $table->get(1, ['contain' => 'Tags']);
+		$this->assertCount(1, $entity->tags, 'Only one tag in the db.');
+		$this->assertEquals($tag->id, $entity->tags[0]->id);
+	}
+
+/**
  * Tests saving belongsToMany records with a validation error and atomic set
  * to true
  *