多対多
『Instagram』を例とします。『Instagram』はひとつの写真に複数のタグ付けができます。こうしたタグ付け機能を実現するには3つのテーブルが必要です。タグを保存するテーブル、写真を保存するテーブル、そして「どの写真にどのタグが登録されているか」を保存するテーブルです。
このうち写真やタグを保存するテーブルの名前はそのままtagsテーブル、photosテーブルとします。「どの写真にどのタグが登録されているか」のテーブルは、慣習的にphotos_tagsテーブル、と名付けます。関係する2つのテーブルをアンダーバーでくっつけています。
例ですがそれぞれのテーブルに保存されているレコードの関係性は以下のようにしたとします。
tagsテーブル | photosテーブル | photos_tagsテーブル |
---|---|---|
id | id | id |
name | name | tag_id |
..... | location | photos_id |
..... | ...... | ..... |
ひとつの写真には「東京/カフェ/おしゃれ」のように複数のタグが属しています。 | ||
また同じ「東京」のタグに対してもたくさんの写真が投稿されるので、ひとつのタグに複数の写真が属しているとも言えます。 | ||
このような関係が多対多です。多対多の関係を表現するには、photos_tagsテーブルのような中間テーブルが必要です。 |
中間テーブル
中間テーブルには、「どの写真がどのタグと関連づいているか」という情報が記載されていることになります。
ひとつのレコードには「photos_id × tags_id」の組み合わせが記録され、全ての写真とタグの組み合わせの数分、レコードが蓄積されていきます。10個の写真にそれぞれ3つずつタグが付いている場合、全ての関係性を表すのに中間テーブルのレコードは30個生成されるということになります。
has_many throughオプション
モデルに多対多を定義するときに利用します。
throughは、「〜を経由する」という意味です。
【例】
# app/models/photo.rb
class Photo < ActiveRecord::Base
has_many :photos_tags
has_many :tags, through: :photos_tags
end
# app/models/tag.rb
class Tag < ActiveRecord::Base
has_many :photos_tags
has_many :photos, through: :photos_tags
end
# app/models/photos_tag.rb
class PhotosTag < ActiveRecord::Base
belongs_to :photo
belongs_to :tag
end
上記のようなアソシエーションを定義することで、自動的に以下のようなメソッドが使えるようになります。
【例】
# 通常レコードの作成
tag1 = Tag.create(name: "東京")
tag2 = Tag.create(name: "おしゃれ")
photo1 = Photo.create(image: "cool_cafe.jpg", location: "渋谷")
# 多対多関係を定義したレコードの作成
tag1.photos.create(image: "cute_shop.jpg", location: "原宿")
# リレーションの追加
tag1.photos << photo1
photo1.tags << tag2
# リレーションを利用したレコードの取得
photo1.tags
# => #<Tag id:1, name:"東京">,#<Tag id:2, name:"おしゃれ">
「通常レコードの作成」のところでは単体のインスタンスを生成しています。【例】は、nameプロパティを持ったtagインスタンスとimage及びlocationプロパティを持ったphotoインスタンスです。これらの間には多対多の関係が定義されています。
「多対多関係を定義したレコードの作成」のところでは多対多の関係性を利用し、tag1に関係したphotoインスタンスを新たに生成しています。photosと、複数形になります。
「リレーションの追加」のところでは後からインスタンス同士を関連付けることも可能です。<<メソッド「配列オブジェクト << 追加する要素」を使用すると実現することができます。
tag1と関係しているphotosの配列に、新たにPhotoクラスのインスタンスを追加することでリレーションを生成しています。
photo1と関係しているtagsの配列に対し、新たにtag2とのアソシエーションを追加しています。
「リレーションを利用したレコードの取得」のところではリレーションしている要素を全て出力することもできます。【例】だとphoto1は「リレーションの追加」でそれぞれtag1・tag2に関連付けられているので、返り値としてtag1ならびにtag2のインスタンスが出力されています。
has_manyメソッドの他のオプション指定
オプション名 | 用途 |
---|---|
class_name | 関連するモデルのクラス名を指定でき、関連名(photos_tags等、has_manyの直後に書くもの)と参照先のクラス名(PhotosTagのようなモデル名)を異なるものにできる |
foreign_key | 参照先を参照する外部キーの名前を指定できる(デフォルトは、参照先のモデル名_id) |
dependent | 親モデルのデータを消したら関連するモデルのデータも連動して消したいときに使用します。destroyとdelete_allでひとつひとつ消していくか、一気に消すかを指定できる |
source | 関連テーブルから先のモデルにアクセスするための(関連モデルから見た)関連名を指定できる |