Refactor product relations to use new category_product and channel_product tables
- Updated queries and relationships in various controllers and models to replace `product_has_relations` with `category_product` and `channel_product`. - Adjusted methods in `ProductFilterer`, `FilterParamsController`, and `FilterController` to reflect the new database structure. - Modified seeder to insert data into the new `channel_product` table. - Updated tests to ensure compatibility with the new product relation structure.
This commit is contained in:
@@ -81,11 +81,10 @@ class ProductFilterer
|
|||||||
$this->queryBuilder->whereIn(
|
$this->queryBuilder->whereIn(
|
||||||
column: 'id',
|
column: 'id',
|
||||||
values: (
|
values: (
|
||||||
fn ($query) => $query->from('product_has_relations')
|
fn ($query) => $query->from('category_product')
|
||||||
->select('product_id')
|
->select('product_id')
|
||||||
->distinct('product_id')
|
->distinct('product_id')
|
||||||
->where('productable_type', '=', 'category')
|
->whereIn('category_id', explode(',', $this->request->categories))
|
||||||
->whereIn('productable_id', explode(',', $this->request->categories))
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,10 +38,9 @@ class FilterParamsController extends Controller
|
|||||||
$category_id = (int) $request->category_id;
|
$category_id = (int) $request->category_id;
|
||||||
|
|
||||||
$brand_ids = DB::table('products')
|
$brand_ids = DB::table('products')
|
||||||
->join('product_has_relations', function ($join) use ($category_id) {
|
->join('category_product', function ($join) use ($category_id) {
|
||||||
$join->on('products.id', '=', 'product_has_relations.product_id')
|
$join->on('products.id', '=', 'category_product.product_id')
|
||||||
->where('product_has_relations.productable_id', '=', $category_id)
|
->where('category_product.category_id', '=', $category_id);
|
||||||
->where('product_has_relations.productable_type', '=', 'category');
|
|
||||||
})
|
})
|
||||||
->select(['id', 'brand_id'])
|
->select(['id', 'brand_id'])
|
||||||
->pluck('brand_id');
|
->pluck('brand_id');
|
||||||
|
|||||||
@@ -97,9 +97,8 @@ class FilterController extends Controller
|
|||||||
->distinct('products.id')
|
->distinct('products.id')
|
||||||
->pluck('products.id');
|
->pluck('products.id');
|
||||||
|
|
||||||
return Category::where('is_visible', true)->ordered()->join('product_has_relations', 'categories.id', '=', 'product_has_relations.productable_id')
|
return Category::where('is_visible', true)->ordered()->join('category_product', 'categories.id', '=', 'category_product.category_id')
|
||||||
->where('product_has_relations.productable_type', '=', 'category')
|
->whereIntegerInRaw('category_product.product_id', $products)
|
||||||
->whereIntegerInRaw('product_has_relations.product_id', $products)
|
|
||||||
->get(['id', 'parent_id', 'name'])
|
->get(['id', 'parent_id', 'name'])
|
||||||
->unique('categories.id');
|
->unique('categories.id');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,9 +87,8 @@ class CategoriesFilter
|
|||||||
->distinct('products.id')
|
->distinct('products.id')
|
||||||
->pluck('products.id');
|
->pluck('products.id');
|
||||||
|
|
||||||
return $this->queryBuilder->join('product_has_relations', 'categories.id', '=', 'product_has_relations.productable_id')
|
return $this->queryBuilder->join('category_product', 'categories.id', '=', 'category_product.category_id')
|
||||||
->where('product_has_relations.productable_type', '=', 'category')
|
->whereIntegerInRaw('category_product.product_id', $products)
|
||||||
->whereIntegerInRaw('product_has_relations.product_id', $products)
|
|
||||||
->get($this->columns)
|
->get($this->columns)
|
||||||
->unique('categories.id');
|
->unique('categories.id');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,18 +16,17 @@ class ProductRelatedController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index(Product $product): JsonResponse
|
public function index(Product $product): JsonResponse
|
||||||
{
|
{
|
||||||
$products = DB::table('product_has_relations')
|
$products = DB::table('category_product')
|
||||||
->select('product_has_relations.product_id')
|
->select('category_product.product_id')
|
||||||
->whereIn('product_has_relations.productable_id', (function ($query) use ($product) {
|
->whereIn('category_product.category_id', (function ($query) use ($product) {
|
||||||
$query->from('product_has_relations')
|
$query->from('category_product')
|
||||||
->select('productable_id')
|
->select('category_id')
|
||||||
->distinct('productable_id')
|
->distinct('category_id')
|
||||||
->where('productable_type', '=', 'category')
|
|
||||||
->where('product_id', '=', $product->id);
|
->where('product_id', '=', $product->id);
|
||||||
}))
|
}))
|
||||||
->limit(12)
|
->limit(12)
|
||||||
->orderByRaw('RANDOM()')
|
->orderByRaw('RANDOM()')
|
||||||
->pluck('product_has_relations.product_id')
|
->pluck('category_product.product_id')
|
||||||
->unique();
|
->unique();
|
||||||
|
|
||||||
return response()->rest(
|
return response()->rest(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Repositories\System\Cache\CacheRepository;
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
@@ -179,9 +180,9 @@ class Channel extends Model implements HasMedia, Sortable
|
|||||||
/**
|
/**
|
||||||
* Products
|
* Products
|
||||||
*/
|
*/
|
||||||
public function products(): MorphToMany
|
public function products(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->morphToMany(Product::class, 'productable', 'product_has_relations');
|
return $this->belongsToMany(Product::class, 'channel_product');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -152,9 +152,9 @@ class Category extends Model implements HasMedia, Sortable
|
|||||||
/**
|
/**
|
||||||
* Category Products
|
* Category Products
|
||||||
*/
|
*/
|
||||||
public function products(): MorphToMany
|
public function products(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->morphToMany(Product::class, 'productable', 'product_has_relations');
|
return $this->belongsToMany(Product::class, 'category_product');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Models\Ecommerce\Product\Collection;
|
|||||||
use App\Models\Ecommerce\Product\Product\Product;
|
use App\Models\Ecommerce\Product\Product\Product;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
use Spatie\EloquentSortable\Sortable;
|
use Spatie\EloquentSortable\Sortable;
|
||||||
use Spatie\EloquentSortable\SortableTrait;
|
use Spatie\EloquentSortable\SortableTrait;
|
||||||
@@ -109,9 +110,9 @@ class Collection extends Model implements HasMedia, Sortable
|
|||||||
/**
|
/**
|
||||||
* Products
|
* Products
|
||||||
*/
|
*/
|
||||||
public function products(): MorphToMany
|
public function products(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->morphToMany(Product::class, 'productable', 'product_has_relations');
|
return $this->belongsToMany(Product::class, 'collection_product');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ trait ProductRelationships
|
|||||||
/**
|
/**
|
||||||
* Related Channels
|
* Related Channels
|
||||||
*/
|
*/
|
||||||
public function channels(): MorphToMany
|
public function channels(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->morphedByMany(Channel::class, 'productable', 'product_has_relations');
|
return $this->belongsToMany(Channel::class, 'channel_product');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,25 +56,25 @@ trait ProductRelationships
|
|||||||
/**
|
/**
|
||||||
* Related products (similar)
|
* Related products (similar)
|
||||||
*/
|
*/
|
||||||
public function relatedProducts(): MorphToMany
|
public function relatedProducts(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->morphedByMany(Product::class, 'productable', 'product_has_relations');
|
return $this->belongsToMany(Product::class, 'product_related', 'product_id', 'related_product_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Related categories
|
* Related categories
|
||||||
*/
|
*/
|
||||||
public function categories(): MorphToMany
|
public function categories(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->morphedByMany(Category::class, 'productable', 'product_has_relations');
|
return $this->belongsToMany(Category::class, 'category_product');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Related Collections
|
* Related Collections
|
||||||
*/
|
*/
|
||||||
public function collections(): MorphToMany
|
public function collections(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->morphedByMany(Collection::class, 'productable', 'product_has_relations');
|
return $this->belongsToMany(Collection::class, 'collection_product');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -33,9 +33,8 @@ class ProductEntrepreneurFilter extends Filter
|
|||||||
*/
|
*/
|
||||||
public function apply(NovaRequest $request, $query, $value)
|
public function apply(NovaRequest $request, $query, $value)
|
||||||
{
|
{
|
||||||
$vendorProducts = DB::table('product_has_relations')
|
$vendorProducts = DB::table('channel_product')
|
||||||
->where('productable_type', 'channel')
|
->where('channel_id', $value)
|
||||||
->where('productable_id', $value)
|
|
||||||
->pluck('product_id');
|
->pluck('product_id');
|
||||||
|
|
||||||
$query->whereIntegerInRaw('id', $vendorProducts);
|
$query->whereIntegerInRaw('id', $vendorProducts);
|
||||||
|
|||||||
@@ -123,9 +123,8 @@ class Product extends Resource
|
|||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
if ($user->hasRole('vendor')) {
|
if ($user->hasRole('vendor')) {
|
||||||
$vendorProducts = DB::table('product_has_relations')
|
$vendorProducts = DB::table('channel_product')
|
||||||
->where('productable_type', 'channel')
|
->where('channel_id', $user->channel()->id)
|
||||||
->where('productable_id', $user->channel()->id)
|
|
||||||
->pluck('product_id');
|
->pluck('product_id');
|
||||||
|
|
||||||
$query->whereIntegerInRaw('id', $vendorProducts);
|
$query->whereIntegerInRaw('id', $vendorProducts);
|
||||||
|
|||||||
@@ -182,9 +182,8 @@ class CategoryRepository
|
|||||||
])) {
|
])) {
|
||||||
$products = $resource->products()->distinct('products.id')->pluck('products.id');
|
$products = $resource->products()->distinct('products.id')->pluck('products.id');
|
||||||
|
|
||||||
return $this->queryBuilder->join('product_has_relations', 'categories.id', '=', 'product_has_relations.productable_id')
|
return $this->queryBuilder->join('category_product', 'categories.id', '=', 'category_product.category_id')
|
||||||
->where('product_has_relations.productable_type', '=', 'category')
|
->whereIntegerInRaw('category_product.product_id', $products)
|
||||||
->whereIntegerInRaw('product_has_relations.product_id', $products)
|
|
||||||
->distinct('categories.id')
|
->distinct('categories.id')
|
||||||
->get(['id', 'slug', 'name']);
|
->get(['id', 'slug', 'name']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// 1. Create new pivot tables
|
||||||
|
Schema::create('category_product', function (Blueprint $table) {
|
||||||
|
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
|
||||||
|
// No primary key on pivot usually, but maybe composite unique?
|
||||||
|
// product_has_relations didn't have one explicitly shown but usually pivots do.
|
||||||
|
// Let's add an index for performance.
|
||||||
|
$table->unique(['product_id', 'category_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('collection_product', function (Blueprint $table) {
|
||||||
|
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignId('collection_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->unique(['product_id', 'collection_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('channel_product', function (Blueprint $table) {
|
||||||
|
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignId('channel_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->unique(['product_id', 'channel_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('product_related', function (Blueprint $table) {
|
||||||
|
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignId('related_product_id')->constrained('products')->cascadeOnDelete();
|
||||||
|
$table->unique(['product_id', 'related_product_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Migrate data
|
||||||
|
// Using raw SQL for efficiency and simplicity
|
||||||
|
DB::statement("INSERT INTO category_product (product_id, category_id) SELECT product_id, productable_id FROM product_has_relations WHERE productable_type = 'category'");
|
||||||
|
DB::statement("INSERT INTO collection_product (product_id, collection_id) SELECT product_id, productable_id FROM product_has_relations WHERE productable_type = 'collection'");
|
||||||
|
DB::statement("INSERT INTO channel_product (product_id, channel_id) SELECT product_id, productable_id FROM product_has_relations WHERE productable_type = 'channel'");
|
||||||
|
DB::statement("INSERT INTO product_related (product_id, related_product_id) SELECT product_id, productable_id FROM product_has_relations WHERE productable_type = 'product'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('category_product');
|
||||||
|
Schema::dropIfExists('collection_product');
|
||||||
|
Schema::dropIfExists('channel_product');
|
||||||
|
Schema::dropIfExists('product_related');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -45,10 +45,9 @@ class ProductsTableSeeder extends Seeder
|
|||||||
});
|
});
|
||||||
|
|
||||||
$channel = $user ? $user->channel() : tmpostChannel();
|
$channel = $user ? $user->channel() : tmpostChannel();
|
||||||
DB::table('product_has_relations')->insert([
|
DB::table('channel_product')->insert([
|
||||||
'product_id' => $data['id'],
|
'product_id' => $data['id'],
|
||||||
'productable_id' => $channel->id,
|
'channel_id' => $channel->id,
|
||||||
'productable_type' => 'channel',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ test('can list related products', function () {
|
|||||||
// Then it selects product_ids from those categories.
|
// Then it selects product_ids from those categories.
|
||||||
// It doesn't explicitly exclude the main product ID in the query shown in tool output?
|
// It doesn't explicitly exclude the main product ID in the query shown in tool output?
|
||||||
// Let's check the controller code again.
|
// Let's check the controller code again.
|
||||||
// It selects `product_has_relations.product_id` where `productable_id` IN (categories of main product).
|
// It selects `category_product.product_id` where `category_id` IN (categories of main product).
|
||||||
// It does NOT seem to exclude the main product ID in the query.
|
// It does NOT seem to exclude the main product ID in the query.
|
||||||
// However, `unique()` is used.
|
// However, `unique()` is used.
|
||||||
// If the main product is returned, count would be 3. If excluded, 2.
|
// If the main product is returned, count would be 3. If excluded, 2.
|
||||||
|
|||||||
Reference in New Issue
Block a user