Skip to content

Commit

Permalink
Sequencing strategy (#15)
Browse files Browse the repository at this point in the history
* feat: ensure the strategy can be set to always

* test: ensure the strategy can be set to always

* feat: ensure the strategy can be set to on create

* test: ensure the strategy can be set to on create

* feat: ensure the strategy can be set to on update

* test: ensure the strategy can be set to on update

* feat: ensure the strategy can be set to never

* test: ensure the strategy can be set to never

* refactor: move commands tests to a different folder

* refactor: set last value equal to the max value in the sequence

* refactor: group withoutSequencing tests

* refactor: rename tests

* refactor: rename test classes

* feat: add strategy config variable

* test: ensure strategy set to always works on create

* test: ensure strategy set to always works on delete

* test: ensure strategy set to always works on update

* feat: do nothing if strategy is set to never

* test: ensure strategy set to never works on create

* feat: ensure it does update the sequence on delete if strategy is set to never

* test: ensure strategy set to never works on delete

* feat: ensure strategy set to never works on update

* test: ensure strategy set to never works on update

* refactor: move strategy check to another method

* feat: ensure strategy set on_create and on_update work

* test: ensure strategy set on_create and on_update work

* feat: ensure on_create and on_update disable sequencing for delete operations

* test: ensure on_create and on_update disable sequencing for delete operations

* refactor: accept array of strategies as param

* docs: add docblock

* fix: rename test class

* refactor: rename test method

* refactor: remove comparison negation

* test: change null assertion to equal

* feat: ensure the next value is greater than the max

* refactor: rename method

* docs: add comments to config file

* docs: update README

* docs: update README

* fix: group class name uppercase
  • Loading branch information
gurgentil authored Jul 31, 2020
1 parent c3b787c commit 3422a64
Show file tree
Hide file tree
Showing 16 changed files with 384 additions and 44 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ php artisan vendor:publish --provider="Gurgentil\LaravelEloquentSequencer\Larave

### Configuration parameters

You can change the default colum name and the initial value for the sequences in `config/eloquentsequencer.php`:
You can change the default colum name, the initial value and the sequencing strategy in `config/eloquentsequencer.php`:

```php
return [
'column_name' => 'position',
'initial_value' => 1,
'strategy' => 'always',
];
```

The `strategy` configuration determines when sequencing should be triggered and accepts one of the following values: `always`, `on_create`, `on_update` or `never`.

### Model configuration

The `$sequenceable` attribute specifies the sequence column name for the model:
Expand Down
13 changes: 13 additions & 0 deletions config/config.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
<?php

return [
/*
* The name of the table column that determines the sequence value.
*/
'column_name' => 'position',

/*
* The value sequences should start at.
*/
'initial_value' => 1,

/*
* Determines when models should be sequenced.
* Possible values: always|never|on_create|on_update
*/
'strategy' => 'always',
];
11 changes: 11 additions & 0 deletions src/SequencingStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Gurgentil\LaravelEloquentSequencer;

class SequencingStrategy
{
const ALWAYS = 'always';
const ON_CREATE = 'on_create';
const ON_UPDATE = 'on_update';
const NEVER = 'never';
}
51 changes: 46 additions & 5 deletions src/Traits/Sequenceable.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Gurgentil\LaravelEloquentSequencer\Traits;

use Gurgentil\LaravelEloquentSequencer\Exceptions\SequenceValueOutOfBoundsException;
use Gurgentil\LaravelEloquentSequencer\SequencingStrategy;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
Expand Down Expand Up @@ -58,6 +59,13 @@ protected function handleSequenceableCreate(): void
{
$value = $this->getSequenceValue();

if (static::strategyIn([
SequencingStrategy::NEVER,
SequencingStrategy::ON_UPDATE,
])) {
return;
}

if (is_null($value)) {
$this->{static::getSequenceColumnName()} = $this->getNextSequenceValue();
}
Expand All @@ -76,6 +84,13 @@ protected function handleSequenceableCreate(): void
*/
protected function handleSequenceableUpdate(): void
{
if (static::strategyIn([
SequencingStrategy::NEVER,
SequencingStrategy::ON_CREATE,
])) {
return;
}

if (! $this->shouldBeSequenced) {
$this->shouldBeSequenced = true;

Expand All @@ -84,7 +99,7 @@ protected function handleSequenceableUpdate(): void

$value = $this->getSequenceValue();

if (! $this->isDirty(static::getSequenceColumnName()) || is_null($value)) {
if ($this->isClean(static::getSequenceColumnName()) || is_null($value)) {
return;
}

Expand All @@ -102,6 +117,14 @@ protected function handleSequenceableUpdate(): void
*/
protected function handleSequenceableDelete(): void
{
if (static::strategyIn([
SequencingStrategy::NEVER,
SequencingStrategy::ON_CREATE,
SequencingStrategy::ON_UPDATE,
])) {
return;
}

if (! $this->shouldBeSequenced) {
$this->shouldBeSequenced = true;

Expand All @@ -116,6 +139,17 @@ protected function handleSequenceableDelete(): void
static::decrementSequenceValues($objects);
}

/**
* Determine if strategy is in array.
*
* @param array $strategies
* @return bool
*/
protected static function strategyIn(array $strategies): bool
{
return in_array(config('eloquentsequencer.strategy'), $strategies);
}

/**
* Determine if new sequence value is out of bounds.
*
Expand Down Expand Up @@ -279,11 +313,13 @@ public static function getSequenceColumnName(): string
/**
* Get sequence value of the last model in the sequence.
*
* @return int
* @return int|null
*/
protected function getLastSequenceValue(): int
protected function getLastSequenceValue(): ?int
{
return $this->getNextSequenceValue() - 1;
$column = static::getSequenceColumnName();

return $this->getSequence()->max($column);
}

/**
Expand All @@ -293,7 +329,12 @@ protected function getLastSequenceValue(): int
*/
public function getNextSequenceValue(): int
{
return static::getInitialSequenceValue() + $this->getSequence()->count();
$column = static::getSequenceColumnName();
$maxSequenceValue = $this->getSequence()->max($column);

return $this->getSequence()->count() === 0
? static::getInitialSequenceValue()
: $maxSequenceValue + 1;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Gurgentil\LaravelEloquentSequencer\Tests\Unit;
namespace Gurgentil\LaravelEloquentSequencer\Tests\Unit\Commands;

use Exception;
use Facades\Gurgentil\LaravelEloquentSequencer\Tests\Factories\Factory;
Expand Down Expand Up @@ -29,7 +29,7 @@ public function the_flush_command_does_not_proceed_when_the_model_count_is_0()
/** @test */
public function the_flush_command_does_not_update_values_that_are_already_null()
{
$group = Factory::of('group')->create();
$group = Factory::of('Group')->create();

$items = Factory::of('Item')->times(4)->create(['group_id' => $group->id]);

Expand All @@ -48,7 +48,7 @@ public function the_flush_command_does_not_update_values_that_are_already_null()
/** @test */
public function the_flush_command_flushes_sequence_values()
{
$group = Factory::of('group')->create();
$group = Factory::of('Group')->create();

$firstItem = Factory::of('Item')->create(['group_id' => $group->id]);
$secondItem = Factory::of('Item')->create(['group_id' => $group->id]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Gurgentil\LaravelEloquentSequencer\Tests\Unit;
namespace Gurgentil\LaravelEloquentSequencer\Tests\Unit\Commands;

use Exception;
use Facades\Gurgentil\LaravelEloquentSequencer\Tests\Factories\Factory;
Expand All @@ -27,9 +27,9 @@ public function the_populate_command_does_not_proceed_when_the_model_count_is_0(
}

/** @test */
public function the_populate_command_does_not_update_values_that_are_already_assigned()
public function the_populate_command_does_not_update_values_that_are_not_null()
{
$group = Factory::of('group')->create();
$group = Factory::of('Group')->create();

$items = Factory::of('Item')
->times(4)
Expand All @@ -48,7 +48,7 @@ public function the_populate_command_does_not_update_values_that_are_already_ass
/** @test */
public function the_populate_command_populates_every_empty_sequence_value()
{
$group = Factory::of('group')->create();
$group = Factory::of('Group')->create();

$firstItem = Factory::of('Item')->create(['group_id' => $group->id]);
$secondItem = Factory::of('Item')->create(['group_id' => $group->id]);
Expand All @@ -58,7 +58,7 @@ public function the_populate_command_populates_every_empty_sequence_value()

$thirdItem->update(['position' => null]);

$this->assertNotNull($firstItem->position);
$this->assertEquals(1, $firstItem->refresh()->position);
$this->assertNull($secondItem->position);
$this->assertNull($thirdItem->position);

Expand Down
23 changes: 0 additions & 23 deletions tests/Unit/DeleteObjectWithoutSequencingTest.php

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Facades\Gurgentil\LaravelEloquentSequencer\Tests\Factories\Factory;
use Gurgentil\LaravelEloquentSequencer\Tests\TestCase;

class RearrangeSequenceWhenAnObjectIsDeletedTest extends TestCase
class DeleteSequenceableTest extends TestCase
{
/** @test */
public function it_assigns_1_to_the_second_object_and_two_to_the_third_one_when_the_first_one_is_deleted()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Gurgentil\LaravelEloquentSequencer\Exceptions\SequenceValueOutOfBoundsException;
use Gurgentil\LaravelEloquentSequencer\Tests\TestCase;

class CreateObjectWithSequenceValueOutOfBoundsTest extends TestCase
class SequenceValueOutOfBoundsOnCreateTest extends TestCase
{
/** @test */
public function it_throws_an_exception_when_the_object_created_has_a_sequence_value_that_is_negative()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Gurgentil\LaravelEloquentSequencer\Exceptions\SequenceValueOutOfBoundsException;
use Gurgentil\LaravelEloquentSequencer\Tests\TestCase;

class UpdateObjectWithSequenceValueOutOfBoundsText extends TestCase
class SequenceValueOutOfBoundsOnUpdateTest extends TestCase
{
/** @test */
public function it_throws_an_exception_when_the_object_updated_has_a_sequence_value_that_is_negative()
Expand Down Expand Up @@ -72,7 +72,7 @@ public function it_throws_an_exception_when_the_updated_object_has_a_sequence_va

$this->expectException(SequenceValueOutOfBoundsException::class);

$item->update(['position' => 3]);
$item->update(['position' => 4]);
}

/** @test */
Expand Down
33 changes: 33 additions & 0 deletions tests/Unit/SequencingStrategyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Gurgentil\LaravelEloquentSequencer\Tests\Unit;

use Gurgentil\LaravelEloquentSequencer\SequencingStrategy;
use Gurgentil\LaravelEloquentSequencer\Tests\TestCase;

class SequencingStrategyTest extends TestCase
{
/** @test **/
public function it_can_be_set_to_always()
{
$this->assertEquals('always', SequencingStrategy::ALWAYS);
}

/** @test **/
public function it_can_be_set_to_on_create()
{
$this->assertEquals('on_create', SequencingStrategy::ON_CREATE);
}

/** @test **/
public function it_can_be_set_to_on_update()
{
$this->assertEquals('on_update', SequencingStrategy::ON_UPDATE);
}

/** @test **/
public function it_can_be_set_to_never()
{
$this->assertEquals('never', SequencingStrategy::NEVER);
}
}
62 changes: 62 additions & 0 deletions tests/Unit/StrategyAlwaysTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Gurgentil\LaravelEloquentSequencer\Tests\Unit;

use Facades\Gurgentil\LaravelEloquentSequencer\Tests\Factories\Factory;
use Gurgentil\LaravelEloquentSequencer\SequencingStrategy;
use Gurgentil\LaravelEloquentSequencer\Tests\TestCase;

class StrategyAlwaysTest extends TestCase
{
/**
* @test
* @group Strategy
*/
public function strategy_set_to_always_works_on_create()
{
config(['eloquentsequencer.strategy' => SequencingStrategy::ALWAYS]);

$group = Factory::of('Group')->create();

$firstItem = Factory::of('Item')->create(['group_id' => $group->id]);

$this->assertEquals(1, $firstItem->refresh()->position);
}

/**
* @test
* @group Strategy
*/
public function strategy_set_to_always_works_on_update()
{
$group = Factory::of('group')->create();

$firstItem = Factory::of('item')->create(['group_id' => $group->id]);
$secondItem = Factory::of('item')->create(['group_id' => $group->id]);

config(['eloquentsequencer.strategy' => SequencingStrategy::ALWAYS]);

$secondItem->update(['position' => 1]);

$this->assertEquals(2, $firstItem->refresh()->position);
$this->assertEquals(1, $secondItem->refresh()->position);
}

/**
* @test
* @group Strategy
*/
public function strategy_set_to_always_works_on_delete()
{
$group = Factory::of('Group')->create();

$firstItem = Factory::of('Item')->create(['group_id' => $group->id]);
$secondItem = Factory::of('Item')->create(['group_id' => $group->id]);

config(['eloquentsequencer.strategy' => SequencingStrategy::ALWAYS]);

$firstItem->delete();

$this->assertEquals(1, $secondItem->refresh()->position);
}
}
Loading

0 comments on commit 3422a64

Please sign in to comment.