Skip to content

Commit 6c60542

Browse files
committed
Improve embedded relations, and allow nested embedded relations
1 parent 0e923bc commit 6c60542

File tree

5 files changed

+223
-7
lines changed

5 files changed

+223
-7
lines changed

src/Jenssegers/Mongodb/Relations/EmbedsMany.php

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,16 @@ public function performInsert(Model $model, array $values)
3232
$model->setAttribute('_id', new MongoId);
3333
}
3434

35+
// For deeply nested documents, let the parent handle the changes.
36+
if ($this->isNested())
37+
{
38+
$this->associate($model);
39+
40+
return $this->parent->save();
41+
}
42+
3543
// Push the new model to the database.
36-
$result = $this->query->push($this->localKey, $model->getAttributes(), true);
44+
$result = $this->getBaseQuery()->push($this->localKey, $model->getAttributes(), true);
3745

3846
// Attach the model to its parent.
3947
if ($result) $this->associate($model);
@@ -49,12 +57,23 @@ public function performInsert(Model $model, array $values)
4957
*/
5058
public function performUpdate(Model $model, array $values)
5159
{
60+
// For deeply nested documents, let the parent handle the changes.
61+
if ($this->isNested())
62+
{
63+
$this->associate($model);
64+
65+
return $this->parent->save();
66+
}
67+
5268
// Get the correct foreign key value.
5369
$foreignKey = $this->getForeignKeyValue($model);
5470

71+
// Use array dot notation for better update behavior.
72+
$values = array_dot($model->getDirty(), $this->localKey . '.$.');
73+
5574
// Update document in database.
56-
$result = $this->query->where($this->localKey . '.' . $model->getKeyName(), $foreignKey)
57-
->update(array($this->localKey . '.$' => $model->getAttributes()));
75+
$result = $this->getBaseQuery()->where($this->localKey . '.' . $model->getKeyName(), $foreignKey)
76+
->update($values);
5877

5978
// Attach the model to its parent.
6079
if ($result) $this->associate($model);

src/Jenssegers/Mongodb/Relations/EmbedsOne.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ public function performInsert(Model $model, array $values)
3232
$model->setAttribute('_id', new MongoId);
3333
}
3434

35-
$result = $this->query->update(array($this->localKey => $model->getAttributes()));
35+
// For deeply nested documents, let the parent handle the changes.
36+
if ($this->isNested())
37+
{
38+
$this->associate($model);
39+
40+
return $this->parent->save();
41+
}
42+
43+
$result = $this->getBaseQuery()->update(array($this->localKey => $model->getAttributes()));
3644

3745
// Attach the model to its parent.
3846
if ($result) $this->associate($model);
@@ -48,7 +56,17 @@ public function performInsert(Model $model, array $values)
4856
*/
4957
public function performUpdate(Model $model, array $values)
5058
{
51-
$result = $this->query->update(array($this->localKey => $model->getAttributes()));
59+
if ($this->isNested())
60+
{
61+
$this->associate($model);
62+
63+
return $this->parent->save();
64+
}
65+
66+
// Use array dot notation for better update behavior.
67+
$values = array_dot($model->getDirty(), $this->localKey . '.');
68+
69+
$result = $this->getBaseQuery()->update($values);
5270

5371
// Attach the model to its parent.
5472
if ($result) $this->associate($model);

src/Jenssegers/Mongodb/Relations/EmbedsOneOrMany.php

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public function __construct(Builder $query, Model $parent, Model $related, $loca
5151
// If this is a nested relation, we need to get the parent query instead.
5252
if ($parentRelation = $this->getParentRelation())
5353
{
54+
//$this->query = $parentRelation->parent->newQuery();
5455
$this->query = $parentRelation->getQuery();
5556
}
5657

@@ -66,7 +67,7 @@ public function addConstraints()
6667
{
6768
if (static::$constraints)
6869
{
69-
$this->query->where($this->parent->getKeyName(), '=', $this->parent->getKey());
70+
$this->query->where($this->getQualifiedParentKeyName(), '=', $this->getParentKey());
7071
}
7172
}
7273

@@ -328,4 +329,54 @@ protected function getParentRelation()
328329
return $this->parent->getParent();
329330
}
330331

332+
/**
333+
* Check if this relation is nested in another relation.
334+
*
335+
* @return boolean
336+
*/
337+
protected function isNested()
338+
{
339+
return $this->getParentRelation() != null;
340+
}
341+
342+
/**
343+
* Get the fully qualified local key name.
344+
*
345+
* @return string
346+
*/
347+
protected function getPathHierarchy($glue = '.')
348+
{
349+
if ($parentRelation = $this->getParentRelation())
350+
{
351+
return $parentRelation->getPathHierarchy($glue) . $glue . $this->localKey;
352+
}
353+
354+
return $this->localKey;
355+
}
356+
357+
/**
358+
* Get the parent's fully qualified key name.
359+
*
360+
* @return string
361+
*/
362+
protected function getQualifiedParentKeyName()
363+
{
364+
if ($parentRelation = $this->getParentRelation())
365+
{
366+
return $parentRelation->getPathHierarchy() . '.' . $this->parent->getKeyName();
367+
}
368+
369+
return $this->parent->getKeyName();
370+
}
371+
372+
/**
373+
* Get the primary key value of the parent.
374+
*
375+
* @return string
376+
*/
377+
protected function getParentKey()
378+
{
379+
return $this->parent->getKey();
380+
}
381+
331382
}

tests/EmbeddedRelationsTest.php

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public function testEmbedsManySave()
7474
$user = User::find($user->_id);
7575
$user->addresses()->save(new Address(array('city' => 'Bruxelles')));
7676
$this->assertEquals(array('London', 'New York', 'Bruxelles'), $user->addresses->lists('city'));
77+
7778
$address = $user->addresses[1];
7879
$address->city = "Manhattan";
7980
$user->addresses()->save($address);
@@ -83,6 +84,29 @@ public function testEmbedsManySave()
8384
$this->assertEquals(array('London', 'Manhattan', 'Bruxelles'), $freshUser->addresses->lists('city'));
8485
}
8586

87+
public function testEmbedsManySaveModel()
88+
{
89+
$user = User::create(array('name' => 'John Doe'));
90+
$address = new Address(array('city' => 'London'));
91+
92+
$address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher'));
93+
$events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($address), $address)->andReturn(true);
94+
$events->shouldReceive('until')->once()->with('eloquent.creating: '.get_class($address), $address)->andReturn(true);
95+
$events->shouldReceive('fire')->once()->with('eloquent.created: '.get_class($address), $address);
96+
$events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($address), $address);
97+
98+
$address->save();
99+
100+
$address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher'));
101+
$events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($address), $address)->andReturn(true);
102+
$events->shouldReceive('until')->once()->with('eloquent.updating: '.get_class($address), $address)->andReturn(true);
103+
$events->shouldReceive('fire')->once()->with('eloquent.updated: '.get_class($address), $address);
104+
$events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($address), $address);
105+
106+
$address->city = 'Paris';
107+
$address->save();
108+
}
109+
86110
public function testEmbedsToArray()
87111
{
88112
$user = User::create(array('name' => 'John Doe'));
@@ -217,6 +241,32 @@ public function testEmbedsManyDestroy()
217241
$this->assertEquals(array('Bristol'), $user->addresses->lists('city'));
218242
}
219243

244+
public function testEmbedsManyDelete()
245+
{
246+
$user = User::create(array('name' => 'John Doe'));
247+
$user->addresses()->saveMany(array(new Address(array('city' => 'London')), new Address(array('city' => 'Bristol')), new Address(array('city' => 'Bruxelles'))));
248+
249+
$address = $user->addresses->first();
250+
251+
$address->setEventDispatcher($events = Mockery::mock('Illuminate\Events\Dispatcher'));
252+
$events->shouldReceive('until')->once()->with('eloquent.deleting: '.get_class($address), Mockery::type('Address'))->andReturn(true);
253+
$events->shouldReceive('fire')->once()->with('eloquent.deleted: '.get_class($address), Mockery::type('Address'));
254+
255+
$address->delete();
256+
257+
$this->assertEquals(2, $user->addresses()->count());
258+
$this->assertEquals(2, $user->addresses->count());
259+
260+
$address->unsetEventDispatcher();
261+
262+
$address = $user->addresses->first();
263+
$address->delete();
264+
265+
$user = User::where('name', 'John Doe')->first();
266+
$this->assertEquals(1, $user->addresses()->count());
267+
$this->assertEquals(1, $user->addresses->count());
268+
}
269+
220270
public function testEmbedsManyDissociate()
221271
{
222272
$user = User::create(array());
@@ -339,7 +389,7 @@ public function testEmbedsManyEagerLoading()
339389
$this->assertTrue(is_array($user->toArray()['addresses']));
340390
}
341391

342-
public function testEmbedsManyDelete()
392+
public function testEmbedsManyDeleteAll()
343393
{
344394
$user1 = User::create(array('name' => 'John Doe'));
345395
$user1->addresses()->save(new Address(array('city' => 'New York')));
@@ -500,4 +550,77 @@ public function testEmbeddedSave()
500550
$this->assertEquals('Mark Doe', $user->father->name);
501551
}
502552

553+
public function testNestedEmbedsOne()
554+
{
555+
$user = User::create(array('name' => 'John Doe'));
556+
$father = $user->father()->create(array('name' => 'Mark Doe'));
557+
$grandfather = $father->father()->create(array('name' => 'Steve Doe'));
558+
$greatgrandfather = $grandfather->father()->create(array('name' => 'Tom Doe'));
559+
560+
$user->name = 'Tim Doe';
561+
$user->save();
562+
563+
$father->name = 'Sven Doe';
564+
$father->save();
565+
566+
$greatgrandfather->name = 'Ron Doe';
567+
$greatgrandfather->save();
568+
569+
$this->assertEquals('Tim Doe', $user->name);
570+
$this->assertEquals('Sven Doe', $user->father->name);
571+
$this->assertEquals('Steve Doe', $user->father->father->name);
572+
$this->assertEquals('Ron Doe', $user->father->father->father->name);
573+
574+
$user = User::where('name', 'Tim Doe')->first();
575+
$this->assertEquals('Tim Doe', $user->name);
576+
$this->assertEquals('Sven Doe', $user->father->name);
577+
$this->assertEquals('Steve Doe', $user->father->father->name);
578+
$this->assertEquals('Ron Doe', $user->father->father->father->name);
579+
}
580+
581+
public function testNestedEmbedsMany()
582+
{
583+
$user = User::create(array('name' => 'John Doe'));
584+
$country1 = $user->addresses()->create(array('country' => 'France'));
585+
$country2 = $user->addresses()->create(array('country' => 'Belgium'));
586+
$city1 = $country1->addresses()->create(array('city' => 'Paris'));
587+
$city2 = $country2->addresses()->create(array('city' => 'Ghent'));
588+
$city3 = $country2->addresses()->create(array('city' => 'Brussels'));
589+
590+
$city3->city = 'Bruges';
591+
$city3->save();
592+
593+
$this->assertEquals(2, $user->addresses()->count());
594+
$this->assertEquals(1, $user->addresses()->first()->addresses()->count());
595+
$this->assertEquals(2, $user->addresses()->last()->addresses()->count());
596+
597+
$user = User::where('name', 'John Doe')->first();
598+
$this->assertEquals(2, $user->addresses()->count());
599+
$this->assertEquals(1, $user->addresses()->first()->addresses()->count());
600+
$this->assertEquals(2, $user->addresses()->last()->addresses()->count());
601+
}
602+
603+
public function testNestedMixedEmbeds()
604+
{
605+
$user = User::create(array('name' => 'John Doe'));
606+
$father = $user->father()->create(array('name' => 'Mark Doe'));
607+
$country1 = $father->addresses()->create(array('country' => 'France'));
608+
$country2 = $father->addresses()->create(array('country' => 'Belgium'));
609+
610+
$country2->country = 'England';
611+
$country2->save();
612+
613+
$father->name = 'Steve Doe';
614+
$father->save();
615+
616+
$this->assertEquals('France', $user->father->addresses()->first()->country);
617+
$this->assertEquals('England', $user->father->addresses()->last()->country);
618+
$this->assertEquals('Steve Doe', $user->father->name);
619+
620+
$user = User::where('name', 'John Doe')->first();
621+
$this->assertEquals('France', $user->father->addresses()->first()->country);
622+
$this->assertEquals('England', $user->father->addresses()->last()->country);
623+
$this->assertEquals('Steve Doe', $user->father->name);
624+
}
625+
503626
}

tests/models/Address.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,9 @@ class Address extends Eloquent {
66

77
protected static $unguarded = true;
88

9+
public function addresses()
10+
{
11+
return $this->embedsMany('Address');
12+
}
13+
914
}

0 commit comments

Comments
 (0)