Skip to main content

Relationships

Vania supports the full range of model relationships: one-to-one, one-to-many, many-to-many, and polymorphic variants. Relationships are defined in the model's registerRelations() method.

Defining Relationships​

Override registerRelations() in your model to declare how it connects to other models:

class User extends Model {
@override
List<String> get fillable => ['name', 'email'];

@override
void registerRelations() {
hasOne('profile', Profile(), foreignKey: 'user_id');
hasMany('posts', Post(), foreignKey: 'user_id');
belongsToMany('roles', Role(), pivotTable: 'role_user');
}
}

One-to-One: hasOne​

A user has one profile:

class User extends Model {
@override
void registerRelations() {
hasOne('profile', Profile(), foreignKey: 'user_id', localKey: 'id');
}
}
ParameterDefaultDescription
name—Relation name (used with include())
related—Instance of the related model
foreignKey{RelatedType}_idForeign key on the related table
localKeyidLocal key on this table

One-to-Many: hasMany​

A user has many posts:

class User extends Model {
@override
void registerRelations() {
hasMany('posts', Post(), foreignKey: 'user_id');
}
}

Inverse: belongsTo​

A post belongs to a user:

class Post extends Model {
@override
void registerRelations() {
belongsTo('user', User(), foreignKey: 'user_id', localKey: 'id');
}
}
ParameterDefaultDescription
foreignKey{RelatedType}_idForeign key on this table
localKeyidPrimary key on the parent table

Many-to-Many: belongsToMany​

Users and roles connected through a pivot table:

class User extends Model {
@override
void registerRelations() {
belongsToMany(
'roles',
Role(),
pivotTable: 'role_user',
parentPivotKey: 'user_id',
relatedPivotKey: 'role_id',
);
}
}

class Role extends Model {
@override
void registerRelations() {
belongsToMany(
'users',
User(),
pivotTable: 'role_user',
parentPivotKey: 'role_id',
relatedPivotKey: 'user_id',
);
}
}
ParameterDefaultDescription
pivotTableDerived from both model namesThe intermediate table
parentPivotKey—This model's key in the pivot table
relatedPivotKey—Related model's key in the pivot table
parentLocalKeyidThis model's primary key
relatedLocalKeyidRelated model's primary key

Polymorphic: morphOne / morphMany​

When a model can belong to more than one type of parent. For example, both posts and videos can have comments:

class Comment extends Model {
@override
void registerRelations() {
morphTo('commentable', Post(), morphKey: 'commentable_id', morphType: 'commentable_type');
}
}

class Post extends Model {
@override
void registerRelations() {
morphMany('comments', Comment(), morphKey: 'commentable_id', morphType: 'commentable_type');
}
}

class Video extends Model {
@override
void registerRelations() {
morphMany('comments', Comment(), morphKey: 'commentable_id', morphType: 'commentable_type');
}
}

morphOne​

Like morphMany but returns a single related model:

class User extends Model {
@override
void registerRelations() {
morphOne('avatar', Image(), morphKey: 'imageable_id', morphType: 'imageable_type');
}
}

morphTo​

The inverse of morphOne and morphMany:

class Image extends Model {
@override
void registerRelations() {
morphTo('imageable', User(), morphKey: 'imageable_id', morphType: 'imageable_type');
}
}

Polymorphic Many-to-Many: morphToMany / morphedByMany​

Tags that can be applied to posts, videos, and other models:

class Post extends Model {
@override
void registerRelations() {
morphToMany(
'tags',
Tag(),
morphKey: 'taggable_id',
morphType: 'taggable_type',
pivotTable: 'taggables',
relatedMorphKey: 'tag_id',
);
}
}

class Tag extends Model {
@override
void registerRelations() {
morphedByMany(
'posts',
Post(),
morphKey: 'taggable_id',
morphType: 'taggable_type',
pivotTable: 'taggables',
relatedMorphKey: 'tag_id',
);
}
}

Eager Loading​

Avoid the N+1 query problem by eager loading relationships:

// Load a single relation
var users = await User().query.include('posts').get();

// Load multiple relations
var users = await User().query
.include('posts')
.include('profile')
.include('roles')
.get();

// Nested eager loading
var users = await User().query.include('posts.comments').get();

// Constrain eager loaded relations
var users = await User().query
.include('posts', (query) => query
.where('published', '=', true)
.orderBy('created_at', 'desc')
.limit(5))
.get();

// Select specific columns
var users = await User().query.include('posts:title,created_at').get();

Querying with Relations​

Use whereHas and whereDoesntHave to filter based on related records:

// Users who have at least one post
var authors = await User().query.whereHas('posts').get();

// Users with no posts
var readers = await User().query.whereDoesntHave('posts').get();

Relation Summary​

RelationshipMethodExample
One-to-OnehasOneUser → Profile
One-to-ManyhasManyUser → Posts
Many-to-OnebelongsToPost → User
Many-to-ManybelongsToManyUser ↔ Roles
Polymorphic OnemorphOneUser → Image
Polymorphic ManymorphManyPost → Comments
Polymorphic InversemorphToComment → Commentable
Polymorphic M2MmorphToManyPost → Tags
Polymorphic M2M InversemorphedByManyTag → Posts