From aed212a7aa5e95010da7c25bb3f618b678a6a12f Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2013 16:55:19 +0000 Subject: [PATCH 1/6] Added *.project to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8cc7a5a20..a4a4579c3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ phpunit.phar composer.phar composer.lock *.sublime-project -*.sublime-workspace \ No newline at end of file +*.sublime-workspace +*.project From e93b26258ce13d46db03ebbb1c071544293bf791 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2013 16:56:34 +0000 Subject: [PATCH 2/6] test models added --- tests/models/Client.php | 14 ++++++++++++++ tests/models/User.php | 15 +++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 tests/models/Client.php diff --git a/tests/models/Client.php b/tests/models/Client.php new file mode 100644 index 000000000..de55ceab6 --- /dev/null +++ b/tests/models/Client.php @@ -0,0 +1,14 @@ +belongsToMany('User'); + } +} \ No newline at end of file diff --git a/tests/models/User.php b/tests/models/User.php index 26143b6be..2e6862760 100644 --- a/tests/models/User.php +++ b/tests/models/User.php @@ -7,13 +7,11 @@ class User extends Eloquent implements UserInterface, RemindableInterface { - protected $collection = 'users'; + protected $collection = 'users'; - protected $dates = array('birthday'); + protected static $unguarded = true; - protected static $unguarded = true; - - public function books() + public function books() { return $this->hasMany('Book', 'author_id'); } @@ -27,6 +25,11 @@ public function role() { return $this->hasOne('Role'); } + + public function clients() + { + return $this->belongsToMany('Client'); + } /** * Get the unique identifier for the user. @@ -58,4 +61,4 @@ public function getReminderEmail() return $this->email; } -} +} \ No newline at end of file From bc9b05ede6f0ae31387baad55d9952fd87dd7068 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2013 16:59:35 +0000 Subject: [PATCH 3/6] Relations test updated --- tests/RelationsTest.php | 110 ++++++++++++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 21 deletions(-) diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index ef21749bf..b646014de 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -3,6 +3,11 @@ class RelationsTest extends PHPUnit_Framework_TestCase { public function setUp() { + User::truncate(); + Book::truncate(); + Item::truncate(); + Role::truncate(); + Client::truncate(); } public function tearDown() @@ -11,8 +16,9 @@ public function tearDown() Book::truncate(); Item::truncate(); Role::truncate(); + Client::truncate(); } - + public function testHasMany() { $author = User::create(array('name' => 'George R. R. Martin')); @@ -101,28 +107,90 @@ public function testWithHasOne() $this->assertInstanceOf('Role', $role); $this->assertEquals('admin', $role->type); } - - public function testEasyRelation() + + public function testHasManyAndBelongsTo() { - // Has Many - $user = User::create(array('name' => 'John Doe')); - $item = Item::create(array('type' => 'knife')); - $user->items()->save($item); - - $user = User::find($user->_id); - $items = $user->items; - $this->assertEquals(1, count($items)); - $this->assertInstanceOf('Item', $items[0]); - - // Has one $user = User::create(array('name' => 'John Doe')); - $role = Role::create(array('type' => 'admin')); - $user->role()->save($role); - - $user = User::find($user->_id); - $role = $user->role; - $this->assertInstanceOf('Role', $role); - $this->assertEquals('admin', $role->type); + + $user->clients()->save(new Client(array('name' => 'Pork Pies Ltd.'))); + $user->clients()->create(array('name' => 'Buffet Bar Inc.')); + + $user = User::with('clients')->find($user->_id); + + $client = Client::with('users')->first(); + + $clients = $client->getRelation('users'); + $users = $user->getRelation('clients'); + + $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $users); + $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $clients); + $this->assertInstanceOf('Client', $users[0]); + $this->assertInstanceOf('User', $clients[0]); + $this->assertCount(2, $user->clients); + $this->assertCount(1, $client->users); + + // Now create a new user to an existing client + $client->users()->create(array('name' => 'Jane Doe')); + + $otherClient = User::where('name', '=', 'Jane Doe')->first()->clients()->get(); + + $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection', $otherClient); + $this->assertInstanceOf('Client', $otherClient[0]); + $this->assertCount(1, $otherClient); + + // Now attach an existing client to an existing user + $user = User::where('name', '=', 'Jane Doe')->first(); + $client = Client::Where('name', '=', 'Buffet Bar Inc.')->first(); + + // Check the models are what they should be + $this->assertInstanceOf('Client', $client); + $this->assertInstanceOf('User', $user); + + // Assert they are not attached + $this->assertFalse(in_array($client->_id, $user->client_ids)); + $this->assertFalse(in_array($user->_id, $client->user_ids)); + + // Attach the client to the user + $user->clients()->attach($client); + + // Get the new user model + $user = User::where('name', '=', 'Jane Doe')->first(); + $client = Client::Where('name', '=', 'Buffet Bar Inc.')->first(); + + // Assert they are attached + $this->assertTrue(in_array($client->_id, $user->client_ids)); + $this->assertTrue(in_array($user->_id, $client->user_ids)); } + public function testHasManyAndBelongsToAttachesExistingModels() + { + $user = User::create(array('name' => 'John Doe', 'client_ids' => array('1234523'))); + + $clients = array( + Client::create(array('name' => 'Pork Pies Ltd.'))->_id, + Client::create(array('name' => 'Buffet Bar Inc.'))->_id + ); + + $moreClients = array( + Client::create(array('name' => 'Boloni Ltd.'))->_id, + Client::create(array('name' => 'Meatballs Inc.'))->_id + ); + + $user->clients()->sync($clients); + + $user = User::with('clients')->find($user->_id); + + // Assert non attached ID's are detached succesfully + $this->assertFalse(in_array('1234523', $user->client_ids)); + + // Assert there are two client objects in the relationship + $this->assertCount(2, $user->clients); + + $user->clients()->sync($moreClients); + + $user = User::with('clients')->find($user->_id); + + // Assert there are now 4 client objects in the relationship + $this->assertCount(4, $user->clients); + } } From 3046a0951e51b8572af165540b4d75795d96cfb0 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2013 17:00:45 +0000 Subject: [PATCH 4/6] Belongs to many relationship class added --- .../Mongodb/Relations/BelongsToMany.php | 402 ++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 src/Jenssegers/Mongodb/Relations/BelongsToMany.php diff --git a/src/Jenssegers/Mongodb/Relations/BelongsToMany.php b/src/Jenssegers/Mongodb/Relations/BelongsToMany.php new file mode 100644 index 000000000..a0aff091e --- /dev/null +++ b/src/Jenssegers/Mongodb/Relations/BelongsToMany.php @@ -0,0 +1,402 @@ +getSelectColumns($columns); + + $models = $this->query->addSelect($select)->getModels(); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded. This will solve the + // n + 1 query problem for the developer and also increase performance. + if (count($models) > 0) + { + $models = $this->query->eagerLoadRelations($models); + } + + return $this->related->newCollection($models); + } + + /** + * Set the select clause for the relation query. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function getSelectColumns(array $columns = array('*')) + { + return $columns; + } + + /** + * Get a paginator for the "select" statement. + * + * @param int $perPage + * @param array $columns + * @return \Illuminate\Pagination\Paginator + */ + public function paginate($perPage = null, $columns = array('*')) + { + $this->query->addSelect($this->getSelectColumns($columns)); + + // When paginating results, we need to add the pivot columns to the query and + // then hydrate into the pivot objects once the results have been gathered + // from the database since this isn't performed by the Eloquent builder. + $pager = $this->query->paginate($perPage, $columns); + + return $pager; + } + + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) + { + // Make sure that the primary key of the parent + // is in the relationship array of keys + $this->query->whereIn($this->foreignKey, array($this->parent->getKey())); + } + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $this->query->whereIn($this->getForeignKey(), $this->getKeys($models)); + } + + /** + * Save a new model and attach it to the parent model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function save(Model $model, array $joining = array(), $touch = true) + { + $model->save(array('touch' => false)); + + $this->attach($model->getKey(), $joining, $touch); + + return $model; + } + + /** + * Create a new instance of the related model. + * + * @param array $attributes + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function create(array $attributes, array $joining = array(), $touch = true) + { + $instance = $this->related->newInstance($attributes); + + // Save the new instance before we attach it to other models + $instance->save(array('touch' => false)); + + // Attach to the parent instance + $this->attach($instance->_id, $attributes, $touch); + + return $instance; + } + + /** + * Sync the intermediate tables with a list of IDs. + * + * @param array $ids + * @param bool $detaching + * @return void + */ + public function sync(array $ids, $detaching = true) + { + // First we need to attach any of the associated models that are not currently + // in this joining table. We'll spin through the given IDs, checking to see + // if they exist in the array of current ones, and if not we will insert. + $current = $this->parent->{$this->otherKey}; + + // Check if the current array exists or not on the parent model and create it + // if it does not exist + if (is_null($current)) $current = array(); + + $records = $this->formatSyncList($ids); + + $detach = array_diff($current, array_keys($records)); + + // Next, we will take the differences of the currents and given IDs and detach + // all of the entities that exist in the "current" array but are not in the + // the array of the IDs given to the method which will complete the sync. + if ($detaching and count($detach) > 0) + { + $this->detach($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); + + $this->touchIfTouching(); + } + + /** + * Format the sync list so that it is keyed by ID. + * + * @param array $records + * @return array + */ + protected function formatSyncList(array $records) + { + $results = array(); + + foreach ($records as $id => $attributes) + { + if ( ! is_array($attributes)) + { + list($id, $attributes) = array($attributes, array()); + } + + $results[$id] = $attributes; + } + + return $results; + } + + /** + * Attach all of the IDs that aren't in the current array. + * + * @param array $records + * @param array $current + * @param bool $touch + * @return void + */ + protected function attachNew(array $records, array $current, $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); + } + } + } + + /** + * Attach a model to the parent. + * + * @param mixed $id + * @param array $attributes + * @param bool $touch + * @return void + */ + public function attach($id, array $attributes = array(), $touch = true) + { + if ($id instanceof Model) $id = $id->getKey(); + + // Generate a new parent query instance + $parent = $this->newParentQuery(); + + // Generate a new related query instance + $related = $this->related->newInstance(); + + // Set contraints on the related query + $related = $related->where($this->related->getKeyName(), $id); + + $records = $this->createAttachRecords((array) $id, $attributes); + + // Get the ID's to attach to the two documents + $otherIds = array_pluck($records, $this->otherKey); + $foreignIds = array_pluck($records, $this->foreignKey); + + // Attach to the parent model + $parent->push($this->otherKey, $otherIds[0])->update(array()); + + // Attach to the related model + $related->push($this->foreignKey, $foreignIds[0])->update(array()); + } + + /** + * Create an array of records to insert into the pivot table. + * + * @param array $ids + * @return void + */ + protected function createAttachRecords($ids, array $attributes) + { + $records = array();; + + // To create the attachment records, we will simply spin through the IDs given + // and create a new record to insert for each ID. Each ID may actually be a + // key in the array, with extra attributes to be placed in other columns. + foreach ($ids as $key => $value) + { + $records[] = $this->attacher($key, $value, $attributes, false); + } + + return $records; + } + + /** + * Detach models from the relationship. + * + * @param int|array $ids + * @param bool $touch + * @return int + */ + public function detach($ids = array(), $touch = true) + { + if ($ids instanceof Model) $ids = (array) $ids->getKey(); + + $query = $this->newParentQuery(); + + // 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; + + if (count($ids) > 0) + { + $query->whereIn($this->otherKey, $ids); + } + + if ($touch) $this->touchIfTouching(); + + // Once we have all of the conditions set on the statement, we are ready + // to run the delete on the pivot table. Then, if the touch parameter + // is true, we will go ahead and touch all related models to sync. + foreach($ids as $id) + { + $query->pull($this->otherKey, $id); + } + + return count($ids); + } + + /** + * If we're touching the parent model, touch. + * + * @return void + */ + public function touchIfTouching() + { + if ($this->touchingParent()) $this->getParent()->touch(); + + if ($this->getParent()->touches($this->relationName)) $this->touch(); + } + + /** + * Determine if we should touch the parent on sync. + * + * @return bool + */ + protected function touchingParent() + { + return $this->getRelated()->touches($this->guessInverseRelation()); + } + + /** + * Attempt to guess the name of the inverse of the relation. + * + * @return string + */ + protected function guessInverseRelation() + { + return strtolower(str_plural(class_basename($this->getParent()))); + } + + /** + * Create a new query builder for the parent + * + * @return Jenssegers\Mongodb\Builder + */ + protected function newParentQuery() + { + $query = $this->parent->newQuery(); + + return $query->where($this->parent->getKeyName(), '=', $this->parent->getKey()); + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array + */ + protected function buildDictionary(Collection $results) + { + $foreign = $this->foreignKey; + + // First we will build a dictionary of child models keyed by the foreign key + // of the relation so that we will easily and quickly match them to their + // parents without having a possibly slow inner loops for every models. + $dictionary = array(); + + foreach ($results as $result) + { + foreach ($result->$foreign as $single) + { + $dictionary[$single][] = $result; + } + } + + return $dictionary; + } + + /** + * Get the related model's updated at column name. + * + * @return string + */ + public function getRelatedFreshUpdate() + { + return array($this->related->getUpdatedAtColumn() => $this->related->freshTimestamp()); + } + + /** + * Get the fully qualified foreign key for the relation. + * + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the fully qualified "other key" for the relation. + * + * @return string + */ + public function getOtherKey() + { + return $this->otherKey; + } +} \ No newline at end of file From bbb799097719f8770d87b24319b5ec139f28257f Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2013 17:01:51 +0000 Subject: [PATCH 5/6] Model updated --- src/Jenssegers/Mongodb/Model.php | 138 +++++++++++++++++-------------- 1 file changed, 76 insertions(+), 62 deletions(-) diff --git a/src/Jenssegers/Mongodb/Model.php b/src/Jenssegers/Mongodb/Model.php index ae43d190e..a3bd99846 100644 --- a/src/Jenssegers/Mongodb/Model.php +++ b/src/Jenssegers/Mongodb/Model.php @@ -7,8 +7,8 @@ use Jenssegers\Mongodb\DatabaseManager as Resolver; use Jenssegers\Mongodb\Builder as QueryBuilder; use Jenssegers\Mongodb\Relations\BelongsTo; +use Jenssegers\Mongodb\Relations\BelongsToMany; -use Carbon\Carbon; use DateTime; use MongoId; use MongoDate; @@ -67,25 +67,19 @@ public function fromDateTime($value) */ protected function asDateTime($value) { - // Convert timestamp - if (is_numeric($value)) - { - return Carbon::createFromTimestamp($value); - } - - // Convert string - if (is_string($value)) + // Convert MongoDate to timestamp + if ($value instanceof MongoDate) { - return new Carbon($value); + $value = $value->sec; } - // Convert MongoDate - if ($value instanceof MongoDate) + // Convert timestamp to string for DateTime + if (is_int($value)) { - return Carbon::createFromTimestamp($value->sec); + $value = "@$value"; } - return Carbon::instance($value); + return new DateTime($value); } /** @@ -121,83 +115,104 @@ public function getTable() } /** - * Define a one-to-one relationship. - * - * @param string $related - * @param string $foreignKey - * @param string $localKey - * @return \Illuminate\Database\Eloquent\Relations\HasOne - */ - public function hasOne($related, $foreignKey = null, $localKey = null) + * Define a one-to-one relationship. + * + * @param string $related + * @param string $foreignKey + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function hasOne($related, $foreignKey = null) { $foreignKey = $foreignKey ?: $this->getForeignKey(); $instance = new $related; - $localKey = $localKey ?: $this->getKeyName(); - - return new HasOne($instance->newQuery(), $this, $foreignKey, $localKey); + return new HasOne($instance->newQuery(), $this, $foreignKey); } /** - * Define a one-to-many relationship. - * - * @param string $related - * @param string $foreignKey - * @param string $localKey - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function hasMany($related, $foreignKey = null, $localKey = null) + * Define a one-to-many relationship. + * + * @param string $related + * @param string $foreignKey + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function hasMany($related, $foreignKey = null) { $foreignKey = $foreignKey ?: $this->getForeignKey(); $instance = new $related; - $localKey = $localKey ?: $this->getKeyName(); - - return new HasMany($instance->newQuery(), $this, $foreignKey, $localKey); + return new HasMany($instance->newQuery(), $this, $foreignKey); } /** - * Define an inverse one-to-one or many relationship. - * - * @param string $related - * @param string $foreignKey - * @param string $otherKey - * @param string $relation - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null) + * Define an inverse one-to-one or many relationship. + * + * @param string $related + * @param string $foreignKey + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function belongsTo($related, $foreignKey = null) { - // If no relation name was given, we will use this debug backtrace to extract - // the calling method's name and use that as the relationship name as most - // of the time this will be what we desire to use for the relatinoships. - if (is_null($relation)) - { - list(, $caller) = debug_backtrace(false); - - $relation = $caller['function']; - } + list(, $caller) = debug_backtrace(false); // If no foreign key was supplied, we can use a backtrace to guess the proper // foreign key name by using the name of the relationship function, which // when combined with an "_id" should conventionally match the columns. + $relation = $caller['function']; + if (is_null($foreignKey)) { $foreignKey = snake_case($relation).'_id'; } - $instance = new $related; - // Once we have the foreign key names, we'll just create a new Eloquent query // for the related models and returns the relationship instance which will // actually be responsible for retrieving and hydrating every relations. - $query = $instance->newQuery(); + $instance = new $related; - $otherKey = $otherKey ?: $instance->getKeyName(); + $query = $instance->newQuery(); - return new BelongsTo($query, $this, $foreignKey, $otherKey, $relation); + return new BelongsTo($query, $this, $foreignKey, $relation); } + + /** + * Define a many-to-many relationship. + * + * @param string $related + * @param string $table + * @param string $foreignKey + * @param string $otherKey + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function belongsToMany($related, $collection = null, $foreignKey = null, $otherKey = null) + { + $caller = $this->getBelongsToManyCaller(); + + // First, we'll need to determine the foreign key and "other key" for the + // relationship. Once we have determined the keys we'll make the query + // instances as well as the relationship instances we need for this. + $foreignKey = $foreignKey ?: $this->getForeignKey() . 's'; + + $instance = new $related; + + $otherKey = $otherKey ?: $instance->getForeignKey() . 's'; + // If no table name was provided, we can guess it by concatenating the two + // models using underscores in alphabetical order. The two model names + // are transformed to snake case from their default CamelCase also. + if (is_null($collection)) + { + $collection = snake_case(str_plural(class_basename($related))); + } + + // Now we're ready to create a new query builder for the related model and + // the relationship instances for the relation. The relations will set + // appropriate query constraint and entirely manages the hydrations. + $query = $instance->newQuery(); + + return new BelongsToMany($query, $this, $collection, $foreignKey, $otherKey, $caller['function']); + } /** * Get a new query builder instance for the connection. @@ -251,7 +266,7 @@ public function dropColumn($columns) { $this->__unset($column); } - + // Perform unset only on current document return $query = $this->newQuery()->where($this->getKeyName(), $this->getKey())->unset($columns); } @@ -265,7 +280,6 @@ public function dropColumn($columns) */ public function __call($method, $parameters) { - // Unset method if ($method == 'unset') { return call_user_func_array(array($this, 'dropColumn'), $parameters); @@ -274,4 +288,4 @@ public function __call($method, $parameters) return parent::__call($method, $parameters); } -} +} \ No newline at end of file From 0eed89649d8977c7a7c75da0274bdb17f19c9bb6 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Nov 2013 17:40:30 +0000 Subject: [PATCH 6/6] Models fixed and tests updated --- src/Jenssegers/Mongodb/Model.php | 100 +++++++++----- tests/ModelTest.php | 3 + tests/RelationsTest.php | 229 +++++++++++++++++-------------- tests/models/User.php | 5 +- 4 files changed, 192 insertions(+), 145 deletions(-) diff --git a/src/Jenssegers/Mongodb/Model.php b/src/Jenssegers/Mongodb/Model.php index a3bd99846..ecc94e304 100644 --- a/src/Jenssegers/Mongodb/Model.php +++ b/src/Jenssegers/Mongodb/Model.php @@ -9,6 +9,7 @@ use Jenssegers\Mongodb\Relations\BelongsTo; use Jenssegers\Mongodb\Relations\BelongsToMany; +use Carbon\Carbon; use DateTime; use MongoId; use MongoDate; @@ -67,19 +68,25 @@ public function fromDateTime($value) */ protected function asDateTime($value) { - // Convert MongoDate to timestamp - if ($value instanceof MongoDate) + // Convert timestamp + if (is_numeric($value)) + { + return Carbon::createFromTimestamp($value); + } + + // Convert string + if (is_string($value)) { - $value = $value->sec; + return new Carbon($value); } - // Convert timestamp to string for DateTime - if (is_int($value)) + // Convert MongoDate + if ($value instanceof MongoDate) { - $value = "@$value"; + return Carbon::createFromTimestamp($value->sec); } - return new DateTime($value); + return Carbon::instance($value); } /** @@ -115,68 +122,84 @@ public function getTable() } /** - * Define a one-to-one relationship. - * - * @param string $related - * @param string $foreignKey - * @return \Illuminate\Database\Eloquent\Relations\HasOne - */ - public function hasOne($related, $foreignKey = null) + * Define a one-to-one relationship. + * + * @param string $related + * @param string $foreignKey + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function hasOne($related, $foreignKey = null, $localKey = null) { $foreignKey = $foreignKey ?: $this->getForeignKey(); $instance = new $related; - return new HasOne($instance->newQuery(), $this, $foreignKey); + $localKey = $localKey ?: $this->getKeyName(); + + return new HasOne($instance->newQuery(), $this, $foreignKey, $localKey); } /** - * Define a one-to-many relationship. - * - * @param string $related - * @param string $foreignKey - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function hasMany($related, $foreignKey = null) + * Define a one-to-many relationship. + * + * @param string $related + * @param string $foreignKey + * @param string $localKey + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function hasMany($related, $foreignKey = null, $localKey = null) { $foreignKey = $foreignKey ?: $this->getForeignKey(); $instance = new $related; - return new HasMany($instance->newQuery(), $this, $foreignKey); + $localKey = $localKey ?: $this->getKeyName(); + + return new HasMany($instance->newQuery(), $this, $foreignKey, $localKey); } /** - * Define an inverse one-to-one or many relationship. - * - * @param string $related - * @param string $foreignKey - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function belongsTo($related, $foreignKey = null) + * Define an inverse one-to-one or many relationship. + * + * @param string $related + * @param string $foreignKey + * @param string $otherKey + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null) { - list(, $caller) = debug_backtrace(false); + // If no relation name was given, we will use this debug backtrace to extract + // the calling method's name and use that as the relationship name as most + // of the time this will be what we desire to use for the relatinoships. + if (is_null($relation)) + { + list(, $caller) = debug_backtrace(false); + + $relation = $caller['function']; + } // If no foreign key was supplied, we can use a backtrace to guess the proper // foreign key name by using the name of the relationship function, which // when combined with an "_id" should conventionally match the columns. - $relation = $caller['function']; - if (is_null($foreignKey)) { $foreignKey = snake_case($relation).'_id'; } + $instance = new $related; + // Once we have the foreign key names, we'll just create a new Eloquent query // for the related models and returns the relationship instance which will // actually be responsible for retrieving and hydrating every relations. - $instance = new $related; - $query = $instance->newQuery(); - return new BelongsTo($query, $this, $foreignKey, $relation); + $otherKey = $otherKey ?: $instance->getKeyName(); + + return new BelongsTo($query, $this, $foreignKey, $otherKey, $relation); } - + /** * Define a many-to-many relationship. * @@ -266,7 +289,7 @@ public function dropColumn($columns) { $this->__unset($column); } - + // Perform unset only on current document return $query = $this->newQuery()->where($this->getKeyName(), $this->getKey())->unset($columns); } @@ -280,6 +303,7 @@ public function dropColumn($columns) */ public function __call($method, $parameters) { + // Unset method if ($method == 'unset') { return call_user_func_array(array($this, 'dropColumn'), $parameters); diff --git a/tests/ModelTest.php b/tests/ModelTest.php index d03a7f207..04e275b83 100644 --- a/tests/ModelTest.php +++ b/tests/ModelTest.php @@ -312,11 +312,14 @@ public function testUnset() public function testDates() { $user = User::create(array('name' => 'John Doe', 'birthday' => new DateTime('1980/1/1'))); + $this->assertInstanceOf('Carbon\Carbon', $user->birthday); $check = User::find($user->_id); + $this->assertInstanceOf('Carbon\Carbon', $check->birthday); $this->assertEquals($user->birthday, $check->birthday); + $user = User::where('birthday', '>', new DateTime('1975/1/1'))->first(); $this->assertEquals('John Doe', $user->name); diff --git a/tests/RelationsTest.php b/tests/RelationsTest.php index b646014de..2b9c0bef0 100644 --- a/tests/RelationsTest.php +++ b/tests/RelationsTest.php @@ -3,110 +3,128 @@ class RelationsTest extends PHPUnit_Framework_TestCase { public function setUp() { - User::truncate(); - Book::truncate(); - Item::truncate(); - Role::truncate(); - Client::truncate(); - } - - public function tearDown() - { - User::truncate(); - Book::truncate(); - Item::truncate(); - Role::truncate(); - Client::truncate(); - } - - public function testHasMany() - { - $author = User::create(array('name' => 'George R. R. Martin')); - Book::create(array('title' => 'A Game of Thrones', 'author_id' => $author->_id)); - Book::create(array('title' => 'A Clash of Kings', 'author_id' => $author->_id)); - - $books = $author->books; - $this->assertEquals(2, count($books)); - - $user = User::create(array('name' => 'John Doe')); - Item::create(array('type' => 'knife', 'user_id' => $user->_id)); - Item::create(array('type' => 'shield', 'user_id' => $user->_id)); - Item::create(array('type' => 'sword', 'user_id' => $user->_id)); - Item::create(array('type' => 'bag', 'user_id' => null)); - - $items = $user->items; - $this->assertEquals(3, count($items)); - } - - public function testBelongsTo() - { - $user = User::create(array('name' => 'George R. R. Martin')); - Book::create(array('title' => 'A Game of Thrones', 'author_id' => $user->_id)); - $book = Book::create(array('title' => 'A Clash of Kings', 'author_id' => $user->_id)); - - $author = $book->author; - $this->assertEquals('George R. R. Martin', $author->name); - - $user = User::create(array('name' => 'John Doe')); - $item = Item::create(array('type' => 'sword', 'user_id' => $user->_id)); - - $owner = $item->user; - $this->assertEquals('John Doe', $owner->name); - } - - public function testHasOne() - { - $user = User::create(array('name' => 'John Doe')); - Role::create(array('type' => 'admin', 'user_id' => $user->_id)); - - $role = $user->role; - $this->assertEquals('admin', $role->type); - } - - public function testWithBelongsTo() - { - $user = User::create(array('name' => 'John Doe')); - Item::create(array('type' => 'knife', 'user_id' => $user->_id)); - Item::create(array('type' => 'shield', 'user_id' => $user->_id)); - Item::create(array('type' => 'sword', 'user_id' => $user->_id)); - Item::create(array('type' => 'bag', 'user_id' => null)); - - $items = Item::with('user')->get(); - - $user = $items[0]->getRelation('user'); - $this->assertInstanceOf('User', $user); - $this->assertEquals('John Doe', $user->name); - $this->assertEquals(1, count($items[0]->getRelations())); - $this->assertEquals(null, $items[3]->getRelation('user')); - } - - public function testWithHashMany() - { - $user = User::create(array('name' => 'John Doe')); - Item::create(array('type' => 'knife', 'user_id' => $user->_id)); - Item::create(array('type' => 'shield', 'user_id' => $user->_id)); - Item::create(array('type' => 'sword', 'user_id' => $user->_id)); - Item::create(array('type' => 'bag', 'user_id' => null)); - - $user = User::with('items')->find($user->_id); - - $items = $user->getRelation('items'); - $this->assertEquals(3, count($items)); - $this->assertInstanceOf('Item', $items[0]); - } - - public function testWithHasOne() - { - $user = User::create(array('name' => 'John Doe')); - Role::create(array('type' => 'admin', 'user_id' => $user->_id)); - Role::create(array('type' => 'guest', 'user_id' => $user->_id)); - - $user = User::with('role')->find($user->_id); - - $role = $user->getRelation('role'); - $this->assertInstanceOf('Role', $role); - $this->assertEquals('admin', $role->type); - } + } + + public function tearDown() + { + User::truncate(); + Book::truncate(); + Item::truncate(); + Role::truncate(); + Client::truncate(); + } + + public function testHasMany() + { + $author = User::create(array('name' => 'George R. R. Martin')); + Book::create(array('title' => 'A Game of Thrones', 'author_id' => $author->_id)); + Book::create(array('title' => 'A Clash of Kings', 'author_id' => $author->_id)); + + $books = $author->books; + $this->assertEquals(2, count($books)); + + $user = User::create(array('name' => 'John Doe')); + Item::create(array('type' => 'knife', 'user_id' => $user->_id)); + Item::create(array('type' => 'shield', 'user_id' => $user->_id)); + Item::create(array('type' => 'sword', 'user_id' => $user->_id)); + Item::create(array('type' => 'bag', 'user_id' => null)); + + $items = $user->items; + $this->assertEquals(3, count($items)); + } + + public function testBelongsTo() + { + $user = User::create(array('name' => 'George R. R. Martin')); + Book::create(array('title' => 'A Game of Thrones', 'author_id' => $user->_id)); + $book = Book::create(array('title' => 'A Clash of Kings', 'author_id' => $user->_id)); + + $author = $book->author; + $this->assertEquals('George R. R. Martin', $author->name); + + $user = User::create(array('name' => 'John Doe')); + $item = Item::create(array('type' => 'sword', 'user_id' => $user->_id)); + + $owner = $item->user; + $this->assertEquals('John Doe', $owner->name); + } + + public function testHasOne() + { + $user = User::create(array('name' => 'John Doe')); + Role::create(array('type' => 'admin', 'user_id' => $user->_id)); + + $role = $user->role; + $this->assertEquals('admin', $role->type); + } + + public function testWithBelongsTo() + { + $user = User::create(array('name' => 'John Doe')); + Item::create(array('type' => 'knife', 'user_id' => $user->_id)); + Item::create(array('type' => 'shield', 'user_id' => $user->_id)); + Item::create(array('type' => 'sword', 'user_id' => $user->_id)); + Item::create(array('type' => 'bag', 'user_id' => null)); + + $items = Item::with('user')->get(); + + $user = $items[0]->getRelation('user'); + $this->assertInstanceOf('User', $user); + $this->assertEquals('John Doe', $user->name); + $this->assertEquals(1, count($items[0]->getRelations())); + $this->assertEquals(null, $items[3]->getRelation('user')); + } + + public function testWithHashMany() + { + $user = User::create(array('name' => 'John Doe')); + Item::create(array('type' => 'knife', 'user_id' => $user->_id)); + Item::create(array('type' => 'shield', 'user_id' => $user->_id)); + Item::create(array('type' => 'sword', 'user_id' => $user->_id)); + Item::create(array('type' => 'bag', 'user_id' => null)); + + $user = User::with('items')->find($user->_id); + + $items = $user->getRelation('items'); + $this->assertEquals(3, count($items)); + $this->assertInstanceOf('Item', $items[0]); + } + + public function testWithHasOne() + { + $user = User::create(array('name' => 'John Doe')); + Role::create(array('type' => 'admin', 'user_id' => $user->_id)); + Role::create(array('type' => 'guest', 'user_id' => $user->_id)); + + $user = User::with('role')->find($user->_id); + + $role = $user->getRelation('role'); + $this->assertInstanceOf('Role', $role); + $this->assertEquals('admin', $role->type); + } + + public function testEasyRelation() + { + // Has Many + $user = User::create(array('name' => 'John Doe')); + $item = Item::create(array('type' => 'knife')); + $user->items()->save($item); + + $user = User::find($user->_id); + $items = $user->items; + $this->assertEquals(1, count($items)); + $this->assertInstanceOf('Item', $items[0]); + + // Has one + $user = User::create(array('name' => 'John Doe')); + $role = Role::create(array('type' => 'admin')); + $user->role()->save($role); + + $user = User::find($user->_id); + $role = $user->role; + $this->assertInstanceOf('Role', $role); + $this->assertEquals('admin', $role->type); + } public function testHasManyAndBelongsTo() { @@ -175,7 +193,8 @@ public function testHasManyAndBelongsToAttachesExistingModels() Client::create(array('name' => 'Boloni Ltd.'))->_id, Client::create(array('name' => 'Meatballs Inc.'))->_id ); - + + // Sync multiple records $user->clients()->sync($clients); $user = User::with('clients')->find($user->_id); diff --git a/tests/models/User.php b/tests/models/User.php index 2e6862760..652d48f51 100644 --- a/tests/models/User.php +++ b/tests/models/User.php @@ -8,7 +8,9 @@ class User extends Eloquent implements UserInterface, RemindableInterface { protected $collection = 'users'; - + + protected $dates = array('birthday'); + protected static $unguarded = true; public function books() @@ -60,5 +62,4 @@ public function getReminderEmail() { return $this->email; } - } \ No newline at end of file