Browse Source

Implemented one more test to link function, in which an already existing link is present in the input data. Implemented replaceLinks method and a test for it.

Luan Hospodarsky 10 years ago
parent
commit
bdf5d76f38
2 changed files with 169 additions and 2 deletions
  1. 60 1
      src/ORM/Association/HasMany.php
  2. 109 1
      tests/TestCase/ORM/TableTest.php

+ 60 - 1
src/ORM/Association/HasMany.php

@@ -214,7 +214,7 @@ class HasMany extends Association
 
         $currentEntities = (new Collection((array)$sourceEntity->get($property)))->append($targetEntities);
 
-        $sourceEntity->set($property, $currentEntities->toList());
+        $sourceEntity->set($property, array_unique($currentEntities->toList()));
 
         $savedEntity = $this->saveAssociated($sourceEntity);
 
@@ -292,6 +292,65 @@ class HasMany extends Association
     }
 
     /**
+     * Replaces existing association links between the source entity and the target
+     * with the ones passed. This method does a smart cleanup, links that are already
+     * persisted and present in `$targetEntities` will not be deleted, new links will
+     * be created for the passed target entities that are not already in the database
+     * and the rest will be removed.
+     *
+     * For example, if an author has many articles, such as 'article1','article 2' and 'article 3' and you pass
+     * to this method an array containing the entities for articles 'article 1' and 'article 4',
+     * only the link for 'article 1' will be kept in database, the links for 'article 2' and 'article 3' will be
+     * deleted and the link for 'article 4' will be created.
+     *
+     * Existing links are not deleted and created again, they are either left untouched
+     * or updated.
+     *
+     * This method does not check link uniqueness.
+     *
+     * On success, the passed `$sourceEntity` will contain `$targetEntities` as  value
+     * in the corresponding property for this association.
+     *
+     * Additional options for new links to be saved can be passed in the third argument,
+     * check `Table::save()` for information on the accepted options.
+     *
+     * ### Example:
+     *
+     * ```
+     * $author->articles = [$article1, $article2, $article3, $article4];
+     * $authors->save($author);
+     * $articles = [$article1, $article3];
+     * $authors->association('articles')->replaceLinks($author, $articles);
+     * ```
+     *
+     * `$author->get('articles')` will contain only `[$article1, $article3]` at the end
+     *
+     * @param \Cake\Datasource\EntityInterface $sourceEntity an entity persisted in the source table for
+     * this association
+     * @param array $targetEntities list of entities from the target table to be linked
+     * @param array $options list of options to be passed to `save` persisting or
+     * updating new links
+     * @throws \InvalidArgumentException if non persisted entities are passed or if
+     * any of them is lacking a primary key value
+     * @return bool success
+     */
+    public function replaceLinks(EntityInterface $sourceEntity, array $targetEntities, array $options = [])
+    {
+        $property = $this->property();
+        $sourceEntity->set($property, $targetEntities);
+        $saveStrategy = $this->saveStrategy();
+        $this->saveStrategy(self::SAVE_REPLACE);
+        $result = $this->saveAssociated($sourceEntity, $options);
+        $ok = ($result instanceof EntityInterface);
+
+        if ($ok) {
+            $sourceEntity = $result;
+        }
+        $this->saveStrategy($saveStrategy);
+        return $ok;
+    }
+
+    /**
      * Deletes/sets null the related objects according to the dependency between source and targets and foreign key nullability
      * Skips deleting records present in $remainingEntities
      *

+ 109 - 1
tests/TestCase/ORM/TableTest.php

@@ -3968,7 +3968,61 @@ class TableTest extends TestCase
         $sizeArticles++;
 
         $this->assertEquals($authors->Articles->findAllByAuthorId($author->id)->count(), $sizeArticles);
-        $this->assertEquals(count($author->articles), $sizeArticles);
+        $this->assertEquals($sizeArticles, count($author->articles));
+    }
+
+    /**
+     * Integration test for linking entities with HasMany. The input contains already linked entities and they should not appeat duplicated
+     *
+     * @return void
+     */
+    public function testLinkHasManyExisting()
+    {
+        $authors = TableRegistry::get('Authors');
+        $articles = TableRegistry::get('Articles');
+
+        $authors->hasMany('Articles', [
+            'foreignKey' => 'author_id',
+            'saveStrategy' => 'replace'
+        ]);
+
+        $author = $authors->newEntity(['name' => 'mylux']);
+        $author = $authors->save($author);
+
+        $newArticles = $articles->newEntities(
+            [
+                [
+                    'title' => 'New bakery next corner',
+                    'body' => 'They sell tastefull cakes'
+                ],
+                [
+                    'title' => 'Spicy cake recipe',
+                    'body' => 'chocolate and peppers'
+                ]
+            ]
+        );
+
+        $this->assertTrue($authors->Articles->link($author, $newArticles));
+
+        $sizeArticles = count($newArticles);
+
+        $newArticles = array_merge(
+            $author->articles,
+            $articles->newEntities(
+                [
+                    [
+                        'title' => 'Nothing but the cake',
+                        'body' => 'It is all that we need'
+                    ]
+                ]
+            )
+        );
+        $this->assertTrue($authors->Articles->link($author, $newArticles));
+
+        $sizeArticles++;
+
+        $this->assertEquals($sizeArticles, $authors->Articles->findAllByAuthorId($author->id)->count());
+        $this->assertEquals($sizeArticles, count($author->articles));
     }
 
     /**
@@ -4217,6 +4271,60 @@ class TableTest extends TestCase
         $this->assertEquals(3, $article->tags[1]->id);
     }
 
+    public function testReplaceLinksHasMany()
+    {
+        $authors = TableRegistry::get('Authors');
+        $articles = TableRegistry::get('Articles');
+
+        $authors->hasMany('Articles', [
+            'foreignKey' => 'author_id'
+        ]);
+
+        $author = $authors->newEntity(['name' => 'mylux']);
+        $author = $authors->save($author);
+
+        $newArticles = $articles->newEntities(
+            [
+                [
+                    'title' => 'New bakery next corner',
+                    'body' => 'They sell tastefull cakes'
+                ],
+                [
+                    'title' => 'Spicy cake recipe',
+                    'body' => 'chocolate and peppers'
+                ]
+            ]
+        );
+
+        $sizeArticles = count($newArticles);
+
+        $this->assertTrue($authors->Articles->link($author, $newArticles));
+
+        $this->assertEquals($authors->Articles->findAllByAuthorId($author->id)->count(), $sizeArticles);
+        $this->assertEquals(count($author->articles), $sizeArticles);
+
+        $newArticles = array_merge(
+            $author->articles,
+            $articles->newEntities(
+                [
+                    [
+                        'title' => 'Cheese cake recipe',
+                        'body' => 'The secrets of mixing salt and sugar'
+                    ],
+                    [
+                        'title' => 'Not another piece of cake',
+                        'body' => 'This is the best'
+                    ]
+                ]
+            )
+        );
+        unset($newArticles[0]);
+
+        $this->assertTrue($authors->Articles->replaceLinks($author, $newArticles));
+        $this->assertEquals(count($newArticles), count($author->articles));
+        $this->assertEquals((new Collection($newArticles))->extract('title'), (new Collection($author->articles))->extract('title'));
+    }
+
     /**
      * Tests that it is possible to call find with no arguments
      *