84

以下に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
4

2 に答える 2

185

外部キーは、1 つの親テーブルのみを参照する必要があります。これは、SQL 構文とリレーショナル理論の両方の基本です。

ポリモーフィック アソシエーションとは、特定の列が 2 つ以上の親テーブルのいずれかを参照できる場合です。その制約を SQL で宣言する方法はありません。

ポリモーフィック アソシエーションの設計は、リレーショナル データベース設計のルールを破ります。使用はお勧めしません。

いくつかの代替手段があります。

  • 排他的アーク: それぞれが 1 つの親を参照する複数の外部キー列を作成します。これらの外部キーの 1 つだけが非 NULL になるように強制します。

  • リレーションシップを逆にする: 3 つの多対多テーブルを使用し、それぞれがコメントとそれぞれの親を参照します。

  • 具体的なスーパーテーブル: 暗黙的な「コメント可能な」スーパークラスの代わりに、各親テーブルが参照する実際のテーブルを作成します。次に、コメントをそのスーパーテーブルにリンクします。疑似 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も、イベントを親として示す行が含まれている場合、結果はどうなるでしょうか? ドロップ テーブルを中止する必要がありますか? 行をComments孤立させる必要がありますか? などの別の既存のテーブルを参照するように変更する必要がありArticlesますか? Eventsを指すときに意味をなすために使用された id 値はありArticlesますか?

これらのジレンマはすべて、ポリモーフィック アソシエーションがデータ (つまり文字列値) を使用してメタデータ (テーブル名) を参照することに依存しているという事実によるものです。これは SQL ではサポートされていません。データとメタデータは別物です。


あなたの「Concrete Supertable」の提案に頭を悩ませています。

  • Rails モデル定義Commentableの単なる形容詞ではなく、実際の SQL テーブルとして定義します。他の列は必要ありません。

    CREATE TABLE Commentable (
      id INT AUTO_INCREMENT PRIMARY KEY
    ) TYPE=InnoDB;
    
  • テーブルArticlesPhotos、および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 しか持っておらず、そのコメントがコメント可能なリソースを見つけたい場合。このために、コメント可能なテーブルが参照するリソースを指定すると役立つ場合があります。

    SELECT commentable_id, commentable_type FROM Commentable t
    JOIN Comments c ON (t.id = c.commentable_id)
    WHERE c.id = 42;
    

    commentable_type次に、結合するテーブルを見つけた後、2 番目のクエリを実行して、それぞれのリソース テーブル (写真、記事など) からデータを取得する必要があります。SQL ではテーブルに明示的に名前を付ける必要があるため、同じクエリでは実行できません。同じクエリのデータ結果によって決定されるテーブルに結合することはできません。

確かに、これらの手順のいくつかは、Rails で使用されている規則を破っています。しかし、適切なリレーショナル データベース設計に関しては、Rails の規則は間違っています。

于 2009-05-28T17:59:42.730 に答える