【Rails】アソシエーションの使い方を徹底解説!

Rails

アソシエーションとは

アソシエーションとはモデル同士の繋がりを指します。
定義しておくとモデルをまたいだデータの呼び出しが非常に簡単になります。

定義の仕方

アソシエーションを定義する前にまずはモデル間の関係を調べる必要があります。
例えばユーザーがつぶやきを投稿できるアプリがあったとします。
ユーザーのデータはusersテーブルに、つぶやきのデータはtweetsテーブルに保存されるとします。

has_many

このアプリでは一つのつぶやきは必ず一人のユーザーが行なっています。
それに対してユーザーが投稿したつぶやきは複数あります。

この時usersテーブルとtweetsテーブルの関係は1対多の関係にあると言います。

ユーザーが複数のつぶやきを持っている状態を「has manyの関係」と呼びます。
この状態をアソシエーションで定義するにはuserモデルに「has_many :tweets」と記述します。
たくさん持っているので「tweets」という風に複数形になっていますね。

belongs_to

逆につぶやきは必ず1人のユーザーに紐づいている状態を「belong toの関係」と呼びます。
この状態をアソシエーションで定義するにはtweetモデルに「belongs_to :user」と記述します。
このとき、紐づいているユーザーは1人なので単数形になっています。

「belongs_to」を定義する際には紐づくモデルに「所属先のテーブル名_id」のカラムが必須になります。
このアプリの場合はusersテーブルがtweetsテーブルの所属先なので「user_id」というカラムがtweetsテーブルに必要ということになります。
この「user_id」のカラムに投稿したユーザーのidが入るので、これによりユーザーとツイートが紐づけられるというわけです。

アソシエーションを使ってみよう

それではアソシエーションを定義するとどんなメリットがあるのでしょうか?
例としてあるユーザーがつぶやいたツイートを取り出す記述をしてみましょう。
ユーザーとツイートは1対多の関係でしたね。
ですのでモデルには「has_many」で定義しました。
まずはアソシエーションを組んでいない場合の記述をしてみましょう。

ruby
1
2
user = User.find(id) 
tweet = Tweet.where(user_id: user.id)

次にアソシエーションを組んでいる場合の記述をみてみましょう。

ruby
1
2
user = User.find(id) 
tweets = user.tweets

ユーザーがつぶやいたツイートは複数あるので、ここで定義したtweetsという変数には配列として格納されます。

アソシエーションを定義しているとこんなに簡単にユーザーのツイートを取得することができます。
非常に便利ですね!

それではツイートをしたユーザーを取り出す記述をしてみましょう。
ツイートは必ず1人のユーザーに紐づいているのでモデルでは「belongs_to」で定義しました。

まずはアソシエーションを組んでいない時の記述をみてみましょう。

ruby
1
2
tweet = Tweet.find(id) 
user = User.find(tweet.user_id)

次にアソシエーションを組んでいる時の記述をみてみましょう。

ruby
1
2
tweet = Tweet.find(id) 
user = tweet.user

こちらもこんなに簡単に書くことができます。
ツイートしたユーザーは1人しかいないのでここで定義したuserという変数にはusersテーブルの1つのレコードが代入されます。
tweetのuserみたいに書けるのでわかりやすいですね。

他のアソシエーションの定義

テーブル同士の関係は上の1対多の関係の他にも1対1や多対多の関係もあります。
その際のアソシエーションの定義方法を確認しておきましょう。

has_one

1対1の時のアソシエーションの定義には「has_one」を使います。
例としてusersテーブルとprojectsテーブルを使って定義してみます。

userモデル

ruby
1
belongs_to :project

projectモデル

ruby
1
has_one :user

has_many through

多対多の関係を表すときに使います。
多対多の時はちょっと複雑になり、中間テーブルというものが必要になります。
中間テーブルは関連するテーブルそれぞれのidを持ったテーブルになります。

例としてユーザーが料理のレシピを投稿でき、それに対する感想をつけられるアプリがあったとします。

  • ユーザー:usersテーブル
  • レシピ:recipesテーブル
  • 感想:reviewsテーブル

ここではreviewsテーブルが「user_id」と「recipe_id」を持っているので中間テーブルになります。
もし両方のidを持っているテーブルがない場合は新たに中間テーブルとして作成する必要があります。

ユーザーはたくさんのレシピを持っていて、レシピは1人のユーザーに紐づいています。
この時ユーザーとレシピの関係は1対多になります。

しかし、ユーザーが感想をつけたレシピとなった時、感想を投稿したユーザーはたくさんいますし、レシピはたくさんの感想を持っています。
ですのでこの条件をつけたときにはusersテーブルとrecipesテーブルは多対多の関係となります。
このときのアソシエーションの定義は下記の通りとなります。

usersテーブル

ruby
1
has_many :recipes,through: :reviews

recipesテーブル

ruby
1
has_many :users,through: :reviews

両方のテーブルのidを持っているreviewsテーブルを通して関連づけるという意味になります。
多対多の関係の他にも1対多の関係も定義したい場合、下記のような記述になります。

usersテーブル

ruby
1
2
has_many :recipes
has_many :recipes,through: :reviews

こうなるとhas_manyのアソシエーションがすでに定義されてしまって、名前がかぶってしまいます。
このような場合は別名をつけてあげる必要があります。

usersテーブル

ruby
1
2
has_many :recipes
has_many :reviewed_recipes,through: :reviews, source: :recipe

sourceオプションを使うとこのように別名でアソシエーションを定義することができます。
今回はレビューしたレシピという風にわかりやすい名前をつけてみました。
sourceオプションがthroughを使ったときにとってくるモデルを指定するオプションになります。
sourceオプションを使った時は単数形になるということも注意しておきましょう。

ruby
1
recipes = user.reviewed_recipes

上のように記述するとユーザーが感想を投稿したレシピ全てをとってくることができます。

実際のアプリでアソシエーションを使ってみよう

それでは実際にアプリの中でアソシエーションを使ってみましょう。
下記のコマンドを順に実行してみましょう。
①git clone -b association https://github.com/miyagit/programan_dojo.git

② cd programan_dojo

③ bundle install
→ rbenv: version ‘2.4.1’ is not installed と表示された場合は、ruby -v と実行してください。

ruby -vと実行し出てきたversion(例: 2.3.1)と出てきたら、

vim .ruby-versionとし、
ruby -vで出てきた値(例: 2.3.1)に書き換えてください。

続いてvim Gemfileとし、ruby 2.4.1と書いてある部分をruby -vで出てきた値(例: 2.3.1)に書き換えてください。

④ rails db:create && rails db:migrate && rails db:seed

環境構築が完了しました。と表示されると、
本当にrails applicationが動作するかrails sコマンドで起動しましょう。

rails sを起動し、ブラウザでlocalhost: 3000と入力して下記のような画面が出てくれば環境構築完了です!

環境構築

ユーザーが投稿したツイートを表示させてみよう

それではアソシエーションを使わないでユーザーが投稿したツイートとそれを投稿したユーザーの名前を表示させてみましょう。
まずはtweetsテーブルから全てのツイートを取得する必要がありますね。
topsコントローラーのindexアクションに下記のコードを追記してください。

ruby
1
@tweets = Tweet.all

次にビューファイルを編集しましょう。
top.html.erbを下記のように編集してください。

erb
1
2
3
4
5
6
<div class='p-top'>
    <p class='p-top__title'>今回の記事の目標:<%= @title %></p>
    <% @tweets.each do |tweet| %>
    <p><%= tweet.text %> 投稿者:<%= User.find(tweet.user_id).name %></p>
    <% end %>
</div>

それではサーバーを立ち上げ表示を確認してみましょう。

表示の確認
このように表示されました。

それではこれをアソシエーションを使ってビューを書き換えてみましょう。
その前にテーブルの関係を把握しておきましょう。

テーブルの関係を把握しよう

今回はusersテーブルとtweetsテーブルの2つを関連づけます。
ユーザーはたくさんのツイートを持っていて、ツイートは1人のユーザーに紐づけられています。
この場合は1対多の関係になりますね。

ですのでアソシエーションの定義は下記のようになります。

userモデル

ruby
1
has_many :tweets

tweetモデル

ruby
1
belongs_to :user

これでテーブル間のアソシエーションの定義ができました。
それではビューファイルをアソシエーションを使った書き方に変えてみましょう。

top.html.erbを下記のように編集してください。

erb
1
2
3
4
5
6
<div class='p-top'>
    <p class='p-top__title'>今回の記事の目標:<%= @title %></p>
    <% @tweets.each do |tweet| %>
    <p><%= tweet.text %> 投稿者:<%= tweet.user.name %></p>
    <% end %>
</div>

ビューが同じように表示されているか確認してみましょう。

まとめ

アソシエーションはモデル同士の繋がりを指し、定義しておくとモデルをまたいだデータの呼び出しが非常に簡単になります。