高级概念
作用域
本节涉及关联作用域. 有关关联作用域与关联模型作用域的定义, 请参阅 作用域.
关联作用域允许你在关联上放置作用域(get
和 create
的一组默认属性). 作用域可以放在关联模型(关联的目标)上,也可以放在n:m关系的中间表上.
1:n
假设我们有模型 Comment,Post 和 Image. Comment可以通过 commentableId
和 commentable
与Image或Post相关联 - 我们称 Post 和 Image 为 `Commentable’
class Post extends Model {}
Post.init({
title: Sequelize.STRING,
text: Sequelize.STRING
}, { sequelize, modelName: 'post' });
class Image extends Model {}
Image.init({
title: Sequelize.STRING,
link: Sequelize.STRING
}, { sequelize, modelName: 'image' });
class Comment extends Model {
getItem(options) {
return this[
'get' +
this.get('commentable')
[0]
.toUpperCase() +
this.get('commentable').substr(1)
](options);
}
}
Comment.init({
title: Sequelize.STRING,
commentable: Sequelize.STRING,
commentableId: Sequelize.INTEGER
}, { sequelize, modelName: 'comment' });
Post.hasMany(Comment, {
foreignKey: 'commentableId',
constraints: false,
scope: {
commentable: 'post'
}
});
Comment.belongsTo(Post, {
foreignKey: 'commentableId',
constraints: false,
as: 'post'
});
Image.hasMany(Comment, {
foreignKey: 'commentableId',
constraints: false,
scope: {
commentable: 'image'
}
});
Comment.belongsTo(Image, {
foreignKey: 'commentableId',
constraints: false,
as: 'image'
});
constraints: false,
禁用引用约束 - 因为 commentableId
列引用了几个表,我们不能为它添加REFERENCES
约束. 请注意,Image -> Comment和Post -> Comment关系定义了一个作用域,分别是 commentable: 'image'
和commentable: 'post'
. 使用关联函数时,将自动应用此作用域:
image.getComments()
// SELECT "id", "title", "commentable", "commentableId", "createdAt", "updatedAt" FROM "comments" AS
// "comment" WHERE "comment"."commentable" = 'image' AND "comment"."commentableId" = 1;
image.createComment({
title: 'Awesome!'
})
// INSERT INTO "comments" ("id","title","commentable","commentableId","createdAt","updatedAt") VALUES
// (DEFAULT,'Awesome!','image',1,'2018-04-17 05:36:40.454 +00:00','2018-04-17 05:36:40.454 +00:00')
// RETURNING *;
image.addComment(comment);
// UPDATE "comments" SET "commentableId"=1,"commentable"='image',"updatedAt"='2018-04-17 05:38:43.948
// +00:00' WHERE "id" IN (1)
Comment
上的 getItem
实用函数完成了图片 - 它只是将commentable
字符串转换为对getImage
或getPost
的调用,提供了 comment 是属于post还是image的抽象. 你可以将普通选项对象作为参数传递给getItem(options)
以指定 where 条件或包含的任何位置.
n:m
继续使用多态模型的概念,考虑一个标签表 - 一个项目可以有多个标签,标签可以与多个项目相关联.
为简洁起见,该示例仅显示Post模型,但实际上Tag将与其他几个模型相关.
class ItemTag extends Model {}
ItemTag.init({
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
tagId: {
type: Sequelize.INTEGER,
unique: 'item_tag_taggable'
},
taggable: {
type: Sequelize.STRING,
unique: 'item_tag_taggable'
},
taggableId: {
type: Sequelize.INTEGER,
unique: 'item_tag_taggable',
references: null
}
}, { sequelize, modelName: 'item_tag' });
class Tag extends Model {}
Tag.init({
name: Sequelize.STRING,
status: Sequelize.STRING
}, { sequelize, modelName: 'tag' });
Post.belongsToMany(Tag, {
through: {
model: ItemTag,
unique: false,
scope: {
taggable: 'post'
}
},
foreignKey: 'taggableId',
constraints: false
});
Tag.belongsToMany(Post, {
through: {
model: ItemTag,
unique: false
},
foreignKey: 'tagId',
constraints: false
});
请注意,作用域列(taggable
)现在位于中间模型(ItemTag
)上.
我们还可以定义一个更具限制性的关联,例如,通过应用中间模型(ItemTag
)和目标模型(Tag
)的作用域来获取post的所有待定tag:
Post.belongsToMany(Tag, {
through: {
model: ItemTag,
unique: false,
scope: {
taggable: 'post'
}
},
scope: {
status: 'pending'
},
as: 'pendingTags',
foreignKey: 'taggableId',
constraints: false
});
post.getPendingTags();
SELECT
"tag"."id",
"tag"."name",
"tag"."status",
"tag"."createdAt",
"tag"."updatedAt",
"item_tag"."id" AS "item_tag.id",
"item_tag"."tagId" AS "item_tag.tagId",
"item_tag"."taggable" AS "item_tag.taggable",
"item_tag"."taggableId" AS "item_tag.taggableId",
"item_tag"."createdAt" AS "item_tag.createdAt",
"item_tag"."updatedAt" AS "item_tag.updatedAt"
FROM
"tags" AS "tag"
INNER JOIN "item_tags" AS "item_tag" ON "tag"."id" = "item_tag"."tagId"
AND "item_tag"."taggableId" = 1
AND "item_tag"."taggable" = 'post'
WHERE
("tag"."status" = 'pending');
constraints: false
禁用 taggableId
列的引用约束. 因为列是多态的,所以我们不能说它 REFERENCES
了一个特定的表.
用关联创建
如果所有元素都是新的,则可以在一个步骤中创建具有嵌套关联的实例.
BelongsTo / HasMany / HasOne 关联
考虑以下模型:
class Product extends Model {}
Product.init({
title: Sequelize.STRING
}, { sequelize, modelName: 'product' });
class User extends Model {}
User.init({
firstName: Sequelize.STRING,
lastName: Sequelize.STRING
}, { sequelize, modelName: 'user' });
class Address extends Model {}
Address.init({
type: Sequelize.STRING,
line1: Sequelize.STRING,
line2: Sequelize.STRING,
city: Sequelize.STRING,
state: Sequelize.STRING,
zip: Sequelize.STRING,
}, { sequelize, modelName: 'address' });
Product.User = Product.belongsTo(User);
User.Addresses = User.hasMany(Address);
// 也能用于 `hasOne`
可以通过以下方式在一个步骤中创建一个新的Product
, User
和一个或多个Address
:
return Product.create({
title: 'Chair',
user: {
firstName: 'Mick',
lastName: 'Broadstone',
addresses: [{
type: 'home',
line1: '100 Main St.',
city: 'Austin',
state: 'TX',
zip: '78704'
}]
}
}, {
include: [{
association: Product.User,
include: [ User.Addresses ]
}]
});
这里,我们的用户模型称为user
,带小写u - 这意味着对象中的属性也应该是user
. 如果给sequelize.define
指定的名称为User
,对象中的键也应为User
. 对于addresses
也是同样的,除了它是一个 hasMany
关联的复数.
用别名创建 BelongsTo 关联
可以将前面的示例扩展为支持关联别名.
const Creator = Product.belongsTo(User, { as: 'creator' });
return Product.create({
title: 'Chair',
creator: {
firstName: 'Matt',
lastName: 'Hansen'
}
}, {
include: [ Creator ]
});
HasMany / BelongsToMany 关联
我们来介绍将产品与许多标签相关联的功能. 设置模型可能如下所示:
class Tag extends Model {}
Tag.init({
name: Sequelize.STRING
}, { sequelize, modelName: 'tag' });
Product.hasMany(Tag);
// 也能用于 `belongsToMany`.
现在,我们可以通过以下方式创建具有多个标签的产品:
Product.create({
id: 1,
title: 'Chair',
tags: [
{ name: 'Alpha'},
{ name: 'Beta'}
]
}, {
include: [ Tag ]
})
然后,我们可以修改此示例以支持别名:
const Categories = Product.hasMany(Tag, { as: 'categories' });
Product.create({
id: 1,
title: 'Chair',
categories: [
{ id: 1, name: 'Alpha' },
{ id: 2, name: 'Beta' }
]
}, {
include: [{
association: Categories,
as: 'categories'
}]
})