Search code examples
laravel-5factorylaravel-seeding

laravel model factory seed generate from existing


I am trying to seed a table with a foreign key and I'm stuck on how I would tell the model to randomly pull values from what already exists.

ModelFactory

$factory->define(App\Vendor::class, function(Faker\Generator $faker) {
    return [
        'name' => $faker->company,
    ];
});

$factory->define(App\Device::class, function(Faker\Generator $faker) {
    return [
        'vendor' => ,
        'name' => $faker->company,
        'mac_address' => $faker->macAddress,
    ];
});

Seeds

VendorTableSeeder

public function run()
{
    factory(App\Vendor::class, 150)->create();
}

DeviceTableSeeder

public function run()
{
    factory(App\Device::class, 50)->create();
}

DataSeeder

$this->call(VendorTableSeeder::class);
$this->call(DeviceTableSeeder::class);

I seed the Vendor table before the Device table and would like to populate a random vendor id from the existing vendors.

'vendor' => 'factory::App\Vendor'

But I am getting

SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: foreign key constraint fails

It looks like the insert is trying to insert factory::App\Vendor as a string for the vendor column. I am trying to figure out how to have it pull from the existing vendors.


Solution

  • Seed them at the same time from the VendorTableSeeder.

    Will you ever need Devices without Vendors? Or Vendors without Devices? If not, then this is the best option. Related tables should be seeded together.

    A better example would be a School. The School is the main seed, and for each School, you would also seed a Principal, several Teachers, and 100 Students. If you seed them all at once that eliminates the concerns about foreign keys or ordering.

    factory(App\Vendor::class, 150)
        ->create()
        ->each(function (App\Vendor $vendor) {
            $vendor->devices()->save(
                factory(App\Device::class)->make()
            );
    
            // For some randomness
            $vendor->devices()->save(
                factory(App\Device::class, rand(0, 4))->make()
            );
        });
    

    If you do want the flexibility of seeding them separately then you have a couple other options. You can pull in a random Vendor inside the Device factory itself.

    $factory->define(App\Device::class, function(Faker\Generator $faker) {
    
        // Grab a random vendor
        $vendor = App\Vendor::orderByRaw('RAND()')->first();
    
        // Or create a new vendor
        $vendor = factory(App\Vendor::class)->create();
    
        return [
            'vendor_id'   => $vendor->id,
            'name'        => $faker->company,
            'mac_address' => $faker->macAddress,
        ];
    });
    

    Or you can pass extra attributes which are merged with the factory-generated attributes.

    // $vendor is a Vendor object
    
    factory(App\Device::class, 50)->create([
        'vendor_id' => $vendor->id,
    ]);