Skip to content

Commit

Permalink
Merge pull request #3 from ajGulati05/versionable
Browse files Browse the repository at this point in the history
Versionable
  • Loading branch information
ajGulati05 committed Jun 1, 2023
2 parents 4b6afd6 + 6fc0cc9 commit 1ac9523
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 72 deletions.
14 changes: 0 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,7 @@
[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/ajgulati05/laravel-concurrency-control/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/ajgulati05/laravel-concurrency-control/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain)
[![Total Downloads](https://img.shields.io/packagist/dt/ajgulati05/laravel-concurrency-control.svg?style=flat-square)](https://packagist.org/packages/ajgulati05/laravel-concurrency-control)

This is where your description should go. Limit it to a paragraph or two. Consider adding a small example.

## Support us

[<img src="https://github-ads.s3.eu-central-1.amazonaws.com/laravel-concurrency-control.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/laravel-concurrency-control)

We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).

## Installation

Expand Down Expand Up @@ -49,12 +41,6 @@ Optionally, you can publish the views using
php artisan vendor:publish --tag="laravel-concurrency-control-views"
```

## Usage

```php
$laravelConcurrencyControl = new AjGulati05\LaravelConcurrencyControl();
echo $laravelConcurrencyControl->echoPhrase('Hello, AjGulati05!');
```

## Testing

Expand Down
2 changes: 1 addition & 1 deletion config/concurrency-control.php → config/concurrency.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
/*
* The column on the model to be used for validating wether or not the data is stale
*/
'version_datetime' => 'updated_date',
'version_datetime' => 'updated_at',
];
Empty file removed resources/views/.gitkeep
Empty file.
86 changes: 57 additions & 29 deletions src/Versioned.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,69 @@ trait Versioned
public static function bootVersioned()
{
// Add versionId when converting to array or json

static::saving(function ($model) {
$model->append('versionId');
});
static::retrieved(function ($model) {
$model->append('versionId');
});
}

// Add versionId to model
public function getVersionIdAttribute()
{
return $this->updated_at->timestamp;
}
// Add versionId to model
public function getVersionIdAttribute()
{
$column = $this->getVersionedColumn();

// Update the model
public function update(array $attributes = [], array $options = [])
{
// Expect versionId in attributes
if (! isset($attributes['versionId'])) {
abort(422, 'versionId is required');
}

// Match versionId with current updated_at timestamp
if (Carbon::createFromTimestamp($attributes['versionId'])->ne($this->updated_at)) {
abort(409, 'The resource has been updated since last retrieval. Please retrieve the resource again.');
}

// Update the model
return $this->update($attributes, $options);
}

// Update the model without expecting versionId
public function updateWithoutExpectingVersionId(array $attributes = [], array $options = [])
{
// Remove versionId from attributes
unset($attributes['versionId']);
return $this->{$column}->timestamp;
}

// Update the model
return $this->update($attributes, $options);
}

// Get the versioned column name from configuration
public function getVersionedColumn(): string
{
$column = config('concurrency.version_datetime', 'updated_at');
$this->validateVersionedColumn($column);

return $column;
}

public function validateVersionedColumn($column)
{
if (! $this->{$column} instanceof Carbon) {
throw new \RuntimeException("The versioned column '{$column}' must be an instance of 'Carbon'.");
}
}

// Update the model
public function update(array $attributes = [], array $options = [])
{
// Expect versionId in attributes
if (! isset($attributes['versionId'])) {
abort(422, 'versionId is required');
}

// Match versionId with current versioned column timestamp
$column = $this->getVersionedColumn();
if (Carbon::createFromTimestamp($attributes['versionId'])->ne($this->{$column})) {
abort(409, 'The resource has been updated since last retrieval. Please retrieve the resource again.');
}

// Remove versionId from attributes
unset($attributes['versionId']);

// Update the model
return parent::update($attributes, $options);
}

// Update the model without expecting versionId
public function updateWithoutExpectingVersionId(array $attributes = [], array $options = [])
{
// Remove versionId from attributes
unset($attributes['versionId']);

// Update the model
return parent::update($attributes, $options);
}
}
5 changes: 0 additions & 5 deletions tests/ArchTest.php

This file was deleted.

5 changes: 0 additions & 5 deletions tests/ExampleTest.php

This file was deleted.

5 changes: 3 additions & 2 deletions tests/Pest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use AjGulati\Concurrency\Tests\TestCase;
use AjGulati05\LaravelConcurrencyControl\Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(TestCase::class)->in(__DIR__);
uses(TestCase::class, RefreshDatabase::class)->in('Feature', 'Unit');
34 changes: 18 additions & 16 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,37 @@

namespace AjGulati05\LaravelConcurrencyControl\Tests;

use AjGulati05\LaravelConcurrencyControl\LaravelConcurrencyControlServiceProvider;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Schema\Blueprint;
use Orchestra\Testbench\TestCase as Orchestra;

class TestCase extends Orchestra
{
protected function setUp(): void
{
parent::setUp();
$this->setUpDatabase();

Factory::guessFactoryNamesUsing(
fn (string $modelName) => 'AjGulati05\\LaravelConcurrencyControl\\Database\\Factories\\'.class_basename($modelName).'Factory'
);
}

protected function getPackageProviders($app)
protected function getEnvironmentSetUp($app)
{
return [
LaravelConcurrencyControlServiceProvider::class,
];
$app['config']->set('database.default', 'sqlite');
$app['config']->set('database.connections.sqlite', [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
]);
}

public function getEnvironmentSetUp($app)
/**
* @param Application $app
*/
protected function setUpDatabase()
{
config()->set('database.default', 'testing');

/*
$migration = include __DIR__.'/../database/migrations/create_laravel-concurrency-control_table.php.stub';
$migration->up();
*/
$this->app->get('db')->connection()->getSchemaBuilder()->create('test_models', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->nullable();
$table->timestamps();
});
}
}
15 changes: 15 additions & 0 deletions tests/TestModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace AjGulati05\LaravelConcurrencyControl\Tests;

use AjGulati05\LaravelConcurrencyControl\Versioned;
use Illuminate\Database\Eloquent\Model;

class TestModel extends Model
{
use Versioned;

public $table = 'test_models';

protected $guarded = [];
}
23 changes: 23 additions & 0 deletions tests/TestModelResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace AjGulati05\LaravelConcurrencyControl\Tests;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class TestModelResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,

];
}
}
81 changes: 81 additions & 0 deletions tests/Unit/VersionedTraitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace AjGulati05\LaravelConcurrencyControl\Tests;

use AjGulati05\LaravelConcurrencyControl\Versioned;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use RuntimeException;

beforeEach(function () {

$this->model = new class extends \Illuminate\Database\Eloquent\Model
{
use Versioned;

protected $guarded = [];

protected $table = 'test';
};

DB::statement('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, created_at DATETIME,updated_at DATETIME)');
});

it('boots versioned', function () {

$this->model->fill(['name' => 'Test', 'updated_at' => Carbon::now()])->save();
$this->assertArrayHasKey('versionId', $this->model->toArray());
});

it('checks the version attribute as timestamp is equal', function () {

$now = Carbon::now();
$this->model->fill(['name' => 'Test', 'updated_at' => $now])->save();
$this->assertEquals($now->timestamp, $this->model->versionId);
});

it('Tests the getVersionedColumn function', function () {

Config::set('concurrency.version_datetime', 'name');
$this->expectException(RuntimeException::class);
$this->model->getVersionedColumn();
});

it('Tests the validateVersionedColumn function ', function () {

$this->model2 = new class extends \Illuminate\Database\Eloquent\Model
{
use Versioned;

protected $guarded = [];

protected $table = 'test';

public function publicValidateVersionedColumn($column)
{
return $this->validateVersionedColumn($column);
}
};

$this->expectException(RuntimeException::class);
$this->model2->publicValidateVersionedColumn('unknown_column');

});

it('Tests the update function ', function () {
$now = Carbon::now();
$this->model->fill(['name' => 'Test', 'updated_at' => $now])->save();

$this->model->update(['name' => 'New Test', 'versionId' => $now->timestamp]);

$this->assertEquals('New Test', $this->model->name);
});

it('Tests the updateWithoutExpectingVersionId function ', function () {
$this->model->fill(['name' => 'Test', 'updated_at' => Carbon::now()])->save();

$this->model->updateWithoutExpectingVersionId(['name' => 'New Test']);

$this->assertEquals('New Test', $this->model->name);
});

0 comments on commit 1ac9523

Please sign in to comment.