見出し画像

多対多のリレーションはどのように実装すればいいのか @TECH CAMP #17

 どうも、とだです。今日は先日投稿した下の記事でちょっと出てきた「中間テーブル」というものについてまとめたいと思います。

中間テーブルについて

 上の記事でも取り上げていますが、LINEのようなチャットアプリを例にします。

あるユーザーは複数のグループに所属する。
あるグループには複数ユーザーが所属する。

 このようなユーザーとグループの関係を 多対多 と言います。
この種類のアソシエーションを定義するには、中間テーブル が必要になります。

多対多の関係とは

 例をもう一つ挙げてみます。『Instagram』などのように、ひとつの写真に複数のタグ付けができるSNSをイメージしてみてください。ひとつの写真は多くのタグを持っていますし、同じタグでも一つのみの写真ではなく別々の写真についています。こういった関係を多対多の関係と言います。
 こうしたタグ付け機能を実現するには3つのテーブルが必要になります。タグを保存するテーブル、写真を保存するテーブル、そして「どの写真にどのタグが登録されているか」を保存するテーブルです。このうち写真やタグを保存するテーブルの名前はそのままtagsテーブル、photosテーブルで良いでしょう。「どの写真にどのタグが 登録されているか」のテーブルは、慣習的にphotos_tagsテーブル、と名付けます。関係する2つのテーブルをアンダーバーでくっつけています。中間テーブルには、「どの写真がどのタグと関連づいているか」という情報が記載されていることになります。中間テーブルでは、tagsテーブルの主キーであるidはtags_idとして保存し、photosテーブルの主キーであるidをphotos_idとして保存します。ひとつのレコードには「photos_id × tags_id」の組み合わせが記録され、全ての写真とタグの組み合わせの数分、レコードが蓄積されていきます。

画像1

 手書きで申し訳ないですがイメージはこんな感じです。

中間テーブルがある時のアソシエーションの記述の仕方

 モデルに多対多の関連を定義するとき、has_manyメソッドにthroughオプションをつけて利用します。throughという名前のとおり、「〜を経由する」という意味です。上記のphotosテーブルとtagsテーブルの例を用いると以下のようになります。

# 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: "noteはじめました")
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:"noteはじめました">,#<Tag id:2, name:"おしゃれ">

# 通常レコードの作成
 ここでは単体のインスタンスを生成しています。今回使用する例は、nameプロパティを持ったtagインスタンスとimage及びlocationプロパティを持ったphotoインスタンスです。これらの間には多対多の関係が定義されています。

# 多対多関係を定義したレコードの作成
 多対多の関係性を利用し、tag1に関係したphotoインスタンスを新たに生成しています。「 tag1.photos.create 」と、複数形になることに注意です。

# リレーションの追加
 後からインスタンス同士を関連付けることも可能です。「<<メソッド」を使用するとこのように簡単に実現することができます。
 補足すると「<<メソッド」とは、生成した配列オブジェクトに新しい要素を追加したいときに使えるメソッドです。<<メソッドを使って、以下のように追加ができます。

配列オブジェクト << 追加する要素

 「tag1.photos << photo1」では、tag1と関係しているphotosの配列に、新たにPhotoクラスのインスタンスを追加することでリレーションを生成しています。また「photo1.tags << tag2」では同様に、photo1と関係しているtagsの配列に対し、新たにtag2とのアソシエーションを追加しています。

# リレーションを利用したレコードの取得
 リレーションしている要素を全て出力することもできます。上記の例だと、photo1は 

tag1.photos << photo1
photo1.tags << tag2

でそれぞれtag1・tag2に関連付けられているので、返り値としてtag1ならびにtag2のインスタンスが出力されています。

おまけ

 has_manyメソッドには他にも次のようなオプションを指定できます。

画像2

 dependentについては、冒頭で紹介した記事でも取り上げています。

終わりに

 自分もこれから開発しようとしているアプリケーションでも、この多対多の関係を持つデータが存在するので、こうしてまとめる事でいい復習ができたかなと思います。

この記事が気に入ったらサポートをしてみませんか?