Skip to content

Commit f92602d

Browse files
Support for auditSync and auditDetach on Auditable custom Pivot class (#954)
* support for auditSync and auditDetach on Auditable Custom Pivot Class * keep default behavior if pivot class is not auditable * formatting * tests for auditSync and auditDetach with auditable custom pivot class * Apply suggestions from code review * spacing * test model factory update --------- Co-authored-by: Will Power <1619102+willpower232@users.noreply.github.com>
1 parent 1317c83 commit f92602d

File tree

7 files changed

+185
-2
lines changed

7 files changed

+185
-2
lines changed

src/Auditable.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Database\Eloquent\Model;
66
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
77
use Illuminate\Database\Eloquent\Relations\MorphMany;
8+
use Illuminate\Database\Eloquent\Relations\Pivot;
89
use Illuminate\Database\Eloquent\SoftDeletes;
910
use Illuminate\Support\Arr;
1011
use Illuminate\Support\Collection;
@@ -13,6 +14,7 @@
1314
use Illuminate\Support\Facades\Event;
1415
use OwenIt\Auditing\Contracts\AttributeEncoder;
1516
use OwenIt\Auditing\Contracts\AttributeRedactor;
17+
use OwenIt\Auditing\Contracts\Auditable as ContractsAuditable;
1618
use OwenIt\Auditing\Contracts\Resolver;
1719
use OwenIt\Auditing\Events\AuditCustom;
1820
use OwenIt\Auditing\Exceptions\AuditableTransitionException;
@@ -709,7 +711,17 @@ public function auditDetach(string $relationName, $ids = null, $touch = true, $c
709711
}
710712

711713
$old = $relationCall->get($columns);
712-
$results = $relationCall->detach($ids, $touch);
714+
715+
$pivotClass = $relationCall->getPivotClass();
716+
717+
if ($pivotClass !== Pivot::class && is_a($pivotClass, ContractsAuditable::class, true)) {
718+
$results = $pivotClass::withoutAuditing(function () use ($relationCall, $ids, $touch) {
719+
return $relationCall->detach($ids, $touch);
720+
});
721+
} else {
722+
$results = $relationCall->detach($ids, $touch);
723+
}
724+
713725
$new = $relationCall->get($columns);
714726

715727
$this->dispatchRelationAuditEvent($relationName, 'detach', $old, $new);
@@ -737,7 +749,16 @@ public function auditSync(string $relationName, $ids, $detaching = true, $column
737749
}
738750

739751
$old = $relationCall->get($columns);
740-
$changes = $relationCall->sync($ids, $detaching);
752+
753+
$pivotClass = $relationCall->getPivotClass();
754+
755+
if ($pivotClass !== Pivot::class && is_a($pivotClass, ContractsAuditable::class, true)) {
756+
$changes = $pivotClass::withoutAuditing(function () use ($relationCall, $ids, $detaching) {
757+
return $relationCall->sync($ids, $detaching);
758+
});
759+
} else {
760+
$changes = $relationCall->sync($ids, $detaching);
761+
}
741762

742763
if (collect($changes)->flatten()->isEmpty()) {
743764
$old = $new = collect([]);

tests/Functional/AuditingTest.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use OwenIt\Auditing\Tests\Models\ArticleCustomAuditMorph;
2121
use OwenIt\Auditing\Tests\Models\ArticleExcludes;
2222
use OwenIt\Auditing\Tests\Models\Category;
23+
use OwenIt\Auditing\Tests\Models\Group;
2324
use OwenIt\Auditing\Tests\Models\User;
2425

2526
class AuditingTest extends AuditingTestCase
@@ -963,4 +964,72 @@ public function test_can_audit_custom_audit_model_implementation(): void
963964
$this->assertNotEmpty($audit);
964965
$this->assertSame(get_class($audit), \OwenIt\Auditing\Tests\Models\CustomAudit::class);
965966
}
967+
968+
/**
969+
* @test
970+
* @return void
971+
*/
972+
public function itWillAuditSyncWithAuditablePivotClass()
973+
{
974+
$group = Group::factory()->create();
975+
$user = User::factory()->create();
976+
977+
$no_of_audits_before = Audit::where('auditable_type', User::class)->count();
978+
979+
$user->auditSync('groups', [$group->getKey() => ["role" => "admin"]]);
980+
981+
$no_of_audits_mid = Audit::where('auditable_type', User::class)->count();
982+
$memberRole = $user->groups()->first()->pivot->role;
983+
984+
$user->auditSync('groups', []);
985+
986+
$no_of_audits_after = Audit::where('auditable_type', User::class)->count();
987+
988+
$this->assertSame("admin", $memberRole);
989+
$this->assertGreaterThan($no_of_audits_before, $no_of_audits_mid);
990+
$this->assertGreaterThan($no_of_audits_mid, $no_of_audits_after);
991+
}
992+
993+
/**
994+
* @test
995+
* @return void
996+
*/
997+
public function itWillAuditAttachWithAuditablePivotClass()
998+
{
999+
$group = Group::factory()->create();
1000+
$user = User::factory()->create();
1001+
1002+
$no_of_audits_before = Audit::where('auditable_type', User::class)->count();
1003+
1004+
$user->auditAttach('groups', $group);
1005+
1006+
$attachedGroup = $user->groups()->first()->getKey();
1007+
$no_of_audits_after = Audit::where('auditable_type', User::class)->count();
1008+
1009+
$this->assertSame($group->getKey(), $attachedGroup);
1010+
$this->assertGreaterThan($no_of_audits_before, $no_of_audits_after);
1011+
}
1012+
1013+
/**
1014+
* @test
1015+
* @return void
1016+
*/
1017+
public function itWillAuditDetachWithAuditablePivotClass()
1018+
{
1019+
$group = Group::factory()->create();
1020+
$user = User::factory()->create();
1021+
1022+
$user->groups()->attach($group);
1023+
1024+
$attachedGroup = $user->groups()->first()->getKey();
1025+
$no_of_audits_before = Audit::where('auditable_type', User::class)->count();
1026+
1027+
$detachedGroups = $user->auditDetach('groups', $group);
1028+
1029+
$no_of_audits_after = Audit::where('auditable_type', User::class)->count();
1030+
1031+
$this->assertSame($group->getKey(), $attachedGroup);
1032+
$this->assertSame(1, $detachedGroups);
1033+
$this->assertGreaterThan($no_of_audits_before, $no_of_audits_after);
1034+
}
9661035
}

tests/Models/Group.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace OwenIt\Auditing\Tests\Models;
4+
5+
use Illuminate\Database\Eloquent\Factories\HasFactory;
6+
use OwenIt\Auditing\Tests\database\factories\GroupFactory;
7+
8+
class Group extends \Illuminate\Database\Eloquent\Model
9+
{
10+
use HasFactory;
11+
protected static string $factory = GroupFactory::class;
12+
}

tests/Models/GroupMember.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace OwenIt\Auditing\Tests\Models;
4+
5+
use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot;
6+
use OwenIt\Auditing\Auditable;
7+
use OwenIt\Auditing\Contracts\Auditable as ContractsAuditable;
8+
9+
class GroupMember extends \Illuminate\Database\Eloquent\Model implements ContractsAuditable
10+
{
11+
use AsPivot;
12+
use Auditable;
13+
14+
public $fillable = [
15+
"role"
16+
];
17+
}

tests/Models/User.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ public function getFirstNameAttribute(string $value): string
3030
{
3131
return ucfirst($value);
3232
}
33+
34+
public function groups()
35+
{
36+
return $this->belongsToMany(Group::class, 'group_members', 'user_id', 'group_id')->using(GroupMember::class)->withPivot('id','role');
37+
}
3338
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace OwenIt\Auditing\Tests\database\factories;
4+
5+
use Illuminate\Database\Eloquent\Factories\Factory;
6+
use OwenIt\Auditing\Tests\Models\Group;
7+
8+
class GroupFactory extends Factory
9+
{
10+
protected $model = Group::class;
11+
12+
public function definition()
13+
{
14+
return [
15+
'name' => fake()->unique()->colorName(),
16+
];
17+
}
18+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
class CreateGroupsTestTable extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*
12+
* @return void
13+
*/
14+
public function up()
15+
{
16+
Schema::create('groups', function (Blueprint $table) {
17+
$table->increments('id');
18+
$table->string('name');
19+
$table->timestamps();
20+
});
21+
22+
Schema::create('group_members', function (Blueprint $table) {
23+
$table->increments('id');
24+
$table->unsignedBigInteger('user_id');
25+
$table->unsignedBigInteger('group_id');
26+
$table->enum('role',['member','admin'])->default('member');
27+
$table->timestamps();
28+
});
29+
}
30+
31+
/**
32+
* Reverse the migrations.
33+
*
34+
* @return void
35+
*/
36+
public function down()
37+
{
38+
Schema::drop('group_members');
39+
Schema::drop('groups');
40+
}
41+
}

0 commit comments

Comments
 (0)