IT技術にまつわる実験ノート

「長編を書くより、短編を数多く完成させてください。それが上達への近道です」 by 手塚治虫

Rails で中間テーブルを使ってみる

はじめに

ここでは以下の記事を参考にして、Rails で中間テーブルを使ってみることにする。

 

中間テーブルの説明

f:id:matt-note:20190502204105p:plain

f:id:matt-note:20190502204116p:plain

 

使ってみる

ざっくりとアプリを作成する。

  • rails new many_to_many_sample
  • many_to_many_sample

 

サンプル用に Book モデルを作成する。

  • bin/rails g model Book title:string price:integer

 

サンプル用に Author モデルを作成する。

  • bin/rails g model Author name:string

 

中間テーブルを作成する。

  • bin/rails g model book_author book:references author:references

 

この時に、Book テーブルで author_id を作成せず、Author テーブルでも book_id を持たないようにするのがポイント。著者が作成した書籍を探す場合は、books テーブルと中間テーブルの book_authors テーブルを結合した結果表の author_id を使って books テーブルで絞りこみを行うようにする。

 

マイグレーションを実行して、DB に設定を反映させる。

 

db/schema.rb で中間テーブルのスキーマを見てみると、以下のようになっている。

f:id:matt-note:20190502205302p:plain

 

モデルで関連を定義する

Bookクラスでは book_authors テーブルを通じて、複数の著者データを取得する。

f:id:matt-note:20190506210221p:plain
-> book.authors として、書籍の著者データを取得できるようになる。books テーブルは book_authors テーブルに対して「1対多」になる。Book は book_authors を経由して authors テーブルのデータを取得する。

 

Authorクラスでは book_authors テーブルを通じて、複数の書籍データを取得する。

f:id:matt-note:20190502213552p:plain

-> author.books として、著者の書籍データを取得できるようになる。authors テーブルも book_authors テーブルに対して「1対多」になる。Author は book_authors を経由して books テーブルのデータを取得する。

 

中間テーブルでは、外部キーで両方のテーブルを参照する。

f:id:matt-note:20190502223043p:plain

これで、多対多の関連を作成できた。

 

サンプル用のデータを投入する

ここでは日本語が含まれる文字をコンソールで整えたいので、gem の hirb-unicode を追加する。

  • echo 'gem "hirb-unicode"' >> Gemfile
  • bundle

 

コンソールを起動して、hirb を有効化する。

  • bin/rails c
  • Hirb.enable

 

サンプル用のデータを投入する。

 

中間テーブルにも、サンプル用のデータを作成する。

  • BookAuthor.create(book: @hikaru_no_go, author: @obata)
  • BookAuthor.create(book: @hikaru_no_go, author: @hotta)
  • BookAuthor.create(book: @death_note, author: @obata)
  • BookAuthor.create(book: @death_note, author: @ooba)

 

中間テーブルを通してデータを取得してみる

小畑健の書籍を取得してみる。

  • @obata.books

f:id:matt-note:20190709191645p:plain

-> 小畑健の書籍であるヒカルの碁デスノートを取得できた。

 

上記の SQL を見てみると、以下のようになる。

  • FROM "books" INNER JOIN "book_authors" -> books テーブルと book_authors テーブルを結合。その条件は…
  • ON "books".id = "book_authors"."book_id" -> books テーブルの idと book_authors テーブルの book_id の値が同じものを条件とする。
  • WHERE "book_authors"."author_id" = 1 -> 結合した結果表から book_authors テーブルの author_id が 1 (小畑健)になっているものをすべて抽出。
  • SELECT "books".* -> books テーブルのすべてのカラムの値を出力。

 

この SQL が実行されると、上記の結果が返されることになる。(Author の name を取得しないので authors テーブルは結合しない。)

 

book_authors テーブルのデータは以下のようになっている。

f:id:matt-note:20190503002218p:plain

-> author_id: 1 に対応する書籍は、book_id: 1 と book_id: 2 になる。(ヒカルの碁デスノート

 

ほったゆみの書籍を取得してみる。

  • @hotta.books

f:id:matt-note:20190709191759p:plain

-> 多対多の関連で、著者と書籍が 1対1 となる場合があっても、中間テーブルを通してデータを取得する。

 

大場つぐみの書籍を取得してみる。

  • @ooba.books

f:id:matt-note:20190709191900p:plain

-> 問題なく取得できる。

 

ヒカルの碁の著者を取得してみる。

  • @hikaru_no_go.authors

f:id:matt-note:20190709192104p:plain

-> ヒカルの碁の著者である小畑健ほったゆみを取得できた。

 

SQL を見てみると、書籍の著者を取得する場合は authors テーブルと中間テーブルの book_authors テーブルを結合して、結果表から book_id が 1 (ヒカルの碁)のレコードをすべて抽出している。その後に authors テーブルのカラム

 

デスノートの著者を取得してみる。

  • @death_note.authors

f:id:matt-note:20190709192219p:plain

-> デスノートの著者である小畑健大場つぐみを取得できた。

 

has_many メソッドの source オプションを使ってみる

著者の作品をbooks ではなく、works(作品)で取得するように変更してみる。

これだと book_authors テーブルに work_id を持っている必要があることになる。ここで has_many メソッドの source オプションに :book を指定すると、works メソッドで book_authors テーブルの book_id を参照するようになる。

f:id:matt-note:20190506192230p:plain

 

小畑健の作品を表示してみる。

  • @obata.works

f:id:matt-note:20190709192502p:plain
-> 定義した works メソッドで、小畑健の作品を取得することができた。

 

小畑健の作品に「バクマン。」を追加してみる。

  • @bakuman = Book.create(title: "バクマン。", price: 390)
  • @obata.works << @bakuman

f:id:matt-note:20190709192832p:plain

f:id:matt-note:20190709193148p:plain

-> 小畑健の作品に「バクマン。」を追加できた。

 

追加したバクマン。を参照できないようにしてみる。

Author と 1対多 の関連になっている book_authors テーブルの book_id を削除する。

  • @obata.book_authors.find_by(book_id: @bakuman)   [テーブルの確認]
  • @obata.book_authors.find_by(book_id: @bakuman).destroy

f:id:matt-note:20190506204710p:plain

f:id:matt-note:20190506204837p:plain

 

削除されたか確認してみる。

  • @obata.reload.works

f:id:matt-note:20190709193329p:plain

-> book_authors テーブルを通してバクマン。を取得できないようにできた。

 

books テーブルにはバクマン。が残るので、こちらも削除しておく。

  • Book.all
  • @bakuman.destroy

f:id:matt-note:20190709193524p:plain

f:id:matt-note:20190709193704p:plain



まとめ

中間テーブルを使うと、多対多の関連を作成できる。

has_many メソッドの source オプションを使うと、中間テーブルの外部キーを指定して、メソッド名をデフォルトの設定から変更できる。