Massimiliano's avatar

User factory with JetStream team

From what I am understanding, when you have JetStream and a UserFactory, you need to create for every single user a team record too. So I am wandering if I should do something like starting from a TeamFactory and populate it like 'user_id' => User::factory()->make() and so on. Do you know if there is an easier way around?

0 likes
14 replies
gitwithravish's avatar

First, ensure you have your factory definitions like this.

UserFactory

class UserFactory extends Factory
{
    protected $model = User::class;

    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => 'yIXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
            'team_id' => Team::factory()->create()->id
        ];
    }
}

TeamFactory

class TeamFactory extends Factory
{
    protected $model = Team::class;

    public function definition()
    {
        return [
            'name' => $this->faker->name
        ];
    }
}
There are two ways to go about this.

Approach 1

User::factory()->count(5)->create();

But in this example if you create 5 users, then 5 different teams will be created and every user will be assigned to one team. I assume this is not what you would normally want.So to work around this, you can do something like this.

Approach 2

Here you will be creating 5 users by overriding the team_id column for all users. So all new users will belong to one team

// create 5 users and assign to the same team
$users = User::factory()->count(5)->create([
	'team_id' => Team::factory()->create()->id
])

Docs

https://laravel.com/docs/8.x/database-testing#factory-relationships

Massimiliano's avatar

Thanks for your detailed reply!

Maybe I still have to understand the logic behind the teams concept, because every time I register a user, a new personal team is born. So i guessed every user has to belong to at least one team, and that is the personal team. This is why I did something like this for TeamFactory:

public function definition()
{
    return [
        'user_id' => User::factory(),
        'name' => function (array $attributes) {
        return User::find($attributes['user_id'])->name . '\'s team';
        },
        'personal_team' => true,
    ];
}

and for the user model:

public function definition()
{
    $currentTeamId = User::count() + 1;
    return [
        'name' => $this->faker->name,
        'email' => $this->faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => 'yIXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
        'current_team_id' => $currentTeamId,
    ];
}
gitwithravish's avatar

I have already mentioned the definitions and implementation in my previous answer @massimiliano

  • A team may consist many users.
  • A user belongs to one team
  • So you have one to many relationship from team to user. Team has many users.

Now, in your latest answer, you have passed a variable $currentTeamId in User factory's definition method. I dont think its a good practice because if you want to assign another set of users to a different team, you will have to come to this class and change the value of $currentTeamId.

I will suggest you to follow my first answer's 2nd approach. In that case, If you create 5 users, then all users will belong to only one team. I believe that's how you want it right ? :)

Massimiliano's avatar

Again, thank you for your reply. I see your point and I think your way makes more sense too, but I am left with a doubt: apart from Factories, when there is a new user that just makes a registration, the default setting is that he is just going to belong to a "personal team" named after himself, right? It's not that he is going to belong to some other team or no team at all, am I correct? I just talk about the first registration for a new user.

gitwithravish's avatar

Well assigning user to his own personal team does not make any sense. But it depends on the application you are trying to build.

Concept of assigning him to his own personal team makes no sense. Instead, you can also keep the team_id field nullable. So when a user freshly registers, you don't assign a team at all. Later you can assign a team_id to the user in your applications.

$users = User::factory()->count(5)->create([
	'team_id' => null
])

Or you can specify null, by in the user factory's definition as well. Its up to you and your application's workflow.

Massimiliano's avatar

Ok but did you notice that by default when you have a fresh installation of Laravel JetStream and you register a user (say for example someone called "Jim"), then in the database the Teams table has a new record called "Jim's Team"? And so on for every new user, so it's not something I wanted to implement myself, just trying to emulate what I see.

gitwithravish's avatar

Okay I got off the topic a bit. I was explaining in terms of one to many relationship.

In jetstream setup, there is many to many relationship. between team and user. So user can be a part of many teams and a team can have many users. In that case, you can populate database like this.

// Populate`teams` table with 6 entries
$teams = Team::factory->count(6)->create();

// Populate `users` table with 30 entries
$users = User::factory->count(30)->create();


// Now you need to populate `team_users` table.
//  Assign 1 to 3 teams to each user (randomly)
$users->each(function ($user) use ($teams) { 
    $user->teams()->attach(
        $teams->random(rand(1, 3))->pluck('id')->toArray()
    ); 
});

Docs for that attach() method

https://laravel.com/docs/8.x/eloquent-relationships#attaching-detaching

raffi001's avatar
raffi001
Best Answer
Level 1

This is what worked for with Jetstream users and teams.

$user = User::factory()
    ->hasAttached(
        Team::factory()
            ->state(function (array $attributes, User $user) {
                return ['user_id' => $user->id, 'personal_team' => true];
            }),
        )
    )
    ->create();
1 like
cbaconnier's avatar

Thanks raffi001!

Since I have to use this everywhere, I added it directly to the UserFactory.php

public function configure()
    {
        return $this->has(
            Team::factory()
                ->state(function (array $attributes, User $user) {
                    return ['user_id' => $user->id, 'personal_team' => true];
                }),
                'ownedTeams'
        );
    }

edit: I also had to change hasAttached to has and add the relationship ownedTeams otherwise it also adds a record in user_team as it was also member but with no role

nikolaileb's avatar

Added this function to UserFactory.php:

    public function configure()
    {
        return $this->afterCreating(function (User $user) {
            $user->ownedTeams()->save(Team::forceCreate([
                'user_id' => $user->id,
                'name' => explode(' ', $user->name, 2)[0]."'s Team",
                'personal_team' => true,
            ]));
        });
    }
TxNuno's avatar

Not sure when this was added but the user factory now has a withPersonalTeam method. So when creating a team using the factory just call this method.

$user    = User::factory()->withPersonalTeam()->create();

here is the method if your user factory does not have it.

/**
     * Indicate that the user should have a personal team.
     *
     * @return $this
     */
    public function withPersonalTeam()
    {
        return $this->has(
            Team::factory()
                ->state(function (array $attributes, User $user) {
                    return ['name' => $user->name.'\'s Team', 'user_id' => $user->id, 'personal_team' => true];
                }),
            'ownedTeams'
        );
    }
4 likes
chongkan's avatar
$user = \App\Models\User::factory()->withPersonalTeam()->create([
            'name' => 'Custom Username',
            'email' => '[email protected]',
            'password' => Hash::make('password'), 
   ]);

If you don't need to specify any custom data, then you may run

\App\Models\Team::factory()->createOne()

And it will create a user automatically based on the user factory.

amitsamtani's avatar

$user = User::factory()->withPersonalTeam()->create();

Can you automatically set the current_team_id in the users table to the team that was created in the above statement?

amitsamtani's avatar

In case someone else comes by this here is the answer:

The trait HasTeams we have a method called currentTeam that can be used. If the current_team_id is not set, the user will be switched to the users personal team.

public function currentTeam()
{
        if (is_null($this->current_team_id) && $this->id) {
            $this->switchTeam($this->personalTeam());
        }

        return $this->belongsTo(Jetstream::teamModel(), 'current_team_id');
}

Please or to participate in this conversation.