Skip to content

Commit

Permalink
feat: add softDelete
Browse files Browse the repository at this point in the history
  • Loading branch information
JoaoFerrazfs committed Aug 16, 2023
1 parent 90c61cb commit 6c762b3
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 2 deletions.
24 changes: 22 additions & 2 deletions src/DataMapper/DataMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Mongolid\DataMapper;

use DateTime;
use InvalidArgumentException;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Collection;
use Mongolid\Container\Container;
use Mongolid\Cursor\CursorInterface;
Expand All @@ -17,6 +19,7 @@
use Mongolid\Schema\Schema;
use Mongolid\Util\ObjectIdUtils;
use Mongolid\Connection\Connection;
Use Mongolid\Util\SoftDeleteQueries;

/**
* The DataMapper class will abstract how an Entity is persisted and retrieved
Expand Down Expand Up @@ -221,6 +224,11 @@ public function update($entity, array $options = []): bool
*/
public function delete($entity, array $options = []): bool
{
if ($entity->enabledSoftDeletes ?? false) {
return $this->executeSoftDelete($entity, $options);
}


if (false === $this->fireEvent('deleting', $entity, true)) {
return false;
}
Expand Down Expand Up @@ -259,13 +267,14 @@ public function where(
$cursorClass = $cacheable ? SchemaCacheableCursor::class : SchemaCursor::class;

$model = new $this->schema->entityClass;
$query = $this->prepareValueQuery($query);

return new $cursorClass(
$this->schema,
$this->getCollection(),
'find',
[
$this->prepareValueQuery($query),
SoftDeleteQueries::insertFilterForSoftDelete($query, $model),
[
'projection' => $this->prepareProjection($projection),
'eagerLoads' => $model->with ?? [],
Expand Down Expand Up @@ -302,8 +311,11 @@ public function first(
return $this->where($query, $projection, true)->first();
}

$model = new $this->schema->entityClass;
$query = $this->prepareValueQuery($query);

$document = $this->getCollection()->findOne(
$this->prepareValueQuery($query),
SoftDeleteQueries::insertFilterForSoftDelete($query, $model),
['projection' => $this->prepareProjection($projection)]
);

Expand Down Expand Up @@ -665,4 +677,12 @@ function ($value) {

return $filtered;
}

private function executeSoftDelete(ModelInterface $entity, $options): bool
{
$deletedAtCoullum = SoftDeleteQueries::getDeletedAtColumn($entity);
$entity->$deletedAtCoullum = new UTCDateTime(new DateTime('now'));

return $this->update($entity, $options);
}
}
42 changes: 42 additions & 0 deletions src/Model/SoftDeletesTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Mongolid\Model;

trait SoftDeletesTrait
{
public $enabledSoftDeletes = true;

public function isTrashed(): bool
{
return !is_null($this->{$this->getDeletedAtColumn()});
}

public function restore(): bool
{
$collumn = $this->getDeletedAtColumn();

if (!$this->{$collumn}) {
return false;
}

$this->{$collumn} = null;

return $this->execute('save');
}

public function forceDelete(): bool
{
$this->forceDelete = true;

return $this->execute('delete');
}

private function getDeletedAtColumn(): string
{
return defined(
static::class . '::DELETED_AT'
)
? static::DELETED_AT
: 'deleted_at';
}
}
36 changes: 36 additions & 0 deletions src/Util/SoftDeleteQueries.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Mongolid\Util;

use Mongolid\Model\ModelInterface;

class SoftDeleteQueries
{
public static function insertFilterForSoftDelete(array $query, ModelInterface $model): array
{
$field = self::getDeletedAtColumn($model);

return array_merge(
$query,
[
'$or' => [
[
$field => null,
],
[
$field => ['$exists' => false],
],
],
]
);
}

public static function getDeletedAtColumn(ModelInterface $model): string
{
return defined(
$model::class . '::DELETED_AT'
)
? $model::DELETED_AT
: 'deleted_at';
}
}
153 changes: 153 additions & 0 deletions tests/Integration/PersisteModelWithSoftDeleteTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace Mongolid\Tests\Integration;

use DateTime;
use MongoDB\BSON\ObjectId;
use MongoDB\BSON\UTCDateTime;
use Mongolid\Model\ModelInterface;
use Mongolid\Tests\Stubs\Product;
use Mongolid\Tests\Stubs\ProductWithSoftDelete;

final class PersisteModelWithSoftDeleteTest extends IntegrationTestCase
{
private ObjectId $_id;

public function testFindNotDeletedProduct(): void
{
// Set
$product = $this->persiteProduct();

// Actions
$result = ProductWithSoftDelete::where()->first();

// Assertion
$this->assertEquals($product, $result);
}

public function testCannotFindDeletedProduct(): void
{
// Set
$this->persiteProduct(true);

// Actions
$result = ProductWithSoftDelete::where()->first();

// Assertion
$this->assertNull($result);
}

public function testFindDeletedProductWithFirst(): void
{
// Set
$product = $this->persiteProduct();

// Actions
$result = ProductWithSoftDelete::first($this->_id);

// Assertion
$this->assertEquals($product, $result);
}

public function testCannotFindDeletedProductWithFirst(): void
{
// Set
$this->persiteProduct(true);

// Actions
$result = ProductWithSoftDelete::first($this->_id);

// Assertion
$this->assertNull($result);
}

public function testRestoreDeletedProduct(): void
{
// Set
$product = $this->persiteProduct(true);

// Actions
$isRestored = $product->restore();
$result = ProductWithSoftDelete::first($this->_id);

// Assertion
$this->assertTrue($isRestored);
$this->assertEquals($product, $result);
}

public function testCannotRestoreAlreadyRestoredProduct(): void
{
// Set
$product = $this->persiteProduct(isRestored: true);

// Actions
$isRestored = $product->restore();
$result = ProductWithSoftDelete::first($this->_id);

// Assertion
$this->assertFalse($isRestored);
$this->assertEquals($product, $result);
}

public function testExecuteSoftDeleteOnProduct(): void
{
// Set
$product = $this->persiteProduct();

// Actions
$isDeleted = $product->delete();
$result = ProductWithSoftDelete::first($this->_id);

// Assertion
$this->assertTrue($isDeleted);
$this->assertNull($result);
$this->assertInstanceOf(UTCDateTime::class, $product->deleted_at);
}

public function testCannotExecuteSoftDeleteOnProduct(): void
{
// Set
$product = $this->persiteProduct(model:Product::class);

// Actions
$isDeleted = $product->delete();
$result = ProductWithSoftDelete::first($this->_id);

// Assertion
$this->assertTrue($isDeleted);
$this->assertNull($result);
$this->assertNull($result->deleted_at ?? null);
}

protected function setUp(): void
{
parent::setUp();

$this->_id = new ObjectId('5bcb310783a7fcdf1bf1a672');
}

private function persiteProduct(
bool $softDeleted = false,
bool $isRestored = false,
string $model = ProductWithSoftDelete::class
): ModelInterface {
$product = new $model();
$product->_id = $this->_id;
$product->short_name = 'Furadeira de Impacto Bosch com Chave de Mandril ';
$product->name = 'Furadeira de Impacto Bosch com Chave de Mandril e Acessórios 550W 1/2 GSB 550 RE 127V (110V)';

if ($softDeleted) {
$date = new UTCDateTime(new DateTime('today'));

$product->deleted_at = $date;
}

if ($isRestored) {
$product->deleted_at = null;
}

$product->save();

return $product;
}
}
10 changes: 10 additions & 0 deletions tests/Stubs/ProductWithSoftDelete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Mongolid\Tests\Stubs;

use Mongolid\Model\SoftDeletesTrait;

class ProductWithSoftDelete extends Product
{
use SoftDeletesTrait;
}
67 changes: 67 additions & 0 deletions tests/Unit/Model/SoftDeletesTraitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace Mongolid\Model;

use DateTime;
use MongoDB\BSON\UTCDateTime;
use Mongolid\DataMapper\DataMapper;
use Mongolid\Schema\DynamicSchema;
use Mongolid\TestCase;
use Mongolid\Tests\Stubs\Product;
use Mockery as m;
use Mongolid\Tests\Stubs\ProductWithSoftDelete;

class SoftDeletesTraitTest extends TestCase
{
public function testShouldReturnStatusOfSoftDelete(): void
{
// Set
$date = new UTCDateTime(new DateTime('today'));
$product = new ProductWithSoftDelete();
$product->deleted_at = $date;

// Actions
$actual = $product->isTrashed();

// Assertions
$this->assertTrue($actual);
}

public function testShouldRestoreProduct(): void
{
// Set
$product = new ProductWithSoftDelete();
$date = new UTCDateTime(new DateTime('today'));
$product->deleted_at = $date;
$dataMapper = $this->instance(
DataMapper::class,
m::mock(DataMapper::class)
);

// Expectations
$dataMapper->expects()
->setSchema(m::type(DynamicSchema::class));

$dataMapper->expects()
->save(m::type(Product::class), m::type('array'))
->andReturnTrue();

// Actions
$actual = $product->restore();

// Assertions
$this->assertTrue($actual);
}

public function testShouldNotRestoreProduct(): void
{
// Set
$product = new ProductWithSoftDelete();

// Actions
$actual = $product->restore();

// Assertions
$this->assertFalse($actual);
}
}

0 comments on commit 6c762b3

Please sign in to comment.