1. ホーム
  2. ruby-on-rails

[解決済み] 多相アソシエーションで外部キーが持てないのはなぜですか?

2023-05-23 04:45:42

質問

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

どのように解決するのですか?

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

多相連関とは、ある列が2つ以上の親テーブルのいずれかを参照する可能性があることです。 SQLでこの制約を宣言することはできません。

多相アソシエーションの設計は、リレーショナルデータベース設計の規則を破ります。 使用することはお勧めしません。

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

  • 専用アーク。 複数の外部キーカラムを作成し、それぞれが一つの親を参照するようにする。 これらの外部キーのうちちょうど1つが非NULLであることを強制します。

  • 関係を逆転させる。 3つの多対多のテーブルを使用し、それぞれがコメントとそれぞれの親を参照します。

  • コンクリートスーパーテーブル。 暗黙のスーパークラスである "commentable" の代わりに、それぞれの親テーブルが参照する実際のテーブルを作成します。 そして、コメントをそのスーパーテーブルにリンクします。 擬似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
    
    

また、私のプレゼンテーションでは、多相アソシエーションを扱っています。 SQLにおける実践的なオブジェクト指向モデル や、拙著 SQLアンチパターン。データベースプログラミングの落とし穴を避けるために .


あなたのコメントについて。 はい、外部キーが指すと思われるテーブルの名前を記した別の列があることは知っています。 このデザインは、SQL の外部キーではサポートされていません。

たとえば、Comment を挿入し、その親テーブルの名前として "Video" を指定した場合、どうなるでしょうか。 Comment ? Video"という名前のテーブルが存在しません。 挿入はエラーで中止されるべきですか? どのような制約に違反していますか? RDBMS は、この列が既存のテーブルの名前を指定することになっていることをどのようにして知るのですか? 大文字と小文字を区別しないテーブル名はどのように処理されるのでしょうか?

同様に、もしあなたが Events テーブルを削除しても Comments に親としてイベントを示す行がある場合、その結果はどうなるでしょうか。 ドロップ テーブルは中止されるべきですか? の行は Comments の行は孤児になるべきでしょうか? のような既存のテーブルを参照するように変更すべきでしょうか? Articles ? を指していた id 値は Events を指していた id 値は Articles ?

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


私はあなたの提案したquot;Concrete Supertable"を理解するのに苦労しているのです。

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

    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 にも新しい行を作成する必要があります。 の場合も同様です。 PhotosEvents .

    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;
    
    

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

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