[解決済み] 多相アソシエーションで外部キーが持てないのはなぜですか?
質問
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
にも新しい行を作成する必要があります。 の場合も同様です。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;
から発見した後、それぞれのリソーステーブル(写真、記事など)からデータを取得するために、2つ目のクエリを実行する必要があります。
commentable_type
からどのテーブルに結合するかを見つけた後、それぞれのリソーステーブル (写真、記事など) からデータを取得するために 2 番目のクエリを実行する必要があります。SQL ではテーブルが明示的に命名される必要があるため、同じクエリでこれを行うことはできません。
確かに、これらの手順のいくつかは、Railsで使用されている規約を破っています。 しかし、適切なリレーショナル データベースの設計に関して、Rails の規約は間違っています。
関連
-
[解決済み】取得中。「プロジェクトのクローンとマイグレート後に、「マイグレーションは保留中です。この問題を解決するには 'bin/rake db:migrate RAILS_ENV=development' を実行してください。
-
[解決済み] AWS S3です。アクセスしようとしているバケットは、指定されたエンドポイントを使用してアドレスされている必要があります。
-
[解決済み] PG::ConnectionBad - サーバーに接続できませんでした。接続が拒否されました。
-
[解決済み] Paramが無いか、値が空である。ParameterMissing in ResultsController#update
-
[解決済み] nil:NilClass の未定義メソッド `each' - しかし、なぜ?
-
[解決済み] rails consoleを起動するとRailsのイニシャライザーが呼ばれる?
-
[解決済み] bundle install --without production は何をするのですか?
-
[解決済み] 構文エラー "構文エラー、予期しない入力終了、keyword_endを期待 (SyntaxError)"
-
[解決済み] 未初期化の定数 "コントローラ名"
-
[解決済み] Mongoidとmongodbでhas_many :through リレーションシップを実装するには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】Rails 4 RoutingError: ルートが一致しない[POST]。
-
[解決済み】コレクションをDESCで並べる方法
-
[解決済み] 新規ユーザー作成時に ActiveModel::ForbiddenAttributesError が発生する。
-
[解決済み] Mac OS Xにhomebrewがインストールされているかどうかを確認する方法
-
[解決済み] Ruby on Railsのためにnetbeansを構成する方法は?
-
[解決済み] Errno::EACCESS: パーミッションが拒否された @ dir_s_mkdir
-
[解決済み] 構文エラー "構文エラー、予期しない入力終了、keyword_endを期待 (SyntaxError)"
-
[解決済み] レイル 4 radio_button_tag default not selected
-
[解決済み] Railsでグループとカウント
-
[解決済み] Ruby on Rails の新規セットアップ : "Expected string default value for '--rc'; got false (boolean)".