Skip to content

[Proposal] Add embedsMany relationship to Eloquent Model #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 27, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/Jenssegers/Eloquent/Model.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<?php namespace Jenssegers\Eloquent;

use LogicException;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\Relation;
use Jenssegers\Mongodb\Relations\BelongsTo;
use Jenssegers\Mongodb\Relations\BelongsToMany;
use Jenssegers\Mongodb\Relations\EmbedsMany;
use Jenssegers\Mongodb\Relations\EmbeddedRelation;
use Jenssegers\Mongodb\Query\Builder as QueryBuilder;

abstract class Model extends \Illuminate\Database\Eloquent\Model {
Expand Down Expand Up @@ -219,6 +223,45 @@ public function belongsToMany($related, $collection = null, $foreignKey = null,
return new BelongsToMany($query, $this, $collection, $foreignKey, $otherKey, $relation);
}

/**
* Define an embedded one-to-many relationship.
*
* @param string $related
* @param string $collection
* @return \Illuminate\Database\Eloquent\Relations\EmbedsMany
*/
protected function embedsMany($related, $collection = null)
{
// If no collection name was provided, we assume that the standard is the
// related model camel_cased, pluralized and suffixed with '_ids'
if (is_null($collection))
{
$collection = str_plural(snake_case($related)) . '_ids';
}

return new EmbedsMany($this, $related, $collection);
}

/**
* Get a relationship value from a method.
*
* @param string $key
* @param string $camelKey
* @return mixed
*/
protected function getRelationshipFromMethod($key, $camelKey)
{
$relations = $this->$camelKey();

if ( ! $relations instanceof Relation and ! $relations instanceof EmbeddedRelation)
{
throw new LogicException('Relationship method must return an object of type '
. 'Illuminate\Database\Eloquent\Relations\Relation or Jenssegers\Mongodb\Relations\EmbeddedRelation');
}

return $this->relations[$key] = $relations->getResults();
}

/**
* Get a new query builder instance for the connection.
*
Expand Down
42 changes: 42 additions & 0 deletions src/Jenssegers/Mongodb/Relations/EmbeddedRelation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php namespace Jenssegers\Mongodb\Relations;

use Illuminate\Database\Eloquent\Model;

abstract class EmbeddedRelation {

/**
* The parent model instance.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $parent;

/**
* The related model class name.
*
* @var string
*/
protected $related;

/**
* Create a new has many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $related
* @param string $collection
* @return void
*/
public function __construct(Model $parent, $related)
{
$this->parent = $parent;
$this->related = $related;
}

/**
* Get the results of the relationship.
*
* @return mixed
*/
abstract public function getResults();

}
185 changes: 185 additions & 0 deletions src/Jenssegers/Mongodb/Relations/EmbedsMany.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php namespace Jenssegers\Mongodb\Relations;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection;

use MongoId;

class EmbedsMany extends EmbeddedRelation {

/**
* The parent collection attribute where the related are stored.
*
* @var string
*/
protected $collection;

/**
* Create a new has many relationship instance.
*
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $related
* @param string $collection
* @return void
*/
public function __construct(Model $parent, $related, $collection)
{
$this->collection = $collection;

parent::__construct($parent, $related);
}

/**
* Get the results of the relationship.
*
* @return Illuminate\Database\Eloquent\Collection
*/
public function getResults()
{
$models = new Collection();

$modelsAttributes = $this->parent->getAttribute($this->collection);

if (is_array($modelsAttributes))
{
foreach ($modelsAttributes as $attributes)
{
$models->push(new $this->related($attributes));
}
}

return $models;
}

/**
* Create a new instance and attach it to the parent model.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function build(array $attributes)
{
if ( ! isset($attributes['_id'])) $attributes['_id'] = new MongoId;

$collection = $this->parent->getAttribute($this->collection);
$collection[''.$attributes['_id']] = $attributes;
$this->parent->setAttribute($this->collection, $collection);

return new $this->related($attributes);
}

/**
* Attach an instance to the parent model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Model
*/
public function add($model)
{
return $this->build($model->toArray());
}

/**
* Create a new instance, attach it to the parent model and save this model.
*
* @param array $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function create(array $attributes)
{
$instance = $this->build($attributes);

$this->parent->save();

return $instance;
}

/**
* Attach a model instance to the parent model and save this model.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Model
*/
public function push($model)
{
return $this->create($model->toArray());
}

/**
* Create an array of new instances and attach them to the parent model.
*
* @param array|Traversable $modelsAttributes
* @return array
*/
public function buildMany($modelsAttributes)
{
$instances = array();

foreach ($modelsAttributes as $attributes)
{
$instances[] = $this->build($attributes);
}

return $instances;
}

/**
* Attach an array of new instances to the parent model.
*
* @param array|Traversable $models
* @return array
*/
public function addMany($models)
{
$modelsAttributes = $this->getAttributesOf($models);

return $this->buildMany($modelsAttributes);
}

/**
* Create an array of new instances, attach them to the parent model and save this model.
*
* @param array|Traversable $modelsAttributes
* @return array
*/
public function createMany($modelsAttributes)
{
$instances = $this->buildMany($modelsAttributes);

$this->parent->save();

return $instances;
}

/**
* Attach an array of new instances to the parent model and save this model.
*
* @param array|Traversable $models
* @return array
*/
public function pushMany($models)
{
$modelsAttributes = $this->getAttributesOf($models);

return $this->createMany($modelsAttributes);
}

/**
* Transform a list of models to a list of models' attributes
*
* @param array|Traversable $models
* @return array
*/
protected function getAttributesOf($models)
{
$modelsAttributes = array();

foreach ($models as $key => $model)
{
$modelsAttributes[$key] = $model->getAttributes();
}

return $modelsAttributes;
}

}
48 changes: 48 additions & 0 deletions tests/RelationsTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Illuminate\Database\Eloquent\Collection;

class RelationsTest extends PHPUnit_Framework_TestCase {

public function setUp() {
Expand Down Expand Up @@ -279,4 +281,50 @@ public function testMorph()
$photo = Photo::first();
$this->assertEquals($photo->imageable->name, $user->name);
}

public function testEmbedsManySaveOneRelated()
{
$user = User::create(array('name' => 'John Doe'));

$user->addresses()->create(array('city' => 'Paris'));
$user->addresses()->push(new Address(array('city' => 'London')));
$user->addresses()->build(array('city' => 'Bruxelles'));
$user->addresses()->add(new Address(array('city' => 'New-York')));

$freshUser = User::find($user->id);
$this->assertEquals(array('Paris', 'London', 'Bruxelles', 'New-York'), $user->addresses->lists('city'));
$this->assertEquals(array('Paris', 'London'), $freshUser->addresses->lists('city'));

$user->save();
$freshUser = User::find($user->id);
$this->assertEquals(array('Paris', 'London', 'Bruxelles', 'New-York'), $freshUser->addresses->lists('city'));

}

public function testEmbedsManySaveManyRelated()
{
$user = User::create(array('name' => 'John Doe'));

$user->addresses()->createMany(array(array('city' => 'Paris'), array('city' => 'Rouen')));
$user->addresses()->pushMany(array(new Address(array('city' => 'London')), new Address(array('city' => 'Bristol'))));
$user->addresses()->buildMany(array(array('city' => 'Bruxelles')));
$user->addresses()->addMany(new Collection(array(new Address(array('city' => 'New-York')))));

$freshUser = User::find($user->id);
$this->assertEquals(array('Paris', 'Rouen', 'London', 'Bristol', 'Bruxelles', 'New-York'), $user->addresses->lists('city'));
$this->assertEquals(array('Paris', 'Rouen', 'London', 'Bristol'), $freshUser->addresses->lists('city'));

$user->save();
$freshUser = User::find($user->id);
$this->assertEquals(array('Paris', 'Rouen', 'London', 'Bristol', 'Bruxelles', 'New-York'), $freshUser->addresses->lists('city'));

}

public function testEmbedsManyCreateId()
{
$user = new User(array('name' => 'John Doe'));
$user->addresses()->build(array('city' => 'Bruxelles'));
$this->assertInstanceOf('MongoId', $user->addresses->first()->_id);
}

}
9 changes: 9 additions & 0 deletions tests/models/Address.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

use Jenssegers\Mongodb\Model as Eloquent;

class Address extends Eloquent {

protected static $unguarded = true;

}
5 changes: 5 additions & 0 deletions tests/models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public function photos()
return $this->morphMany('Photo', 'imageable');
}

public function addresses()
{
return $this->embedsMany('Address');
}

/**
* Get the unique identifier for the user.
*
Expand Down