多态一对多

在面向对象编程里面有一个特性就是多态,多态就是适应多种情况,有的时候我们有这样一个需求。评论需求,因为网站本来就不大,所以我期望所有的评论都放在一个表里面,可以评论图片,评论文章等等。

首先评论这个是一对多的关系。所以说应该在评论表里面存储一个被评论模型(比如文章、图片)的 Id,但是这个时候呢,文章的 ID 与图片的 ID 一定会有重合的情况。例如某用户评论了 Id 为 1的文章,某用户评论了 Id 为1的图片,这个时候我们查询的时候就不清楚这个用户到底是评论了图片还说文章。

针对这一问题,我们需要在多的这一方,也就是评论表里面增加一个字段,比如 type,当 type 为 post 的时候,我们就知道是文章,当 type 为 image 的时候,我们就知道是图片。

当然这个东西可能有更专业的名称 (association scopes) ,但是我更喜欢把它理解为多态。

为了更快速的实现,就不再写接口了。关于如何定义接口让代码提示正常工作,前面的代码已经基本演示完了。

例子

新建 comment.ts

  1. export default (sequelize, dataTypes) => {
  2. const Comment = sequelize.define('Comment', {
  3. text: dataTypes.STRING,
  4. type: dataTypes.STRING,
  5. type_id: dataTypes.INTEGER
  6. });
  7. Comment.associate = function(models) {
  8. this.belongsTo(models.Post);
  9. this.belongsTo(models.Image);
  10. }
  11. return Comment;
  12. }

新建 post.ts

  1. export default (sequelize, dataTypes) => {
  2. const Post = sequelize.define('Post', {
  3. title: dataTypes.STRING,
  4. content: dataTypes.TEXT,
  5. });
  6. Post.associate = function(models) {
  7. this.hasMany(models.Comment, {
  8. foreignKey: 'type_id',
  9. constraints: false,
  10. scope: {
  11. type: 'post'
  12. }
  13. });
  14. }
  15. return Post;
  16. }

多态都是通过增加一个标识字段来区分不同的类型,而在声明关系的时候,可以通过 scope 来指定这个标识是什么,这样就可以区分开来了。constraints 是为了关闭外键所存在的约束,对于多态的,请关闭它。foreignKey 是为了指定相关联的字段值。

新建 image.ts

  1. export default (sequelize, dataTypes) => {
  2. const Image = sequelize.define('Image', {
  3. title: dataTypes.STRING,
  4. imageURL: dataTypes.STRING,
  5. });
  6. Image.associate = function(models) {
  7. this.hasMany(models.Comment, {
  8. foreignKey: 'type_id',
  9. constraints: false,
  10. scope: {
  11. type: 'image'
  12. }
  13. });
  14. }
  15. return Image;
  16. }

在 index.ts 里面添加如下代码

  1. const Comment = sequelize.import('./comment');
  2. const Post = sequelize.import('./post');
  3. const Image = sequelize.import('./image');
  4. let post = await Post.create({
  5. title: 'name',
  6. content: 'some'
  7. });
  8. let image = await Image.create({
  9. title: 'image',
  10. imageURL: 'url'
  11. });
  12. console.log((post as any).__proto__)
  13. await (post as any).createComment({
  14. text: 'hello'
  15. });
  16. await (image as any).createComment({
  17. text: 'hello2'
  18. });

打印一下 (post as any).proto 我们便可知道它有哪些关于 comment 的方法。

  1. getComments: [Function]
  2. countComments: [Function]
  3. hasComment: [Function]
  4. hasComments: [Function]
  5. setComments: [Function]
  6. addComment: [Function]
  7. addComments: [Function]
  8. removeComment: [Function]
  9. removeComments: [Function]
  10. createComment: [Function]

记得别忘了同步数据库表,完成运行之后,可以看到所有的评论都存在了一个表里面。