csprick's avatar

Transitive relation?

I have three models: Group, Activity, User. With the following relationships in the models.

  • Activities belongTo a Group (with the group_id being a field in the activity)
  • Users belongTo a group (with the group_id being a field in the user)
  • Likewise the group hasMany Activities and also hasMany Users.

Users and Activities are each only associated with one group.

I want to find the users for a particular activity (via the common group). I don't think this is a hasManyThrough relation - Eloquent complains that there isn't a group.activity_id

How should I define the relationship between Activities and Users who share a common group_id, so that I can get the list of users for a given activity?

Group
  name:string

Activity
  name: string
  group_id: int

User
  name: string
  group_id: int

class Group extends Model
{
 public function activities(): HasMany
    {
        return $this->hasMany(Activity::class);
    }
 public function users(): HasMany
    {
        return $this->hasMany(User::class);
    }
}

class User extends Model
{
public function group(): BelongsTo
    {
        return $this->belongsTo(Group::class);
    }
}

class Activity extends Model
{
public function group(): BelongsTo
    {
        return $this->belongsTo(Group::class);
    }

    public function users(): HasManyThrough
    {
        return $this->hasManyThrough(User::class, Group::class);
    }
}
0 likes
3 replies
gych's avatar
gych
Best Answer
Level 29

You have a relation for users in the Group model and a relation for group in the Activity model so you could try this to call the users relation from the group relation in the Acivity Model

class Activity extends Model
{
    public function group(): BelongsTo
    {
        return $this->belongsTo(Group::class);
    }

    public function users()
    {
        return $this->group->users(); 
    }
}
1 like
csprick's avatar

@gych Awesome and very compact. Thanks! I hoped it would be something like this.

I also type hinted the user function with HasMany.

public function users() : HasMany
    {
        return $this->group->users(); 
    }
1 like
csprick's avatar

@IGP from StackOverflow also answered this question and had some good insight into how things work behind the scenes. I wanted to include this here for completeness.

You can use a HasMany relationship to return a collection of models based on the query it generates with the > relationship method's parameters.

You're not strictly forced to user foreign keys and primary keys when using Eloquent Relationships. It's just a (very sensible) convention.

For Activity -> User, it's very straightforward.

class Activity extends Model
{
   public function users(): HasMany
   {
      return $this->hasMany(
         related: User::class,
           foreignKey: 'group_id',
           localKey: 'group_id',
       );
   }
}

In this case the generated SQL should be something like

SELECT * FROM {related} WHERE {related}.{foreignKey} = {$activity}.{localKey}

Your HasManyThrough throws an error because it expects a different structure than what you have.

For it to work your relationships should have been

if User hasMany Group and Group hasMany Activity then User hasManyThrough Activity (using Group as intermediate table).

1 like

Please or to participate in this conversation.