Skip to content

Commit bab6b2d

Browse files
committed
Tweaking belongsToMany relations and push/pull logic, fixes #272
1 parent 54e6ff9 commit bab6b2d

File tree

4 files changed

+165
-40
lines changed

4 files changed

+165
-40
lines changed

src/Jenssegers/Mongodb/Model.php

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,27 @@ public function push()
358358
{
359359
if ($parameters = func_get_args())
360360
{
361+
$unique = false;
362+
363+
if (count($parameters) == 3)
364+
{
365+
list($column, $values, $unique) = $parameters;
366+
}
367+
else
368+
{
369+
list($column, $values) = $parameters;
370+
}
371+
372+
if ( ! is_array($values))
373+
{
374+
$values = array($values);
375+
}
376+
361377
$query = $this->setKeysForSaveQuery($this->newQuery());
362378

363-
return call_user_func_array(array($query, 'push'), $parameters);
379+
$this->pushAttributeValues($column, $values, $unique);
380+
381+
return $query->push($column, $values, $unique);
364382
}
365383

366384
return parent::push();
@@ -371,11 +389,69 @@ public function push()
371389
*
372390
* @return mixed
373391
*/
374-
public function pull()
392+
public function pull($column, $values)
375393
{
394+
if ( ! is_array($values))
395+
{
396+
$values = array($values);
397+
}
398+
376399
$query = $this->setKeysForSaveQuery($this->newQuery());
377400

378-
return call_user_func_array(array($query, 'pull'), func_get_args());
401+
$this->pullAttributeValues($column, $values);
402+
403+
return $query->pull($column, $values);
404+
}
405+
406+
/**
407+
* Append one or more values to the underlying attribute value and sync with original.
408+
*
409+
* @param string $column
410+
* @param array $values
411+
* @param bool $unique
412+
* @return void
413+
*/
414+
protected function pushAttributeValues($column, array $values, $unique = false)
415+
{
416+
$current = $this->getAttributeFromArray($column) ?: array();
417+
418+
foreach ($values as $value)
419+
{
420+
// Don't add duplicate values when we only want unique values.
421+
if ($unique and in_array($value, $current)) continue;
422+
423+
array_push($current, $value);
424+
}
425+
426+
$this->attributes[$column] = $current;
427+
428+
$this->syncOriginalAttribute($column);
429+
}
430+
431+
/**
432+
* Rempove one or more values to the underlying attribute value and sync with original.
433+
*
434+
* @param string $column
435+
* @param array $values
436+
* @return void
437+
*/
438+
protected function pullAttributeValues($column, array $values)
439+
{
440+
$current = $this->getAttributeFromArray($column) ?: array();
441+
442+
foreach ($values as $value)
443+
{
444+
$keys = array_keys($current, $value);
445+
446+
foreach ($keys as $key)
447+
{
448+
unset($current[$key]);
449+
}
450+
}
451+
452+
$this->attributes[$column] = $current;
453+
454+
$this->syncOriginalAttribute($column);
379455
}
380456

381457
/**

src/Jenssegers/Mongodb/Query/Builder.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,10 @@ public function push($column, $value = null, $unique = false)
519519
{
520520
$query = array($operator => $column);
521521
}
522+
else if (is_array($value))
523+
{
524+
$query = array($operator => array($column => array('$each' => $value)));
525+
}
522526
else
523527
{
524528
$query = array($operator => array($column => $value));
@@ -536,13 +540,16 @@ public function push($column, $value = null, $unique = false)
536540
*/
537541
public function pull($column, $value = null)
538542
{
543+
// If we are pulling multiple values, we need to use $pullAll.
544+
$operator = is_array($value) ? '$pullAll' : '$pull';
545+
539546
if (is_array($column))
540547
{
541-
$query = array('$pull' => $column);
548+
$query = array($operator => $column);
542549
}
543550
else
544551
{
545-
$query = array('$pull' => array($column => $value));
552+
$query = array($operator => array($column => $value));
546553
}
547554

548555
return $this->performUpdate($query);

src/Jenssegers/Mongodb/Relations/BelongsToMany.php

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@ public function addConstraints()
4343
/**
4444
* Sync the intermediate tables with a list of IDs or collection of models.
4545
*
46-
* @param array $ids
46+
* @param mixed $ids
4747
* @param bool $detaching
48-
* @return void
48+
* @return array
4949
*/
5050
public function sync($ids, $detaching = true)
5151
{
52+
$changes = array(
53+
'attached' => array(), 'detached' => array(), 'updated' => array()
54+
);
55+
5256
if ($ids instanceof Collection) $ids = $ids->modelKeys();
5357

5458
// First we need to attach any of the associated models that are not currently
@@ -66,36 +70,36 @@ public function sync($ids, $detaching = true)
6670
if ($detaching and count($detach) > 0)
6771
{
6872
$this->detach($detach);
73+
74+
$changes['detached'] = (array) array_map('intval', $detach);
6975
}
7076

7177
// Now we are finally ready to attach the new records. Note that we'll disable
7278
// touching until after the entire operation is complete so we don't fire a
7379
// ton of touch operations until we are totally done syncing the records.
74-
$this->attachNew($records, $current, false);
80+
$changes = array_merge(
81+
$changes, $this->attachNew($records, $current, false)
82+
);
7583

76-
$this->touchIfTouching();
84+
if (count($changes['attached']) || count($changes['updated']))
85+
{
86+
$this->touchIfTouching();
87+
}
88+
89+
return $changes;
7790
}
7891

7992
/**
80-
* Attach all of the IDs that aren't in the current array.
93+
* Update an existing pivot record on the table.
8194
*
82-
* @param array $records
83-
* @param array $current
95+
* @param mixed $id
96+
* @param array $attributes
8497
* @param bool $touch
8598
* @return void
8699
*/
87-
protected function attachNew(array $records, array $current, $touch = true)
100+
public function updateExistingPivot($id, array $attributes, $touch = true)
88101
{
89-
foreach ($records as $id => $attributes)
90-
{
91-
// If the ID is not in the list of existing pivot IDs, we will insert a new pivot
92-
// record, otherwise, we will just update this existing record on this joining
93-
// table, so that the developers will easily update these records pain free.
94-
if ( ! in_array($id, $current))
95-
{
96-
$this->attach($id, $attributes, $touch);
97-
}
98-
}
102+
// TODO
99103
}
100104

101105
/**
@@ -112,21 +116,21 @@ public function attach($id, array $attributes = array(), $touch = true)
112116

113117
$records = $this->createAttachRecords((array) $id, $attributes);
114118

115-
// Get the ID's to attach to the two documents
119+
// Get the ids to attach to the parent and related model.
116120
$otherIds = array_pluck($records, $this->otherKey);
117121
$foreignIds = array_pluck($records, $this->foreignKey);
118122

119-
// Attach to the parent model
120-
$this->parent->push($this->otherKey, $otherIds[0]);
123+
// Attach the new ids to the parent model.
124+
$this->parent->push($this->otherKey, $otherIds, true);
121125

122-
// Generate a new related query instance
123-
$query = $this->getNewRelatedQuery();
126+
// Generate a new related query instance.
127+
$query = $this->newRelatedQuery();
124128

125-
// Set contraints on the related query
129+
// Set contraints on the related query.
126130
$query->where($this->related->getKeyName(), $id);
127131

128-
// Attach to the related model
129-
$query->push($this->foreignKey, $foreignIds[0]);
132+
// Attach the new ids to the related model.
133+
$query->push($this->foreignKey, $foreignIds, true);
130134

131135
if ($touch) $this->touchIfTouching();
132136
}
@@ -142,18 +146,15 @@ public function detach($ids = array(), $touch = true)
142146
{
143147
if ($ids instanceof Model) $ids = (array) $ids->getKey();
144148

145-
$query = $this->getNewRelatedQuery();
149+
$query = $this->newRelatedQuery();
146150

147151
// If associated IDs were passed to the method we will only delete those
148152
// associations, otherwise all of the association ties will be broken.
149153
// We'll return the numbers of affected rows when we do the deletes.
150154
$ids = (array) $ids;
151155

152-
// Pull each id from the parent.
153-
foreach ($ids as $id)
154-
{
155-
$this->parent->pull($this->otherKey, $id);
156-
}
156+
// Detach all ids from the parent model.
157+
$this->parent->pull($this->otherKey, $ids);
157158

158159
// Prepare the query to select all related objects.
159160
if (count($ids) > 0)
@@ -196,11 +197,21 @@ protected function buildDictionary(Collection $results)
196197
}
197198

198199
/**
199-
* Get a new related query.
200+
* Create a new query builder for the related model.
201+
*
202+
* @return \Illuminate\Database\Query\Builder
203+
*/
204+
protected function newPivotQuery()
205+
{
206+
return $this->newRelatedQuery();
207+
}
208+
209+
/**
210+
* Create a new query builder for the related model.
200211
*
201212
* @return \Illuminate\Database\Query\Builder
202213
*/
203-
public function getNewRelatedQuery()
214+
public function newRelatedQuery()
204215
{
205216
return $this->related->newQuery();
206217
}

tests/RelationsTest.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ public function testNestedKeys()
417417
$this->assertEquals('Paris', $address->data['city']);
418418
}
419419

420-
public function testDoubleSave()
420+
public function testDoubleSaveOneToMany()
421421
{
422422
$author = User::create(array('name' => 'George R. R. Martin'));
423423
$book = Book::create(array('title' => 'A Game of Thrones'));
@@ -426,14 +426,45 @@ public function testDoubleSave()
426426
$author->books()->save($book);
427427
$author->save();
428428
$this->assertEquals(1, $author->books()->count());
429+
$this->assertEquals($author->_id, $book->author_id);
429430

430431
$author = User::where('name', 'George R. R. Martin')->first();
432+
$book = Book::where('title', 'A Game of Thrones')->first();
431433
$this->assertEquals(1, $author->books()->count());
434+
$this->assertEquals($author->_id, $book->author_id);
432435

433436
$author->books()->save($book);
434437
$author->books()->save($book);
435438
$author->save();
436439
$this->assertEquals(1, $author->books()->count());
440+
$this->assertEquals($author->_id, $book->author_id);
441+
}
442+
443+
public function testDoubleSaveManyToMany()
444+
{
445+
$user = User::create(array('name' => 'John Doe'));
446+
$client = Client::create(array('name' => 'Admins'));
447+
448+
$user->clients()->save($client);
449+
$user->clients()->save($client);
450+
$user->save();
451+
452+
$this->assertEquals(1, $user->clients()->count());
453+
//$this->assertEquals(array($user->_id), $client->user_ids); TODO
454+
$this->assertEquals(array($client->_id), $user->client_ids);
455+
456+
$user = User::where('name', 'John Doe')->first();
457+
$client = Client::where('name', 'Admins')->first();
458+
$this->assertEquals(1, $user->clients()->count());
459+
//$this->assertEquals(array($user->_id), $client->user_ids); TODO
460+
$this->assertEquals(array($client->_id), $user->client_ids);
461+
462+
$user->clients()->save($client);
463+
$user->clients()->save($client);
464+
$user->save();
465+
$this->assertEquals(1, $user->clients()->count());
466+
//$this->assertEquals(array($user->_id), $client->user_ids); TODO
467+
$this->assertEquals(array($client->_id), $user->client_ids);
437468
}
438469

439470
}

0 commit comments

Comments
 (0)