One To Many
A one-to-many relationship is used to define relationships where a single model owns any amount of other models. For example, a blog post may have an infinite number of comments. Pinia ORM provides the relationship attribute to define such a relationship.
Defining The One To Many Relationship
Like all other Pinia ORM relationships, one-to-many relationships are defined by placing a relationship attribute as a model field. As for one-to-many relationship, you may define the field using the hasMany
attribute. You may define the Post
model that has many Comment
models as shown below:
class Post extends Model {
static entity = 'posts'
static fields () {
return {
id: this.attr(null),
title: this.string(''),
comments: this.hasMany(Comment, 'postId')
}
}
}
class Comment extends Model {
static entity = 'comments'
static fields () {
return {
id: this.attr(null),
postId: this.attr(null),
body: this.string('')
}
}
}
The first argument passed to the hasMany
method is the related model, and the second argument is the foreign key.
Remember that Pinia ORM assumes that the foreign key should have a value matching the id
(or the custom static primaryKey
) field of the parent. Pinia ORM will look for the value of the user's id
field in the userId
field of the Comment record. If you would like the relationship to use a value other than id
, you may pass a third argument to the hasMany
method specifying your custom key:
class Post extends Model {
static entity = 'posts'
static fields () {
return {
id: this.attr(null),
localId: this.attr(null),
title: this.string(''),
comments: this.hasMany(Comment, 'postId')
}
}
}
One To Many (Inverse)
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 on the child model, which calls the belongsTo
method just like with the hasOne
relation:
class Comment extends Model {
static entity = 'comments'
static fields () {
return {
id: this.attr(null),
postId: this.attr(null),
body: this.string(''),
post: this.belongsTo(Post, 'postId')
}
}
}
In the example above, Pinia ORM will try to match the postId
from the Comment
model to an id
on the Post
model.
If your parent model does not use id
as its primary key, or you wish to join the child model to a different field, you may pass a third argument to the belongsTo
method specifying your parent model's custom key:
class Comment extends Model {
static entity = 'comments'
static fields () {
return {
id: this.attr(null),
postId: this.attr(null),
body: this.string(''),
post: this.belongsTo(Post, 'postId', 'otherKey')
}
}
}
One To Many By
One To Many By is similar to One To Many relation but having foreign keys at parent Model as an array. For example, there could be a situation where you must parse data looks something like:
{
nodes: {
1: { id: 1, name: 'Node 01' },
2: { id: 2, name: 'Node 02' }
},
clusters: {
1: {
id: 1,
name: 'Cluster 01',
nodeIds: [1, 2]
}
}
}
As you can see, clusters
have "Has Many" relationship with nodes
, but nodes
do not have foreign key set (clusterId
). We can't use Has Many relation in this case because there is no foreign key to look for. In such cases, you may use hasManyBy
relationship.
class Node extends Model {
static entity = 'nodes'
static fields () {
return {
id: this.attr(null),
name: this.string('')
}
}
}
class Cluster extends Model {
static entity = 'clusters'
static fields () {
return {
id: this.attr(null),
nodeIds: this.attr([]),
name: this.string('')
nodes: this.hasManyBy(Node, 'nodeIds')
}
}
}
Now the Cluster model is going to look for Nodes using ids at Cluster's own nodeIds
attributes.
As always, you can pass the third argument to specify which id to look for.
class Cluster extends Model {
static entity = 'clusters'
static fields () {
return {
id: this.attr(null),
nodeIds: this.attr(null),
nodes: this.hasManyBy(Node, 'nodeIds', 'nodeId')
}
}
}
Has Many Through
The "has-many-through" relationship provides a convenient shortcut for accessing distant relations via an intermediate relation. For example, a Country might have many Posts through an intermediate User. In this example, you could easily gather all posts for a given country. Let's look at the models required to define this relationship:
class Country extends Model {
static entity = 'countries'
static fields () {
return {
id: this.attr(null),
posts: this.hasManyThrough(Post, User, 'country_id', 'user_id')
}
}
}
class User extends Model {
static entity = 'users'
static fields () {
return {
id: this.attr(null),
country_id: this.attr(null)
}
}
}
class Post extends Model {
static entity = 'posts'
static fields () {
return {
id: this.attr(null),
user_id: this.attr(null)
}
}
}
Though posts do not contain a country_id column, the hasManyThrough
relation provides access to a country's posts. To perform this query, Pinia ORM inspects the country_id
on the intermediate User model. After finding the matching user IDs, they are used to query the Post model.
The first argument passed to the hasManyThrough
method is the final model we wish to access, while the second argument is the intermediate model. The third argument is the name of the foreign key on the intermediate model. The fourth argument is the name of the foreign key on the final model.
If you would like to customize the local key for the models, you could also pass the 5th argument which is the local key, while the 6th argument is the local key of the intermediate model.
this.hasManyThrough(
Post, // Final model we wish to access.
User, // Intermediate model.
'country_id', // Foreign key on User model.
'user_id', // Foreign key on Post model.
'local_country_id', // Local key on Country model.
'local_user_id' // Local key on User model.
)
hasManyThrough
relationship without intermediate relation, the intermediate record will not be generated.