Commit bab6b2d2 authored by Jens Segers's avatar Jens Segers

Tweaking belongsToMany relations and push/pull logic, fixes #272

parent 54e6ff97
...@@ -358,9 +358,27 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -358,9 +358,27 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
{ {
if ($parameters = func_get_args()) if ($parameters = func_get_args())
{ {
$unique = false;
if (count($parameters) == 3)
{
list($column, $values, $unique) = $parameters;
}
else
{
list($column, $values) = $parameters;
}
if ( ! is_array($values))
{
$values = array($values);
}
$query = $this->setKeysForSaveQuery($this->newQuery()); $query = $this->setKeysForSaveQuery($this->newQuery());
return call_user_func_array(array($query, 'push'), $parameters); $this->pushAttributeValues($column, $values, $unique);
return $query->push($column, $values, $unique);
} }
return parent::push(); return parent::push();
...@@ -371,11 +389,69 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -371,11 +389,69 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
* *
* @return mixed * @return mixed
*/ */
public function pull() public function pull($column, $values)
{
if ( ! is_array($values))
{ {
$values = array($values);
}
$query = $this->setKeysForSaveQuery($this->newQuery()); $query = $this->setKeysForSaveQuery($this->newQuery());
return call_user_func_array(array($query, 'pull'), func_get_args()); $this->pullAttributeValues($column, $values);
return $query->pull($column, $values);
}
/**
* Append one or more values to the underlying attribute value and sync with original.
*
* @param string $column
* @param array $values
* @param bool $unique
* @return void
*/
protected function pushAttributeValues($column, array $values, $unique = false)
{
$current = $this->getAttributeFromArray($column) ?: array();
foreach ($values as $value)
{
// Don't add duplicate values when we only want unique values.
if ($unique and in_array($value, $current)) continue;
array_push($current, $value);
}
$this->attributes[$column] = $current;
$this->syncOriginalAttribute($column);
}
/**
* Rempove one or more values to the underlying attribute value and sync with original.
*
* @param string $column
* @param array $values
* @return void
*/
protected function pullAttributeValues($column, array $values)
{
$current = $this->getAttributeFromArray($column) ?: array();
foreach ($values as $value)
{
$keys = array_keys($current, $value);
foreach ($keys as $key)
{
unset($current[$key]);
}
}
$this->attributes[$column] = $current;
$this->syncOriginalAttribute($column);
} }
/** /**
......
...@@ -519,6 +519,10 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -519,6 +519,10 @@ class Builder extends \Illuminate\Database\Query\Builder {
{ {
$query = array($operator => $column); $query = array($operator => $column);
} }
else if (is_array($value))
{
$query = array($operator => array($column => array('$each' => $value)));
}
else else
{ {
$query = array($operator => array($column => $value)); $query = array($operator => array($column => $value));
...@@ -536,13 +540,16 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -536,13 +540,16 @@ class Builder extends \Illuminate\Database\Query\Builder {
*/ */
public function pull($column, $value = null) public function pull($column, $value = null)
{ {
// If we are pulling multiple values, we need to use $pullAll.
$operator = is_array($value) ? '$pullAll' : '$pull';
if (is_array($column)) if (is_array($column))
{ {
$query = array('$pull' => $column); $query = array($operator => $column);
} }
else else
{ {
$query = array('$pull' => array($column => $value)); $query = array($operator => array($column => $value));
} }
return $this->performUpdate($query); return $this->performUpdate($query);
......
...@@ -43,12 +43,16 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -43,12 +43,16 @@ class BelongsToMany extends EloquentBelongsToMany {
/** /**
* Sync the intermediate tables with a list of IDs or collection of models. * Sync the intermediate tables with a list of IDs or collection of models.
* *
* @param array $ids * @param mixed $ids
* @param bool $detaching * @param bool $detaching
* @return void * @return array
*/ */
public function sync($ids, $detaching = true) public function sync($ids, $detaching = true)
{ {
$changes = array(
'attached' => array(), 'detached' => array(), 'updated' => array()
);
if ($ids instanceof Collection) $ids = $ids->modelKeys(); if ($ids instanceof Collection) $ids = $ids->modelKeys();
// First we need to attach any of the associated models that are not currently // First we need to attach any of the associated models that are not currently
...@@ -66,36 +70,36 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -66,36 +70,36 @@ class BelongsToMany extends EloquentBelongsToMany {
if ($detaching and count($detach) > 0) if ($detaching and count($detach) > 0)
{ {
$this->detach($detach); $this->detach($detach);
$changes['detached'] = (array) array_map('intval', $detach);
} }
// Now we are finally ready to attach the new records. Note that we'll disable // Now we are finally ready to attach the new records. Note that we'll disable
// touching until after the entire operation is complete so we don't fire a // touching until after the entire operation is complete so we don't fire a
// ton of touch operations until we are totally done syncing the records. // ton of touch operations until we are totally done syncing the records.
$this->attachNew($records, $current, false); $changes = array_merge(
$changes, $this->attachNew($records, $current, false)
);
if (count($changes['attached']) || count($changes['updated']))
{
$this->touchIfTouching(); $this->touchIfTouching();
} }
return $changes;
}
/** /**
* Attach all of the IDs that aren't in the current array. * Update an existing pivot record on the table.
* *
* @param array $records * @param mixed $id
* @param array $current * @param array $attributes
* @param bool $touch * @param bool $touch
* @return void * @return void
*/ */
protected function attachNew(array $records, array $current, $touch = true) public function updateExistingPivot($id, array $attributes, $touch = true)
{
foreach ($records as $id => $attributes)
{
// If the ID is not in the list of existing pivot IDs, we will insert a new pivot
// record, otherwise, we will just update this existing record on this joining
// table, so that the developers will easily update these records pain free.
if ( ! in_array($id, $current))
{ {
$this->attach($id, $attributes, $touch); // TODO
}
}
} }
/** /**
...@@ -112,21 +116,21 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -112,21 +116,21 @@ class BelongsToMany extends EloquentBelongsToMany {
$records = $this->createAttachRecords((array) $id, $attributes); $records = $this->createAttachRecords((array) $id, $attributes);
// Get the ID's to attach to the two documents // Get the ids to attach to the parent and related model.
$otherIds = array_pluck($records, $this->otherKey); $otherIds = array_pluck($records, $this->otherKey);
$foreignIds = array_pluck($records, $this->foreignKey); $foreignIds = array_pluck($records, $this->foreignKey);
// Attach to the parent model // Attach the new ids to the parent model.
$this->parent->push($this->otherKey, $otherIds[0]); $this->parent->push($this->otherKey, $otherIds, true);
// Generate a new related query instance // Generate a new related query instance.
$query = $this->getNewRelatedQuery(); $query = $this->newRelatedQuery();
// Set contraints on the related query // Set contraints on the related query.
$query->where($this->related->getKeyName(), $id); $query->where($this->related->getKeyName(), $id);
// Attach to the related model // Attach the new ids to the related model.
$query->push($this->foreignKey, $foreignIds[0]); $query->push($this->foreignKey, $foreignIds, true);
if ($touch) $this->touchIfTouching(); if ($touch) $this->touchIfTouching();
} }
...@@ -142,18 +146,15 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -142,18 +146,15 @@ class BelongsToMany extends EloquentBelongsToMany {
{ {
if ($ids instanceof Model) $ids = (array) $ids->getKey(); if ($ids instanceof Model) $ids = (array) $ids->getKey();
$query = $this->getNewRelatedQuery(); $query = $this->newRelatedQuery();
// If associated IDs were passed to the method we will only delete those // If associated IDs were passed to the method we will only delete those
// associations, otherwise all of the association ties will be broken. // associations, otherwise all of the association ties will be broken.
// We'll return the numbers of affected rows when we do the deletes. // We'll return the numbers of affected rows when we do the deletes.
$ids = (array) $ids; $ids = (array) $ids;
// Pull each id from the parent. // Detach all ids from the parent model.
foreach ($ids as $id) $this->parent->pull($this->otherKey, $ids);
{
$this->parent->pull($this->otherKey, $id);
}
// Prepare the query to select all related objects. // Prepare the query to select all related objects.
if (count($ids) > 0) if (count($ids) > 0)
...@@ -196,11 +197,21 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -196,11 +197,21 @@ class BelongsToMany extends EloquentBelongsToMany {
} }
/** /**
* Get a new related query. * Create a new query builder for the related model.
*
* @return \Illuminate\Database\Query\Builder
*/
protected function newPivotQuery()
{
return $this->newRelatedQuery();
}
/**
* Create a new query builder for the related model.
* *
* @return \Illuminate\Database\Query\Builder * @return \Illuminate\Database\Query\Builder
*/ */
public function getNewRelatedQuery() public function newRelatedQuery()
{ {
return $this->related->newQuery(); return $this->related->newQuery();
} }
......
...@@ -417,7 +417,7 @@ class RelationsTest extends TestCase { ...@@ -417,7 +417,7 @@ class RelationsTest extends TestCase {
$this->assertEquals('Paris', $address->data['city']); $this->assertEquals('Paris', $address->data['city']);
} }
public function testDoubleSave() public function testDoubleSaveOneToMany()
{ {
$author = User::create(array('name' => 'George R. R. Martin')); $author = User::create(array('name' => 'George R. R. Martin'));
$book = Book::create(array('title' => 'A Game of Thrones')); $book = Book::create(array('title' => 'A Game of Thrones'));
...@@ -426,14 +426,45 @@ class RelationsTest extends TestCase { ...@@ -426,14 +426,45 @@ class RelationsTest extends TestCase {
$author->books()->save($book); $author->books()->save($book);
$author->save(); $author->save();
$this->assertEquals(1, $author->books()->count()); $this->assertEquals(1, $author->books()->count());
$this->assertEquals($author->_id, $book->author_id);
$author = User::where('name', 'George R. R. Martin')->first(); $author = User::where('name', 'George R. R. Martin')->first();
$book = Book::where('title', 'A Game of Thrones')->first();
$this->assertEquals(1, $author->books()->count()); $this->assertEquals(1, $author->books()->count());
$this->assertEquals($author->_id, $book->author_id);
$author->books()->save($book); $author->books()->save($book);
$author->books()->save($book); $author->books()->save($book);
$author->save(); $author->save();
$this->assertEquals(1, $author->books()->count()); $this->assertEquals(1, $author->books()->count());
$this->assertEquals($author->_id, $book->author_id);
}
public function testDoubleSaveManyToMany()
{
$user = User::create(array('name' => 'John Doe'));
$client = Client::create(array('name' => 'Admins'));
$user->clients()->save($client);
$user->clients()->save($client);
$user->save();
$this->assertEquals(1, $user->clients()->count());
//$this->assertEquals(array($user->_id), $client->user_ids); TODO
$this->assertEquals(array($client->_id), $user->client_ids);
$user = User::where('name', 'John Doe')->first();
$client = Client::where('name', 'Admins')->first();
$this->assertEquals(1, $user->clients()->count());
//$this->assertEquals(array($user->_id), $client->user_ids); TODO
$this->assertEquals(array($client->_id), $user->client_ids);
$user->clients()->save($client);
$user->clients()->save($client);
$user->save();
$this->assertEquals(1, $user->clients()->count());
//$this->assertEquals(array($user->_id), $client->user_ids); TODO
$this->assertEquals(array($client->_id), $user->client_ids);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment