laravel/nova-issues

Morphto can't save in morph table and depend on the main table , "Unknown Column 'itemable_model' in Polymorphic Relationship with Laravel Nova"

Closed this issue · 0 comments

Laravel Version:

10.10

Nova Version:

4.0

PHP Version:

8.1

Database Driver & Version:

MySQL 8.0

Operating System and Version:

macOS

Browser Type and Version:

Chrome 101

Description:

I'm encountering an issue with polymorphic relationships in Laravel Nova where the itemable_model column is not found in the cardables table, leading to a SQL error. The exact error message is:

PDOException(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'itemable_model' in 'field list'

Detailed Steps to Reproduce the Issue on a Fresh Nova Installation:

  1. Create Migrations:

    • Migration for cards table:
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration
    {
        public function up()
        {
            Schema::create('cards', function (Blueprint $table) {
                $table->id();
                $table->json('title');
                $table->json('body');
                $table->string('avatar_icon')->nullable();
                $table->string('background_color', 7)->nullable();
                $table->string('artwork', 255)->nullable();
                $table->string('card_design_type')->comment('basic, elevated, or narrow');
                $table->string('cta_type');
                $table->string('cta_content');
                $table->string('cta_design')->nullable()->comment('box, underline');
                $table->string('cta_action');
                $table->unsignedBigInteger('campaign_id');
                $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade');
                $table->boolean('is_active')->default(0);
                $table->timestamps();
            });
        }
    
        public function down()
        {
            Schema::dropIfExists('cards');
        }
    };
    • Migration for cardables table:
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration
    {
        public function up()
        {
            Schema::create('cardables', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('itemable_id');
                $table->string('itemable_model'); // Ensure this column exists
                $table->unsignedBigInteger('card_id');
                $table->unsignedBigInteger('screen_id');
                $table->foreign('screen_id')->references('id')->on('screens')->onDelete('cascade');
                $table->foreign('card_id')->references('id')->on('cards')->onDelete('cascade');
                $table->timestamps();
            });
        }
    
        public function down()
        {
            Schema::dropIfExists('cardables');
        }
    };
  2. Define Models:

    • Card Model:
    namespace Modules\Card\Models;
    
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Database\Eloquent\Relations\MorphTo;
    use Illuminate\Database\Eloquent\Relations\MorphToMany;
    use Modules\Category\Models\Category;
    use Spatie\Translatable\HasTranslations;
    
    class Card extends Model
    {
        use HasTranslations;
    
        protected $fillable = [
            'title',
            'body',
            'avatar_icon',
            'background_color',
            'artwork',
            'card_design_type',
            'cta_type',
            'cta_content',
            'cta_design',
            'cta_action',
            'campaign_id',
            'is_active',
        ];
    
        protected $translatable = [
            'title',
            'body',
            'cta_content'
        ];
    
        protected $casts = [
            'title' => 'json',
            'body' => 'json',
            'cta_content' => 'json',
            'is_active' => 'bool'
        ];
    
        public function campaign()
        {
            return $this->belongsTo(Campaign::class);
        }
    
        public function itemable(): MorphTo
        {
            return $this->morphTo('itemable', 'itemable_model', 'itemable_id');
        }
    
        public function categories(): MorphToMany
        {
            return $this->morphedByMany(Category::class, 'itemable', 'cardables');
        }
    
        public function screen()
        {
            return $this->belongsTo(Screen::class);
        }
    }
    • Category Model:
    namespace Modules\Category\Models;
    
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Database\Eloquent\Relations\MorphToMany;
    use Modules\Card\Models\Card;
    use Modules\HomeSection\Models\Item;
    
    class Category extends Model
    {
        use HasFactory;
    
        protected $primaryKey = 'cat_id';
    
        public const CREATED_AT = 'date_created';
    
        public const UPDATED_AT = 'date_modified';
    
        protected $fillable = [];
    
        protected static function newFactory()
        {
            return \Modules\Category\Database\factories\CategoryFactory::new();
        }
    
        public function items()
        {
            return $this->morphMany(Item::class, 'itemable');
        }
    
        public function cards(): MorphToMany
        {
            return $this->morphToMany(Card::class, 'itemable', 'cardables');
        }
    }
  3. Define Nova Resource:

    • Card Nova Resource:
    namespace Modules\Card\Resources;
    
    use App\Nova\Resource;
    use Illuminate\Http\Request;
    use Laravel\Nova\Fields\BelongsTo;
    use Laravel\Nova\Fields\Boolean;
    use Laravel\Nova\Fields\Color;
    use Laravel\Nova\Fields\ID;
    use Laravel\Nova\Fields\MorphTo;
    use Laravel\Nova\Fields\Select;
    use Laravel\Nova\Fields\Text;
    use Laravel\Nova\Fields\Textarea;
    use Laravel\Nova\Fields\URL;
    use Modules\Card\Enums\CardDesignTypeEnum;
    use Modules\Card\Enums\CtaDesignEnum;
    use Modules\Card\Enums\CtaTypeEnum;
    use Modules\Category\Resources\Category;
    
    class Card extends Resource
    {
        public static $model = \Modules\Card\Models\Card::class;
    
        public static $title = 'title';
    
        public static $search = [
            'title',
            'card_design_type',
            'cta_type',
            'cta_content',
            'cta_action',
            'is_active'
        ];
    
        public static $group = 'CARD';
    
        public function fields(Request $request)
        {
            return [
                ID::make()->sortable(),
                Text::make('Title', 'title')
                    ->rules('required')
                    ->translatable(),
    
                Textarea::make('Body', 'body')
                    ->rules('required')
                    ->hideFromIndex()
                    ->translatable(),
    
                Select::make('Card Design Type', 'card_design_type')
                    ->rules('required')
                    ->searchable()
                    ->options(getEnumOptionsAsArray(CardDesignTypeEnum::cases()))
                    ->displayUsingLabels(),
    
                Color::make('Background Color', 'background_color')
                    ->rules('required')
                    ->hideFromIndex(),
    
                URL::make('Artwork', 'artwork')
                    ->rules('required')
                    ->hideFromIndex(),
    
                URL::make('Avatar Icon')
                    ->hideFromIndex(),
    
                Select::make('CTA Type', 'cta_type')
                    ->rules('required')
                    ->searchable()
                    ->options(getEnumOptionsAsArray(CtaTypeEnum::cases()))
                    ->displayUsingLabels(),
    
                Textarea::make('CTA Content', 'cta_content')
                    ->rules('required')
                    ->hideFromIndex()
                    ->translatable(),
    
                Text::make('CTA Action', 'cta_action')
                    ->rules('required')
                    ->hideFromIndex(),
    
                Select::make('CTA Design', 'cta_design')
                    ->rules('required')
                    ->options(getEnumOptionsAsArray(CtaDesignEnum::cases()))
                    ->hideFromIndex(),
    
                BelongsTo::make('Campaign', 'campaign', Campaign::class),
    
                MorphTo::make('Itemable', 'itemable')
                    ->types([
                        Category::class,
                        // Add other polymorphic types here if needed
                    ])->hideFromIndex(),
    
                BelongsTo::make('Screen', 'screen', Screen::class)
                    ->display('name')
                    ->rules('required')
                    ->hideFromIndex(),
    
                Boolean::make('Active', 'is_active'),
            ];
        }
    
        public function cards(Request $request)
        {
            return [];
        }
    
        public function filters(Request $request)
        {
            return [];
        }
    
        public function lenses(Request $request)
        {
            return [];
        }
    
        public function actions(Request $request)
        {
            return [];
        }
    }
  4. Run Migrations:

    php artisan migrate

Expected Outcome:

The polymorphic relationships should be set up correctly, and data should be inserted into the cardables table without any SQL errors.

Actual Outcome:

I encountered the following error:

PDOException(code: 42S22): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'itemable_model' in 'field list'
Screenshot 2024-06-24 at 4 13 31 PM