🐕

Railsのアソシエーション(関連付け)の種類について大まかにまとめてみた

2023/12/18に公開

Railsのモデルにはアソシエーションという機能が存在し、モデルとモデルを関連付けすることによって便利にデータを操作することができます。
ただ、この機能がかなり複雑で理解が難しいため、自分で情報を噛み砕く意味でまとめてみました。
アソシエーションはあまりに機能が多く、かいつまんだ紹介になってしまうことをお許しください。

アソシエーションとは

アソシエーションとは、Railsのモデルに備わった機能で、データベース間のリレーションをモデル上で再現し、利用できるようにする仕組みのことです。
アソシエーションを用いることで、直感的にデータを操作することができます。
例えば、userの投稿したpostを全件取得したい場合、アソシエーションなしだと、以下のようにwhereメソッドを用いる必要があります。

user = User.find(1)
posts = Post.where(user_id: user.id)

これがアソシエーションを行っていた場合、以下のように書くことができます。

posts = User.find(1).posts

whereメソッドでの検索が要らなくなり、尚且つ非常に直感的なコードになりました。

アソシエーションの種類

アソシエーションを機能させるにはモデルにテーブル関連付けの情報を定義してあげる必要があります。
この定義方法は複数あり、データベースの定義に則したものを使わなければなりません。
以下で各定義について解説します。

belongs_to関連付け

belongs_toは関連付け元のモデルが関連付け先のモデルに対して従属関係にあることを示します。
userに紐づくpostの関係で説明すると、postはuserによって作成されているためuserに従属しているということが出来ます。
具体的に説明するとuserの主キーがpostの外部キーになっている状態ですね。
このような場合、belongs_to関連付けが適切と言えます。
postモデルに以下のように定義を行います。

post.rb
belongs_to: user

belongs_to関連付けの場合、相手モデルを単数形で表記します。

has_many関連付け

has_manyは関連付け先のモデルが関連付け元のモデルに対して多数存在する場合に使用します。
userに紐づくpostの関係で説明すると、userはpostを複数投稿することができるため、
has_many関連付けが適切と言えます。
has_many関連付けはbelongs_to関連付けの相手先によく設定します。
userモデルに以下のように定義を行います。

user.rb
has_many: posts

has_many関連付けの場合、相手モデルを複数形で表記します。

has_one関連付け

has_oneは関連関連付け先のモデルが関連付け元のモデルに対して1つしか存在しない場合に使用します。
例えば、user各々がprofileを持っている場合などです。
belongs_toとの使い方の違いとしては、こちらは主となるモデルに対して設定する関連付けだということです。
belongs_toの相手側に対して設定する関連付けというわけですね。
userモデルに以下のように定義を行います。

user.rb
has_one: profile

has_one関連付けの場合、相手モデルを単数形で表記します。

has_many :through関連付け

ここから関連付けが複雑になってきます。
has_many :throughはモデル同士が多対多の関係にある時によく利用されます。
例えば、userが複数のworkspaceに所属することができるとします。
この場合、workspaceにも複数のuserが所属しているため、多対多の関係にあると言えます。
has_many :throughを用いる場合、多対多の関係を中間モデルを用いることで表現します。
今回は、user_idとworkspace_idを持つjoinモデルを用意して表現したいと思います。
各モデルに以下のように定義を行います。

user.rb
has_many: workspaces, through: joins
join.rb
belongs_to: user
belongs_to: workspace
workspace.rb
has_many: users, through: joins

これで多対多を中間モデルを用いて表現することが出来ました。
記載するときは単数形と複数形の使い分けに注意してください。

ちなみに、なぜ中間モデルを用いるのかですが、中間モデルを用いないとデータベース設計上好ましくないからです。
例えば、joinモデルを用いなかった場合、userモデルには所属しているworkspaceの分だけworkspace_idカラムが必要となります。
このままではカラム数が無限に増殖してしまうため、中間モデルに切り離してあげるわけです。

また、has_many :throughはネストしたモデル関係を表現するのにも非常に便利です。
userが複数のpostを投稿でき、さらにそのpostには複数のcommentが存在する場合、userモデルに以下のような表現をしてあげます。

user.rb
has_many: comments, through: posts

こうした場合、userの全postに投稿されたcommentを一括で取得したい場合、以下のように書くことができるようになります。

user.comments

他にもhas_many :throughの便利な機能はたくさんあるので、気になった方は是非詳しく調べてみてください!

has_one :through関連付け

has_one :throughはhas_many :throughとほとんど同じです。
has_many :throughが多対多の関係もしくはネストした一対多の関係を表すのに対し、
has_one :throughはネストした一対一の関係を表します。
例えば、userは一つのprofileを持ち、profileは一つのimageを持つとします。
その場合以下のように構造を表現することが出来ます。

user.rb
has_one: image, through: profile
profile.rb
belongs_to: user
has_one: image
image.rb
belongs_to: profile

has_one :throughの場合も以下のようにuserから直接imageを参照することが出来ます。

user.image

has_and_belongs_to_many関連付け

最後にこのhas_and_belongs_to_manyです。
これはhas_many :throughと同じく多対多の関係を表現するためのものですが、大きな違いとして中間モデルが必要ないという点が挙げられます。
ただし、中間テーブルは必要です。
つまり中間テーブルをモデルを介さずに使用し、多対多を表現しているということです。
has_many :throughの時にも用いたuserとworkspaceの関係をこちらでも使うと、以下のような記述になります。

user.rb
has_and_belongs_to_many :workspaces
workspace.rb
has_and_belongs_to_many :users

繰り返しますが、joinテーブル自体は必要です。
あくまでjoinモデルを作成することなく、多対多を表現できるだけです。
中間テーブルの存在をRails上で意識しなくて済むことが大きなメリットだと感じます。

最後に

今回はアソシエーションの種類について大まかにまとめましたが、重要なアソシエーションの便利な機能についてはほとんど説明できていません。
また他の機会にアソシエーションの機能編としてまとめたいと考えています。

Discussion