Commit 0a256970 authored by Jens Segers's avatar Jens Segers

Merge pull request #279 from jenssegers/embedded-models

New v2 release
parents 7534e6f9 94314570
This diff is collapsed.
<?php namespace Jenssegers\Mongodb\Auth; <?php namespace Jenssegers\Mongodb\Auth;
use DateTime;
use MongoDate;
class DatabaseReminderRepository extends \Illuminate\Auth\Reminders\DatabaseReminderRepository { class DatabaseReminderRepository extends \Illuminate\Auth\Reminders\DatabaseReminderRepository {
/**
* Build the record payload for the table.
*
* @param string $email
* @param string $token
* @return array
*/
protected function getPayload($email, $token)
{
return array('email' => $email, 'token' => $token, 'created_at' => new MongoDate);
}
/** /**
* Determine if the reminder has expired. * Determine if the reminder has expired.
* *
...@@ -10,16 +25,22 @@ class DatabaseReminderRepository extends \Illuminate\Auth\Reminders\DatabaseRemi ...@@ -10,16 +25,22 @@ class DatabaseReminderRepository extends \Illuminate\Auth\Reminders\DatabaseRemi
*/ */
protected function reminderExpired($reminder) protected function reminderExpired($reminder)
{ {
// Convert to array so that we can pass it to the parent method // Convert MongoDate to a date string.
if (is_object($reminder)) if ($reminder['created_at'] instanceof MongoDate)
{ {
$reminder = (array) $reminder; $date = new DateTime;
$date->setTimestamp($reminder['created_at']->sec);
$reminder['created_at'] = $date->format('Y-m-d H:i:s');
} }
// Convert the DateTime object that got saved to MongoDB // Convert DateTime to a date string (backwards compatibility).
if (is_array($reminder['created_at'])) elseif (is_array($reminder['created_at']))
{ {
$reminder['created_at'] = $reminder['created_at']['date'] + $reminder['created_at']['timezone']; $date = DateTime::__set_state($reminder['created_at']);
$reminder['created_at'] = $date->format('Y-m-d H:i:s');
} }
return parent::reminderExpired($reminder); return parent::reminderExpired($reminder);
......
...@@ -26,6 +26,7 @@ class Collection { ...@@ -26,6 +26,7 @@ class Collection {
public function __construct(Connection $connection, MongoCollection $collection) public function __construct(Connection $connection, MongoCollection $collection)
{ {
$this->connection = $connection; $this->connection = $connection;
$this->collection = $collection; $this->collection = $collection;
} }
...@@ -38,29 +39,34 @@ class Collection { ...@@ -38,29 +39,34 @@ class Collection {
*/ */
public function __call($method, $parameters) public function __call($method, $parameters)
{ {
$query = array();
// Build the query string. // Build the query string.
$query = $parameters; foreach ($parameters as $parameter)
foreach ($query as &$param)
{ {
try try
{ {
$param = json_encode($param); $query[] = json_encode($parameter);
} }
catch (Exception $e) catch (Exception $e)
{ {
$param = '{...}'; $query[] = '{...}';
} }
} }
$start = microtime(true); $start = microtime(true);
// Execute the query.
$result = call_user_func_array(array($this->collection, $method), $parameters); $result = call_user_func_array(array($this->collection, $method), $parameters);
// Log the query. // Once we have run the query we will calculate the time that it took to run and
$this->connection->logQuery( // then log the query, bindings, and execution time so we will report them on
$this->collection->getName() . '.' . $method . '(' . join(',', $query) . ')', // the event that the developer needs them. We'll log time in milliseconds.
array(), $this->connection->getElapsedTime($start)); $time = $this->connection->getElapsedTime($start);
// Convert the query to a readable string.
$queryString = $this->collection->getName() . '.' . $method . '(' . join(',', $query) . ')';
$this->connection->logQuery($queryString, array(), $time);
return $result; return $result;
} }
......
...@@ -16,6 +16,146 @@ class Builder extends EloquentBuilder { ...@@ -16,6 +16,146 @@ class Builder extends EloquentBuilder {
'count', 'min', 'max', 'avg', 'sum', 'exists', 'push', 'pull' 'count', 'min', 'max', 'avg', 'sum', 'exists', 'push', 'pull'
); );
/**
* Update a record in the database.
*
* @param array $values
* @return int
*/
public function update(array $values)
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation())
{
$relation->performUpdate($this->model, $values);
return 1;
}
return parent::update($values);
}
/**
* Insert a new record into the database.
*
* @param array $values
* @return bool
*/
public function insert(array $values)
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation())
{
$relation->performInsert($this->model, $values);
return true;
}
return parent::insert($values);
}
/**
* Insert a new record and get the value of the primary key.
*
* @param array $values
* @param string $sequence
* @return int
*/
public function insertGetId(array $values, $sequence = null)
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation())
{
$relation->performInsert($this->model, $values);
return $this->model->getKey();
}
return parent::insertGetId($values, $sequence);
}
/**
* Delete a record from the database.
*
* @return mixed
*/
public function delete()
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation())
{
$relation->performDelete($this->model);
return $this->model->getKey();
}
return parent::delete();
}
/**
* Increment a column's value by a given amount.
*
* @param string $column
* @param int $amount
* @param array $extra
* @return int
*/
public function increment($column, $amount = 1, array $extra = array())
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation())
{
$value = $this->model->{$column};
// When doing increment and decrements, Eloquent will automatically
// sync the original attributes. We need to change the attribute
// temporary in order to trigger an update query.
$this->model->{$column} = null;
$this->model->syncOriginalAttribute($column);
$result = $this->model->update(array($column => $value));
return $result;
}
return parent::increment($column, $amount, $extra);
}
/**
* Decrement a column's value by a given amount.
*
* @param string $column
* @param int $amount
* @param array $extra
* @return int
*/
public function decrement($column, $amount = 1, array $extra = array())
{
// Intercept operations on embedded models and delegate logic
// to the parent relation instance.
if ($relation = $this->model->getParentRelation())
{
$value = $this->model->{$column};
// When doing increment and decrements, Eloquent will automatically
// sync the original attributes. We need to change the attribute
// temporary in order to trigger an update query.
$this->model->{$column} = null;
$this->model->syncOriginalAttribute($column);
return $this->model->update(array($column => $value));
}
return parent::decrement($column, $amount, $extra);
}
/** /**
* Add the "has" condition where clause to the query. * Add the "has" condition where clause to the query.
* *
...@@ -77,26 +217,12 @@ class Builder extends EloquentBuilder { ...@@ -77,26 +217,12 @@ class Builder extends EloquentBuilder {
// Get raw results from the query builder. // Get raw results from the query builder.
$results = $this->query->raw($expression); $results = $this->query->raw($expression);
$connection = $this->model->getConnectionName();
// Convert MongoCursor results to a collection of models. // Convert MongoCursor results to a collection of models.
if ($results instanceof MongoCursor) if ($results instanceof MongoCursor)
{ {
$results = iterator_to_array($results, false); $results = iterator_to_array($results, false);
$models = array(); return $this->model->hydrate($results);
// Once we have the results, we can spin through them and instantiate a fresh
// model instance for each records we retrieved from the database. We will
// also set the proper connection name for the model after we create it.
foreach ($results as $result)
{
$models[] = $model = $this->model->newFromBuilder($result);
$model->setConnection($connection);
}
return $this->model->newCollection($models);
} }
// The result is a single object. // The result is a single object.
...@@ -104,7 +230,7 @@ class Builder extends EloquentBuilder { ...@@ -104,7 +230,7 @@ class Builder extends EloquentBuilder {
{ {
$model = $this->model->newFromBuilder($results); $model = $this->model->newFromBuilder($results);
$model->setConnection($connection); $model->setConnection($this->model->getConnection());
return $model; return $model;
} }
......
<?php namespace Jenssegers\Mongodb\Eloquent;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
class Collection extends EloquentCollection {
/**
* Simulate a get clause on the collection.
*
* @param mixed $key
* @param mixed $default
* @return mixed
*/
public function get($key = null, $default = null)
{
if (is_null($key) and is_null($default))
{
return $this;
}
return parent::get($key, $default);
}
/**
* Simulate a basic where clause on the collection.
*
* @param string $key
* @param string $operator
* @param mixed $value
* @param string $boolean
* @return $this
*/
public function where($key, $operator = null, $value = null)
{
// Here we will make some assumptions about the operator. If only 2 values are
// passed to the method, we will assume that the operator is an equals sign
// and keep going.
if (func_num_args() == 2)
{
list($value, $operator) = array($operator, '=');
}
return $this->filter(function($item) use ($key, $operator, $value)
{
$actual = $item->{$key};
switch ($operator)
{
case '<>':
case '!=':
return $actual != $value;
break;
case '>':
return $actual > $value;
break;
case '<':
return $actual < $value;
break;
case '>=':
return $actual >= $value;
break;
case '<=':
return $actual <= $value;
break;
case 'between':
return $actual >= $value[0] and $actual <= $value[1];
break;
case 'not between':
return $actual < $value[0] or $actual > $value[1];
break;
case 'in':
return in_array($actual, $value);
break;
case 'not in':
return ! in_array($actual, $value);
break;
case '=':
default:
return $actual == $value;
break;
}
});
}
/**
* Add a where between statement to the query.
*
* @param string $column
* @param array $values
* @param string $boolean
* @param bool $not
* @return $this
*/
public function whereBetween($column, array $values, $boolean = 'and', $not = false)
{
$type = $not ? 'not between' : 'between';
return $this->where($column, $type, $values);
}
/**
* Add a where not between statement to the query.
*
* @param string $column
* @param array $values
* @param string $boolean
* @return $this
*/
public function whereNotBetween($column, array $values, $boolean = 'and')
{
return $this->whereBetween($column, $values, $boolean, true);
}
/**
* Add a "where in" clause to the query.
*
* @param string $column
* @param mixed $values
* @param string $boolean
* @param bool $not
* @return $this
*/
public function whereIn($column, $values, $boolean = 'and', $not = false)
{
$type = $not ? 'not in' : 'in';
return $this->where($column, $type, $values);
}
/**
* Add a "where not in" clause to the query.
*
* @param string $column
* @param mixed $values
* @param string $boolean
* @return $this
*/
public function whereNotIn($column, $values, $boolean = 'and')
{
return $this->whereIn($column, $values, $boolean, true);
}
/**
* Add a "where null" clause to the query.
*
* @param string $column
* @param string $boolean
* @param bool $not
* @return $this
*/
public function whereNull($column, $boolean = 'and', $not = false)
{
return $this->where($column, '=', null);
}
/**
* Add a "where not null" clause to the query.
*
* @param string $column
* @param string $boolean
* @return $this
*/
public function whereNotNull($column, $boolean = 'and')
{
return $this->where($column, '!=', null);
}
/**
* Simulate order by clause on the collection.
*
* @param string $key
* @param string $direction
* @return $this
*/
public function orderBy($key, $direction = 'asc')
{
$descending = strtolower($direction) == 'desc';
return $this->sortBy($key, SORT_REGULAR, $descending);
}
/**
* Add an "order by" clause for a timestamp to the query.
*
* @param string $column
* @return $this
*/
public function latest($column = 'created_at')
{
return $this->orderBy($column, 'desc');
}
/**
* Add an "order by" clause for a timestamp to the query.
*
* @param string $column
* @return $this
*/
public function oldest($column = 'created_at')
{
return $this->orderBy($column, 'asc');
}
/**
* Set the "offset" value of the query.
*
* @param int $value
* @return $this
*/
public function offset($value)
{
$offset = max(0, $value);
return $this->slice($offset);
}
/**
* Alias to set the "offset" value of the query.
*
* @param int $value
* @return $this
*/
public function skip($value)
{
return $this->offset($value);
}
/**
* Set the "limit" value of the query.
*
* @param int $value
* @return $this
*/
public function limit($value)
{
return $this->take($value);
}
}
...@@ -4,6 +4,8 @@ use Illuminate\Database\Eloquent\Collection; ...@@ -4,6 +4,8 @@ use Illuminate\Database\Eloquent\Collection;
use Jenssegers\Mongodb\DatabaseManager as Resolver; use Jenssegers\Mongodb\DatabaseManager as Resolver;
use Jenssegers\Mongodb\Eloquent\Builder; use Jenssegers\Mongodb\Eloquent\Builder;
use Jenssegers\Mongodb\Query\Builder as QueryBuilder; use Jenssegers\Mongodb\Query\Builder as QueryBuilder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Jenssegers\Mongodb\Relations\EmbedsOneOrMany;
use Jenssegers\Mongodb\Relations\EmbedsMany; use Jenssegers\Mongodb\Relations\EmbedsMany;
use Jenssegers\Mongodb\Relations\EmbedsOne; use Jenssegers\Mongodb\Relations\EmbedsOne;
...@@ -29,11 +31,11 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -29,11 +31,11 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
protected $primaryKey = '_id'; protected $primaryKey = '_id';
/** /**
* The attributes that should be exposed for toArray and toJson. * The parent relation instance.
* *
* @var array * @var Relation
*/ */
protected $exposed = array(); protected $parentRelation;
/** /**
* The connection resolver instance. * The connection resolver instance.
...@@ -88,7 +90,7 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -88,7 +90,7 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
if (is_null($localKey)) if (is_null($localKey))
{ {
$localKey = '_' . $relation; $localKey = $relation;
} }
if (is_null($foreignKey)) if (is_null($foreignKey))
...@@ -124,7 +126,7 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -124,7 +126,7 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
if (is_null($localKey)) if (is_null($localKey))
{ {
$localKey = '_' . $relation; $localKey = $relation;
} }
if (is_null($foreignKey)) if (is_null($foreignKey))
...@@ -220,7 +222,7 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -220,7 +222,7 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
public function getAttribute($key) public function getAttribute($key)
{ {
// Check if the key is an array dot notation. // Check if the key is an array dot notation.
if (strpos($key, '.') !== false) if (str_contains($key, '.'))
{ {
$attributes = array_dot($this->attributes); $attributes = array_dot($this->attributes);
...@@ -230,6 +232,32 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -230,6 +232,32 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
} }
} }
$camelKey = camel_case($key);
// If the "attribute" exists as a method on the model, it may be an
// embedded model. If so, we need to return the result before it
// is handled by the parent method.
if (method_exists($this, $camelKey))
{
$relations = $this->$camelKey();
// This attribute matches an embedsOne or embedsMany relation so we need
// to return the relation results instead of the interal attributes.
if ($relations instanceof EmbedsOneOrMany)
{
// If the key already exists in the relationships array, it just means the
// relationship has already been loaded, so we'll just return it out of
// here because there is no need to query within the relations twice.
if (array_key_exists($key, $this->relations))
{
return $this->relations[$key];
}
// Get the relation results.
return $this->getRelationshipFromMethod($key, $camelKey);
}
}
return parent::getAttribute($key); return parent::getAttribute($key);
} }
...@@ -241,12 +269,8 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -241,12 +269,8 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
*/ */
protected function getAttributeFromArray($key) protected function getAttributeFromArray($key)
{ {
if (array_key_exists($key, $this->attributes)) // Support keys in dot notation.
{ if (str_contains($key, '.'))
return $this->attributes[$key];
}
else if (strpos($key, '.') !== false)
{ {
$attributes = array_dot($this->attributes); $attributes = array_dot($this->attributes);
...@@ -255,6 +279,8 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -255,6 +279,8 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
return $attributes[$key]; return $attributes[$key];
} }
} }
return parent::getAttributeFromArray($key);
} }
/** /**
...@@ -270,9 +296,21 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -270,9 +296,21 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
if ($key == '_id' and is_string($value)) if ($key == '_id' and is_string($value))
{ {
$builder = $this->newBaseQueryBuilder(); $builder = $this->newBaseQueryBuilder();
$value = $builder->convertKey($value); $value = $builder->convertKey($value);
} }
// Support keys in dot notation.
elseif (str_contains($key, '.'))
{
if (in_array($key, $this->getDates()) && $value)
{
$value = $this->fromDateTime($value);
}
array_set($this->attributes, $key, $value); return;
}
parent::setAttribute($key, $value); parent::setAttribute($key, $value);
} }
...@@ -295,22 +333,6 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -295,22 +333,6 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
{ {
$value = (string) $value; $value = (string) $value;
} }
// If the attribute starts with an underscore, it might be the
// internal array of embedded documents. In that case, we need
// to hide these from the output so that the relation-based
// attribute can take over.
else if (starts_with($key, '_') and ! in_array($key, $this->exposed))
{
$camelKey = camel_case($key);
// If we can find a method that responds to this relation we
// will remove it from the output.
if (method_exists($this, $camelKey))
{
unset($attributes[$key]);
}
}
} }
return $attributes; return $attributes;
...@@ -345,9 +367,25 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -345,9 +367,25 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
{ {
if ($parameters = func_get_args()) if ($parameters = func_get_args())
{ {
$unique = false;
if (count($parameters) == 3)
{
list($column, $values, $unique) = $parameters;
}
else
{
list($column, $values) = $parameters;
}
// Do batch push by default.
if ( ! is_array($values)) $values = array($values);
$query = $this->setKeysForSaveQuery($this->newQuery()); $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(); return parent::push();
...@@ -358,22 +396,87 @@ abstract class Model extends \Jenssegers\Eloquent\Model { ...@@ -358,22 +396,87 @@ abstract class Model extends \Jenssegers\Eloquent\Model {
* *
* @return mixed * @return mixed
*/ */
public function pull() public function pull($column, $values)
{ {
// Do batch pull by default.
if ( ! is_array($values)) $values = array($values);
$query = $this->setKeysForSaveQuery($this->newQuery()); $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);
} }
/** /**
* Set the exposed attributes for the model. * Rempove one or more values to the underlying attribute value and sync with original.
* *
* @param array $exposed * @param string $column
* @param array $values
* @return void * @return void
*/ */
public function setExposed(array $exposed) 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] = array_values($current);
$this->syncOriginalAttribute($column);
}
/**
* Set the parent relation.
*
* @param Relation $relation
*/
public function setParentRelation(Relation $relation)
{
$this->parentRelation = $relation;
}
/**
* Get the parent relation.
*
* @return Relation
*/
public function getParentRelation()
{ {
$this->exposed = $exposed; return $this->parentRelation;
} }
/** /**
......
...@@ -6,10 +6,11 @@ use MongoDate; ...@@ -6,10 +6,11 @@ use MongoDate;
use DateTime; use DateTime;
use Closure; use Closure;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\Expression;
use Jenssegers\Mongodb\Connection; use Jenssegers\Mongodb\Connection;
class Builder extends \Illuminate\Database\Query\Builder { class Builder extends QueryBuilder {
/** /**
* The database collection * The database collection
...@@ -18,6 +19,20 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -18,6 +19,20 @@ class Builder extends \Illuminate\Database\Query\Builder {
*/ */
protected $collection; protected $collection;
/**
* The column projections.
*
* @var array
*/
public $projections;
/**
* The cursor timeout value.
*
* @var int
*/
public $timeout;
/** /**
* All of the available clause operators. * All of the available clause operators.
* *
...@@ -59,6 +74,32 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -59,6 +74,32 @@ class Builder extends \Illuminate\Database\Query\Builder {
$this->connection = $connection; $this->connection = $connection;
} }
/**
* Set the projections.
*
* @param array $columns
* @return $this
*/
public function project($columns)
{
$this->projections = is_array($columns) ? $columns : func_get_args();
return $this;
}
/**
* Set the cursor timeout in seconds.
*
* @param int $seconds
* @return $this
*/
public function timeout($seconds)
{
$this->timeout = $seconds;
return $this;
}
/** /**
* Execute a query for a single record by ID. * Execute a query for a single record by ID.
* *
...@@ -71,6 +112,17 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -71,6 +112,17 @@ class Builder extends \Illuminate\Database\Query\Builder {
return $this->where('_id', '=', $this->convertKey($id))->first($columns); return $this->where('_id', '=', $this->convertKey($id))->first($columns);
} }
/**
* Execute the query as a "select" statement.
*
* @param array $columns
* @return array|static[]
*/
public function get($columns = array())
{
return parent::get($columns);
}
/** /**
* Execute the query as a fresh "select" statement. * Execute the query as a fresh "select" statement.
* *
...@@ -142,6 +194,7 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -142,6 +194,7 @@ class Builder extends \Illuminate\Database\Query\Builder {
foreach ($this->columns as $column) foreach ($this->columns as $column)
{ {
$key = str_replace('.', '_', $column); $key = str_replace('.', '_', $column);
$group[$key] = array('$last' => '$' . $column); $group[$key] = array('$last' => '$' . $column);
} }
} }
...@@ -152,9 +205,10 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -152,9 +205,10 @@ class Builder extends \Illuminate\Database\Query\Builder {
$pipeline[] = array('$group' => $group); $pipeline[] = array('$group' => $group);
// Apply order and limit // Apply order and limit
if ($this->orders) $pipeline[] = array('$sort' => $this->orders); if ($this->orders) $pipeline[] = array('$sort' => $this->orders);
if ($this->offset) $pipeline[] = array('$skip' => $this->offset); if ($this->offset) $pipeline[] = array('$skip' => $this->offset);
if ($this->limit) $pipeline[] = array('$limit' => $this->limit); if ($this->limit) $pipeline[] = array('$limit' => $this->limit);
if ($this->projections) $pipeline[] = array('$project' => $this->projections);
// Execute aggregation // Execute aggregation
$results = $this->collection->aggregate($pipeline); $results = $this->collection->aggregate($pipeline);
...@@ -179,18 +233,27 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -179,18 +233,27 @@ class Builder extends \Illuminate\Database\Query\Builder {
else else
{ {
$columns = array(); $columns = array();
// Convert select columns to simple projections.
foreach ($this->columns as $column) foreach ($this->columns as $column)
{ {
$columns[$column] = true; $columns[$column] = true;
} }
// Add custom projections.
if ($this->projections)
{
$columns = array_merge($columns, $this->projections);
}
// Execute query and get MongoCursor // Execute query and get MongoCursor
$cursor = $this->collection->find($wheres, $columns); $cursor = $this->collection->find($wheres, $columns);
// Apply order, offset and limit // Apply order, offset and limit
if ($this->orders) $cursor->sort($this->orders); if ($this->timeout) $cursor->timeout($this->timeout);
if ($this->offset) $cursor->skip($this->offset); if ($this->orders) $cursor->sort($this->orders);
if ($this->limit) $cursor->limit($this->limit); if ($this->offset) $cursor->skip($this->offset);
if ($this->limit) $cursor->limit($this->limit);
// Return results as an array with numeric keys // Return results as an array with numeric keys
return iterator_to_array($cursor, false); return iterator_to_array($cursor, false);
...@@ -313,17 +376,18 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -313,17 +376,18 @@ class Builder extends \Illuminate\Database\Query\Builder {
// Since every insert gets treated like a batch insert, we will have to detect // Since every insert gets treated like a batch insert, we will have to detect
// if the user is inserting a single document or an array of documents. // if the user is inserting a single document or an array of documents.
$batch = true; $batch = true;
foreach ($values as $value) foreach ($values as $value)
{ {
// As soon as we find a value that is not an array we assume the user is // As soon as we find a value that is not an array we assume the user is
// inserting a single document. // inserting a single document.
if (!is_array($value)) if ( ! is_array($value))
{ {
$batch = false; break; $batch = false; break;
} }
} }
if (!$batch) $values = array($values); if ( ! $batch) $values = array($values);
// Batch insert // Batch insert
$result = $this->collection->batchInsert($values); $result = $this->collection->batchInsert($values);
...@@ -344,7 +408,7 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -344,7 +408,7 @@ class Builder extends \Illuminate\Database\Query\Builder {
if (1 == (int) $result['ok']) if (1 == (int) $result['ok'])
{ {
if (!$sequence) if (is_null($sequence))
{ {
$sequence = '_id'; $sequence = '_id';
} }
...@@ -363,7 +427,13 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -363,7 +427,13 @@ class Builder extends \Illuminate\Database\Query\Builder {
*/ */
public function update(array $values, array $options = array()) public function update(array $values, array $options = array())
{ {
return $this->performUpdate(array('$set' => $values), $options); // Use $set as default operator.
if ( ! starts_with(key($values), '$'))
{
$values = array('$set' => $values);
}
return $this->performUpdate($values, $options);
} }
/** /**
...@@ -376,11 +446,9 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -376,11 +446,9 @@ class Builder extends \Illuminate\Database\Query\Builder {
*/ */
public function increment($column, $amount = 1, array $extra = array(), array $options = array()) public function increment($column, $amount = 1, array $extra = array(), array $options = array())
{ {
$query = array( $query = array('$inc' => array($column => $amount));
'$inc' => array($column => $amount)
);
if (!empty($extra)) if ( ! empty($extra))
{ {
$query['$set'] = $extra; $query['$set'] = $extra;
} }
...@@ -389,6 +457,7 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -389,6 +457,7 @@ class Builder extends \Illuminate\Database\Query\Builder {
$this->where(function($query) use ($column) $this->where(function($query) use ($column)
{ {
$query->where($column, 'exists', false); $query->where($column, 'exists', false);
$query->orWhereNotNull($column); $query->orWhereNotNull($column);
}); });
...@@ -437,6 +506,7 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -437,6 +506,7 @@ class Builder extends \Illuminate\Database\Query\Builder {
public function delete($id = null) public function delete($id = null)
{ {
$wheres = $this->compileWheres(); $wheres = $this->compileWheres();
$result = $this->collection->remove($wheres); $result = $this->collection->remove($wheres);
if (1 == (int) $result['ok']) if (1 == (int) $result['ok'])
...@@ -490,7 +560,7 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -490,7 +560,7 @@ class Builder extends \Illuminate\Database\Query\Builder {
} }
// Create an expression for the given value // Create an expression for the given value
else if (!is_null($expression)) else if ( ! is_null($expression))
{ {
return new Expression($expression); return new Expression($expression);
} }
...@@ -511,10 +581,17 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -511,10 +581,17 @@ class Builder extends \Illuminate\Database\Query\Builder {
// Use the addToSet operator in case we only want unique items. // Use the addToSet operator in case we only want unique items.
$operator = $unique ? '$addToSet' : '$push'; $operator = $unique ? '$addToSet' : '$push';
// Check if we are pushing multiple values.
$batch = (is_array($value) and array_keys($value) === range(0, count($value) - 1));
if (is_array($column)) if (is_array($column))
{ {
$query = array($operator => $column); $query = array($operator => $column);
} }
else if ($batch)
{
$query = array($operator => array($column => array('$each' => $value)));
}
else else
{ {
$query = array($operator => array($column => $value)); $query = array($operator => array($column => $value));
...@@ -532,13 +609,19 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -532,13 +609,19 @@ class Builder extends \Illuminate\Database\Query\Builder {
*/ */
public function pull($column, $value = null) public function pull($column, $value = null)
{ {
// Check if we passed an associative array.
$batch = (is_array($value) and array_keys($value) === range(0, count($value) - 1));
// If we are pulling multiple values, we need to use $pullAll.
$operator = $batch ? '$pullAll' : '$pull';
if (is_array($column)) if (is_array($column))
{ {
$query = array('$pull' => $column); $query = array($operator => $column);
} }
else else
{ {
$query = array('$pull' => array($column => $value)); $query = array($operator => array($column => $value));
} }
return $this->performUpdate($query); return $this->performUpdate($query);
...@@ -552,7 +635,7 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -552,7 +635,7 @@ class Builder extends \Illuminate\Database\Query\Builder {
*/ */
public function drop($columns) public function drop($columns)
{ {
if (!is_array($columns)) $columns = array($columns); if ( ! is_array($columns)) $columns = array($columns);
$fields = array(); $fields = array();
...@@ -585,13 +668,14 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -585,13 +668,14 @@ class Builder extends \Illuminate\Database\Query\Builder {
*/ */
protected function performUpdate($query, array $options = array()) protected function performUpdate($query, array $options = array())
{ {
// Default options // Update multiple items by default.
$default = array('multiple' => true); if ( ! array_key_exists('multiple', $options))
{
// Merge options and override default options $options['multiple'] = true;
$options = array_merge($default, $options); }
$wheres = $this->compileWheres(); $wheres = $this->compileWheres();
$result = $this->collection->update($wheres, $query, $options); $result = $this->collection->update($wheres, $query, $options);
if (1 == (int) $result['ok']) if (1 == (int) $result['ok'])
...@@ -682,7 +766,7 @@ class Builder extends \Illuminate\Database\Query\Builder { ...@@ -682,7 +766,7 @@ class Builder extends \Illuminate\Database\Query\Builder {
} }
// Convert id's. // Convert id's.
if (isset($where['column']) and $where['column'] == '_id') if (isset($where['column']) and ($where['column'] == '_id' or ends_with($where['column'], '._id')))
{ {
// Multiple values. // Multiple values.
if (isset($where['values'])) if (isset($where['values']))
......
<?php namespace Jenssegers\Mongodb\Relations; <?php namespace Jenssegers\Mongodb\Relations;
use Jenssegers\Mongodb\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentBelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentBelongsToMany;
...@@ -40,15 +40,58 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -40,15 +40,58 @@ class BelongsToMany extends EloquentBelongsToMany {
} }
} }
/**
* 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, $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);
// Once we save the related model, we need to attach it to the base model via
// through intermediate table so we'll use the existing "attach" method to
// accomplish this which will insert the record and any more attributes.
$instance->save(array('touch' => false));
$this->attach($instance, $joining, $touch);
return $instance;
}
/** /**
* Sync the intermediate tables with a list of IDs or collection of models. * Sync the intermediate tables with a list of IDs or collection of models.
* *
* @param array $ids * @param mixed $ids
* @param bool $detaching * @param bool $detaching
* @return void * @return array
*/ */
public function sync($ids, $detaching = true) public function sync($ids, $detaching = true)
{ {
$changes = array(
'attached' => array(), 'detached' => array(), 'updated' => array()
);
if ($ids instanceof Collection) $ids = $ids->modelKeys(); if ($ids instanceof Collection) $ids = $ids->modelKeys();
// First we need to attach any of the associated models that are not currently // First we need to attach any of the associated models that are not currently
...@@ -56,6 +99,9 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -56,6 +99,9 @@ class BelongsToMany extends EloquentBelongsToMany {
// if they exist in the array of current ones, and if not we will insert. // if they exist in the array of current ones, and if not we will insert.
$current = $this->parent->{$this->otherKey} ?: array(); $current = $this->parent->{$this->otherKey} ?: array();
// See issue #256.
if ($current instanceof Collection) $current = $ids->modelKeys();
$records = $this->formatSyncList($ids); $records = $this->formatSyncList($ids);
$detach = array_diff($current, array_keys($records)); $detach = array_diff($current, array_keys($records));
...@@ -66,36 +112,36 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -66,36 +112,36 @@ class BelongsToMany extends EloquentBelongsToMany {
if ($detaching and count($detach) > 0) if ($detaching and count($detach) > 0)
{ {
$this->detach($detach); $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 // 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 // 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. // 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 mixed $id
* @param array $current * @param array $attributes
* @param bool $touch * @param bool $touch
* @return void * @return void
*/ */
protected function attachNew(array $records, array $current, $touch = true) public function updateExistingPivot($id, array $attributes, $touch = true)
{ {
foreach ($records as $id => $attributes) // Do nothing, we have no pivot table.
{
// 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);
}
}
} }
/** /**
...@@ -108,25 +154,37 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -108,25 +154,37 @@ class BelongsToMany extends EloquentBelongsToMany {
*/ */
public function attach($id, array $attributes = array(), $touch = true) public function attach($id, array $attributes = array(), $touch = true)
{ {
if ($id instanceof Model) $id = $id->getKey(); if ($id instanceof Model)
{
$model = $id; $id = $model->getKey();
}
$records = $this->createAttachRecords((array) $id, $attributes); $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); $otherIds = array_pluck($records, $this->otherKey);
$foreignIds = array_pluck($records, $this->foreignKey); $foreignIds = array_pluck($records, $this->foreignKey);
// Attach to the parent model // Attach the new ids to the parent model.
$this->parent->push($this->otherKey, $otherIds[0]); $this->parent->push($this->otherKey, $otherIds, true);
// Generate a new related query instance // If we have a model instance, we can psuh the ids to that model,
$query = $this->getNewRelatedQuery(); // so that the internal attributes are updated as well. Otherwise,
// we will just perform a regular database query.
if (isset($model))
{
// Attach the new ids to the related model.
$model->push($this->foreignKey, $foreignIds, true);
}
else
{
$query = $this->newRelatedQuery();
// Set contraints on the related query $query->where($this->related->getKeyName(), $id);
$query->where($this->related->getKeyName(), $id);
// Attach to the related model // Attach the new ids to the related model.
$query->push($this->foreignKey, $foreignIds[0]); $query->push($this->foreignKey, $foreignIds, true);
}
if ($touch) $this->touchIfTouching(); if ($touch) $this->touchIfTouching();
} }
...@@ -142,18 +200,15 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -142,18 +200,15 @@ class BelongsToMany extends EloquentBelongsToMany {
{ {
if ($ids instanceof Model) $ids = (array) $ids->getKey(); 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 // If associated IDs were passed to the method we will only delete those
// associations, otherwise all of the association ties will be broken. // associations, otherwise all of the association ties will be broken.
// We'll return the numbers of affected rows when we do the deletes. // We'll return the numbers of affected rows when we do the deletes.
$ids = (array) $ids; $ids = (array) $ids;
// Pull each id from the parent. // Detach all ids from the parent model.
foreach ($ids as $id) $this->parent->pull($this->otherKey, $ids);
{
$this->parent->pull($this->otherKey, $id);
}
// Prepare the query to select all related objects. // Prepare the query to select all related objects.
if (count($ids) > 0) if (count($ids) > 0)
...@@ -196,11 +251,21 @@ class BelongsToMany extends EloquentBelongsToMany { ...@@ -196,11 +251,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 * @return \Illuminate\Database\Query\Builder
*/ */
public function getNewRelatedQuery() public function newRelatedQuery()
{ {
return $this->related->newQuery(); return $this->related->newQuery();
} }
......
<?php namespace Jenssegers\Mongodb\Relations; <?php namespace Jenssegers\Mongodb\Relations;
use MongoId;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Eloquent\Collection;
use MongoId;
class EmbedsOne extends EmbedsOneOrMany { class EmbedsOne extends EmbedsOneOrMany {
/** /**
* Get the results of the relationship. * Get the results of the relationship.
* *
* @return Illuminate\Database\Eloquent\Collection * @return \Illuminate\Database\Eloquent\Model
*/ */
public function getResults() public function getResults()
{ {
...@@ -19,75 +18,105 @@ class EmbedsOne extends EmbedsOneOrMany { ...@@ -19,75 +18,105 @@ class EmbedsOne extends EmbedsOneOrMany {
} }
/** /**
* Check if a model is already embedded. * Save a new model and attach it to the parent model.
* *
* @param mixed $key * @param \Illuminate\Database\Eloquent\Model $model
* @return bool * @return \Illuminate\Database\Eloquent\Model
*/ */
public function contains($key) public function performInsert(Model $model, array $values)
{ {
if ($key instanceof Model) $key = $key->getKey(); // Generate a new key if needed.
if ($model->getKeyName() == '_id' and ! $model->getKey())
{
$model->setAttribute('_id', new MongoId);
}
$embedded = $this->getEmbedded(); // For deeply nested documents, let the parent handle the changes.
if ($this->isNested())
{
$this->associate($model);
$primaryKey = $this->related->getKeyName(); return $this->parent->save();
}
$result = $this->getBaseQuery()->update(array($this->localKey => $model->getAttributes()));
// Attach the model to its parent.
if ($result) $this->associate($model);
return ($embedded and $embedded[$primaryKey] == $key); return $result ? $model : false;
} }
/** /**
* Associate the model instance to the given parent, without saving it to the database. * Save an existing model and attach it to the parent model.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param \Illuminate\Database\Eloquent\Model $model
* @return \Illuminate\Database\Eloquent\Model * @return Model|bool
*/ */
public function associate(Model $model) public function performUpdate(Model $model, array $values)
{ {
// Create a new key if needed. if ($this->isNested())
if ( ! $model->getAttribute('_id'))
{ {
$model->setAttribute('_id', new MongoId); $this->associate($model);
return $this->parent->save();
} }
$this->setEmbedded($model->getAttributes()); // Use array dot notation for better update behavior.
$values = array_dot($model->getDirty(), $this->localKey . '.');
return $model; $result = $this->getBaseQuery()->update($values);
// Attach the model to its parent.
if ($result) $this->associate($model);
return $result ? $model : false;
} }
/** /**
* Save a new model and attach it to the parent model. * Delete an existing model and detach it from the parent model.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param Model $model
* @return \Illuminate\Database\Eloquent\Model * @return int
*/ */
protected function performInsert(Model $model) public function performDelete(Model $model)
{ {
// Create a new key if needed. // For deeply nested documents, let the parent handle the changes.
if ( ! $model->getAttribute('_id')) if ($this->isNested())
{ {
$model->setAttribute('_id', new MongoId); $this->dissociate($model);
return $this->parent->save();
} }
$result = $this->query->update(array($this->localKey => $model->getAttributes())); // Overwrite the local key with an empty array.
$result = $this->getBaseQuery()->update(array($this->localKey => null));
if ($result) $this->associate($model); // Detach the model from its parent.
if ($result) $this->dissociate();
return $result ? $model : false; return $result;
} }
/** /**
* Save an existing model and attach it to the parent model. * Attach the model to its parent.
* *
* @param \Illuminate\Database\Eloquent\Model $model * @param \Illuminate\Database\Eloquent\Model $model
* @return Model|bool * @return \Illuminate\Database\Eloquent\Model
*/ */
protected function performUpdate(Model $model) public function associate(Model $model)
{ {
$result = $this->query->update(array($this->localKey => $model->getAttributes())); return $this->setEmbedded($model->getAttributes());
}
if ($result) $this->associate($model);
return $result ? $model : false; /**
* Detach the model from its parent.
*
* @return \Illuminate\Database\Eloquent\Model
*/
public function dissociate()
{
return $this->setEmbedded(null);
} }
/** /**
...@@ -97,22 +126,9 @@ class EmbedsOne extends EmbedsOneOrMany { ...@@ -97,22 +126,9 @@ class EmbedsOne extends EmbedsOneOrMany {
*/ */
public function delete() public function delete()
{ {
// Overwrite the local key with an empty array. $model = $this->getResults();
$result = $this->query->update(array($this->localKey => null));
// If the update query was successful, we will remove the embedded records return $this->performDelete($model);
// of the parent instance.
if ($result)
{
$count = $this->count();
$this->setEmbedded(null);
// Return the number of deleted embedded records.
return $count;
}
return $result;
} }
} }
...@@ -20,4 +20,14 @@ class HasMany extends EloquentHasMany { ...@@ -20,4 +20,14 @@ class HasMany extends EloquentHasMany {
return $query->select($this->getHasCompareKey())->where($this->getHasCompareKey(), 'exists', true); return $query->select($this->getHasCompareKey())->where($this->getHasCompareKey(), 'exists', true);
} }
/**
* Get the plain foreign key.
*
* @return string
*/
public function getPlainForeignKey()
{
return $this->getForeignKey();
}
} }
...@@ -20,4 +20,14 @@ class HasOne extends EloquentHasOne { ...@@ -20,4 +20,14 @@ class HasOne extends EloquentHasOne {
return $query->select($this->getHasCompareKey())->where($this->getHasCompareKey(), 'exists', true); return $query->select($this->getHasCompareKey())->where($this->getHasCompareKey(), 'exists', true);
} }
/**
* Get the plain foreign key.
*
* @return string
*/
public function getPlainForeignKey()
{
return $this->getForeignKey();
}
} }
...@@ -36,6 +36,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint { ...@@ -36,6 +36,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint {
public function __construct(Connection $connection, $collection) public function __construct(Connection $connection, $collection)
{ {
$this->connection = $connection; $this->connection = $connection;
$this->collection = $connection->getCollection($collection); $this->collection = $connection->getCollection($collection);
} }
...@@ -50,11 +51,12 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint { ...@@ -50,11 +51,12 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint {
{ {
$columns = $this->fluent($columns); $columns = $this->fluent($columns);
// Columns are passed as a default array // Columns are passed as a default array.
if (is_array($columns) && is_int(key($columns))) if (is_array($columns) && is_int(key($columns)))
{ {
// Transform the columns to the required array format // Transform the columns to the required array format.
$transform = array(); $transform = array();
foreach ($columns as $column) foreach ($columns as $column)
{ {
$transform[$column] = 1; $transform[$column] = 1;
...@@ -78,11 +80,12 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint { ...@@ -78,11 +80,12 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint {
{ {
$columns = $this->fluent($columns); $columns = $this->fluent($columns);
// Columns are passed as a default array // Columns are passed as a default array.
if (is_array($columns) && is_int(key($columns))) if (is_array($columns) && is_int(key($columns)))
{ {
// Transform the columns to the required array format // Transform the columns to the required array format.
$transform = array(); $transform = array();
foreach ($columns as $column) foreach ($columns as $column)
{ {
$transform[$column] = 1; $transform[$column] = 1;
...@@ -105,6 +108,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint { ...@@ -105,6 +108,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint {
public function unique($columns = null, $name = null) public function unique($columns = null, $name = null)
{ {
$columns = $this->fluent($columns); $columns = $this->fluent($columns);
$this->index($columns, array('unique' => true)); $this->index($columns, array('unique' => true));
return $this; return $this;
...@@ -119,6 +123,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint { ...@@ -119,6 +123,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint {
public function background($columns = null) public function background($columns = null)
{ {
$columns = $this->fluent($columns); $columns = $this->fluent($columns);
$this->index($columns, array('background' => true)); $this->index($columns, array('background' => true));
return $this; return $this;
...@@ -149,6 +154,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint { ...@@ -149,6 +154,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint {
public function expire($columns, $seconds) public function expire($columns, $seconds)
{ {
$columns = $this->fluent($columns); $columns = $this->fluent($columns);
$this->index($columns, array('expireAfterSeconds' => $seconds)); $this->index($columns, array('expireAfterSeconds' => $seconds));
return $this; return $this;
...@@ -163,8 +169,9 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint { ...@@ -163,8 +169,9 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint {
{ {
$collection = $this->collection->getName(); $collection = $this->collection->getName();
// Ensure the collection is created
$db = $this->connection->getMongoDB(); $db = $this->connection->getMongoDB();
// Ensure the collection is created.
$db->createCollection($collection); $db->createCollection($collection);
} }
...@@ -189,6 +196,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint { ...@@ -189,6 +196,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint {
protected function addColumn($type, $name, array $parameters = array()) protected function addColumn($type, $name, array $parameters = array())
{ {
$this->fluent($name); $this->fluent($name);
return $this; return $this;
} }
...@@ -221,6 +229,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint { ...@@ -221,6 +229,7 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint {
*/ */
public function __call($method, $args) public function __call($method, $args)
{ {
// Dummy.
return $this; return $this;
} }
......
<?php
class AuthTest extends TestCase {
public function tearDown()
{
User::truncate();
DB::collection('password_reminders')->truncate();
}
public function testAuthAttempt()
{
$user = User::create(array(
'name' => 'John Doe',
'email' => 'john@doe.com',
'password' => Hash::make('foobar')
));
$this->assertTrue(Auth::attempt(array('email' => 'john@doe.com', 'password' => 'foobar'), true));
$this->assertTrue(Auth::check());
}
public function testRemind()
{
$mailer = Mockery::mock('Illuminate\Mail\Mailer');
$this->app->instance('mailer', $mailer);
$user = User::create(array(
'name' => 'John Doe',
'email' => 'john@doe.com',
'password' => Hash::make('foobar')
));
$mailer->shouldReceive('send')->once();
Password::remind(array('email' => 'john@doe.com'));
$this->assertEquals(1, DB::collection('password_reminders')->count());
$reminder = DB::collection('password_reminders')->first();
$this->assertEquals('john@doe.com', $reminder['email']);
$this->assertNotNull($reminder['token']);
$this->assertInstanceOf('MongoDate', $reminder['created_at']);
$credentials = array(
'email' => 'john@doe.com',
'password' => 'foobar',
'password_confirmation' => 'foobar',
'token' => $reminder['token']
);
$response = Password::reset($credentials, function($user, $password)
{
$user->password = Hash::make($password);
$user->save();
});
$this->assertEquals('reminders.reset', $response);
$this->assertEquals(0, DB::collection('password_reminders')->count());
}
public function testDeprecatedRemind()
{
$mailer = Mockery::mock('Illuminate\Mail\Mailer');
$this->app->instance('mailer', $mailer);
$user = User::create(array(
'name' => 'John Doe',
'email' => 'john@doe.com',
'password' => Hash::make('foobar')
));
$mailer->shouldReceive('send')->once();
Password::remind(array('email' => 'john@doe.com'));
DB::collection('password_reminders')->update(array('created_at' => new DateTime));
$reminder = DB::collection('password_reminders')->first();
$this->assertTrue(is_array($reminder['created_at']));
$credentials = array(
'email' => 'john@doe.com',
'password' => 'foobar',
'password_confirmation' => 'foobar',
'token' => $reminder['token']
);
$response = Password::reset($credentials, function($user, $password)
{
$user->password = Hash::make($password);
$user->save();
});
$this->assertEquals('reminders.reset', $response);
$this->assertEquals(0, DB::collection('password_reminders')->count());
}
}
This diff is collapsed.
...@@ -267,29 +267,33 @@ class ModelTest extends TestCase { ...@@ -267,29 +267,33 @@ class ModelTest extends TestCase {
public function testSoftDelete() public function testSoftDelete()
{ {
$user = new Soft; Soft::create(array('name' => 'John Doe'));
$user->name = 'Softy'; Soft::create(array('name' => 'Jane Doe'));
$user->save();
$this->assertEquals(2, Soft::count());
$user = Soft::where('name', 'John Doe')->first();
$this->assertEquals(true, $user->exists); $this->assertEquals(true, $user->exists);
$this->assertEquals(false, $user->trashed());
$this->assertNull($user->deleted_at);
$user->delete(); $user->delete();
$this->assertEquals(true, $user->trashed());
$this->assertNotNull($user->deleted_at);
$check = Soft::find($user->_id); $user = Soft::where('name', 'John Doe')->first();
$this->assertEquals(null, $check); $this->assertNull($user);
$all = Soft::get();
$this->assertEquals(0, $all->count());
$all = Soft::withTrashed()->get(); $this->assertEquals(1, Soft::count());
$this->assertEquals(1, $all->count()); $this->assertEquals(2, Soft::withTrashed()->count());
$check = $all[0]; $user = Soft::withTrashed()->where('name', 'John Doe')->first();
$this->assertInstanceOf('Carbon\Carbon', $check->deleted_at); $this->assertNotNull($user);
$this->assertEquals(true, $check->trashed()); $this->assertInstanceOf('Carbon\Carbon', $user->deleted_at);
$this->assertEquals(true, $user->trashed());
$check->restore(); $user->restore();
$all = Soft::get(); $this->assertEquals(2, Soft::count());
$this->assertEquals(1, $all->count());
} }
public function testPrimaryKey() public function testPrimaryKey()
...@@ -393,6 +397,12 @@ class ModelTest extends TestCase { ...@@ -393,6 +397,12 @@ class ModelTest extends TestCase {
$user = User::create(array('name' => 'Jane Doe', 'birthday' => '2005-08-08')); $user = User::create(array('name' => 'Jane Doe', 'birthday' => '2005-08-08'));
$this->assertInstanceOf('Carbon\Carbon', $user->birthday); $this->assertInstanceOf('Carbon\Carbon', $user->birthday);
$user = User::create(array('name' => 'Jane Doe', 'entry' => array('date' => '2005-08-08')));
$this->assertInstanceOf('Carbon\Carbon', $user->getAttribute('entry.date'));
$user->setAttribute('entry.date', new DateTime);
$this->assertInstanceOf('Carbon\Carbon', $user->getAttribute('entry.date'));
} }
public function testIdAttribute() public function testIdAttribute()
...@@ -406,14 +416,28 @@ class ModelTest extends TestCase { ...@@ -406,14 +416,28 @@ class ModelTest extends TestCase {
public function testPushPull() public function testPushPull()
{ {
$user = User::create(array('name' => 'John Doe', 'tags' => array())); $user = User::create(array('name' => 'John Doe'));
$result = User::where('_id', $user->_id)->push('tags', 'tag1'); $user->push('tags', 'tag1');
$user->push('tags', array('tag1', 'tag2'));
$user->push('tags', 'tag2', true);
$this->assertEquals(array('tag1', 'tag1', 'tag2'), $user->tags);
$user = User::where('_id', $user->_id)->first();
$this->assertEquals(array('tag1', 'tag1', 'tag2'), $user->tags);
$user->pull('tags', 'tag1');
$this->assertEquals(array('tag2'), $user->tags);
$user = User::where('_id', $user->_id)->first(); $user = User::where('_id', $user->_id)->first();
$this->assertEquals(array('tag2'), $user->tags);
$user->push('tags', 'tag3');
$user->pull('tags', array('tag2', 'tag3'));
$this->assertTrue(is_int($result)); $this->assertEquals(array(), $user->tags);
$this->assertTrue(is_array($user->tags)); $user = User::where('_id', $user->_id)->first();
$this->assertEquals(1, count($user->tags)); $this->assertEquals(array(), $user->tags);
} }
public function testRaw() public function testRaw()
......
...@@ -611,4 +611,20 @@ class QueryBuilderTest extends TestCase { ...@@ -611,4 +611,20 @@ class QueryBuilderTest extends TestCase {
$this->assertEquals(1, $user['age']); $this->assertEquals(1, $user['age']);
} }
public function testProjections()
{
DB::collection('items')->insert(array(
array('name' => 'fork', 'tags' => array('sharp', 'pointy')),
array('name' => 'spork', 'tags' => array('sharp', 'pointy', 'round', 'bowl')),
array('name' => 'spoon', 'tags' => array('round', 'bowl')),
));
$results = DB::collection('items')->project(array('tags' => array('$slice' => 1)))->get();
foreach ($results as $result)
{
$this->assertEquals(1, count($result['tags']));
}
}
} }
...@@ -134,7 +134,7 @@ class QueryTest extends TestCase { ...@@ -134,7 +134,7 @@ class QueryTest extends TestCase {
$this->assertEquals(4, count($users)); $this->assertEquals(4, count($users));
$users = User::whereNotNull('age') $users = User::whereNotNull('age')
->whereNotIn('age', array(33, 35))->get(); ->whereNotIn('age', array(33, 35))->get();
$this->assertEquals(3, count($users)); $this->assertEquals(3, count($users));
} }
...@@ -214,31 +214,56 @@ class QueryTest extends TestCase { ...@@ -214,31 +214,56 @@ class QueryTest extends TestCase {
public function testSubquery() public function testSubquery()
{ {
$users = User::where('title', 'admin')->orWhere(function($query) $users = User::where('title', 'admin')->orWhere(function($query)
{ {
$query->where('name', 'Tommy Toe') $query->where('name', 'Tommy Toe')
->orWhere('name', 'Error'); ->orWhere('name', 'Error');
}) })
->get(); ->get();
$this->assertEquals(5, count($users)); $this->assertEquals(5, count($users));
$users = User::where('title', 'user')->where(function($query) $users = User::where('title', 'user')->where(function($query)
{ {
$query->where('age', 35) $query->where('age', 35)
->orWhere('name', 'like', '%harry%'); ->orWhere('name', 'like', '%harry%');
}) })
->get(); ->get();
$this->assertEquals(2, count($users)); $this->assertEquals(2, count($users));
$users = User::where('age', 35)->orWhere(function($query) $users = User::where('age', 35)->orWhere(function($query)
{ {
$query->where('title', 'admin') $query->where('title', 'admin')
->orWhere('name', 'Error'); ->orWhere('name', 'Error');
}) })
->get(); ->get();
$this->assertEquals(5, count($users)); $this->assertEquals(5, count($users));
$users = User::whereNull('deleted_at')
->where('title', 'admin')
->where(function($query)
{
$query->where('age', '>', 15)
->orWhere('name', 'Harry Hoe');
})
->get();
$this->assertEquals(3, $users->count());
$users = User::whereNull('deleted_at')
->where(function($query)
{
$query->where('name', 'Harry Hoe')
->orWhere(function($query)
{
$query->where('age', '>', 15)
->where('title', '<>', 'admin');
});
})
->get();
$this->assertEquals(5, $users->count());
} }
public function testWhereRaw() public function testWhereRaw()
...@@ -280,4 +305,15 @@ class QueryTest extends TestCase { ...@@ -280,4 +305,15 @@ class QueryTest extends TestCase {
$this->assertEquals(2, count($users)); $this->assertEquals(2, count($users));
} }
public function testPaginate()
{
$results = User::paginate(2);
$this->assertEquals(2, $results->count());
$this->assertNotNull($results->first()->title);
$results = User::paginate(2, array('name', 'age'));
$this->assertEquals(2, $results->count());
$this->assertNull($results->first()->title);
}
} }
...@@ -131,6 +131,7 @@ class RelationsTest extends TestCase { ...@@ -131,6 +131,7 @@ class RelationsTest extends TestCase {
$items = $user->items; $items = $user->items;
$this->assertEquals(1, count($items)); $this->assertEquals(1, count($items));
$this->assertInstanceOf('Item', $items[0]); $this->assertInstanceOf('Item', $items[0]);
$this->assertEquals($user->_id, $items[0]->user_id);
// Has one // Has one
$user = User::create(array('name' => 'John Doe')); $user = User::create(array('name' => 'John Doe'));
...@@ -141,6 +142,7 @@ class RelationsTest extends TestCase { ...@@ -141,6 +142,7 @@ class RelationsTest extends TestCase {
$role = $user->role; $role = $user->role;
$this->assertInstanceOf('Role', $role); $this->assertInstanceOf('Role', $role);
$this->assertEquals('admin', $role->type); $this->assertEquals('admin', $role->type);
$this->assertEquals($user->_id, $role->user_id);
} }
public function testBelongsToMany() public function testBelongsToMany()
...@@ -415,6 +417,59 @@ class RelationsTest extends TestCase { ...@@ -415,6 +417,59 @@ class RelationsTest extends TestCase {
$address = $client->addresses->first(); $address = $client->addresses->first();
$this->assertEquals('Paris', $address->data['city']); $this->assertEquals('Paris', $address->data['city']);
$client = Client::with('addresses')->first();
$this->assertEquals('Paris', $client->addresses->first()->data['city']);
}
public function testDoubleSaveOneToMany()
{
$author = User::create(array('name' => 'George R. R. Martin'));
$book = Book::create(array('title' => 'A Game of Thrones'));
$author->books()->save($book);
$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);
$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);
$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);
$this->assertEquals(array($client->_id), $user->client_ids);
} }
} }
...@@ -9,7 +9,10 @@ class TestCase extends Orchestra\Testbench\TestCase { ...@@ -9,7 +9,10 @@ class TestCase extends Orchestra\Testbench\TestCase {
*/ */
protected function getPackageProviders() protected function getPackageProviders()
{ {
return array('Jenssegers\Mongodb\MongodbServiceProvider'); return array(
'Jenssegers\Mongodb\MongodbServiceProvider',
'Jenssegers\Mongodb\Auth\ReminderServiceProvider',
);
} }
/** /**
......
...@@ -6,4 +6,9 @@ class Address extends Eloquent { ...@@ -6,4 +6,9 @@ class Address extends Eloquent {
protected static $unguarded = true; protected static $unguarded = true;
public function addresses()
{
return $this->embedsMany('Address');
}
} }
...@@ -19,6 +19,6 @@ class Client extends Eloquent { ...@@ -19,6 +19,6 @@ class Client extends Eloquent {
public function addresses() public function addresses()
{ {
return $this->hasMany('Address', 'data.client_id', 'data.address_id'); return $this->hasMany('Address', 'data.address_id', 'data.client_id');
} }
} }
...@@ -5,9 +5,10 @@ use Jenssegers\Mongodb\Eloquent\SoftDeletingTrait; ...@@ -5,9 +5,10 @@ use Jenssegers\Mongodb\Eloquent\SoftDeletingTrait;
class Soft extends Eloquent { class Soft extends Eloquent {
use SoftDeletingTrait; use SoftDeletingTrait;
protected $collection = 'soft'; protected $collection = 'soft';
protected $dates = array('deleted_at'); protected static $unguarded = true;
protected $dates = array('deleted_at');
} }
...@@ -2,12 +2,16 @@ ...@@ -2,12 +2,16 @@
use Jenssegers\Mongodb\Model as Eloquent; use Jenssegers\Mongodb\Model as Eloquent;
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface; use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface; use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface { class User extends Eloquent implements UserInterface, RemindableInterface {
protected $dates = array('birthday'); use UserTrait, RemindableTrait;
protected $dates = array('birthday', 'entry.date');
protected static $unguarded = true; protected static $unguarded = true;
public function books() public function books()
...@@ -60,66 +64,6 @@ class User extends Eloquent implements UserInterface, RemindableInterface { ...@@ -60,66 +64,6 @@ class User extends Eloquent implements UserInterface, RemindableInterface {
return $this->embedsOne('User'); return $this->embedsOne('User');
} }
/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* @return string
*/
public function getReminderEmail()
{
return $this->email;
}
/**
* Get the token value for the "remember me" session.
*
* @return string
*/
public function getRememberToken()
{
return $this->rememberToken;
}
/**
* Set the token value for the "remember me" session.
*
* @param string $value
* @return void
*/
public function setRememberToken($value)
{
$this->rememberToken = $value;
}
/**
* Get the column name for the "remember me" token.
*
* @return string
*/
public function getRememberTokenName() {
return 'remember_token';
}
protected function getDateFormat() protected function getDateFormat()
{ {
return 'l jS \of F Y h:i:s A'; return 'l jS \of F Y h:i:s A';
......
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