Permalink
Browse files

HasOne, HasMany, and morph relationships may now use any key on paren…

…t model, not just primary key.
  • Loading branch information...
taylorotwell committed Oct 31, 2013
1 parent c7d2740 commit a634f457860c8ac7c091da4912351dcdc29562c1
@@ -549,15 +549,18 @@ public static function with($relations)
*
* @param string $related
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function hasOne($related, $foreignKey = null)
public function hasOne($related, $foreignKey = null, $localKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey);
$localKey = $localKey ?: $this->getKeyName();
return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}
/**
@@ -569,15 +572,17 @@ public function hasOne($related, $foreignKey = null)
* @param string $id
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function morphOne($related, $name, $type = null, $id = null)
public function morphOne($related, $name, $type = null, $id = null, $localKey = null)
{
$instance = new $related;
list($type, $id) = $this->getMorphs($name, $type, $id);
$table = $instance->getTable();
return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id);
$localKey = $localKey ?: $this->getKeyName();
return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}
/**
@@ -649,15 +654,18 @@ public function morphTo($name = null, $type = null, $id = null)
*
* @param string $related
* @param string $foreignKey
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function hasMany($related, $foreignKey = null)
public function hasMany($related, $foreignKey = null, $localKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey);
$localKey = $localKey ?: $this->getKeyName();
return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}
/**
@@ -687,9 +695,10 @@ public function hasManyThrough($related, $through, $firstKey = null, $secondKey
* @param string $name
* @param string $type
* @param string $id
* @param string $localKey
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function morphMany($related, $name, $type = null, $id = null)
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
{
$instance = new $related;
@@ -700,7 +709,9 @@ public function morphMany($related, $name, $type = null, $id = null)
$table = $instance->getTable();
return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id);
$localKey = $localKey ?: $this->getKeyName();
return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}
/**
@@ -13,6 +13,13 @@
*/
protected $foreignKey;
/**
* The local key of the parent model.
*
* @var string
*/
protected $localKey;
/**
* Create a new has many relationship instance.
*
@@ -21,8 +28,9 @@
* @param string $foreignKey
* @return void
*/
public function __construct(Builder $query, Model $parent, $foreignKey)
public function __construct(Builder $query, Model $parent, $foreignKey, $localKey)
{
$this->localKey = $localKey;
$this->foreignKey = $foreignKey;
parent::__construct($query, $parent);
@@ -37,9 +45,7 @@ public function addConstraints()
{
if (static::$constraints)
{
$key = $this->parent->getKey();
$this->query->where($this->foreignKey, '=', $key);
$this->query->where($this->foreignKey, '=', $this->getParentKey());
}
}
@@ -98,7 +104,7 @@ protected function matchOneOrMany(array $models, Collection $results, $relation,
// matching very convenient and easy work. Then we'll just return them.
foreach ($models as $model)
{
$key = $model->getKey();
$key = $model->getAttribute($this->localKey);
if (isset($dictionary[$key]))
{
@@ -157,7 +163,7 @@ protected function buildDictionary(Collection $results)
*/
public function save(Model $model)
{
$model->setAttribute($this->getPlainForeignKey(), $this->parent->getKey());
$model->setAttribute($this->getPlainForeignKey(), $this->getParentKey());
return $model->save() ? $model : false;
}
@@ -184,7 +190,7 @@ public function saveMany(array $models)
public function create(array $attributes)
{
$foreign = array(
$this->getPlainForeignKey() => $this->parent->getKey()
$this->getPlainForeignKey() => $this->getParentKey(),
);
// Here we will set the raw attributes to avoid hitting the "fill" method so
@@ -255,4 +261,14 @@ public function getPlainForeignKey()
return $segments[count($segments) - 1];
}
/**
* Get the key value of the paren's local key.
*
* @return mixed
*/
protected function getParentKey()
{
return $this->parent->getAttribute($this->localKey);
}
}
@@ -26,15 +26,16 @@
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $type
* @param string $id
* @param string $localKey
* @return void
*/
public function __construct(Builder $query, Model $parent, $type, $id)
public function __construct(Builder $query, Model $parent, $type, $id, $localKey)
{
$this->morphType = $type;
$this->morphClass = get_class($parent);
parent::__construct($query, $parent, $id);
parent::__construct($query, $parent, $id, $localKey);
}
/**
@@ -76,7 +77,7 @@ public function addEagerConstraints(array $models)
parent::addEagerConstraints($models);
$this->query->where($this->morphType, $this->morphClass);
}
}
/**
* Attach a model instance to the parent model.
@@ -120,7 +121,7 @@ public function create(array $attributes)
*/
protected function getForeignAttributesForCreate()
{
$foreign = array($this->getPlainForeignKey() => $this->parent->getKey());
$foreign = array($this->getPlainForeignKey() => $this->getParentKey());
$foreign[last(explode('.', $this->morphType))] = $this->morphClass;
@@ -37,7 +37,8 @@
{"message": "Allow comma delimited list of queues to be passed to queue:listen / queue:work to implement queue priority.", "backport": null},
{"message": "When new bindings are added to container, old aliases bound to that key will now be dropped.", "backport": null},
{"message": "Added new 'resolvable' and 'isAlias' methods to the container.", "backport": null},
{"message": "BelongsTo relationships may now reference any key on parent model, not just primary key.", "backport": null}
{"message": "BelongsTo relationships may now reference any key on parent model, not just primary key.", "backport": null},
{"message": "HasOne, HasMany, and morph relationships may now use any key on parent model, not just primary key.", "backport": null}
],
"4.0.x": [
{"message": "Added implode method to query builder and Collection class.", "backport": null},
@@ -96,10 +96,10 @@ protected function getRelation()
$related = m::mock('Illuminate\Database\Eloquent\Model');
$builder->shouldReceive('getModel')->andReturn($related);
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$parent->shouldReceive('getCreatedAtColumn')->andReturn('created_at');
$parent->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at');
return new HasMany($builder, $parent, 'table.foreign_key');
return new HasMany($builder, $parent, 'table.foreign_key', 'id');
}
}
@@ -117,10 +117,10 @@ protected function getRelation()
$related = m::mock('Illuminate\Database\Eloquent\Model');
$builder->shouldReceive('getModel')->andReturn($related);
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$parent->shouldReceive('getCreatedAtColumn')->andReturn('created_at');
$parent->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at');
return new HasOne($builder, $parent, 'table.foreign_key');
return new HasOne($builder, $parent, 'table.foreign_key', 'id');
}
}
@@ -75,9 +75,9 @@ protected function getOneRelation()
$related = m::mock('Illuminate\Database\Eloquent\Model');
$builder->shouldReceive('getModel')->andReturn($related);
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$builder->shouldReceive('where')->once()->with('table.morph_type', get_class($parent));
return new MorphOne($builder, $parent, 'table.morph_type', 'table.morph_id');
return new MorphOne($builder, $parent, 'table.morph_type', 'table.morph_id', 'id');
}
@@ -88,9 +88,9 @@ protected function getManyRelation()
$related = m::mock('Illuminate\Database\Eloquent\Model');
$builder->shouldReceive('getModel')->andReturn($related);
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$builder->shouldReceive('where')->once()->with('table.morph_type', get_class($parent));
return new MorphMany($builder, $parent, 'table.morph_type', 'table.morph_id');
return new MorphMany($builder, $parent, 'table.morph_type', 'table.morph_id', 'id');
}
}
@@ -15,10 +15,10 @@ public function testTouchMethodUpdatesRelatedTimestamps()
{
$builder = m::mock('Illuminate\Database\Eloquent\Builder');
$parent = m::mock('Illuminate\Database\Eloquent\Model');
$parent->shouldReceive('getKey')->andReturn(1);
$parent->shouldReceive('getAttribute')->with('id')->andReturn(1);
$builder->shouldReceive('getModel')->andReturn($related = m::mock('StdClass'));
$builder->shouldReceive('where');
$relation = new HasOne($builder, $parent, 'foreign_key');
$relation = new HasOne($builder, $parent, 'foreign_key', 'id');
$related->shouldReceive('getTable')->andReturn('table');
$related->shouldReceive('getUpdatedAtColumn')->andReturn('updated_at');
$related->shouldReceive('freshTimestampString')->andReturn(new DateTime);

0 comments on commit a634f45

Please sign in to comment.