Skip to content

Commit 810d0c9

Browse files
authored
Implement MassPrunable without chunks limit (#2598)
Custom implementation of MassPrunable is required to prevent using the limit. #2591 added an exception when limited is used because MongoDB Delete operation doesn't support it. - MassPrunable::pruneAll() is called by the command model:prune. - Using the parent trait is required because it's used to detect prunable models. - Prunable feature was introducted in Laravel 8.x by laravel/framework#37889. Users have to be aware that MassPrunable can break relationships as it doesn't call model methods to remove links.
1 parent 51bbdf7 commit 810d0c9

File tree

6 files changed

+130
-0
lines changed

6 files changed

+130
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file.
2222
- Fix Query on `whereDate`, `whereDay`, `whereMonth`, `whereYear`, `whereTime` to use MongoDB operators [#2570](https://github.com/mongodb/laravel-mongodb/pull/2376) by [@Davpyu](https://github.com/Davpyu) and [@GromNaN](https://github.com/GromNaN).
2323
- `Model::unset()` does not persist the change. Call `Model::save()` to persist the change [#2578](https://github.com/mongodb/laravel-mongodb/pull/2578) by [@GromNaN](https://github.com/GromNaN).
2424
- Support delete one document with `Query\Builder::limit(1)->delete()` [#2591](https://github.com/mongodb/laravel-mongodb/pull/2591) by [@GromNaN](https://github.com/GromNaN)
25+
- Add trait `MongoDB\Laravel\Eloquent\MassPrunable` to replace the Eloquent trait on MongoDB models [#2598](https://github.com/mongodb/laravel-mongodb/pull/2598) by [@GromNaN](https://github.com/GromNaN)
2526

2627
## [3.9.2] - 2022-09-01
2728

README.md

+22
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ It is compatible with Laravel 10.x. For older versions of Laravel, please refer
4444
- [Cross-Database Relationships](#cross-database-relationships)
4545
- [Authentication](#authentication)
4646
- [Queues](#queues)
47+
- [Prunable](#prunable)
4748
- [Upgrading](#upgrading)
4849
- [Upgrading from version 2 to 3](#upgrading-from-version-2-to-3)
4950
- [Security contact information](#security-contact-information)
@@ -1189,14 +1190,35 @@ Add the service provider in `config/app.php`:
11891190
MongoDB\Laravel\MongodbQueueServiceProvider::class,
11901191
```
11911192

1193+
### Prunable
1194+
1195+
`Prunable` and `MassPrunable` traits are Laravel features to automatically remove models from your database. You can use
1196+
`Illuminate\Database\Eloquent\Prunable` trait to remove models one by one. If you want to remove models in bulk, you need
1197+
to use the `MongoDB\Laravel\Eloquent\MassPrunable` trait instead: it will be more performant but can break links with
1198+
other documents as it does not load the models.
1199+
1200+
1201+
```php
1202+
use MongoDB\Laravel\Eloquent\Model;
1203+
use MongoDB\Laravel\Eloquent\MassPrunable;
1204+
1205+
class Book extends Model
1206+
{
1207+
use MassPrunable;
1208+
}
1209+
```
1210+
11921211
Upgrading
11931212
---------
11941213

11951214
#### Upgrading from version 3 to 4
11961215

11971216
Change project name in composer.json to `mongodb/laravel` and run `composer update`.
1217+
11981218
Change namespace from `Jenssegers\Mongodb` to `MongoDB\Laravel` in your models and config.
11991219

1220+
Replace `Illuminate\Database\Eloquent\MassPrunable` with `MongoDB\Laravel\Eloquent\MassPrunable` in your models.
1221+
12001222
## Security contact information
12011223

12021224
To report a security vulnerability, follow [these steps](https://tidelift.com/security).

src/Eloquent/MassPrunable.php

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace MongoDB\Laravel\Eloquent;
4+
5+
use Illuminate\Database\Eloquent\MassPrunable as EloquentMassPrunable;
6+
use Illuminate\Database\Events\ModelsPruned;
7+
8+
trait MassPrunable
9+
{
10+
use EloquentMassPrunable;
11+
12+
/**
13+
* Prune all prunable models in the database.
14+
*
15+
* @see \Illuminate\Database\Eloquent\MassPrunable::pruneAll()
16+
*/
17+
public function pruneAll(): int
18+
{
19+
$query = $this->prunable();
20+
$total = in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))
21+
? $query->forceDelete()
22+
: $query->delete();
23+
24+
event(new ModelsPruned(static::class, $total));
25+
26+
return $total;
27+
}
28+
}

tests/Eloquent/MassPrunableTest.php

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Eloquent;
6+
7+
use Illuminate\Database\Console\PruneCommand;
8+
use Illuminate\Database\Eloquent\MassPrunable;
9+
use Illuminate\Database\Eloquent\Prunable;
10+
use MongoDB\Laravel\Tests\Models\Soft;
11+
use MongoDB\Laravel\Tests\Models\User;
12+
use MongoDB\Laravel\Tests\TestCase;
13+
14+
class MassPrunableTest extends TestCase
15+
{
16+
public function tearDown(): void
17+
{
18+
User::truncate();
19+
Soft::truncate();
20+
}
21+
22+
public function testPruneWithQuery(): void
23+
{
24+
$this->assertTrue($this->isPrunable(User::class));
25+
26+
User::insert([
27+
['name' => 'John Doe', 'age' => 35],
28+
['name' => 'Jane Doe', 'age' => 32],
29+
['name' => 'Tomy Doe', 'age' => 11],
30+
]);
31+
32+
$model = new User();
33+
$total = $model->pruneAll();
34+
$this->assertEquals(2, $total);
35+
$this->assertEquals(1, User::count());
36+
}
37+
38+
public function testPruneSoftDelete(): void
39+
{
40+
$this->assertTrue($this->isPrunable(Soft::class));
41+
42+
Soft::insert([
43+
['name' => 'John Doe'],
44+
['name' => 'Jane Doe'],
45+
]);
46+
47+
$model = new Soft();
48+
$total = $model->pruneAll();
49+
$this->assertEquals(2, $total);
50+
$this->assertEquals(0, Soft::count());
51+
$this->assertEquals(0, Soft::withTrashed()->count());
52+
}
53+
54+
/**
55+
* @see PruneCommand::isPrunable()
56+
*/
57+
protected function isPrunable($model)
58+
{
59+
$uses = class_uses_recursive($model);
60+
61+
return in_array(Prunable::class, $uses) || in_array(MassPrunable::class, $uses);
62+
}
63+
}

tests/Models/Soft.php

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace MongoDB\Laravel\Tests\Models;
66

7+
use MongoDB\Laravel\Eloquent\Builder;
8+
use MongoDB\Laravel\Eloquent\MassPrunable;
79
use MongoDB\Laravel\Eloquent\Model as Eloquent;
810
use MongoDB\Laravel\Eloquent\SoftDeletes;
911

@@ -15,9 +17,15 @@
1517
class Soft extends Eloquent
1618
{
1719
use SoftDeletes;
20+
use MassPrunable;
1821

1922
protected $connection = 'mongodb';
2023
protected $collection = 'soft';
2124
protected static $unguarded = true;
2225
protected $casts = ['deleted_at' => 'datetime'];
26+
27+
public function prunable(): Builder
28+
{
29+
return $this->newQuery();
30+
}
2331
}

tests/Models/User.php

+8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
use Illuminate\Database\Eloquent\Casts\Attribute;
1313
use Illuminate\Notifications\Notifiable;
1414
use Illuminate\Support\Str;
15+
use MongoDB\Laravel\Eloquent\Builder;
1516
use MongoDB\Laravel\Eloquent\HybridRelations;
17+
use MongoDB\Laravel\Eloquent\MassPrunable;
1618
use MongoDB\Laravel\Eloquent\Model as Eloquent;
1719

1820
/**
@@ -35,6 +37,7 @@ class User extends Eloquent implements AuthenticatableContract, CanResetPassword
3537
use CanResetPassword;
3638
use HybridRelations;
3739
use Notifiable;
40+
use MassPrunable;
3841

3942
protected $connection = 'mongodb';
4043
protected $casts = [
@@ -106,4 +109,9 @@ protected function username(): Attribute
106109
set: fn ($value) => Str::slug($value)
107110
);
108111
}
112+
113+
public function prunable(): Builder
114+
{
115+
return $this->where('age', '>', 18);
116+
}
109117
}

0 commit comments

Comments
 (0)