Polymorphic
A polymorphic relationship is where a model can belong to more than one type of model on a single association.
One To One
A one-to-one polymorphic relation is similar to a simple one-to-one relation; however, the target model can belong to more than one type of model on a single association. For example, an Image
might be associated with a User
or Post
model.
Defining A One To One Polymorphic Relationship
To define this relationship, for example, a User
or Post
model might be associated with one Image
, we define a morphOne
field to the User
and Post
models.
class Image extends Model {
static entity = 'images'
static fields () {
return {
id: this.number(0),
url: this.string(''),
imageableId: this.number(0),
imageableType: this.string(''),
}
}
}
class User extends Model {
static entity = 'users'
static fields () {
return {
id: this.number(0),
name: this.string(''),
image: this.morphOne(Image, 'imageableId', 'imageableType')
}
}
}
class Post extends Model {
static entity = 'posts'
static fields () {
return {
id: this.number(0),
title: this.string(''),
image: this.morphOne(Image, 'imageableId', 'imageableType')
}
}
}
The first argument passed to the morphOne
method is the name of the model, the second argument is the name of the field which will contain the id
of the model, and the third argument is the name of the field which will contain the entity
of the parent model. The third argument is used to determine the "type" of the related parent model.
Additionally, Pinia ORM assumes that the foreign key should have a value matching the id
(or the custom static primaryKey
) field of the parent. In other words, Pinia ORM will look for the value of the user's id
field in the imageableId
field of the Image
record. If you would like the relationship to use a value other than id
, you may pass a fourth argument to the morphOne
method specifying your custom key:
class User extends Model {
static entity = 'users'
static fields () {
return {
id: this.number(0),
userId: this.string(''),
name: this.string(''),
image: this.morphOne(Image, 'imageableId', 'imageableType', 'userId')
}
}
}
Defining The Inverse Of The Relationship
So, we can access the Image
model from our User
or Post
. Now, let's define a relationship on the Image
model that will let us access the model which owns the image. We can define the inverse of a morphOne
relationship using the morphTo
attribute:
class Image extends Model {
static entity = 'images'
static fields () {
return {
id: this.number(0),
url: this.string(''),
imageableId: this.number(0),
imageableType: this.string(''),
imageable: this.morphTo(
[User, Post],
'imageableId',
'imageableType'
)
}
}
}
The first argument passed to the morphTo
method is an array of models which are related, the second argument is the name of the field which will contain the id
of the model, and the third argument is the name of the field which will contain the entity
of the related model. The third argument is used to determine the "type" of the related model. You may also pass a fourth argument to the morphTo
method specifying your custom key on the related model.
class Image extends Model {
static entity = 'images'
static fields () {
return {
id: this.number(0),
url: this.string(''),
imageableId: this.number(0),
imageableType: this.string(''),
imageable: this.morphTo(
[User, Post],
'imageableId',
'imageableType',
'morphableId'
)
}
}
}
One To Many
A one-to-many polymorphic relation is similar to a simple one-to-many relation; however, the target model can belong to
more than one type of model on a single association. For example, a Comment
might be associated with a Post
or
Video
model.
Defining A One To Many Polymorphic Relationship
To define this relationship, for example, a Post
or Video
model might be associated with one or more Comment
(s), we
define a morphMany
field to the Post
and Video
models.
class Comment extends Model {
static entity = 'comments'
static fields () {
return {
id: this.number(0),
url: this.string(''),
commentableId: this.number(0),
commentableType: this.string(''),
}
}
}
class Video extends Model {
static entity = 'videos'
static fields () {
return {
id: this.number(0),
link: this.string(''),
comments: this.morphMany(Comment, 'commentableId', 'commentableType')
}
}
}
class Post extends Model {
static entity = 'posts'
static fields () {
return {
id: this.number(0),
title: this.string(''),
comments: this.morphMany(Comment, 'commentableId', 'commentableType')
}
}
}
The first argument passed to the morphMany
method is the name of the model, the second argument is the name of the
field which will contain the id
of the model, and the third argument is the name of the field which will contain the
entity
of the parent model. The third argument is used to determine the "type" of the related parent model.
Additionally, Pinia ORM assumes that the foreign key should have a value matching the id
(or the custom static primaryKey
) field of the parent. In other words, Pinia ORM will look for the value of the
video's id
field in the commentableId
field of the Comment
record. If you would like the relationship to use a
value other than id
, you may pass a fourth argument to the morphMany
method specifying your custom key:
class Video extends Model {
static entity = 'videos'
static fields () {
return {
id: this.number(0),
videoId: this.string(''),
link: this.string(''),
comments: this.morphMany(Comment, 'commentableId', 'commentableType', 'videoId')
}
}
}
Many To Many (Polymorphic)
In addition to traditional polymorphic relations, you may also define "many-to-many" polymorphic relations. For example, a blog Post and Video model could share a polymorphic relation to a Tag model. Using a many-to-many polymorphic relation allows you to have a single list of unique tags that are shared across blog posts and videos.
You can define many-to-many polymorphic relations by using the this.morphToMany
attribute.
class Post extends Model {
static entity = 'posts'
static fields () {
return {
id: this.attr(null),
tags: this.morphToMany(Tag, Taggable, 'tag_id', 'taggable_id', 'taggable_type')
}
}
}
class Video extends Model {
static entity = 'videos'
static fields () {
return {
id: this.attr(null),
tags: this.morphToMany(Tag, Taggable, 'tag_id', 'taggable_id', 'taggable_type')
}
}
}
class Tag extends Model {
static entity = 'tags'
static fields () {
return {
id: this.attr(null),
name: this.attr('')
}
}
}
class Taggable extends Model {
static entity = 'taggables'
static primaryKey = ['tag_id', 'taggable_id', 'taggable_type']
static fields () {
return {
id: this.attr(null),
tag_id: this.attr(null),
taggable_id: this.attr(null),
taggable_type: this.attr(null)
}
}
}
Defining The Inverse Of The Relationship
To define the inverse relation to fetch related record – for this example it's for Tag model – you can use the this.morphedByMany()
attribute.
class Tag extends Model {
static entity = 'tags'
static fields () {
return {
id: this.attr(null),
name: this.attr(''),
posts: this.morphedByMany(
Post, Taggable, 'tag_id', 'taggable_id', 'taggable_type'
),
videos: this.morphedByMany(
Video, Taggable, 'tag_id', 'taggable_id', 'taggable_type'
)
}
}
}
Access Intermediate Model
As the same as belongsToMany
relationship, you may access the intermediate model for polymorphic many-to-many relationship through pivot
attribute on the model.
const post = Post.query().with('tags').first()
post.tags.forEach((tag) => {
console.log(tag.pivot)
})
Each Tag
model we retrieve is automatically assigned a pivot
attribute. This attribute contains a model representing the intermediate model and may be used like any other model.