3

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.

jon3laze
  • 3,188
  • 6
  • 36
  • 69

3 Answers3

4

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,
]);
andrewtweber
  • 24,520
  • 22
  • 88
  • 110
1

Here is how I did it with my addresses, I made it so 10% of the time it would create a new address otherwise use a random one.

// Grab a random address
$address = App\Address::orderByRaw('RAND()')->first();

// 10% that we will generate a new address
if ($faker->boolean(10) || !$address)
{
    $address = factory(App\Address::class)->create();
}

Basically I grab a random address, if it exists there is a is a 10% chance that $faker->boolean(10) will return true and it will generate a new address. However if there is no address ($address == false) we always to generate a new one.

Maarten de Graaf
  • 516
  • 2
  • 5
  • 20
0

Here is simple way to generator factory using random() method

$factory->define(App\Device::class, function(Faker\Generator $faker) {
    return [
        // another way to, Get any random row from existing records
        'vendor_id' => App\Vendor::all()->random()->user_id,
        'name'        => $faker->company,
        'mac_address' => $faker->macAddress,
    ];
});
dipenparmar12
  • 3,042
  • 1
  • 29
  • 39