0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第15章|今さら学ぶ「ActiveRecord実践」

0
Last updated at Posted at 2026-02-23

第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_manybelongs_to で1対多、has_many :through で多対多を表現します。アソシエーションを定義すると、user.articles のようにRubyのメソッドチェーンで関連データにアクセスできるようになり、SQLを直接書く必要がなくなります。」

深掘りされたら:

  • 「dependent: :destroy とは?」→ 親レコードが削除されたとき、紐づく子レコードも自動で削除する設定。ユーザーを消したら、そのユーザーの記事やコメントも削除される。
  • 「N+1問題とは?」→ ループ内で関連データを取得するたびにSQLが発行されてしまう問題。includes を使って事前にまとめて読み込むことで解決する。

🔗 もっと深く知りたい人へ(1次情報リンク)


まとめ

  • ✅ ActiveRecordはRubyとDBの「通訳者」。SQLを書かずにRubyのメソッドでDBを操作できる
  • ✅ クラス=テーブル、インスタンス=行。Railsの命名規約で自動対応
  • ✅ マイグレーションで設計変更を履歴管理。チーム全員がDBを再現可能
  • ✅ has_many / belongs_to で1対多、has_many :through で多対多を表現
  • ✅ includes でN+1問題を解決。kaminari でページネーションを実装

📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?