Skip to content

Commit

Permalink
Add helper methods updateWithUserstamps, deleteWithUserstamps
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmcdonald-uk committed Oct 16, 2019
1 parent 892a158 commit 4d15ebc
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 49 deletions.
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ $model -> stopUserstamping(); // stops userstamps being maintained on the model
$model -> startUserstamping(); // resumes userstamps being maintained on the model
```

## Limitations
## Workarounds

This package works by by hooking into Eloquent's model event listeners, and is subject to the same limitations of all such listeners.

When you make changes to models that bypass Eloquent, the event listeners won't be fired and userstamps will not be updated.

Commonly this would happen if mass updating or deleting models, or their relations.
Commonly this will happen if bulk updating or deleting models, or their relations.

In this example, model relations are updated via Eloquent and userstamps **will** be maintained:

Expand All @@ -104,14 +104,34 @@ $model->foos->each(function ($item) {
});
```

However, in this example, model relations are mass updated and bypass Eloquent. Userstamps **will not** be maintained:
However in this example, model relations are bulk updated and bypass Eloquent. Userstamps **will not** be maintained:

```php
$model->foos()->update([
'bar' => 'x',
]);
```

As a workaroud to this issue two helper methods are available - `updateWithUserstamps` and `deleteWithUserstamps`. Their behaviour is identical to `update` and `delete`, but they ensure the `updated_by` and `deleted_by` properties are maintained on the model.

You generally won't have to use these methods, unless making bulk updates that bypass Eloquent events.

In this example, models are bulk updated and userstamps **will not** be maintained:

```php
$model->where('name', 'foo')->update([
'name' => 'bar',
]);
```

However in this example, models are bulk updated using the helper method and userstamps **will** be maintained:

```php
$model->where('name', 'foo')->updateWithUserstamps([
'name' => 'bar',
]);
```

## Sponsors

<a href="https://wildside.uk">
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wildside/userstamps",
"description": "Eloquent trait to automatically maintain created_by and updated_by (and deleted_by when using SoftDeletes) on your models",
"description": "Laravel Userstamps provides an Eloquent trait which automatically maintains `created_by` and `updated_by` columns on your model, populated by the currently authenticated user in your application.",
"license": "MIT",
"keywords": [
"laravel",
Expand Down
2 changes: 2 additions & 0 deletions src/Userstamps.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ trait Userstamps
*/
public static function bootUserstamps()
{
static::addGlobalScope(new UserstampsScope);

static::registerListeners();
}

Expand Down
53 changes: 53 additions & 0 deletions src/UserstampsScope.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Wildside\Userstamps;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class UserstampsScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
return $builder;
}

/**
* Extend the query builder with the needed functions.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
public function extend(Builder $builder)
{
$builder->macro('updateWithUserstamps', function (Builder $builder, $values) {
if (! $builder->getModel()->isUserstamping() || is_null(auth()->id())) {
return $builder->update($values);
}

$values[$builder->getModel()->getUpdatedByColumn()] = auth()->id();

return $builder->update($values);
});

$builder->macro('deleteWithUserstamps', function (Builder $builder) {
if (! $builder->getModel()->isUserstamping() || is_null(auth()->id())) {
return $builder->delete();
}

$builder->update([
$builder->getModel()->getDeletedByColumn() => auth()->id(),
]);

return $builder->delete();
});
}
}
113 changes: 68 additions & 45 deletions tests/UserstampsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ protected static function handleSetUp()
Schema::create('foos', function (Blueprint $table) {
$table->increments('id');
$table->string('bar');
$table->timestamps();
$table->softDeletes();
$table->unsignedBigInteger('created_by')->nullable();
$table->unsignedBigInteger('updated_by')->nullable();
Expand All @@ -53,16 +54,6 @@ protected static function handleSetUp()
$table->unsignedBigInteger('alt_deleted_by')->nullable();
});

Schema::create('bars', function (Blueprint $table) {
$table->increments('id');
$table->unsignedBigInteger('foo_id');
$table->string('foo');
$table->softDeletes();
$table->unsignedBigInteger('created_by')->nullable();
$table->unsignedBigInteger('updated_by')->nullable();
$table->unsignedBigInteger('deleted_by')->nullable();
});

TestUser::create([
'id' => 1,
]);
Expand Down Expand Up @@ -100,19 +91,6 @@ protected function createFooWithNullColumnNames()
]);
}

protected function createFooWithBars()
{
$foo = $this->createFoo();

$foo->bars()->saveMany([
new Bar([
'foo' => 1,
]),
]);

return $foo;
}

public function testCreatedByAndUpdatedByIsSetOnNewModelWhenUserIsPresent()
{
$this->app['auth']->loginUsingId(1);
Expand Down Expand Up @@ -345,6 +323,73 @@ public function testDestroyerMethodWorks()

$this->assertEquals($user, $foo->destroyer);
}

public function testUpdateWithUserstampsMethod()
{
$this->app['auth']->loginUsingId(1);

$this->createFoo();

$this->app['auth']->loginUsingId(2);

Foo::where('bar', 'foo')->updateWithUserstamps([
'bar' => 'bar',
]);

$this->assertEquals(2, Foo::first()->updated_by);
}

public function testDeleteWithUserstampsMethod()
{
$this->app['auth']->loginUsingId(1);

$this->createFooWithSoftDeletes();

$this->app['auth']->loginUsingId(2);

FooWithSoftDeletes::where('bar', 'foo')->deleteWithUserstamps();

$this->assertEquals(2, FooWithSoftDeletes::withTrashed()->first()->deleted_by);
}

public function testDeleteWithUserstampsMethodDoesntTouchUpdatedBy()
{
$this->app['auth']->loginUsingId(1);

$foo = $this->createFooWithSoftDeletes();
$updatedAt = $foo->updated_at;

$this->app['auth']->loginUsingId(2);

FooWithSoftDeletes::where('bar', 'foo')->deleteWithUserstamps();

$foo = FooWithSoftDeletes::withTrashed()->first();

$this->assertEquals(1, $foo->updated_by);
$this->assertEquals(2, $foo->deleted_by);
}

public function testBuilderMethodWorksWithCustomColumnNames()
{
$this->app['auth']->loginUsingId(1);

$this->createFooWithCustomColumnNames();

$this->app['auth']->loginUsingId(2);

FooWithCustomColumnNames::where('bar', 'foo')->updateWithUserstamps([
'bar' => 'bar',
]);

FooWithCustomColumnNames::where('bar', 'bar')->deleteWithUserstamps();

$foo = FooWithCustomColumnNames::withTrashed()->where('bar', 'bar')->first();

$this->assertNull($foo->updated_by);
$this->assertNull($foo->deleted_by);
$this->assertEquals(2, $foo->alt_updated_by);
$this->assertEquals(2, $foo->alt_deleted_by);
}
}

class Foo extends Model
Expand All @@ -354,19 +399,13 @@ class Foo extends Model
public $table = 'foos';
public $timestamps = false;
protected $guarded = [];

public function bars()
{
return $this->hasMany(Bar::class)->withTrashed();
}
}

class FooWithSoftDeletes extends Model
{
use SoftDeletes, Userstamps;

public $table = 'foos';
public $timestamps = false;
protected $guarded = [];
}

Expand All @@ -375,7 +414,6 @@ class FooWithCustomColumnNames extends Model
use SoftDeletes, Userstamps;

public $table = 'foos';
public $timestamps = false;
protected $guarded = [];

const CREATED_BY = 'alt_created_by';
Expand All @@ -388,28 +426,13 @@ class FooWithNullColumnNames extends Model
use SoftDeletes, Userstamps;

public $table = 'foos';
public $timestamps = false;
protected $guarded = [];

const CREATED_BY = null;
const UPDATED_BY = null;
const DELETED_BY = null;
}

class Bar extends Model
{
use SoftDeletes, Userstamps;

public $table = 'bars';
public $timestamps = false;
protected $guarded = [];

public function foo()
{
return $this->belongsTo(Foo::class);
}
}

class TestUser extends Authenticatable
{
public $table = 'users';
Expand Down

0 comments on commit 4d15ebc

Please sign in to comment.