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 {
{
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());
return call_user_func_array(array($query, 'push'), $parameters);
$this->pushAttributeValues($column, $values, $unique);
return $query->push($column, $values, $unique);
}
return parent::push();
......@@ -371,11 +389,69 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
*
* @return mixed
*/
public function pull()
public function pull($column, $values)
{
if ( ! is_array($values))
{
$values = array($values);
}
$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 {
{
$query = array($operator => $column);
}
else if (is_array($value))
{
$query = array($operator => array($column => array('$each' => $value)));
}
else
{
$query = array($operator => array($column => $value));
......@@ -536,13 +540,16 @@ class Builder extends \Illuminate\Database\Query\Builder {
*/
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))
{
$query = array('$pull' => $column);
$query = array($operator => $column);
}
else
{
$query = array('$pull' => array($column => $value));
$query = array($operator => array($column => $value));
}
return $this->performUpdate($query);
......
......@@ -43,12 +43,16 @@ class BelongsToMany extends EloquentBelongsToMany {
/**
* Sync the intermediate tables with a list of IDs or collection of models.
*
* @param array $ids
* @param mixed $ids
* @param bool $detaching
* @return void
* @return array
*/
public function sync($ids, $detaching = true)
{
$changes = array(
'attached' => array(), 'detached' => array(), 'updated' => array()
);
if ($ids instanceof Collection) $ids = $ids->modelKeys();
// First we need to attach any of the associated models that are not currently
......@@ -66,36 +70,36 @@ class BelongsToMany extends EloquentBelongsToMany {
if ($detaching and count($detach) > 0)
{
$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
// 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.
$this->attachNew($records, $current, false);
$changes = array_merge(
$changes, $this->attachNew($records, $current, false)
);
$this->touchIfTouching();
if (count($changes['attached']) || count($changes['updated']))
{
$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 array $current
* @param mixed $id
* @param array $attributes
* @param bool $touch
* @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 {
$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);
$foreignIds = array_pluck($records, $this->foreignKey);
// Attach to the parent model
$this->parent->push($this->otherKey, $otherIds[0]);
// Attach the new ids to the parent model.
$this->parent->push($this->otherKey, $otherIds, true);
// Generate a new related query instance
$query = $this->getNewRelatedQuery();
// Generate a new related query instance.
$query = $this->newRelatedQuery();
// Set contraints on the related query
// Set contraints on the related query.
$query->where($this->related->getKeyName(), $id);
// Attach to the related model
$query->push($this->foreignKey, $foreignIds[0]);
// Attach the new ids to the related model.
$query->push($this->foreignKey, $foreignIds, true);
if ($touch) $this->touchIfTouching();
}
......@@ -142,18 +146,15 @@ class BelongsToMany extends EloquentBelongsToMany {
{
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
// associations, otherwise all of the association ties will be broken.
// We'll return the numbers of affected rows when we do the deletes.
$ids = (array) $ids;
// Pull each id from the parent.
foreach ($ids as $id)
{
$this->parent->pull($this->otherKey, $id);
}
// Detach all ids from the parent model.
$this->parent->pull($this->otherKey, $ids);
// Prepare the query to select all related objects.
if (count($ids) > 0)
......@@ -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
*/
public function getNewRelatedQuery()
public function newRelatedQuery()
{
return $this->related->newQuery();
}
......
......@@ -417,7 +417,7 @@ class RelationsTest extends TestCase {
$this->assertEquals('Paris', $address->data['city']);
}
public function testDoubleSave()
public function testDoubleSaveOneToMany()
{
$author = User::create(array('name' => 'George R. R. Martin'));
$book = Book::create(array('title' => 'A Game of Thrones'));
......@@ -426,14 +426,45 @@ class RelationsTest extends TestCase {
$author->books()->save($book);
$author->save();
$this->assertEquals(1, $author->books()->count());
$this->assertEquals($author->_id, $book->author_id);
$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($author->_id, $book->author_id);
$author->books()->save($book);
$author->books()->save($book);
$author->save();
$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