1. 関連付けとは?
2つのActive Recordモデル同士のつながりのことです。
どのモデルとどのモデルの間に関連があるのか、その関連はどのような形態なのかを定義することができます。
一つの例として以下のように記述します。
class User < ApplicationRecord has_many :posts end class Post < ApplicationRecord belongs_to :user end
上の関連付けでは、 User と Post の間に1対多の関連付けを定義しています。
2. 関連付けの種類
Railsの関連付けにはいくつかの種類があります。この記事で取り上げる関連付けを以下に示します。
- belongs_to
- has_one
- has_many
- has_many :through
- has_one :through
- has_and_belongs_to_many
2.1. belongs_to
関連付け
belongs_to
は1対1の関連付けを表します。belongs_toの宣言を行ったモデルのインスタンスは別のモデルに従属 (belongs to) します。
例えば、記事(Post)とユーザー(User)の関係を考えましょう。
1つの記事は1人のユーザーに所属(従属)しています。つまり、記事はユーザーにbelongs_toしていると言えます。
この場合のモデルは以下のようになります。
class Post < ApplicationRecord belongs_to :user end class User < ApplicationRecord end
Postモデルではbelongs_to :userと定義することで、Userモデルとの1対1の所属関係を表現しています。
belongs_toでは、外部キーとして所属するモデルのidが自動でカラムとして追加されます。 この場合、postsテーブルにはuser_idというカラムができます。
また、belongs_toを定義したモデルでは、所属するモデルのインスタンスに簡単にアクセスできるようになります。
post = Post.first post.user # postに関連付いたuserオブジェクトを取得できる
この関連付けに対応するマイグレーションは例えば、以下のようになります。
class CreatePosts < ActiveRecord::Migration[7.1] def change create_table :users do |t| t.string :name t.timestamps end create_table :posts do |t| t.belongs_to :user t.datetime :published_at t.timestamps end end end
2.2. has_one
関連付け
has_one
もbelongs_toと同様に1対1の関連付けを表します。ただし、belongs_toとは逆に関連付けしたモデルを従属させます。
例えば、ユーザーとプロフィールの関係を考えましょう。
1人のユーザーは、1つのプロフィールを持っています。 逆に、1つのプロフィールは1人のユーザーに所属しています。
この場合のモデルは以下のようになります。
class User < ApplicationRecord has_one :profile end class Profile < ApplicationRecord belongs_to :user end
Userモデルではhas_one :profileと定義することで、Profileモデルとの1対1の関係を表現しています。
has_oneの場合、外部キーは相手側のモデルに置かれます。 この例では、profilesテーブルにuser_idというカラムが追加されます。
また、has_oneを定義した側では、関連するモデルのインスタンスにアクセスできます。
user = User.first user.profile # userに関連づいたprofileオブジェクトを取得できる
この関連付けに対応するマイグレーションは例えば、以下のようになります。
class CreateUsers < ActiveRecord::Migration[7.1] def change create_table :users do |t| t.string :name t.timestamps end create_table :profiles do |t| t.belongs_to :user t.string :profile t.timestamps end end end
このように、has_oneによって「1つ持っている」関係を表現できます。
belongs_toとhas_oneは同じ1対1の関係ですが、視点が逆なので注意が必要です。
2.3. has_many
関連付け
has_many
関連付けは1対多の関連付けを表します。has_one
と似ていますが、一つのインスタンスが相手のインスタンスを複数持っている点が異なります。
例えば、ユーザーと記事の関係を考えましょう。
1人のユーザーは、多数の記事を書くことができます。 逆に、1つの記事は1人のユーザーに所属しています。
この場合の定義は以下のようになります。
class User < ApplicationRecord has_many :posts end class Post < ApplicationRecord belongs_to :user end
Userモデルではhas_many :postsと定義することで、1対多の関係を表現しています。
has_manyの場合も、belongs_to側のモデルに外部キーが置かれます。 postsテーブルにはuser_idが追加されます。
また、has_manyを定義した側では、関連するモデルのコレクションにアクセスできます。
user = User.first user.posts # userが書いた複数のpostオブジェクトを取得できる
この関連付けに対応するマイグレーションは例えば、以下のようになります。
class CreateUsers < ActiveRecord::Migration[7.1] def change create_table :users do |t| t.string :name t.timestamps end create_table :posts do |t| t.belongs_to :user t.timestamps end end end
このように、has_manyを使うことで1対多の関連付けができます。アプリケーションではよく利用する関連付けの1つです。
2.4. has_many :through
関連付け
has_many :through
関連付けは2つのモデルの多対多の関連付けを表現します。中間テーブルを介して、それを経由(through)して2つのモデルを関連付けます。
例えば、ユーザーとグループの関係を考えましょう。
- 1人のユーザーは複数のグループに所属できます
- 1つのグループは複数のユーザーを含むことができます
この場合の定義は以下のようになります。
class User < ApplicationRecord has_many :group_users has_many :groups, through: :group_users end class Group < ApplicationRecord has_many :group_users has_many :users, through: :group_users end class GroupUser < ApplicationRecord belongs_to :user belongs_to :group end
GroupUserが中間テーブルとなり、このテーブルを介してUserとGroupは多対多で関連付けられます。
このように、has_many :throughを使うことで、複雑な多対多の関連付けが定義できます。
この関連付けに対応するマイグレーションは例えば、以下のようになります。
class CreateUsers < ActiveRecord::Migration[7.1] def change create_table :Users do |t| t.string :name t.timestamps end create_table :Groups do |t| t.string :name t.timestamps end create_table :GroupUsers do |t| t.belongs_to :user t.belongs_to :group t.timestamps end end end
またhas_many :throughは、中間テーブルにカラムを追加して、関連付けに属性を持たせることもできます。
2.5. has_one :through
関連付け
has_one :through
関連付けは、2つのモデル間の1対1の関連付けを中間テーブルを介して表現するものです。
例えば、ユーザーとプロフィールの関係を考えましょう。 ユーザー登録時に住所情報も保存したい場合を考えます。
- 1人のユーザーは1つのプロフィールを持つ
- 1つのプロフィールは1つの住所を持つ
この場合の定義は以下のようになります。
class User < ApplicationRecord has_one :profile end class Profile < ApplicationRecord belongs_to :user has_one :address end class Address < ApplicationRecord belongs_to :profile end
この場合、UserとAddressは直接関連付けされていませんが、中間のProfileモデルを介することで、 1対1の関連付けが表現できます。
この関連付けに対応するマイグレーションは例えば、以下のようになります。
class CreateUsers < ActiveRecord::Migration[7.1] def change create_table :users do |t| t.string :name t.timestamps end create_table :profiles do |t| t.belongs_to :user t.timestamps end create_table :addresses do |t| t.belongs_to :profile t.timestamps end end end
2.6. has_and_belongs_to_many
関連付け
has_and_belongs_to_many
関連付けは、2つのモデル間の多対多の関連付けを、中間モデルを使用することなく直接定義できるものです。ただし、データベースに中間テーブルは必要です。
例えば、ユーザーとスキルの関係を考えましょう。
- 1人のユーザーは複数のスキルを持っている
- 1つのスキルは複数のユーザーが持っている
この場合の定義は以下のようになります。
class User < ApplicationRecord has_and_belongs_to_many :skills end class Skill < ApplicationRecord has_and_belongs_to_many :users end
この定義により、バックグラウンドでusers_skillsという中間テーブルが作成されます。 このテーブルを介して、ユーザーとスキルは多対多で関連付けられます。
また、関連するレコードには以下のようにアクセスできます。
user = User.first user.skills # ユーザーの持つスキル skill = Skill.first skill.users # そのスキルを持つユーザー
このように、has_and_belongs_to_manyは中間テーブルを意識せずに、 多対多の関連付けを定義できる便利な関連付けです。
3. 関連付けを使うメリット
関連付けを行うと、モデル間の関連データを簡単に操作できるようになります。
例えば、UserとPostの関連について考えてみましょう。
関連付けを行っていない場合は、新しいpostを作成したいときに以下のようなコードを実行する必要があります。
@post = Post.create(content: "Hello world", user_id: @user.id)
同じようにユーザーを1人削除する場合は、ユーザーとそのユーザーのポストをすべて削除する必要があります。
@posts = Post.where(user_id: @user_id) @posts.each do |post| post.destroy end @user.destroy
以下の関連付けを定義しておくと上記の操作をもっと簡単に記述することができます。
class User < ApplicationRecord has_many :posts, dependent: :destroy end class Post < ApplicationRecord belongs_to :user end
上記の関連付けを記述した場合は以下のように1行で新しいポストを追加することができるようになります。
@post = @user.posts.create(content: "Hello World")
同じように1行でuserと関連するpostをすべて削除することができます。
@user.destroy
4. まとめ
関連付けを使うことでモデル同士の関連を簡単に扱えるようになります。そのことにより適切に使用すれば、アプリケーションの保守性や拡張性を向上させることができます。