第15章|今さら学ぶ「ActiveRecord実践」
📚 シリーズ目次はこちら → 「今さら学ぶ」シリーズ — はじめに
🗺️ KnowledgeNoteの設計を確認 → 設計マップ
この章でわかること
- ActiveRecordとは — RubyでDBと会話する「通訳者」
- クラス=テーブル、インスタンス=行の対応関係
- CRUD操作(create / find / update / destroy)の全パターン
- マイグレーション — 設計図の履歴管理
- アソシエーション(has_many / belongs_to / has_one / through)
- includes / preload / eager_load — まとめ買いで往復を減らす
- ページネーション(kaminari)— 一覧を30件ずつ表示する
🏠 たとえ話で掴む「ActiveRecord」
ActiveRecord は、Rubyの世界とデータベースの世界をつなぐ 通訳者 です。
海外のレストランに行ったとき、メニューが読めなくて困る場面を想像するとわかりやすいです。通訳者がいれば、「パスタをください」と日本語で言うだけで、通訳者が現地語に翻訳して注文してくれます。
ActiveRecordも同じです。皆さんはRubyで Article.find(1) と書くだけ。ActiveRecordが裏側で SELECT * FROM articles WHERE id = 1 というSQLに翻訳してDBに問い合わせてくれます。
| 海外レストラン | ActiveRecord |
|---|---|
| 「パスタください」(日本語) |
Article.find(1)(Ruby) |
| 通訳者が翻訳 | ActiveRecordがSQLに変換 |
| 現地語で注文 | SELECT * FROM articles WHERE id = 1 |
| 料理が届く | Articleオブジェクトが返る |
📖 ActiveRecordとは何か — 技術的な定義
ActiveRecordは ORM(Object-Relational Mapping) と呼ばれるデザインパターンの実装です。
そもそもRubyのオブジェクトとリレーショナルデータベース(RDB)は、データの扱い方がまったく異なります。Rubyはオブジェクト(クラスとインスタンス)で世界を表現し、RDBはテーブル(行と列)で世界を表現します。この2つの世界を橋渡しし、 テーブルをクラスに、行をインスタンスに、カラムを属性に対応させる のがORMです。
ActiveRecordが存在する理由は、開発者がSQLを直接書かなくても、Rubyのメソッドだけでデータベースを操作できるようにするためです。Article.where(published: true) と書けば、内部で SELECT * FROM articles WHERE published = true が生成されます。SQLの知識がゼロでよいわけではありませんが(→ 第17章)、日常的なCRUD操作はRubyだけで完結します。
もう一つ重要な特徴は、Railsの 命名規約(Convention over Configuration) と組み合わさることで、設定ファイルなしにクラスとテーブルが自動対応する点です。Article クラスを定義するだけで、articles テーブルと紐づきます。
📐 モデルとテーブルの対応関係
ActiveRecordでは、 クラス=テーブル、インスタンス=行 という対応関係があります。
# app/models/article.rb
class Article < ApplicationRecord
# このクラスが articles テーブルに対応する(規約!)
end
articles テーブル
+----+-------------------+-------------+-----------+---------+
| id | title | body | published | user_id |
+----+-------------------+-------------+-----------+---------+
| 1 | Ruby入門 | Rubyとは... | true | 1 | ← 1行 = 1インスタンス
| 2 | Rails基礎 | MVCとは... | false | 1 | ← 1行 = 1インスタンス
+----+-------------------+-------------+-----------+---------+
↑ カラム = 属性(attribute)
article = Article.find(1)
article.title # => "Ruby入門"
article.published # => true
article.user_id # => 1
🔄 CRUD操作 — 作る・読む・更新する・消す
Create(作成)
# 方法①:new + save(2ステップ)
article = Article.new(title: "Ruby入門", body: "Rubyとは...", user_id: 1)
article.save # => true(バリデーション通過で保存成功)
# 方法②:create(1ステップ)
article = Article.create(title: "Ruby入門", body: "Rubyとは...", user_id: 1)
# 方法③:create!(失敗時に例外を発生させる)
article = Article.create!(title: "", body: "")
# => ActiveRecord::RecordInvalid(バリデーションエラー)
Read(読み取り)
# 1件取得
Article.find(1) # id=1を取得(なければ例外)
Article.find_by(title: "Ruby入門") # 条件に一致する最初の1件(なければnil)
# 複数取得
Article.all # 全件(ActiveRecord::Relation)
Article.where(published: true) # 条件に一致する全件
Article.where(published: true).order(created_at: :desc) # 並び替え
Article.where(published: true).limit(10) # 件数制限
# 集計
Article.count # 件数
Article.where(published: true).count
Update(更新)
article = Article.find(1)
# 方法①:update(失敗時はfalseを返す)
article.update(title: "Ruby入門(改訂版)")
# 方法②:update!(失敗時に例外を発生させる)
article.update!(title: "")
# => ActiveRecord::RecordInvalid
Destroy(削除)
article = Article.find(1)
article.destroy # DBから削除される
save と save! / update と update! の使い分け
| メソッド | 失敗時の挙動 | 使いどころ |
|---|---|---|
save / update
|
false を返す |
コントローラの if/else 分岐 |
save! / update!
|
例外を発生させる | トランザクション内(→ 第17章) |
🏗️ マイグレーション — 設計図の履歴管理
マイグレーション は、DBのテーブル構造を変更するための仕組みです。設計変更の 履歴 がファイルとして残るので、チーム全員が同じDB構造を再現できます。
# マイグレーションファイルの生成
$ rails generate migration CreateArticles title:string body:text published:boolean user:references
# db/migrate/20250215120000_create_articles.rb
class CreateArticles < ActiveRecord::Migration[8.0]
def change
create_table :articles do |t|
t.string :title, null: false # NOT NULL制約
t.text :body, null: false
t.boolean :published, default: false # デフォルト値
t.references :user, null: false, foreign_key: true # 外部キー
t.timestamps # created_at と updated_at を自動追加
end
add_index :articles, [:user_id, :created_at] # 複合インデックス
end
end
# マイグレーション実行
$ rails db:migrate
# ロールバック(1つ前に戻す)
$ rails db:rollback
カラムの追加や変更も、マイグレーションで行います。
$ rails generate migration AddStatusToArticles status:integer
class AddStatusToArticles < ActiveRecord::Migration[8.0]
def change
add_column :articles, :status, :integer, default: 0, null: false
end
end
🤝 アソシエーション — テーブル間の関連付け
アソシエーションは、テーブル同士の 人間関係を整理する 仕組みです。
has_many / belongs_to — 1対多
# 1人のユーザーが複数の記事を持つ
class User < ApplicationRecord
has_many :articles, dependent: :destroy
# ↑ ユーザーが削除されたら記事も削除(dependent: :destroy)
end
class Article < ApplicationRecord
belongs_to :user
# ↑ articles テーブルに user_id カラムがある側
end
user = User.find(1)
user.articles # => このユーザーの記事一覧
user.articles.create(title: "新しい記事", body: "本文") # 紐付いた記事を作成
article = Article.find(1)
article.user # => この記事の著者
has_many :through — 多対多
タグと記事の関係は「多対多」です。1つの記事に複数のタグ、1つのタグに複数の記事が紐づきます。
class Article < ApplicationRecord
has_many :article_tags, dependent: :destroy
has_many :tags, through: :article_tags
end
class Tag < ApplicationRecord
has_many :article_tags, dependent: :destroy
has_many :articles, through: :article_tags
end
class ArticleTag < ApplicationRecord
belongs_to :article
belongs_to :tag
end
article = Article.find(1)
article.tags # => [#<Tag name: "Ruby">, #<Tag name: "Rails">]
tag = Tag.find_by(name: "Ruby")
tag.articles # => Rubyタグがついた全記事
has_one — 1対1
# 1人のユーザーが1つのプロフィールを持つ
class User < ApplicationRecord
has_one :profile, dependent: :destroy
end
class Profile < ApplicationRecord
belongs_to :user
end
user = User.find(1)
user.profile # => #<Profile ...>
user.profile.bio # => "Railsエンジニアです"
💡 画像添付には
has_one_attachedを使いますが、これはActive Storageの仕組みであり、アソシエーションとは別物です(KnowledgeNoteではArticleモデルでhas_one_attached :imageとして使用)。
⚡ includes / preload / eager_load — まとめ買いで往復を減らす
N+1問題とは
# ❌ N+1問題が発生するコード
articles = Article.all
articles.each do |article|
puts article.user.name # ← 記事ごとにSQLが発行される!
end
# SQL: SELECT * FROM articles (1回)
# SQL: SELECT * FROM users WHERE id = 1 (N回)
# SQL: SELECT * FROM users WHERE id = 2
# SQL: SELECT * FROM users WHERE id = 3
# → 合計 N+1 回のSQL!
includesで解決
# ✅ includes でまとめて取得(2回のSQLで済む)
articles = Article.includes(:user).all
articles.each do |article|
puts article.user.name # ← 追加のSQLは発行されない
end
# SQL: SELECT * FROM articles
# SQL: SELECT * FROM users WHERE id IN (1, 2, 3)
# → 合計2回のSQL!
| メソッド | 戦略 | 使いどころ |
|---|---|---|
includes |
Railsが最適な方法を選ぶ | 基本はこれでOK |
preload |
必ず別クエリ | JOINしたくないとき |
eager_load |
必ずLEFT JOIN | WHERE条件で関連テーブルを使うとき |
迷ったら includes を使えば大丈夫です(→ 第17章でN+1問題を詳しく扱います)。
📄 ページネーション(kaminari)
記事が1000件あるのに全件表示したらページが重くなります(→ 第24章でパフォーマンス最適化を詳しく扱います)。 ページネーション で30件ずつ表示するのが定石です。
Railsのページネーション gem は主に2つあります。
| gem | 特徴 |
|---|---|
| kaminari | 日本製。Railsとの統合が深く、スコープチェーンが自然に書ける。KnowledgeNoteではこちらを採用 |
| will_paginate | 歴史が長い。シンプルなAPIで軽量。既存プロジェクトで見かけることも多い |
どちらを選んでも機能はほぼ同じです。ここでは kaminari で進めます。
# Gemfile
gem "kaminari"
# コントローラ
def index
@articles = Article.published.recent.page(params[:page]).per(30)
# ↑ params[:page] が 2 なら 31〜60件目を取得
end
<%# ビュー %>
<%= render @articles %>
<%# ページネーションリンクを表示 %>
<%= paginate @articles %>
<%# → < 1 2 3 4 5 > のようなページ送りUIが自動生成 %>
🛠️ KnowledgeNoteでの具体例
# app/models/article.rb
class Article < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
has_many :likes, as: :likeable, dependent: :destroy
has_many :article_tags, dependent: :destroy
has_many :tags, through: :article_tags
has_one_attached :image # Active Storage
validates :title, presence: true, length: { maximum: 100 }
validates :body, presence: true
scope :published, -> { where(published: true) }
scope :draft, -> { where(published: false) }
scope :recent, -> { order(created_at: :desc) }
end
# コントローラでの典型的な使い方
class ArticlesController < ApplicationController
def index
@articles = Article.published
.recent
.includes(:user, :tags) # N+1対策
.page(params[:page])
.per(20)
end
end
💼 面接で聞かれたら?
Q:ActiveRecordのアソシエーションについて説明してください。
「アソシエーションはモデル間のリレーション(関連)を定義する仕組みです。
has_manyとbelongs_toで1対多、has_many :throughで多対多を表現します。アソシエーションを定義すると、user.articlesのようにRubyのメソッドチェーンで関連データにアクセスできるようになり、SQLを直接書く必要がなくなります。」深掘りされたら:
- 「dependent: :destroy とは?」→ 親レコードが削除されたとき、紐づく子レコードも自動で削除する設定。ユーザーを消したら、そのユーザーの記事やコメントも削除される。
- 「N+1問題とは?」→ ループ内で関連データを取得するたびにSQLが発行されてしまう問題。includes を使って事前にまとめて読み込むことで解決する。
🔗 もっと深く知りたい人へ(1次情報リンク)
- Rails ガイド:Active Record の基礎 — CRUD操作・命名規約の全容
- Rails ガイド:Active Record マイグレーション — マイグレーションの全コマンド
- Rails ガイド:Active Record の関連付け — アソシエーションの全パターン
- kaminari(GitHub) — ページネーションGemの公式ドキュメント
まとめ
- ✅ ActiveRecordはRubyとDBの「通訳者」。SQLを書かずにRubyのメソッドでDBを操作できる
- ✅ クラス=テーブル、インスタンス=行。Railsの命名規約で自動対応
- ✅ マイグレーションで設計変更を履歴管理。チーム全員がDBを再現可能
- ✅ has_many / belongs_to で1対多、has_many :through で多対多を表現
- ✅ includes でN+1問題を解決。kaminari でページネーションを実装
📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに