Well, today I've encountered a small problem when working with Laravel, and I decided to use a hacky trick to bypass it. Here it is explained, why does it happen and what to do in order to solve it.
First of all, let's imagine we are designing a project with its database iteration after iteration. In of those iterations, we have to establish a 1:n relationship between a parent and child tables that were previously not related at all. Also, the parent_id on the child table has to be mandatory, there should be no children without parent and therefore the parent_id column on the child table can't be nullable.
You may be thinking, ok, I know how the migration is going to be something like this:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AssociateChildrenToParents extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('children', function ($t) {
$t->integer('parent_id')->unsigned()->after('id');
$t->foreign('parent_id')
->references('id')
->on('parents');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('children', function ($t) {
$t->dropForeign('children_parent_id_foreign');
$t->dropColumn('parent_id');
});
}
}
This is what we want after all right? But SQLite disagrees, when you execute your tests every single test is going to return an error like this:
Cannot add a NOT NULL column with default value NULL: ALTER TABLE "children" ADD "parent_id" integer NOT NULL
Basically this tells you something that makes sense, as I've said before I should have no children without a parent, but I'm not saying what to do when I encounter a children record that had no parent before. The problem is that on our SQLite generated on the fly, we don't have no parent to associate with the records, and also no children to associate. Even in this situation, we seem to be forced to provide a default value for the parent_id column, so we may be in a locked up situation.
Browsig on stack overflow I found this answer for dealing with a similar problem and a solution written in Ruby. The key is that checks that SQLite is doing when adding a foreign key column, are not the same that is going to do when altering a column, so the dirty trick comes here, we create the foreign key column as nullable, and right after we alter it setting the nullable to false like this:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AssociateChildrenToParent extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('children', function ($t) {
$t->integer('parent_id')->unsigned()->nullable()->after('id');
$t->foreign('parent_id')
->references('id')
->on('parents')
->onDelete('cascade')
->onUpdate('cascade');
});
Schema::table('children', function ($t) {
$t->integer('parent_id')->nullable(false)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('children', function ($t) {
$t->dropForeign('children_parent_id_foreign');
$t->dropColumn('parent_id');
});
}
}
This way you will end up with a non nullable foreign key column, and your tests will not break. What do you think? Have you found a better solution?