From 806009b5e5ed69c46bc3b5b9a6e0c51af53398b9 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Thu, 22 Aug 2024 13:34:01 +0100 Subject: [PATCH] Handle model deletion better to prevent FK exceptions (#1859) Co-authored-by: alecritson --- packages/core/src/Models/Attribute.php | 10 ++-- packages/core/src/Models/Brand.php | 11 ++++ packages/core/src/Models/TaxZone.php | 11 ++++ tests/core/Unit/Models/AddressTest.php | 39 +++++++++++++- tests/core/Unit/Models/AttributeGroupTest.php | 30 +++++++++-- tests/core/Unit/Models/AttributeTest.php | 45 +++++++++++++++- tests/core/Unit/Models/BrandTest.php | 48 ++++++++++++++++- tests/core/Unit/Models/ChannelTest.php | 13 ++++- tests/core/Unit/Models/TaxZoneTest.php | 54 +++++++++++++++++++ 9 files changed, 249 insertions(+), 12 deletions(-) diff --git a/packages/core/src/Models/Attribute.php b/packages/core/src/Models/Attribute.php index 4f322e2a1c..76acc3c37e 100644 --- a/packages/core/src/Models/Attribute.php +++ b/packages/core/src/Models/Attribute.php @@ -5,7 +5,6 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\AsCollection; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; use Lunar\Base\BaseModel; @@ -39,14 +38,15 @@ class Attribute extends BaseModel use HasMacros; use HasTranslations; - public static function boot() + protected static function booted(): void { - static::deleting(function (Model $model) { + static::deleting(function (self $attribute) { + DB::beginTransaction(); DB::table( config('lunar.database.table_prefix').'attributables' - )->where('attribute_id', '=', $model->id)->delete(); + )->where('attribute_id', '=', $attribute->id)->delete(); + DB::commit(); }); - parent::boot(); } /** diff --git a/packages/core/src/Models/Brand.php b/packages/core/src/Models/Brand.php index 48764558dc..7ec881a3e8 100644 --- a/packages/core/src/Models/Brand.php +++ b/packages/core/src/Models/Brand.php @@ -15,6 +15,7 @@ use Lunar\Base\Traits\LogsActivity; use Lunar\Base\Traits\Searchable; use Lunar\Database\Factories\BrandFactory; +use Lunar\Facades\DB; use Spatie\MediaLibrary\HasMedia as SpatieHasMedia; /** @@ -55,6 +56,16 @@ protected static function newFactory(): BrandFactory return BrandFactory::new(); } + protected static function booted(): void + { + static::deleting(function (self $brand) { + DB::beginTransaction(); + $brand->discounts()->detach(); + $brand->collections()->detach(); + DB::commit(); + }); + } + /** * Return the product relationship. */ diff --git a/packages/core/src/Models/TaxZone.php b/packages/core/src/Models/TaxZone.php index 30fcfacc3a..eafc2b0407 100644 --- a/packages/core/src/Models/TaxZone.php +++ b/packages/core/src/Models/TaxZone.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; +use Illuminate\Support\Facades\DB; use Lunar\Base\BaseModel; use Lunar\Base\Traits\HasDefaultRecord; use Lunar\Base\Traits\HasMacros; @@ -37,6 +38,16 @@ protected static function booted(): void static::created($handleDefaultFunction); static::updated($handleDefaultFunction); + + static::deleting(function (self $taxZone) { + DB::beginTransaction(); + $taxZone->countries()->delete(); + $taxZone->states()->delete(); + $taxZone->postcodes()->delete(); + $taxZone->customerGroups()->delete(); + $taxZone->taxRates()->delete(); + DB::commit(); + }); } /** diff --git a/tests/core/Unit/Models/AddressTest.php b/tests/core/Unit/Models/AddressTest.php index fb41ccbc2b..f4f80f6e59 100644 --- a/tests/core/Unit/Models/AddressTest.php +++ b/tests/core/Unit/Models/AddressTest.php @@ -1,10 +1,13 @@ group('models'); + use Lunar\Models\Address; use Lunar\Models\Country; use Lunar\Models\Customer; +use function Pest\Laravel\{assertDatabaseMissing}; + uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); test('can make an address with minimal attributes', function () { @@ -60,3 +63,37 @@ expect($address->customer)->toBeInstanceOf(Customer::class); expect($address->country)->toBeInstanceOf(Country::class); }); + +test('can delete address', function () { + $country = Country::factory()->create(); + $customer = Customer::factory()->create(); + + $data = [ + 'country_id' => $country->id, + 'customer_id' => $customer->id, + 'first_name' => 'Tony', + 'last_name' => 'Stark', + 'line_one' => 'Stark Industries Headquarters', + 'line_two' => 'Line Two', + 'line_three' => 'Line Three', + 'state' => 'Southern California', + 'postcode' => 123456, + 'delivery_instructions' => 'Pass on to Happy', + 'contact_email' => 'deliveries@stark.com', + 'contact_phone' => '123123123', + 'meta' => [ + 'door_code' => 0000, + ], + 'shipping_default' => true, + 'billing_default' => true, + 'city' => 'Los Angeles', + ]; + + $address = Address::create($data); + + $address->delete(); + + assertDatabaseMissing(Address::class, [ + 'id' => $address->id, + ]); +}); diff --git a/tests/core/Unit/Models/AttributeGroupTest.php b/tests/core/Unit/Models/AttributeGroupTest.php index 1ba4dcc744..42d84f01a3 100644 --- a/tests/core/Unit/Models/AttributeGroupTest.php +++ b/tests/core/Unit/Models/AttributeGroupTest.php @@ -1,9 +1,12 @@ group('models'); + use Lunar\Models\Attribute; use Lunar\Models\AttributeGroup; +use function Pest\Laravel\{assertDatabaseMissing}; + uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); test('can make a attribute group', function () { @@ -31,11 +34,32 @@ 'position' => 5, ]); - expect($attributeGroup->attributes)->toHaveCount(0); + expect($attributeGroup->attributes()->count())->toBe(0); $attributeGroup->attributes()->create( Attribute::factory()->make()->toArray() ); - expect($attributeGroup->refresh()->attributes)->toHaveCount(1); + expect($attributeGroup->refresh()->attributes()->count())->toBe(1); +}); + +test('can delete attribute group', function () { + $attributeGroup = AttributeGroup::factory()->create([ + 'attributable_type' => 'product_type', + 'name' => [ + 'en' => 'SEO', + ], + 'handle' => 'seo', + 'position' => 5, + ]); + + $attributeGroup->attributes()->create( + Attribute::factory()->make()->toArray() + ); + + $attributeGroup->delete(); + + assertDatabaseMissing(AttributeGroup::class, [ + 'id' => $attributeGroup->id, + ]); }); diff --git a/tests/core/Unit/Models/AttributeTest.php b/tests/core/Unit/Models/AttributeTest.php index f21d757d90..b0c859e1dc 100644 --- a/tests/core/Unit/Models/AttributeTest.php +++ b/tests/core/Unit/Models/AttributeTest.php @@ -1,9 +1,12 @@ group('models'); + use Lunar\Models\Attribute; use Lunar\Models\AttributeGroup; +use function Pest\Laravel\{assertDatabaseMissing}; + uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); test('can make a attribute', function () { @@ -41,3 +44,43 @@ expect($attribute->position)->toEqual(4); expect($attribute->configuration->get('options'))->toEqual($options); }); + +test('can delete an attribute', function () { + $options = [ + 'Red', + 'Blue', + 'Green', + ]; + + $attribute = Attribute::factory() + ->for(AttributeGroup::factory()) + ->create([ + 'position' => 4, + 'name' => [ + 'en' => 'Meta Description', + ], + 'description' => [ + 'en' => 'Meta Description', + ], + 'handle' => 'meta_description', + 'section' => 'product_variant', + 'type' => \Lunar\FieldTypes\Text::class, + 'required' => false, + 'default_value' => '', + 'configuration' => [ + 'options' => $options, + ], + 'system' => true, + ]); + + \Illuminate\Support\Facades\DB::table('lunar_attributables')->insert([ + 'attributable_type' => 'Lunar\Models\ProductType', + 'attributable_id' => 1, + 'attribute_id' => $attribute->id, + ]); + + $attribute->delete(); + assertDatabaseMissing(Attribute::class, [ + 'id' => $attribute->id, + ]); +}); diff --git a/tests/core/Unit/Models/BrandTest.php b/tests/core/Unit/Models/BrandTest.php index a0ae081eb3..10356efdd3 100644 --- a/tests/core/Unit/Models/BrandTest.php +++ b/tests/core/Unit/Models/BrandTest.php @@ -1,12 +1,16 @@ group('models'); + use Illuminate\Support\Facades\Config; use Lunar\Generators\UrlGenerator; use Lunar\Models\Brand; use Lunar\Models\Language; use Lunar\Models\Url; +use function Pest\Laravel\assertDatabaseHas; +use function Pest\Laravel\assertDatabaseMissing; + uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); test('can make a brand', function () { @@ -75,3 +79,45 @@ ]); expect($brand->mappedAttributes)->toHaveCount(1); }); + +test('can delete a brand', function () { + $brand = Brand::factory()->create([ + 'name' => 'Test Brand', + ]); + + \Lunar\Models\Product::factory()->create([ + 'brand_id' => $brand->id, + ]); + + $discount = \Lunar\Models\Discount::factory()->create(); + $collection = \Lunar\Models\Collection::factory()->create(); + + $brand->discounts()->attach($discount); + $brand->collections()->attach($collection); + + assertDatabaseHas($brand->discounts()->getTable(), [ + 'brand_id' => $brand->id, + 'discount_id' => $discount->id, + ]); + + assertDatabaseHas($brand->collections()->getTable(), [ + 'brand_id' => $brand->id, + 'collection_id' => $collection->id, + ]); + + $brand->delete(); + + assertDatabaseMissing($brand->discounts()->getTable(), [ + 'brand_id' => $brand->id, + 'discount_id' => $discount->id, + ]); + + assertDatabaseMissing($brand->collections()->getTable(), [ + 'brand_id' => $brand->id, + 'collection_id' => $collection->id, + ]); + + assertDatabaseMissing(Brand::class, [ + 'id' => $brand->id, + ]); +}); diff --git a/tests/core/Unit/Models/ChannelTest.php b/tests/core/Unit/Models/ChannelTest.php index a9d6448f84..61226e8914 100644 --- a/tests/core/Unit/Models/ChannelTest.php +++ b/tests/core/Unit/Models/ChannelTest.php @@ -1,6 +1,6 @@ group('models'); use Lunar\Models\Channel; @@ -51,3 +51,14 @@ expect($channel->refresh()->discounts)->toHaveCount(1); }); + +test('can soft delete a channel', function () { + $channel = Channel::factory()->create(); + + $channel->delete(); + + \Pest\Laravel\assertDatabaseHas(Channel::class, [ + 'id' => $channel->id, + 'deleted_at' => now(), + ]); +}); diff --git a/tests/core/Unit/Models/TaxZoneTest.php b/tests/core/Unit/Models/TaxZoneTest.php index 52f5efe064..8315eb7481 100644 --- a/tests/core/Unit/Models/TaxZoneTest.php +++ b/tests/core/Unit/Models/TaxZoneTest.php @@ -1,11 +1,15 @@ refresh()->customerGroups)->toHaveCount(1); }); + +test('can delete a tax zone', function () { + $data = [ + 'name' => 'L.A.', + 'zone_type' => 'state', + 'price_display' => 'tax_inclusive', + 'active' => true, + 'default' => true, + ]; + + $zone = TaxZone::factory()->create($data); + + \Pest\Laravel\assertDatabaseHas((new TaxZone)->getTable(), $data); + + $country = Country::factory()->create(); + $state = State::factory()->create(); + + expect($zone->refresh()->customerGroups)->toHaveCount(0); + + $zone->customerGroups()->create([ + 'customer_group_id' => CustomerGroup::factory()->create()->id, + ]); + + expect($zone->refresh()->customerGroups)->toHaveCount(1); + + $zone->countries()->create(['country_id' => $country->id]); + $zone->states()->create(['state_id' => $state->id]); + $zone->postcodes()->create([ + 'country_id' => $country->id, + 'postcode' => 'ABC 123', + ]); + + \Lunar\Models\TaxRate::factory()->create([ + 'tax_zone_id' => $zone->id, + ]); + + assertDatabaseHas(\Lunar\Models\TaxZoneCountry::class, ['tax_zone_id' => $zone->id]); + assertDatabaseHas(\Lunar\Models\TaxZoneCustomerGroup::class, ['tax_zone_id' => $zone->id]); + assertDatabaseHas(\Lunar\Models\TaxZoneState::class, ['tax_zone_id' => $zone->id]); + assertDatabaseHas(\Lunar\Models\TaxZonePostcode::class, ['tax_zone_id' => $zone->id]); + assertDatabaseHas(\Lunar\Models\TaxRate::class, ['tax_zone_id' => $zone->id]); + + $zone->delete(); + + assertDatabaseMissing(\Lunar\Models\TaxZoneCountry::class, ['tax_zone_id' => $zone->id]); + assertDatabaseMissing(\Lunar\Models\TaxZoneCustomerGroup::class, ['tax_zone_id' => $zone->id]); + assertDatabaseMissing(\Lunar\Models\TaxZoneState::class, ['tax_zone_id' => $zone->id]); + assertDatabaseMissing(\Lunar\Models\TaxZonePostcode::class, ['tax_zone_id' => $zone->id]); + assertDatabaseMissing(\Lunar\Models\TaxRate::class, ['tax_zone_id' => $zone->id]); +})->group('foo');