Skip to content

Commit

Permalink
Fix multiple bugs + handle item delete + add tests
Browse files Browse the repository at this point in the history
Fixes #7
  • Loading branch information
pionl committed Nov 29, 2020
1 parent b3e68bc commit 995d31f
Show file tree
Hide file tree
Showing 16 changed files with 430 additions and 38 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ and updates the other entries based on the models position value.

## Installation

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

Expand Down
10 changes: 8 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
"name": "pion/laravel-eloquent-position",
"description": "Position logic for Eloquent models with minimum setup",
"require": {
"laravel/framework": ">=5.1"
"laravel/framework": ">=5.5"
},
"require-dev": {
"squizlabs/php_codesniffer": "2.*"
"squizlabs/php_codesniffer": "2.*",
"orchestra/testbench": "~3.6.7 || ~3.7.8 || ~3.8.6 || ^4.8 || ^5.2 || ^6.0"
},
"scripts": {
"lint": "./vendor/bin/phpcs --standard=PSR2 src/",
Expand All @@ -16,6 +17,11 @@
"Pion\\Support\\Eloquent\\Position\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"license": "MIT",
"authors": [
{
Expand Down
29 changes: 29 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
verbose="true">
<coverage includeUncoveredFiles="false">
<include>
<directory suffix=".php">src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<php>
<server name="DB_CONNECTION" value="testing" />
<server name="APP_ENV" value="testing"/>
<server name="APP_KEY" value="AckfSECXIvnK5r28GVIWUAxmbBSjTsmF"/>
</php>
</phpunit>
29 changes: 15 additions & 14 deletions src/Commands/RecalculatePositionCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,25 @@ public function handle()
$groups = $model->getPositionGroup();

// Run sorted query for every entry
$modelClass::sorted()->chunk(200, function (Collection $collection) use ($groups, &$positionsByGroup) {
/** @var PositionTrait|Model $model */
foreach ($collection as $model) {
// Builds the group key to get position
$groupKey = $this->buildGroupKeyForPosition($model, $groups);

// Set the new position
$model->setPosition($this->getPositionForGroup($groupKey, $positionsByGroup))
->save();
}
});
$modelClass::sorted()
->chunk(200, function (Collection $collection) use ($groups, &$positionsByGroup) {
/** @var PositionTrait|Model $model */
foreach ($collection as $model) {
// Builds the group key to get position
$groupKey = $this->buildGroupKeyForPosition($model, $groups);

// Set the new position
$model->setPosition($this->getPositionForGroup($groupKey, $positionsByGroup))
->save();
}
});

// Render the table layout about the positions
$this->table([
'Group', 'Last position'
], collect($positionsByGroup)->map(function ($value, $key) {
], array_map(function ($value, $key) {
return [$key, $value];
}));
}, $positionsByGroup));

return true;
}
Expand Down Expand Up @@ -107,7 +108,7 @@ protected function getPositionForGroup($groupKey, &$positionsByGroup)
/**
* Builds the group key from the group columns and the values form the model
*
* @param Model|PositionTrait $model The eloquent model
* @param Model|PositionTrait $model The eloquent model
* @param array $groups
*
* @return string
Expand Down
20 changes: 18 additions & 2 deletions src/PositionObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function __construct(Dispatcher $events)
*/
public function saving($model)
{
if (!$model->isPositionUpdateDisabled()) {
if ($model->isPositionUpdateDisabled() === false) {
// Get the position for current and old value
$position = $model->getPosition();

Expand All @@ -61,7 +61,23 @@ public function saving($model)
}

/**
* Forces the new position, will be overriden if it's out of maximum bounds.
* Updates the position before saving
*
* @param Model|PositionTrait $model
*/
public function deleted($model)
{
if ($model->isPositionUpdateDisabled() === false) {
// Get the old position
$oldPosition = $model->getOriginal($model->getPositionColumn());

// Append deleted model as last to re-index other models
$this->appendLast($model, $oldPosition);
}
}

/**
* Forces the new position, will be overridden if it's out of maximum bounds.
*
* @param Model|PositionTrait $model
* @param int $position
Expand Down
9 changes: 6 additions & 3 deletions src/Query/LastPositionQuery.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace Pion\Support\Eloquent\Position\Query;

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

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

Expand Down
34 changes: 21 additions & 13 deletions src/Query/MoveQuery.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace Pion\Support\Eloquent\Position\Query;

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

/**
* Comparision condition for old position value.
* Comparison condition for old position value.
* In default is for decrement.
* @var string
*/
protected $oldComparisionCondition = '>';
protected $oldComparisonCondition = '>';
/**
* Comparision condition for new position value.
* Comparison condition for new position value.
* In default is for decrement.
* @var string
*/
protected $newComparisionCondition = '<=';
protected $newComparisonCondition = '<=';


/**
* PositionQuery constructor.
*
* @param Model|PositionTrait $model
* @param int $position
* @param int|null $oldPosition
Expand All @@ -56,7 +55,7 @@ public function __construct($model, $position, $oldPosition)
$this->positionColumn = $model->getPositionColumn();

// Build the comparision condition
$this->buildComparisionCondition();
$this->buildComparisonCondition();

// Prepare the query
$this->query = $this->buildQuery();
Expand All @@ -76,6 +75,15 @@ public function runQuery($query)
if ($this->increment) {
return $query->increment($this->positionColumn);
} else {
// Get the last position and move the position
$lastPosition = $query->max($this->positionColumn) ?: 0;

// If the set position is out of the bounds of current items, force new position
if ($this->position > $lastPosition) {
$this->position = $lastPosition;
$this->model()->setPosition($this->position);
}

return $query->decrement($this->positionColumn);
}
}
Expand All @@ -90,18 +98,18 @@ protected function buildQuery()
$query = parent::buildQuery();

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

/**
* Builds the correct comparision condition
* Builds the correct comparison condition
*/
protected function buildComparisionCondition()
protected function buildComparisonCondition()
{
if ($this->increment) {
$this->oldComparisionCondition = '<';
$this->newComparisionCondition = '>=';
$this->oldComparisonCondition = '<';
$this->newComparisonCondition = '>=';
}
}
//endregion
Expand Down
4 changes: 2 additions & 2 deletions src/Query/PositionQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ protected function buildQuery()
$query = parent::buildQuery();

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

// If the new position is last position, just update the position or if
// new position is out of bounds
if ($this->position >= $lastPosition) {
if ($this->position > $lastPosition) {
$this->model()->setPosition($lastPosition + 1);
} else {
$this->shouldRunQuery = true;
Expand Down
2 changes: 1 addition & 1 deletion src/Traits/PositionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function getPositionGroup()
*/
public function isPositionUpdateDisabled()
{
return $this->positionOption('disablePositionUpdate');
return $this->positionOption('disablePositionUpdate', false);
}

//endregion
Expand Down
36 changes: 36 additions & 0 deletions tests/GroupItemPositionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Tests;

use Illuminate\Database\Eloquent\Builder;
use Tests\Models\AbstractPositionModel;
use Tests\Models\GroupItem;

class GroupItemPositionTest extends SingleItemPositionTest
{
protected function setUp(): void
{
parent::setUp();

// Create items for different group - single item position test should contain only its own group
$this->createItem('DifferentGroup', null, 2);
$this->createItem('DifferentGroup', null, 2);
$this->createItem('DifferentGroup', null, 2);
$this->createItem('DifferentGroup', null, 2);
}


protected function query(): Builder
{
return GroupItem::query()->where('group', 1);
}

protected function createItem(string $name, string $position = null, int $group = 1): AbstractPositionModel
{
return GroupItem::create([
'name' => $name,
'position' => $position,
'group' => $group,
]);
}
}
23 changes: 23 additions & 0 deletions tests/Models/AbstractPositionModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php


namespace Tests\Models;


use Illuminate\Database\Eloquent\Model;
use Pion\Support\Eloquent\Position\Traits\PositionTrait;


/**
* Only for tests
* @property string $name
* @property int $position
* @property int $group
*/
abstract class AbstractPositionModel extends Model
{
use PositionTrait;

public $timestamps = false;

}
18 changes: 18 additions & 0 deletions tests/Models/GroupItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Tests\Models;


/**
* @property int $group
*/
class GroupItem extends AbstractPositionModel
{
protected $fillable = [
'name',
'position',
'group',
];

public $positionGroup = ['group'];
}
13 changes: 13 additions & 0 deletions tests/Models/SingleItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tests\Models;


class SingleItem extends AbstractPositionModel
{
public $timestamps = false;
protected $fillable = [
'name',
'position',
];
}
Loading

0 comments on commit 995d31f

Please sign in to comment.