Relation One To Many
A one-to-many relationship is used to define relationships where a single model is the parent to one or more child models. For example, a blog post may have an infinite number of comments. Like all other Fedaco relationships, one-to-many relationships are defined by defining an annotation on your Fedaco model property:
import {HasManyColumn, forwardRef} from "@gradii/fedaco";
class Post extends Model {
/**
* Get the comments for the blog post.
*/
@HasManyColumn({
related: forwardRef(() => Comment)
})
public comments: FedacoRelationType<Comment>
}
Remember, Fedaco will automatically determine the proper foreign key column for the Comment
model. By convention, Fedaco will take the "snake case" name of the parent model and suffix it with _id
. So, in this example, Fedaco will assume the foreign key column on the Comment
model is post_id
.
comments = await (await Post.createQuery().find(1)).comments;
for (const comment of comments) {
// ...
}
Since all relationships also serve as query builders, you may add further constraints to the relationship query by calling the comments
method and continuing to chain conditions onto the query:
comment = (await Post::find(1)).NewRelation('comments')
.where('title', 'foo')
.first();
Like the hasOne
method, you may also override the foreign and local keys by passing additional config to the HasManyColumn
annotation:
class Post extends Model {
@HasManyColumn({
related: forwardRef(() => Comment),
foreignKey: 'foreign_key'
})
public comments: FedacoRelationType<Comment>
}
class Post extends Model {
@HasManyColumn({
related: forwardRef(() => Comment),
foreignKey: 'foreign_key',
localKey: 'local_key'
})
public comments: FedacoRelationType<Comment>
}
One to Many (Inverse) / Belongs To
Now that we can access all of a post's comments, let's define a relationship to allow a comment to access its parent post. To define the inverse of a hasMany
relationship, define a relationship method on the child model which calls the belongsTo
method:
class Comment extends Model {
/**
* Get the post that owns the comment.
*/
@BelongsToColumn({
related: forwardRef(() => Post)
})
public post: FedacoRelationType<Post>
}
Once the relationship has been defined, we can retrieve a comment's parent post by accessing the post
"relationship property":
comment = await Comment.createQuery().find(1);
return (await comment.post).title;
In the example above, Fedaco will attempt to find a Post
model that has an id
which matches the post_id
column on the Comment
model.
Fedaco determines the default foreign key name by examining the name of the relationship method and suffixing the method name with a _
followed by the name of the parent model's primary key column. So, in this example, Fedaco will assume the Post
model's foreign key on the comments
table is post_id
.
However, if the foreign key for your relationship does not follow these conventions, you may pass a custom foreign key name as the second argument to the belongsTo
method:
class Comment extends Model {
/**
* Get the post that owns the comment.
*/
@BelongsToColumn({
related: forwardRef(() => Post),
foreignKey: 'foreign_key'
})
public post: FedacoRelationType<Post>;
}
If your parent model does not use id
as its primary key, or you wish to find the associated model using a different column, you may pass a third argument to the belongsTo
method specifying your parent table's custom key:
class Comment extends Model {
/**
* Get the post that owns the comment.
*/
@BelongsToColumn({
related: forwardRef(() => Post),
foreignKey: 'foreign_key',
ownerKey: 'owner_key'
})
public post: FedacoRelationType<Post>;
}
Default Models
The belongsTo
, hasOne
, hasOneThrough
, and morphOne
relationships allow you to define a default model that will be returned if the given relationship is null
. This pattern is often referred to as the Null Object pattern and can help remove conditional checks in your code. In the following example, the user
relation will return an empty App\Models\User
model if no user is attached to the Post
model:
class Post extends Model {
/**
* Get the author of the post.
*/
@BelongsToColumn({
related: forwardRef(() => User),
onQuery: (q => q.withDefault())
})
public user: FedacoRelationType<User>
}
To populate the default model with attributes, you may pass an array or closure to the withDefault
method:
class Post extends Model {
/**
* Get the author of the post.
*/
@BelongsToColumn({
related: forwardRef(() => User),
onQuery: (q => q.withDefault({
'name': 'Guest Author',
}))
})
public user: FedacoRelationType<User>
}
class Post extends Model {
/**
* Get the author of the post.
*/
@BelongsToColumn({
related: forwardRef(() => User),
onQuery: (q => q.withDefault((user: User, post: Post) => {
user.name = 'Guest Author';
}))
})
public user: FedacoRelationType<User>
}