Loon
Loon

Reputation: 91

Laravel: Seeding table with several belongs to relationship using factories

tl;dr:

I am using Laravel and Eloquent and trying to seed a database and attach several belongs to relationship to one table, but I get the error:

Call to undefined method Illuminate\Database\Eloquent\Relations\BelongsTo::attach()

Detailed explanation:

I am using php 7.1 and Laravel 6 with Eloquent. I am trying to seed a test database. I have the following relationship structure:

one user has several posts / one post belongs to one user

one category has several posts / one post belongs to one category

User model:

class User extends Model
{
    protected $table = 'users';

    public function posts()
    {
        return $this->hasMany('App\Models\Post');
    }
} 

Category model:

class Category extends Model
{
    protected $table = 'categories';

    public function posts()
    {
        return $this->hasMany('App\Models\Category');
    }
}

Post model:

class Post extends Model
{
    protected $table = 'posts';

    public function category()
    {
        return $this->belongsTo('App\Models\Category');
    }

    public function user()
    {
        return $this->belongsTo('App\Models\User');
    }
}

User factory:

$factory->define(User::class, function (Faker $faker) {
    return [
        'id' => $faker->unique()->randomNumber(3),
        'name' => $faker->name(),
    ];
});

Category factory:

$factory->define(Category::class, function (Faker $faker) {
    return [
        'id' => $faker->unique()->randomNumber(3),
        'name' => $faker->word(),
    ];
});

Post factory:

$factory->define(User::class, function (Faker $faker) {
    return [
        'id' => $faker->unique()->randomNumber(3),
        'name' => $faker->realText($maxNbChars = 200, $indexSize = 2),
    ];
});

If I only had users and posts, I know that I could seed the database the following way:

public function seed()
{
    $users = factory(App\User::class, 10)
        ->create()
        ->each(function ($user) {
            $user->posts()->createMany(factory(App\Post::class, 10)->make()->toArray());
        });
}

but obviously that won't work in my case. I have tried the following:

public function seed()
{
    factory(User::class, 10)->create();
    factory(Category::class, 10)->create();
    factory(Post::class, 100)->create()->each(function ($post){
        $post->user()->attach(User::all()->random(1));
        $post->category()->attach(Category::all()->random(1));
    });
}

as well as the same function using make/save instead of create:

public function seed()
    {
        factory(User::class, 10)->create();
        factory(Category::class, 10)->create();
        factory(Post::class, 100)->make()->each(function ($post) {
            $post->user()->attach(User::all()->random(1));
            $post->category()->attach(Category::all()->random(1));
        })->save();
    }

but in both cases, I get the error:

Call to undefined method Illuminate\Database\Eloquent\Relations\BelongsTo::attach()

Upvotes: 2

Views: 4758

Answers (1)

Remul
Remul

Reputation: 8252

You have to use associate():

public function seed()
{
    factory(User::class, 10)->create();
    factory(Category::class, 10)->create();
    factory(Post::class, 100)->make()->each(function ($post) {
        $post->user()->associate(User::inRandomOrder()->first());
        $post->category()->associate(Category::inRandomOrder()->first());
        $post->save();
    });
}

Also User::all()->random(1) will return a collection and not a model, which will throw an exception, I replaced it with Model::inRandomOrder()->first(), which will fetch a random model from the database.

From the docs:

When updating a belongsTo relationship, you may use the associate method. This method will set the foreign key on the child model:

$account = App\Account::find(10);

$user->account()->associate($account);

$user->save();

Update

In your Modelfactory:

/** @var Factory $factory */
$factory->define(Post::class, function (Faker\Generator $faker) {
    return [
        // not sure why you do this, is it not a autoincrement column?
        'id' => $faker->unique()->randomNumber(3), 
        'name' => $faker->realText($maxNbChars = 200, $indexSize = 2),
        'category_id' => function () {
            if ($category = Category::inRandomOrder()->first()) {
                return $category->id;
            }

            return factory(Category::class)->create()->id;
        },
        'user_id' => function () {
            if ($user = User::inRandomOrder()->first()) {
                return $user->id;
            }

            return factory(User::class)->create()->id;
        },
    ];
});

In your seeder:

public function seed()
{
    factory(User::class, 10)->create();
    factory(Category::class, 10)->create();
    factory(Post::class, 100)->create();
}

Upvotes: 6

Related Questions