Skip to content

Commit da2a89d

Browse files
committed
fix issue with scopes being applied twice in paginated queries on BelongsToManyThrough
1 parent ae0e107 commit da2a89d

3 files changed

Lines changed: 27 additions & 19 deletions

File tree

app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
/**
1515
* Users table migration
16-
* Removed the 'display_name', 'title', 'secret_token', and 'flag_password_reset' fields, and added first and last name and 'last_activity_at'.
16+
* Removed the 'display_name', 'title', 'secret_token', and 'flag_password_reset' fields, and added first and last name and 'last_activity_id'.
1717
* Version 4.0.0
1818
*
1919
* See https://laravel.com/docs/5.4/migrations#tables

app/sprinkles/core/src/Database/Relations/Concerns/Unique.php

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -274,25 +274,30 @@ public function getPaginatedQuery(Builder $query, $limit = null, $offset = null)
274274
// the desired set of unique model _ids_, and then constrain our final query
275275
// to these models with a whereIn clause.
276276
$relatedKeyName = $this->related->getQualifiedKeyName();
277-
$constrainedBuilder = $constrainedBuilder
278-
->select($relatedKeyName)
279-
->groupBy($relatedKeyName);
277+
278+
// Apply an additional scope to override any selected columns in other global scopes
279+
$uniqueIdScope = function ($subQuery) use ($relatedKeyName) {
280+
$subQuery->select($relatedKeyName)
281+
->groupBy($relatedKeyName);
282+
};
283+
284+
$identifier = spl_object_hash($uniqueIdScope);
285+
286+
$constrainedBuilder->withGlobalScope($identifier, $uniqueIdScope);
280287

281288
if ($limit) {
282-
$constrainedBuilder = $constrainedBuilder->limit($limit);
289+
$constrainedBuilder->limit($limit);
283290
}
284291

285292
if ($offset) {
286-
$constrainedBuilder = $constrainedBuilder->offset($offset);
293+
$constrainedBuilder->offset($offset);
287294
}
288295

289296
$primaryKeyName = $this->getParent()->getKeyName();
290297
$modelIds = $constrainedBuilder->get()->pluck($primaryKeyName)->toArray();
291298

292299
// Modify the unconstrained query to limit to these models
293-
$query = $query->whereIn($relatedKeyName, $modelIds);
294-
295-
return $query;
300+
return $query->whereIn($relatedKeyName, $modelIds);
296301
}
297302

298303
/**
@@ -323,18 +328,18 @@ public function getModels($columns = ['*'], $condenseModels = true)
323328
// models with the result of those columns as a separate model relation.
324329
$columns = $this->query->getQuery()->columns ? [] : $columns;
325330

331+
// Add any necessary pagination on the related models
332+
if ($this->limit || $this->offset) {
333+
$this->getPaginatedQuery($this->query, $this->limit, $this->offset);
334+
}
335+
326336
// Apply scopes to the Eloquent\Builder instance.
327337
$builder = $this->query->applyScopes();
328338

329339
$builder = $builder->addSelect(
330340
$this->shouldSelect($columns)
331341
);
332342

333-
// Add any necessary pagination on the related models
334-
if ($this->limit || $this->offset) {
335-
$builder = $this->getPaginatedQuery($builder, $this->limit, $this->offset);
336-
}
337-
338343
$models = $builder->getModels();
339344

340345
// Hydrate the pivot models so we can load the via models

app/sprinkles/core/tests/Unit/BelongsToManyThroughTest.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace UserFrosting\Tests\Unit;
99

1010
use Illuminate\Database\Capsule\Manager as DB;
11+
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
1112
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
1213
use Mockery as m;
1314
use ReflectionClass;
@@ -31,17 +32,19 @@ public function tearDown()
3132

3233
function testPaginatedQuery()
3334
{
35+
// Creates a real BelongsToManyThrough object
3436
$relation = $this->getRelation();
3537

3638
// We need to define a mock base query, because Eloquent\Builder will pass through many calls
3739
// to this underlying Query\Builder object.
3840
$baseQuery = m::mock(QueryBuilder::class);
39-
$builder = m::mock('Illuminate\Database\Eloquent\Builder', [$baseQuery])->makePartial();
41+
$builder = m::mock(EloquentBuilder::class, [$baseQuery])->makePartial();
4042

4143
$related = $relation->getRelated();
4244
$related->shouldReceive('getQualifiedKeyName')->once()->andReturn('users.id');
43-
$builder->shouldReceive('select')->once()->with('users.id')->andReturnSelf();
44-
$builder->shouldReceive('groupBy')->once()->with('users.id')->andReturnSelf();
45+
46+
$builder->shouldReceive('withGlobalScope')->once()->andReturnSelf();
47+
4548
$builder->shouldReceive('limit')->once()->with(2)->andReturnSelf();
4649
$builder->shouldReceive('offset')->once()->with(1)->andReturnSelf();
4750

@@ -63,14 +66,14 @@ function testPaginatedQuery()
6366
protected function getRelation()
6467
{
6568
// We simulate a BelongsToManyThrough relationship that gets all related users for a specified permission(s).
66-
$builder = m::mock('Illuminate\Database\Eloquent\Builder');
69+
$builder = m::mock(EloquentBuilder::class);
6770
$related = m::mock('Illuminate\Database\Eloquent\Model');
6871
$related->shouldReceive('getKey')->andReturn(1);
6972
$related->shouldReceive('getTable')->andReturn('users');
7073
$related->shouldReceive('getKeyName')->andReturn('id');
7174
// Tie the mocked builder to the mocked related model
7275
$builder->shouldReceive('getModel')->andReturn($related);
73-
76+
7477
// Mock the intermediate role->permission BelongsToMany relation
7578
$intermediateRelationship = m::mock(BelongsToMany::class);
7679
$intermediateRelationship->shouldReceive('getTable')->once()->andReturn('permission_roles');

0 commit comments

Comments
 (0)