読者です 読者をやめる 読者になる 読者になる

Railsで多対多の関連を実装する

Rails Ruby

参考記事

以下を参考にさせていただきました。

開発現場でちゃんと使えるRails 4入門(6):ActiveRecordにおけるモデルの「関連」とコールバックの使い方 (2/3) - @IT

Railsで多対多のモデルを簡単に扱えるhas_many :through - このブログは証明できない。

やり方

「多対多の関連」は中間モデルとhas_manyの:throughオプションで簡単に実装できる。

今回はUbernのようなアプリで、Userがどのドライバーをりようしたかということを記録したい、というシチュエーションで。UserモデルとDriverモデルはすでに作成済みの状態からいきます。

まずは関連情報を扱う中間モデルを作成

$ rails g model History user:references driver:references
      invoke  active_record
      create    db/migrate/20150318014312_create_histories.rb
      create    app/models/history.rb
      invoke    rspec
      create      spec/models/history_spec.rb
      invoke      factory_girl
      create        spec/factories/histories.rb

dbに反映させます。

$ bundle exec rake db:migrate

referencesは実際にテーブルに格納された時にただのintegerになるのだけど、自動で関連を調べてカラムの名前をつけてくれるみたい。例えばUserクラスとDriverクラスのひもづけなら、user_idとdriver_idという名前を自動で振ってくれれてる。

そしたら各モデルのファイルに以下を加えます。

# app/models/user.rb
has_many :histories
has_many :drivers, through: :histories
# app/models/driver.rb
has_many :histories
has_many :users, through: :histories

両方がhas_manyの関係になってます。そしてthrough:にすることで

ちなみに変則的な複数名詞への変換も、だいたいの単語はActiveSupportが対応してやってくれてるらしい。Railsってやっぱ巨大ですげぇ...

あとはこんな感じにするだけであるユーザにひも付けられているドライバーだけを全て取り出せます。すごい!

history_drivers = current_user.drivers

テストは以下のように書けばokです!

  it { is_expected.to have_many(:drivers).through(:histories) }