laravellaravel-8laravel-migrations

Laravel softDelete migration issue


We recently tried to add a soft delete to an existing model through a new migration. Everything works fine locally, but it was failing our unit Test because it could not find the deleted_at column of the new migration.

Our unit tests rebuild the database each time, so it will run all the migrations and seed the tables.

After a little bit of an investigation, he failure stemmed from a previous migration that performed referenced that table and pulled data from it.

Initial Migration code:

Schema::create('inclusions', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('affiliate_id')->index();
            $table->string('owner_type', 50);          
            $table->timestamps();

            $table->foreign('affiliate_id')
                ->references('id')
                ->on('affiliates');
        });

Down the road, we created a new table that referenced this table and another and populated the data.

Schema::create('inclusion_vertical', function (Blueprint $table) {
            $table->foreignId('inclusion_id');
            $table->foreignId('vertical_id');

            $table->primary(['inclusion_id', 'vertical_id']);

            $table->foreign('inclusion_id')
                ->references('id')
                ->on('inclusions');

            $table->foreign('vertical_id')
                ->references('id')
                ->on('verticals');
        });

        $verticals = Vertical::all();

        // Link the inclusions to the new verticals table
        Inclusion::each(function (Inclusion $inclusion) use ($verticals) {
            $verticalIds = json_decode($inclusion->getAttribute('verticals'));

            $inclusion->verticals()->sync($verticals->whereIn('external_id', $verticalIds));
        });

Recently we added the migration to add soft deletes on that inclusions table (as well as updating the model itself to use softDeletes.

Schema::table('inclusions', function (Blueprint $table) {
            $table->softDeletes();
        });

So because the model says there should be softDeletes (the deleted_at column) and there's a migration that attempts to select data from that table using eloquent, it fails the test because the deleted_at column doesn't exist.

My question becomes, is there a solution for this? The one we came up with is to update the 2nd migration and change the query so it doesn't use eloquent or maybe append ->withTrashed (which would bypass the deleted_at), but there seems to be some conflict in terms of whether you should ever edit previous migrations. We want to avoid a solution where we have to put a note in and change things depending on whether we are simply doing the unit test or re-deploying (i.e. new local host user).

Just for some clarification, here are the steps to reproduce:

It works perfectly fine on local and staging because the model update and migration to add the softDelete don't have issues. But it's failing the unit tests (and presumably any fresh builds) because it starts the migrations from the first one and goes down the list. And because the model says it uses soft deletes, when it gets to the migration to load data from one table to another, it can't do it because the migration that adds the soft delete happens later on.


Solution

  • So your problem is you added soft deletes to a model after the initial migration, and an intermediate migration is accessing the table and causing errors?

    You can just use the query builder which does not use Eloquent bits and pieces like soft deletes:

    $verticals = Vertical::all();
    DB::table('inclusions')
        ->get()
        ->each(
            fn (Inclusion $inc) => $inc->verticals()
                ->sync($verticals->whereIn('external_id', json_decode($inc->verticals)))
        );
    

    But since you're presumably deploying this code into a new environment without any records, the easy solution seems to be to just remove this code from the migration altogether. The whole point of code like this is to patch in new relationships when you have existing data.

    Generally migrations don't get touched, but there's no point in being religious about it, especially when a migration that once had to deal with live data is now used only in new installations.

    One other note is that you can squash your migrations to prevent the migration files from being individually run.