はじめに
Railsの学習でテーブル設計に取り組んでいたところ、「アソシエーション」という言葉が出てきました。最初は「テーブル設計とモデルの関連付けって何が違うの?」と混乱したので、アウトプットも兼ねて自分の理解を整理します。
初学者の理解なので、間違いがあればコメントで教えていただけると嬉しいです。
この記事で書くこと
- テーブル設計(ER図)とマイグレーションの関係
- アソシエーションとは何か
-
belongs_to/has_many/has_oneの違い - 外部キーとの関係
-
users/boards/commentsの関係
テーブル設計は「設計図を描く作業」
最初に混乱したのが、「テーブル設計って、db/migrate の中身を自分で書くことと同じ?」という点でした。
整理すると、次の流れになります。
① どんな情報を保存する必要があるか考える
↓
② テーブル名・カラム名・型・関連を決める(ER図を描く)
↓
③ その設計を元にマイグレーションファイルを作成する
↓
④ rails db:migrate を実行してテーブルが作られる
テーブル設計(ER図)は、マイグレーションファイルを書く前の設計図です。設計を飛ばしていきなり実装すると、次のような手戻りが起きやすくなります。
- 「このカラムの型、文字列でよかったっけ?」
- 「外部キーを入れ忘れてた」
- 「あとからカラムを追加しないといけない」
紙やdraw.ioなどで先に整理しておくと、実装がスムーズになるようです。
アソシエーションとは
アソシエーションとは、Railsのモデル同士の**「関連付け」**を表す仕組みです。
たとえば、ユーザーが複数の投稿(Board)を持つ場合、「ユーザーと投稿は関連している」という情報をモデルに定義できます。
アソシエーションがない場合
@user = User.find(1)
@boards = Board.where(user_id: @user.id) # 毎回条件を自分で書く必要がある
アソシエーションがある場合
@user = User.find(1)
@boards = @user.boards # 関連データをすぐ取得できる
関連データの取得や操作がシンプルに書けるのが、アソシエーションの便利なところです。
主な3つの関連付け
| 関連付け | 説明 | 具体例 |
|---|---|---|
belongs_to |
あるモデルが他のモデルに「従属」する | 投稿(Board)はユーザー(User)に属する |
has_many |
あるモデルが複数の他モデルを「持つ」 | ユーザーは複数の投稿を持つ |
has_one |
あるモデルが1つの他モデルを「持つ」 | ユーザーは1つのプロフィールを持つ |
has_one の例として、プロフィール情報を別テーブルに分ける場合はこう書きます。
# Userモデル
class User < ApplicationRecord
has_one :profile
end
# Profileモデル
class Profile < ApplicationRecord
belongs_to :user
end
こう定義すると user.profile でプロフィールを取得できます。
どっちに何を書くか迷ったら
最初は「どっちに has_many、どっちに belongs_to を書くんだっけ?」と迷いました。次のように覚えると整理しやすかったです。
1対多の「多」側に
belongs_toを書く
外部キーとの関係
アソシエーションを使うには、通常 belongs_to 側のテーブルに外部キー(例: user_id)のカラムが必要です。
テーブル設計の段階で「どのテーブルが誰に属するか」を考えておくことが、そのままアソシエーションの設計につながります。ここで設計とモデルの関連付けがつながりました。
マイグレーションでは references を使うと、外部キーのカラムをまとめて作れます。
class CreateBoards < ActiveRecord::Migration[7.1]
def change
create_table :boards do |t|
t.string :title, null: false
t.text :body, null: false
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end
t.references :user で user_id カラムが作られ、foreign_key: true で外部キー制約も付きます。
「誰が親で、誰が子か」を考える
-
usersとboards:1人のユーザーが複数の投稿をするので、usersが親、boardsが子 -
boardsとcomments:1つの投稿に複数のコメントがつくので、boardsが親、commentsが子 -
usersとcomments:1人のユーザーが複数のコメントをするので、usersが親、commentsが子
comments テーブルには user_id と board_id の2つの外部キーが必要になります。「誰が」「どの投稿に」コメントしたのかを両方記録する必要があるからです。
class CreateComments < ActiveRecord::Migration[7.1]
def change
create_table :comments do |t|
t.text :body, null: false
t.references :user, null: false, foreign_key: true
t.references :board, null: false, foreign_key: true
t.timestamps
end
end
end
dependent: :destroy について
親を削除したときに、子のデータも一緒に削除する指定です。たとえばユーザーを削除したとき、そのユーザーの投稿やコメントが残ってしまうのを防げます。
開発の進め方についての理解
「最低限の機能から作って、あとから足していく」という進め方についても整理しました。
- アプリ全体で必要なテーブルを考えて、ER図を描く
- 実装の優先順位を決める(ユーザー登録・ログインができないと他の機能が作れない)
- 優先度の高い機能から実装 → 動作確認 → デプロイ
- 残りの機能(投稿、コメント、いいねなど)を段階的に追加する
つまり「全体のER図は先に考えておくけど、実装は優先度順に進める」というイメージです。全体像が見えていないと、あとから「このテーブルも必要だった」と大きな手戻りになりやすいので、最初に全体を見渡しておくことが大事なんだと理解しました。
まとめ
- テーブル設計(ER図)は、マイグレーションを書く前の設計図
- アソシエーションは、モデル同士の関連付けで、関連データの取得が簡単になる
-
belongs_to側のテーブルには外部キー(user_idなど)が必要 - 「1対多の『多』側に
belongs_to」と覚えると迷いにくい - 全体のER図を先に描き、実装は優先度順に進める
テーブル設計の段階で「どのテーブルが誰に属するか」を考えておくことが、そのままアソシエーションの理解につながると実感しました。