Skip to content

Commit

Permalink
Adding saveStrategy to BelongsToMany for handling whether to append or
Browse files Browse the repository at this point in the history
replace to the junction table
  • Loading branch information
lorenzo committed Dec 18, 2013
1 parent 86f5fd1 commit ae6bf52
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 16 deletions.
78 changes: 67 additions & 11 deletions Cake/ORM/Association/BelongsToMany.php
Expand Up @@ -35,6 +35,20 @@ class BelongsToMany extends Association {
_options as _externalOptions;
}

/**
* Saving strategy that will only append to the links set
*
* @var string
*/
const SAVE_APPEND = 'append';

/**
* Saving strategy that will replace the links with the provided set
*
* @var string
*/
const SAVE_REPLACE = 'replace';

/**
* Whether this association can be expressed directly in a query join
*
Expand Down Expand Up @@ -86,6 +100,13 @@ class BelongsToMany extends Association {
*/
protected $_junctionProperty = '_joinData';

/**
* Saving strategy to be used by this association
*
* @var string
*/
protected $_saveStrategy = self::SAVE_APPEND;

/**
* Sets the table instance for the junction relation. If no arguments
* are passed, the current configured table instance is returned
Expand Down Expand Up @@ -282,15 +303,39 @@ public function isOwningSide(Table $side) {
return true;
}

/**
* Sets the strategy that should be used for saving. If called with no
* arguments, it will return the currently configured strategy
*
* @param string $strategy the strategy name to be used
* @throws \InvalidArgumentException if an invalid strategy name is passed
* @return string the strategy to be used for saving
*/
public function saveStrategy($strategy = null) {
if ($strategy === null) {
return $this->_saveStrategy;
}
if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE])) {
$msg = __d('cake_dev', 'Invalid save strategy "%s"', $strategy);
throw new \InvalidArgumentException($msg);
}
return $this->_strategy = $strategy;
}

/**
* Takes an entity from the source table and looks if there is a field
* matching the property name for this association. The found entity will be
* saved on the target table for this association by passing supplied
* `$options`
*
* Using this save function will only create new links between each side
* of this association. It will not destroy existing ones even though they
* may not be present in the array of entities to be saved.
* When using the 'append' strategy, this function will only create new links
* between each side of this association. It will not destroy existing ones even
* though they may not be present in the array of entities to be saved.
*
* When using the 'replace' strategy, existing links will be removed and new ones
* will be created in between both tables in the association. If there exists
* links in the database to some of the entities intended to be saved by this method,
* they will be updated, not deleted.
*
* @param \Cake\ORM\Entity $entity an entity from the source table
* @param array|\ArrayObject $options options to be passed to the save method in
Expand All @@ -300,17 +345,26 @@ public function isOwningSide(Table $side) {
* @return boolean|Entity false if $entity could not be saved, otherwise it returns
* the saved entity
* @see Table::save()
* @see BelongsToMany::replaceLinks()
*/
public function save(Entity $entity, $options = []) {
$property = $this->property();
$targetEntity = $entity->get($this->property());
$success = false;
$strategy = $this->saveStrategy();

if (!$targetEntity) {
return false;
}

if ($strategy === self::SAVE_APPEND) {
return $this->_saveTarget($entity, $targetEntity, $options);
}

if ($targetEntity) {
$success = $this->_saveTarget($entity, $targetEntity, $options);
if ($this->replaceLinks($entity, $targetEntity, $options)) {
return $entity;
}

return $success;
return false;
}

/**
Expand Down Expand Up @@ -583,14 +637,13 @@ function() use ($sourceEntity, $targetEntities, $primaryValue, $options) {

$jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities);
$inserts = $this->_diffLinks($existing, $jointEntities, $targetEntities);
$options += ['associated' => false];

$property = $this->property();
$sourceEntity->set($property, $inserts);

if ($inserts && !$this->save($sourceEntity, $options + ['associated' => false])) {
if ($inserts && !$this->_saveTarget($sourceEntity, $inserts, $options)) {
return false;
}

$property = $this->property();
$sourceEntity->set($property, $targetEntities);
$sourceEntity->dirty($property, false);
return true;
Expand Down Expand Up @@ -820,6 +873,9 @@ protected function _options(array $opts) {
if (!empty($opts['joinTable'])) {
$this->_junctionTableName($opts['joinTable']);
}
if (!empty($opts['saveStrategy'])) {
$this->saveStrategy($opts['saveStrategy']);
}
$this->_externalOptions($opts);
}

Expand Down
10 changes: 5 additions & 5 deletions Cake/Test/TestCase/ORM/Association/BelongsToManyTest.php
Expand Up @@ -950,7 +950,7 @@ public function testReplaceLinkSuccess() {
];
$assoc = $this->getMock(
'\Cake\ORM\Association\BelongsToMany',
['_collectJointEntities', 'save'],
['_collectJointEntities', '_saveTarget'],
['tags', $config]
);

Expand Down Expand Up @@ -1010,10 +1010,10 @@ public function testReplaceLinkSuccess() {

$options = ['foo' => 'bar'];
$assoc->expects($this->once())
->method('save')
->with($entity, $options + ['associated' => false])
->will($this->returnCallback(function($entity) use ($tags) {
$this->assertSame([$tags[1], $tags[2]], $entity->get('tags'));
->method('_saveTarget')
->with($entity, [$tags[1], $tags[2]], $options + ['associated' => false])
->will($this->returnCallback(function($entity, $inserts) use ($tags) {
$this->assertSame([$tags[1], $tags[2]], $inserts);
return true;
}));

Expand Down

0 comments on commit ae6bf52

Please sign in to comment.