HasManyThrough
The "has-many-through" relationship provides a convenient shortcut for accessing distant relations via an intermediate relation.
This is useful when you want to jump over an intermediate model to fetch distant records without writing complex manual joins.
Standard Relationship
Consider a scenario with Countries, Users, and Posts:
- A
Countryhas manyUsers. - A
Userhas manyPosts. - Therefore, a
Countryhas manyPoststhrough its users.
Table Structure
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - stringModel Definition
class Country extends Model {
// Define the distant relationship
HasManyThrough<Post, User> posts() => hasManyThrough(Post.new, User.new);
}By default, Bavard uses convention to determine the keys:
- First Key:
country_idon the intermediate table (users). - Second Key:
user_idon the target table (posts).
Usage
final country = await Country().query().find(1);
// Fetch all posts written by users of this country
final posts = await country?.posts().get();Polymorphic HasManyThrough
A common real-world scenario involves polymorphic tables (like comments or media). Imagine you want to retrieve "All comments posted on a User's posts".
Structure:
Userhas manyPosts.Posthas manyComments (Polymorphic:commentable_id,commentable_type).
Model Definition
Use the hasManyThroughPolymorphic method. This helper automatically handles the polymorphic type constraints for you.
class User extends Model {
// 1. Standard relation to posts
HasMany<Post> posts() => hasMany(Post.new);
// 2. Distant relation: Get all comments on this user's posts
HasManyThrough<Comment, Post> postComments() {
return hasManyThroughPolymorphic(
Comment.new,
Post.new,
name: 'commentable', // The prefix for _id and _type columns
type: 'posts', // The value stored in the _type column
);
}
}How it works
Under the hood, this method:
- Sets the second foreign key to
commentable_id(derived fromname). - Automatically adds a
WHERE commentable_type = 'posts'clause to the query.
Usage
final user = await User().query().find(1);
// Fetch all comments that belongs to the user's posts
final comments = await user?.postComments().get();Overriding Keys (Advanced)
If your database does not follow standard conventions (e.g., using nation_code instead of country_id), you can manually override the keys.
class Country extends Model {
HasManyThrough<Post, User> posts() => hasManyThrough(
Post.new,
User.new,
firstKey: 'nation_code', // Foreign key on 'users' table pointing to 'countries'
secondKey: 'author_id', // Foreign key on 'posts' table pointing to 'users'
);
}