Skip to content

Commit

Permalink
Add is() method to 1-1 relations for model comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
sebdesign committed Oct 5, 2020
1 parent 576dd3d commit 963f698
Show file tree
Hide file tree
Showing 12 changed files with 1,015 additions and 3 deletions.
24 changes: 23 additions & 1 deletion src/Illuminate/Database/Eloquent/Relations/BelongsTo.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\ComparesWithModels;
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;

class BelongsTo extends Relation
{
use SupportsDefaultModels;
use ComparesWithModels, SupportsDefaultModels;

/**
* The child model instance of the relation.
Expand Down Expand Up @@ -340,6 +341,16 @@ public function getQualifiedForeignKeyName()
return $this->child->qualifyColumn($this->foreignKey);
}

/**
* Get the key value of the child's foreign key.
*
* @return mixed
*/
public function getParentKey()
{
return $this->child->{$this->foreignKey};
}

/**
* Get the associated key of the relationship.
*
Expand All @@ -360,6 +371,17 @@ public function getQualifiedOwnerKeyName()
return $this->related->qualifyColumn($this->ownerKey);
}

/**
* Get the value of the model's associated key.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return mixed
*/
protected function getRelatedKeyFrom(Model $model)
{
return $model->{$this->ownerKey};
}

/**
* Get the name of the relationship.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Illuminate\Database\Eloquent\Relations\Concerns;

use Illuminate\Database\Eloquent\Model;

trait ComparesWithModels
{
/**
* Determine if the model is the related instance of the relationship.
*
* @param \Illuminate\Database\Eloquent\Model|null $model
* @return bool
*/
public function is($model)
{
return ! is_null($model) &&
$this->compareKeys($this->getParentKey(), $this->getRelatedKeyFrom($model)) &&
$this->related->getTable() === $model->getTable() &&
$this->related->getConnectionName() === $model->getConnectionName();
}

/**
* Determine if the model is not the related instance of the relationship.
*
* @param \Illuminate\Database\Eloquent\Model|null $model
* @return bool
*/
public function isNot($model)
{
return ! $this->is($model);
}

/**
* Get the value of the parent model's key.
*
* @return mixed
*/
abstract public function getParentKey();

/**
* Get the value of the model's related key.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return mixed
*/
abstract protected function getRelatedKeyFrom(Model $model);

/**
* Compare the parent key with the related key.
*
* @param mixed $parentKey
* @param mixed $relatedKey
* @return bool
*/
protected function compareKeys($parentKey, $relatedKey)
{
if (empty($parentKey) || empty($relatedKey)) {
return false;
}

if (is_int($parentKey) || is_int($relatedKey)) {
return (int) $parentKey === (int) $relatedKey;
}

return $parentKey === $relatedKey;
}
}
14 changes: 13 additions & 1 deletion src/Illuminate/Database/Eloquent/Relations/HasOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\ComparesWithModels;
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;

class HasOne extends HasOneOrMany
{
use SupportsDefaultModels;
use ComparesWithModels, SupportsDefaultModels;

/**
* Get the results of the relationship.
Expand Down Expand Up @@ -65,4 +66,15 @@ public function newRelatedInstanceFor(Model $parent)
$this->getForeignKeyName(), $parent->{$this->localKey}
);
}

/**
* Get the value of the model's foreign key.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return mixed
*/
protected function getRelatedKeyFrom(Model $model)
{
return $model->getAttribute($this->getForeignKeyName());
}
}
14 changes: 13 additions & 1 deletion src/Illuminate/Database/Eloquent/Relations/MorphOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\ComparesWithModels;
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;

class MorphOne extends MorphOneOrMany
{
use SupportsDefaultModels;
use ComparesWithModels, SupportsDefaultModels;

/**
* Get the results of the relationship.
Expand Down Expand Up @@ -65,4 +66,15 @@ public function newRelatedInstanceFor(Model $parent)
->setAttribute($this->getForeignKeyName(), $parent->{$this->localKey})
->setAttribute($this->getMorphType(), $this->morphClass);
}

/**
* Get the value of the model's foreign key.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return mixed
*/
protected function getRelatedKeyFrom(Model $model)
{
return $model->getAttribute($this->getForeignKeyName());
}
}
163 changes: 163 additions & 0 deletions tests/Database/DatabaseEloquentBelongsToTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,169 @@ public function testDefaultEagerConstraintsWhenNotIncrementing()
$relation->addEagerConstraints($models);
}

public function testIsNotNull()
{
$relation = $this->getRelation();

$this->related->shouldReceive('getConnectionName')->never();

$this->assertFalse($relation->is(null));
}

public function testIsModel()
{
$relation = $this->getRelation();

$this->related->shouldReceive('getConnectionName')->once()->andReturn('relation');

$model = m::mock(Model::class);
$model->shouldReceive('getAttribute')->once()->with('id')->andReturn('foreign.value');
$model->shouldReceive('getTable')->once()->andReturn('relation');
$model->shouldReceive('getConnectionName')->once()->andReturn('relation');

$this->assertTrue($relation->is($model));
}

public function testIsModelWithIntegerParentKey()
{
$parent = m::mock(Model::class);

// when addConstraints is called we need to return the foreign value
$parent->shouldReceive('getAttribute')->once()->with('foreign_key')->andReturn('foreign.value');
// when getParentKey is called we want to return an integer
$parent->shouldReceive('getAttribute')->once()->with('foreign_key')->andReturn(1);

$relation = $this->getRelation($parent);

$this->related->shouldReceive('getConnectionName')->once()->andReturn('relation');

$model = m::mock(Model::class);
$model->shouldReceive('getAttribute')->once()->with('id')->andReturn('1');
$model->shouldReceive('getTable')->once()->andReturn('relation');
$model->shouldReceive('getConnectionName')->once()->andReturn('relation');

$this->assertTrue($relation->is($model));
}

public function testIsModelWithIntegerRelatedKey()
{
$parent = m::mock(Model::class);

// when addConstraints is called we need to return the foreign value
$parent->shouldReceive('getAttribute')->once()->with('foreign_key')->andReturn('foreign.value');
// when getParentKey is called we want to return a string
$parent->shouldReceive('getAttribute')->once()->with('foreign_key')->andReturn('1');

$relation = $this->getRelation($parent);

$this->related->shouldReceive('getConnectionName')->once()->andReturn('relation');

$model = m::mock(Model::class);
$model->shouldReceive('getAttribute')->once()->with('id')->andReturn(1);
$model->shouldReceive('getTable')->once()->andReturn('relation');
$model->shouldReceive('getConnectionName')->once()->andReturn('relation');

$this->assertTrue($relation->is($model));
}

public function testIsModelWithIntegerKeys()
{
$parent = m::mock(Model::class);

// when addConstraints is called we need to return the foreign value
$parent->shouldReceive('getAttribute')->once()->with('foreign_key')->andReturn('foreign.value');
// when getParentKey is called we want to return an integer
$parent->shouldReceive('getAttribute')->once()->with('foreign_key')->andReturn(1);

$relation = $this->getRelation($parent);

$this->related->shouldReceive('getConnectionName')->once()->andReturn('relation');

$model = m::mock(Model::class);
$model->shouldReceive('getAttribute')->once()->with('id')->andReturn(1);
$model->shouldReceive('getTable')->once()->andReturn('relation');
$model->shouldReceive('getConnectionName')->once()->andReturn('relation');

$this->assertTrue($relation->is($model));
}

public function testIsNotModelWithNullParentKey()
{
$parent = m::mock(Model::class);

// when addConstraints is called we need to return the foreign value
$parent->shouldReceive('getAttribute')->once()->with('foreign_key')->andReturn('foreign.value');
// when getParentKey is called we want to return null
$parent->shouldReceive('getAttribute')->once()->with('foreign_key')->andReturn(null);

$relation = $this->getRelation($parent);

$this->related->shouldReceive('getConnectionName')->never();

$model = m::mock(Model::class);
$model->shouldReceive('getAttribute')->once()->with('id')->andReturn('foreign.value');
$model->shouldReceive('getTable')->never();
$model->shouldReceive('getConnectionName')->never();

$this->assertFalse($relation->is($model));
}

public function testIsNotModelWithNullRelatedKey()
{
$relation = $this->getRelation();

$this->related->shouldReceive('getConnectionName')->never();

$model = m::mock(Model::class);
$model->shouldReceive('getAttribute')->once()->with('id')->andReturn(null);
$model->shouldReceive('getTable')->never();
$model->shouldReceive('getConnectionName')->never();

$this->assertFalse($relation->is($model));
}

public function testIsNotModelWithAnotherKey()
{
$relation = $this->getRelation();

$this->related->shouldReceive('getConnectionName')->never();

$model = m::mock(Model::class);
$model->shouldReceive('getAttribute')->once()->with('id')->andReturn('foreign.value.two');
$model->shouldReceive('getTable')->never();
$model->shouldReceive('getConnectionName')->never();

$this->assertFalse($relation->is($model));
}

public function testIsNotModelWithAnotherTable()
{
$relation = $this->getRelation();

$this->related->shouldReceive('getConnectionName')->never();

$model = m::mock(Model::class);
$model->shouldReceive('getAttribute')->once()->with('id')->andReturn('foreign.value');
$model->shouldReceive('getTable')->once()->andReturn('table.two');
$model->shouldReceive('getConnectionName')->never();

$this->assertFalse($relation->is($model));
}

public function testIsNotModelWithAnotherConnection()
{
$relation = $this->getRelation();

$this->related->shouldReceive('getConnectionName')->once()->andReturn('relation');

$model = m::mock(Model::class);
$model->shouldReceive('getAttribute')->once()->with('id')->andReturn('foreign.value');
$model->shouldReceive('getTable')->once()->andReturn('relation');
$model->shouldReceive('getConnectionName')->once()->andReturn('relation.two');

$this->assertFalse($relation->is($model));
}

protected function getRelation($parent = null, $keyType = 'int')
{
$this->builder = m::mock(Builder::class);
Expand Down
Loading

0 comments on commit 963f698

Please sign in to comment.