Skip to content

Commit 995d31f

Browse files
committed
Fix multiple bugs + handle item delete + add tests
Fixes #7
1 parent b3e68bc commit 995d31f

16 files changed

+430
-38
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ and updates the other entries based on the models position value.
2323

2424
## Installation
2525

26-
> Tested in Laravel 5.1 - 5.7, should work in all 5.*/6.* releases
26+
> Tested in Laravel 5.4 - 8.
2727
2828
**Install via composer**
2929

composer.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
"name": "pion/laravel-eloquent-position",
33
"description": "Position logic for Eloquent models with minimum setup",
44
"require": {
5-
"laravel/framework": ">=5.1"
5+
"laravel/framework": ">=5.5"
66
},
77
"require-dev": {
8-
"squizlabs/php_codesniffer": "2.*"
8+
"squizlabs/php_codesniffer": "2.*",
9+
"orchestra/testbench": "~3.6.7 || ~3.7.8 || ~3.8.6 || ^4.8 || ^5.2 || ^6.0"
910
},
1011
"scripts": {
1112
"lint": "./vendor/bin/phpcs --standard=PSR2 src/",
@@ -16,6 +17,11 @@
1617
"Pion\\Support\\Eloquent\\Position\\": "src/"
1718
}
1819
},
20+
"autoload-dev": {
21+
"psr-4": {
22+
"Tests\\": "tests/"
23+
}
24+
},
1925
"license": "MIT",
2026
"authors": [
2127
{

phpunit.xml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
4+
backupGlobals="false"
5+
backupStaticAttributes="false"
6+
bootstrap="vendor/autoload.php"
7+
colors="true"
8+
convertErrorsToExceptions="true"
9+
convertNoticesToExceptions="true"
10+
convertWarningsToExceptions="true"
11+
processIsolation="false"
12+
stopOnFailure="false"
13+
verbose="true">
14+
<coverage includeUncoveredFiles="false">
15+
<include>
16+
<directory suffix=".php">src/</directory>
17+
</include>
18+
</coverage>
19+
<testsuites>
20+
<testsuite name="Test Suite">
21+
<directory suffix="Test.php">./tests/</directory>
22+
</testsuite>
23+
</testsuites>
24+
<php>
25+
<server name="DB_CONNECTION" value="testing" />
26+
<server name="APP_ENV" value="testing"/>
27+
<server name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
28+
</php>
29+
</phpunit>

src/Commands/RecalculatePositionCommand.php

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,24 +62,25 @@ public function handle()
6262
$groups = $model->getPositionGroup();
6363

6464
// Run sorted query for every entry
65-
$modelClass::sorted()->chunk(200, function (Collection $collection) use ($groups, &$positionsByGroup) {
66-
/** @var PositionTrait|Model $model */
67-
foreach ($collection as $model) {
68-
// Builds the group key to get position
69-
$groupKey = $this->buildGroupKeyForPosition($model, $groups);
70-
71-
// Set the new position
72-
$model->setPosition($this->getPositionForGroup($groupKey, $positionsByGroup))
73-
->save();
74-
}
75-
});
65+
$modelClass::sorted()
66+
->chunk(200, function (Collection $collection) use ($groups, &$positionsByGroup) {
67+
/** @var PositionTrait|Model $model */
68+
foreach ($collection as $model) {
69+
// Builds the group key to get position
70+
$groupKey = $this->buildGroupKeyForPosition($model, $groups);
71+
72+
// Set the new position
73+
$model->setPosition($this->getPositionForGroup($groupKey, $positionsByGroup))
74+
->save();
75+
}
76+
});
7677

7778
// Render the table layout about the positions
7879
$this->table([
7980
'Group', 'Last position'
80-
], collect($positionsByGroup)->map(function ($value, $key) {
81+
], array_map(function ($value, $key) {
8182
return [$key, $value];
82-
}));
83+
}, $positionsByGroup));
8384

8485
return true;
8586
}
@@ -107,7 +108,7 @@ protected function getPositionForGroup($groupKey, &$positionsByGroup)
107108
/**
108109
* Builds the group key from the group columns and the values form the model
109110
*
110-
* @param Model|PositionTrait $model The eloquent model
111+
* @param Model|PositionTrait $model The eloquent model
111112
* @param array $groups
112113
*
113114
* @return string

src/PositionObserver.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function __construct(Dispatcher $events)
4242
*/
4343
public function saving($model)
4444
{
45-
if (!$model->isPositionUpdateDisabled()) {
45+
if ($model->isPositionUpdateDisabled() === false) {
4646
// Get the position for current and old value
4747
$position = $model->getPosition();
4848

@@ -61,7 +61,23 @@ public function saving($model)
6161
}
6262

6363
/**
64-
* Forces the new position, will be overriden if it's out of maximum bounds.
64+
* Updates the position before saving
65+
*
66+
* @param Model|PositionTrait $model
67+
*/
68+
public function deleted($model)
69+
{
70+
if ($model->isPositionUpdateDisabled() === false) {
71+
// Get the old position
72+
$oldPosition = $model->getOriginal($model->getPositionColumn());
73+
74+
// Append deleted model as last to re-index other models
75+
$this->appendLast($model, $oldPosition);
76+
}
77+
}
78+
79+
/**
80+
* Forces the new position, will be overridden if it's out of maximum bounds.
6581
*
6682
* @param Model|PositionTrait $model
6783
* @param int $position

src/Query/LastPositionQuery.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
namespace Pion\Support\Eloquent\Position\Query;
34

45
use Pion\Support\Eloquent\Position\Traits\PositionTrait;
@@ -19,7 +20,7 @@ class LastPositionQuery extends AbstractPositionQuery
1920
* Creates the base query and builds the query
2021
*
2122
* @param Model|PositionTrait $model
22-
* @param int $oldPosition
23+
* @param int|null $oldPosition
2324
*/
2425
public function __construct($model, $oldPosition)
2526
{
@@ -39,8 +40,10 @@ public function runQuery($query)
3940
// Get the last position and move the position
4041
$lastPosition = $query->max($this->model()->getPositionColumn()) ?: 0;
4142

42-
// Check if the last position is not same as original position - the same object
43-
if (empty($this->oldPosition) || $lastPosition != $this->oldPosition) {
43+
if (empty($this->oldPosition) === false) {
44+
(new MoveQuery($this->model, $lastPosition, $this->oldPosition))->run();
45+
} else if ($this->oldPosition === null || $lastPosition != $this->oldPosition) {
46+
// Check if the last position is not same as original position - the same object
4447
$this->model()->setPosition($lastPosition + 1);
4548
}
4649

src/Query/MoveQuery.php

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
namespace Pion\Support\Eloquent\Position\Query;
34

45
use Pion\Support\Eloquent\Position\Traits\PositionTrait;
@@ -23,22 +24,20 @@ class MoveQuery extends AbstractPositionQuery
2324
protected $position;
2425

2526
/**
26-
* Comparision condition for old position value.
27+
* Comparison condition for old position value.
2728
* In default is for decrement.
2829
* @var string
2930
*/
30-
protected $oldComparisionCondition = '>';
31+
protected $oldComparisonCondition = '>';
3132
/**
32-
* Comparision condition for new position value.
33+
* Comparison condition for new position value.
3334
* In default is for decrement.
3435
* @var string
3536
*/
36-
protected $newComparisionCondition = '<=';
37+
protected $newComparisonCondition = '<=';
3738

3839

3940
/**
40-
* PositionQuery constructor.
41-
*
4241
* @param Model|PositionTrait $model
4342
* @param int $position
4443
* @param int|null $oldPosition
@@ -56,7 +55,7 @@ public function __construct($model, $position, $oldPosition)
5655
$this->positionColumn = $model->getPositionColumn();
5756

5857
// Build the comparision condition
59-
$this->buildComparisionCondition();
58+
$this->buildComparisonCondition();
6059

6160
// Prepare the query
6261
$this->query = $this->buildQuery();
@@ -76,6 +75,15 @@ public function runQuery($query)
7675
if ($this->increment) {
7776
return $query->increment($this->positionColumn);
7877
} else {
78+
// Get the last position and move the position
79+
$lastPosition = $query->max($this->positionColumn) ?: 0;
80+
81+
// If the set position is out of the bounds of current items, force new position
82+
if ($this->position > $lastPosition) {
83+
$this->position = $lastPosition;
84+
$this->model()->setPosition($this->position);
85+
}
86+
7987
return $query->decrement($this->positionColumn);
8088
}
8189
}
@@ -90,18 +98,18 @@ protected function buildQuery()
9098
$query = parent::buildQuery();
9199

92100
// Build the where condition for the position. This will ensure to update only required rows
93-
return $query->where($this->positionColumn, $this->oldComparisionCondition, $this->oldPosition)
94-
->where($this->positionColumn, $this->newComparisionCondition, $this->position);
101+
return $query->where($this->positionColumn, $this->oldComparisonCondition, $this->oldPosition)
102+
->where($this->positionColumn, $this->newComparisonCondition, $this->position);
95103
}
96104

97105
/**
98-
* Builds the correct comparision condition
106+
* Builds the correct comparison condition
99107
*/
100-
protected function buildComparisionCondition()
108+
protected function buildComparisonCondition()
101109
{
102110
if ($this->increment) {
103-
$this->oldComparisionCondition = '<';
104-
$this->newComparisionCondition = '>=';
111+
$this->oldComparisonCondition = '<';
112+
$this->newComparisonCondition = '>=';
105113
}
106114
}
107115
//endregion

src/Query/PositionQuery.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ protected function buildQuery()
5252
$query = parent::buildQuery();
5353

5454
// Get the last position and move the position
55-
$lastPosition = $query->max($this->positionColumn);
55+
$lastPosition = $query->max($this->positionColumn) ?: 0;
5656

5757
// If the new position is last position, just update the position or if
5858
// new position is out of bounds
59-
if ($this->position >= $lastPosition) {
59+
if ($this->position > $lastPosition) {
6060
$this->model()->setPosition($lastPosition + 1);
6161
} else {
6262
$this->shouldRunQuery = true;

src/Traits/PositionTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function getPositionGroup()
7070
*/
7171
public function isPositionUpdateDisabled()
7272
{
73-
return $this->positionOption('disablePositionUpdate');
73+
return $this->positionOption('disablePositionUpdate', false);
7474
}
7575

7676
//endregion

tests/GroupItemPositionTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Tests;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use Tests\Models\AbstractPositionModel;
7+
use Tests\Models\GroupItem;
8+
9+
class GroupItemPositionTest extends SingleItemPositionTest
10+
{
11+
protected function setUp(): void
12+
{
13+
parent::setUp();
14+
15+
// Create items for different group - single item position test should contain only its own group
16+
$this->createItem('DifferentGroup', null, 2);
17+
$this->createItem('DifferentGroup', null, 2);
18+
$this->createItem('DifferentGroup', null, 2);
19+
$this->createItem('DifferentGroup', null, 2);
20+
}
21+
22+
23+
protected function query(): Builder
24+
{
25+
return GroupItem::query()->where('group', 1);
26+
}
27+
28+
protected function createItem(string $name, string $position = null, int $group = 1): AbstractPositionModel
29+
{
30+
return GroupItem::create([
31+
'name' => $name,
32+
'position' => $position,
33+
'group' => $group,
34+
]);
35+
}
36+
}

0 commit comments

Comments
 (0)