Skip to content

Commit

Permalink
Properly apply conditions to belongsToMany / morphToMany relations (#543
Browse files Browse the repository at this point in the history
)

Co-authored-by: Ben Thomson <git@alfreido.com>
  • Loading branch information
mjauvin and Ben Thomson authored Dec 1, 2020
1 parent 778cbb3 commit ef6b7f0
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 43 deletions.
28 changes: 7 additions & 21 deletions src/Database/Relations/BelongsToMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,10 @@ public function attach($id, array $attributes = [], $touch = true)
return;
}

// Here we will insert the attachment records into the pivot table. Once we have
// inserted the records, we will touch the relationships if necessary and the
// function will return. We can parse the IDs before inserting the records.
$this->newPivotStatement()->insert($insertData);

if ($touch) {
$this->touchIfTouching();
}
/**
* @see Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable
*/
parent::attach($id, $attributes, $touch);

/**
* @event model.relation.afterAttach
Expand All @@ -164,7 +160,7 @@ public function detach($ids = null, $touch = true)
{
$attachedIdList = $this->parseIds($ids);
if (empty($attachedIdList)) {
$attachedIdList = $this->allRelatedIds()->all();
$attachedIdList = $this->allRelatedIds();
}

/**
Expand All @@ -185,8 +181,8 @@ public function detach($ids = null, $touch = true)
return;
}

/*
* See Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable
/**
* @see Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable
*/
parent::detach($attachedIdList, $touch);

Expand Down Expand Up @@ -409,14 +405,4 @@ public function getRelatedIds($sessionKey = null)
traceLog('Method BelongsToMany::getRelatedIds has been deprecated, use BelongsToMany::allRelatedIds instead.');
return $this->allRelatedIds($sessionKey)->all();
}

/**
* Get the pivot models that are currently attached (taking conditions & scopes into account).
*
* @return \Illuminate\Support\Collection
*/
protected function getCurrentlyAttachedPivots()
{
return $this->getQuery()->get();
}
}
19 changes: 18 additions & 1 deletion src/Database/Relations/DefinedConstraints.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/
trait DefinedConstraints
{

/**
* Set the defined constraints on the relation query.
*
Expand Down Expand Up @@ -122,4 +121,22 @@ public function addDefinedConstraintsToQuery($query, $args = null)
$query->$scope($this->parent);
}
}

/**
* Create a new query builder for the pivot table.
*
* This is an extension of Laravel's `newPivotQuery` method that allows `belongsToMany` and `morphToMany` relations
* to have conditions.
*
* @return \Illuminate\Database\Query\Builder
*/
public function newPivotQuery()
{
$query = parent::newPivotQuery();

// add relation's conditions and scopes to the query
$this->addDefinedConstraintsToQuery($query);

return $query->join($this->related->getTable(), $this->relatedPivotKey, '=', $this->relatedKey);
}
}
111 changes: 90 additions & 21 deletions tests/Database/RelationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public function createTables()

$this->db->schema()->create('terms', function ($table) {
$table->increments('id');
$table->string('type');
$table->string('type')->index();
$table->string('name');
$table->timestamps();
});

$this->db->schema()->create('posts_terms', function ($table) {
$table->increments('id');
$table->primary(['post_id', 'term_id']);
$table->unsignedInteger('post_id');
$table->unsignedInteger('term_id');
});
Expand Down Expand Up @@ -66,8 +66,9 @@ public function testTablesProperlySeeded()
public function testBelongsToManyCount()
{
$post = Post::first();
$this->assertEquals(2, $post->tags->count());
$this->assertEquals(2, $post->categories->count());
$this->assertEquals(2, $post->tags()->count());
$this->assertEquals(2, $post->categories()->count());
$this->assertEquals(4, $post->terms()->count());
}

public function testBelongsToManySyncAll()
Expand All @@ -77,17 +78,21 @@ public function testBelongsToManySyncAll()
$catid = $post->categories()->first()->id;
$tagid = $post->tags()->first()->id;

Post::flushDuplicateCache();
$this->assertEquals(2, $post->categories()->count());
$this->assertEquals(2, $post->tags()->count());

$post->categories()->sync([$catid]);
$post->tags()->sync([$tagid]);

$this->assertEquals(1, $post->categories->count());
$this->assertEquals($catid, $post->categories()->first()->id);
$post->reloadRelations();

$post->tags()->sync([$tagid]);
$this->assertEquals(1, $post->categories()->count());
$this->assertEquals($catid, $post->categories()->first()->id);

$this->assertEquals(1, $post->tags->count());
$this->assertEquals(1, $post->tags()->count());
$this->assertEquals($tagid, $post->tags()->first()->id);

$this->assertEquals(2, $post->terms()->count());
}

public function testBelongsToManySyncTags()
Expand All @@ -96,15 +101,14 @@ public function testBelongsToManySyncTags()

$id = $post->tags()->first()->id;

Post::flushDuplicateCache();

$post->categories()->detach();
$this->assertEquals(0, $post->categories()->count());

$post->tags()->sync([$id]);

$this->assertEquals(1, $post->tags->count());
$this->assertEquals(0, $post->categories()->count());
$this->assertEquals(1, $post->tags()->count());
$this->assertEquals($id, $post->tags()->first()->id);

$this->assertEquals(1, $post->terms()->count());
}

public function testBelongsToManySyncCategories()
Expand All @@ -113,26 +117,26 @@ public function testBelongsToManySyncCategories()

$id = $post->categories()->first()->id;

Post::flushDuplicateCache();

$post->categories()->sync([$id]);
$post->tags()->detach();

$this->assertEquals(1, $post->categories()->count());
$this->assertEquals($id, $post->categories()->first()->id);
$this->assertEquals(0, $post->tags()->count());

$post->tags()->detach();
$this->assertEquals(0, $post->tags->count());
$this->assertEquals(1, $post->terms()->count());
}

public function testBelongsToManyDetach()
{
$post = Post::first();

$post->categories()->detach();
$this->assertEquals(0, $post->categories()->count());

$post->tags()->detach();

$this->assertEquals(0, $post->categories()->count());
$this->assertEquals(0, $post->tags()->count());
$this->assertEquals(0, $post->terms()->count());
}

public function testBelongsToManySyncMultipleCategories()
Expand All @@ -144,17 +148,76 @@ public function testBelongsToManySyncMultipleCategories()

$post->categories()->sync($category_ids);
$this->assertEquals(4, $post->categories()->count());
$this->assertEquals(2, $post->tags()->count());
$this->assertEquals(6, $post->terms()->count());
}

public function testBelongsToManyDetachOneCategory()
{
$post = Post::first();

$id = $post->categories()->get()->last()->id;
Post::flushDuplicateCache();

$this->assertEquals(2, $post->categories()->count());
$this->assertEquals(2, $post->tags()->count());
$this->assertEquals(4, $post->terms()->count());

$post->categories()->detach([$id]);
$post->reloadRelations();

$this->assertEquals(1, $post->categories()->count());
$this->assertEquals(2, $post->tags()->count());
$this->assertEquals(3, $post->terms()->count());
}

public function testTerms()
{
$post = Post::create([
'title' => 'B Post',
]);

$term1 = Term::create(['name' => 'term #1', 'type' => 'any']);
$term2 = Term::create(['name' => 'term #2', 'type' => 'any']);
$term3 = Term::create(['name' => 'term #3', 'type' => 'any']);

// Add/remove to collection
$this->assertFalse($post->terms->contains($term1->id));
$post->terms()->add($term1);
$post->terms()->add($term2);
$this->assertTrue($post->terms->contains($term1->id));
$this->assertTrue($post->terms->contains($term2->id));

// Set by Model object
$post->terms = $term1;
$this->assertEquals(1, $post->terms->count());
$this->assertEquals('term #1', $post->terms->first()->name);

$post->terms = [$term1, $term2, $term3];
$this->assertEquals(3, $post->terms->count());

// Set by primary key
$post->terms = $term2->id;
$this->assertEquals(1, $post->terms->count());
$this->assertEquals('term #2', $post->terms->first()->name);

$post->terms = [$term2->id, $term3->id];
$this->assertEquals(2, $post->terms->count());

// Nullify
$post->terms = null;
$this->assertEquals(0, $post->terms->count());

// Extra nullify checks (still exists in DB until saved)
$post->reloadRelations('terms');
$this->assertEquals(2, $post->terms->count());
$post->save();
$post->reloadRelations('terms');
$this->assertEquals(0, $post->terms->count());

// Deferred in memory
$post->terms = [$term2->id, $term3->id];
$this->assertEquals(2, $post->terms->count());
$this->assertEquals('term #2', $post->terms->first()->name);
}
}

Expand All @@ -179,6 +242,12 @@ class Post extends \October\Rain\Database\Model
'otherKey' => 'term_id',
'conditions' => 'type = "category"'
],
'terms' => [
Term::class,
'table' => 'posts_terms',
'key' => 'post_id',
'otherKey' => 'term_id',
],
];
}

Expand Down

0 comments on commit ef6b7f0

Please sign in to comment.