ruby-on-rails - 为什么在多态关联中不能有外键?

标签 ruby-on-rails database foreign-key-relationship polymorphic-associations

为什么在多态关联中不能有外键,例如下面表示为 Rails 模型的那个?

class Comment < ActiveRecord::Base
  belongs_to :commentable, :polymorphic => true
end

class Article < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

class Photo < ActiveRecord::Base
  has_many :comments, :as => :commentable
  #...
end

class Event < ActiveRecord::Base
  has_many :comments, :as => :commentable
end

最佳答案

一个外键只能引用一个父表。这是 SQL 语法和关系理论的基础。

多态关联是指给定的列可以引用两个或多个父表中的任何一个。您无法在 SQL 中声明该约束。

多态关联设计打破了关系数据库设计的规则。我不建议使用它。

有几种选择:

  • 专属弧线:创建多个外键列,每个外键列引用一个父列。强制这些外键之一可以是非 NULL。
  • 反转关系:使用三个多对多表,每个表引用 Comments 和各自的父级。
  • 混凝土 super 表:代替隐含的“可评论”父类(super class),创建一个真实的表,每个父表都引用它。然后将您的评论链接到该 super 表。伪 Rails 代码类似于以下内容(我不是 Rails 用户,因此将其视为指南,而不是文字代码):
    class Commentable < ActiveRecord::Base
      has_many :comments
    end
    
    class Comment < ActiveRecord::Base
      belongs_to :commentable
    end
    
    class Article < ActiveRecord::Base
      belongs_to :commentable
    end
    
    class Photo < ActiveRecord::Base
      belongs_to :commentable
    end
    
    class Event < ActiveRecord::Base
      belongs_to :commentable
    end
    

  • 我还在演讲 Practical Object-Oriented Models in SQL 中介绍了多态关联和我的书 SQL Antipatterns: Avoiding the Pitfalls of Database Programming .

    回复您的评论:是的,我知道还有另一列记录了外键应该指向的表的名称。 SQL 中的外键不支持这种设计。

    例如,如果您插入评论并将“视频”命名为该 Comment 的父表的名称,会发生什么情况? ?不存在名为“视频”的表。插入是否应该因错误而中止?违反了什么约束? RDBMS 如何知道该列应该命名现有表?它如何处理不区分大小写的表名?

    同样,如果您删除 Events表,但您在 Comments 中有行表明 Events 作为它们的父级,结果应该是什么?应该中止删除表吗?应该在 Comments 行成为孤儿?它们是否应该更改为引用另一个现有表,例如 Articles ?做以前指向Events的id值指向 Articles 时有意义?

    这些困境都是由于多态关联依赖于使用数据(即字符串值)来引用元数据(表名)这一事实。这不受 SQL 支持。数据和元数据是分开的。

    I'm having a hard time wrapping my head around your "Concrete Supertable" proposal.


  • 定义 Commentable作为真正的 SQL 表,而不仅仅是 Rails 模型定义中的形容词。不需要其他列。
    CREATE TABLE Commentable (
      id INT AUTO_INCREMENT PRIMARY KEY
    ) TYPE=InnoDB;
    
  • 定义表 Articles , Photos , 和 Events作为 Commentable 的“子类” ,通过使它们的主键也是引用 Commentable 的外键.
    CREATE TABLE Articles (
      id INT PRIMARY KEY, -- not auto-increment
      FOREIGN KEY (id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
    -- similar for Photos and Events.
    
  • 定义 Comments带有 Commentable 外键的表.
    CREATE TABLE Comments (
      id INT PRIMARY KEY AUTO_INCREMENT,
      commentable_id INT NOT NULL,
      FOREIGN KEY (commentable_id) REFERENCES Commentable(id)
    ) TYPE=InnoDB;
    
  • 当你想创建一个 Article (例如),您必须在 Commentable 中创建一个新行也是。 Photos也是如此和 Events .
    INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 1
    INSERT INTO Articles (id, ...) VALUES ( LAST_INSERT_ID(), ... );
    
    INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 2
    INSERT INTO Photos (id, ...) VALUES ( LAST_INSERT_ID(), ... );
    
    INSERT INTO Commentable (id) VALUES (DEFAULT); -- generate a new id 3
    INSERT INTO Events (id, ...) VALUES ( LAST_INSERT_ID(), ... );
    
  • 当你想创建一个 Comment ,使用存在于 Commentable 中的值.
    INSERT INTO Comments (id, commentable_id, ...)
    VALUES (DEFAULT, 2, ...);
    
  • 当您想查询给定 Photo 的评论时,做一些连接:
    SELECT * FROM Photos p JOIN Commentable t ON (p.id = t.id)
    LEFT OUTER JOIN Comments c ON (t.id = c.commentable_id)
    WHERE p.id = 2;
    
  • 当您只有评论的 id 并且您想找到它是评论的可评论资源时。为此,您可能会发现 Commentable 表指定它引用的资源很有帮助。
    SELECT commentable_id, commentable_type FROM Commentable t
    JOIN Comments c ON (t.id = c.commentable_id)
    WHERE c.id = 42;
    

    然后,在从 commentable_type 发现之后,您需要运行第二个查询以从相应的资源表(照片、文章等)中获取数据。要加入哪个表。您不能在同一个查询中执行此操作,因为 SQL 要求显式命名表;您不能加入由同一查询中的数据结果确定的表。

  • 诚然,其中一些步骤违反了 Rails 使用的约定。但是 Rails 约定在正确的关系数据库设计方面是错误的。

    关于ruby-on-rails - 为什么在多态关联中不能有外键?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/922184/

    相关文章:

    ruby-on-rails - 在 Controller 中运行 capybara 来测试一些东西但只能运行一次

    ruby-on-rails - 如何将 IPN 处理程序与 Pay 操作一起使用并接收消息?

    sql - 将一行插入到可能存在多行的数据库中

    MySQL 的更新级联未更新父 FK

    function - laravel - 为什么函数调用没有括号?

    ruby-on-rails - 如何在 Rails 中跟踪用户事件?

    sql - 类别数据库设计

    javascript - 识别 javascript 字符串中的子查询

    java - 在 JTable 中为数据库外键添加组合框

    ruby-on-rails - Cucumber:Factory Girl 不修改数据库——使用 .create 创建对象但之后数据库表仍然为空