Ecto.Migration约束配置:新闻评论与一级子评论关联规则
嘿,我来一步步帮你把这些规则用数据库约束落地,全程不用额外业务代码校验,数据库层面直接把逻辑焊死,稳得很~
1. 确保news_id与comment_id的唯一约束(一个评论仅归属一个新闻)
你提到已经设置了这个约束,不过为了更明确,我们可以在迁移里显式添加唯一索引(PostgreSQL会自动生成对应的唯一约束):
create unique_index(:news_comments, [:news_id, :comment_id])
这个约束能保证同一个评论不会被关联到多个新闻,同时为后面的子评论规则打下基础。
2. 子评论必须与父评论属于同一新闻
要实现「插入子评论时,news_id与parent_comment_id的组合已存在于news_comments表」,我们可以添加复合外键约束,让子评论的(news_id, parent_comment_id)关联到父评论在news_comments中的(news_id, comment_id)记录:
alter table(:news_comments) do add constraint(:news_comments, :valid_parent_comment, foreign_key: [ columns: [:news_id, :parent_comment_id], references: [:news_comments, :news_id, :comment_id], on_delete: :nilify_all ] ) end
这个约束会强制:当插入带parent_comment_id的子评论时,必须存在一条news_comments记录,其news_id和当前子评论的news_id完全相同,且comment_id等于当前的parent_comment_id,从根源上保证子评论和父评论属于同一新闻。
3. 禁止嵌套子评论(仅允许给顶级评论加子评论)
要实现「仅允许为无parent_comment_id的评论添加子评论」,我们用CHECK约束结合子查询来校验父评论的层级:
alter table(:news_comments) do add constraint(:news_comments, :no_nested_replies, check: """ parent_comment_id IS NULL OR (SELECT parent_comment_id FROM news_comments WHERE comment_id = news_comments.parent_comment_id) IS NULL """ ) end
这个约束的逻辑很直白:要么当前评论是顶级评论(parent_comment_id为空),要么它的父评论本身是顶级评论(父评论的parent_comment_id为空),直接把嵌套子评论的路堵死,和YouTube的规则完全一致。
完整迁移代码
把所有约束整合到你的迁移文件里,最终代码如下:
def change do create table(:news_comments, primary_key: false) do add :id, :uuid, primary_key: true add :news_id, references(:news, type: :uuid, on_delete: :nilify_all) add :comment_id, references(:comments, type: :uuid, on_delete: :nilify_all) add :parent_comment_id, references(:comments, type: :uuid, on_delete: :nilify_all) end # 规则1:news_id与comment_id唯一约束 create unique_index(:news_comments, [:news_id, :comment_id]) # 规则2:子评论必须与父评论同属一个新闻 alter table(:news_comments) do add constraint(:news_comments, :valid_parent_comment, foreign_key: [ columns: [:news_id, :parent_comment_id], references: [:news_comments, :news_id, :comment_id], on_delete: :nilify_all ] ) end # 规则3:禁止嵌套子评论 alter table(:news_comments) do add constraint(:news_comments, :no_nested_replies, check: """ parent_comment_id IS NULL OR (SELECT parent_comment_id FROM news_comments WHERE comment_id = news_comments.parent_comment_id) IS NULL """ ) end end
小提示
这些约束都是数据库层面的,不管业务代码怎么写,数据库都会强制校验,安全性拉满。如果你用的是PostgreSQL,这些语法直接能用;如果是其他数据库,可能需要微调CHECK约束的写法,但核心逻辑完全通用。
内容的提问来源于stack exchange,提问作者Sardoan

