第9章|今さら学ぶ「Rails基礎(MVC / 規約)」
📚 シリーズ目次はこちら → 「今さら学ぶ」シリーズ — はじめに
🗺️ KnowledgeNoteの設計を確認 → 設計マップ
この章でわかること
- Railsの最大の特徴「レールが敷いてある」とはどういうことか
- MVCの責務分担を「レストラン」で理解する
- CoC(設定より規約)— 暗黙のルールで手間を省く仕組み
- DRY(Don't Repeat Yourself)— 同じことを繰り返さない原則
- KnowledgeNoteのディレクトリ構造で見る「Railsの規約」
- Rackとリクエストライフサイクル — HTTPリクエストがコントローラに届くまでの全体フロー
🏠 たとえ話で掴む「Railsの世界」
Ruby on Rails の最大の魅力を一言でいうと、 「レールが敷いてある安心感」 です。
鉄道旅行を想像してください。自分で道を切り開いて目的地に行くこともできますが、線路に乗ってしまえば、迷わずに到着できます。途中の分岐も、駅員さん(Railsの規約)が「こっちです」と案内してくれます。
| 鉄道旅行 | Rails |
|---|---|
| 線路が敷いてある | ファイルの置き場所・名前の付け方が決まっている |
| 駅員が案内してくれる | エラーメッセージが「こうすればいいよ」と教えてくれる |
| 切符を買えば乗れる |
rails new で必要なものが揃う |
| 指定席に座る | MVC(役割分担)が決まっている |
| 時刻表通りに走る | リクエスト→レスポンスの流れが決まっている |
レールから外れた独自のやり方もできますが、 レールに乗っている限り、最小限の設定でアプリが動く。これがRailsの思想です。
Rails / MVC とは何か — 技術的な定義
Railsというフレームワーク
Ruby on Rails は、Rubyで書かれたWebアプリケーションフレームワークです。2004年にDavid Heinemeier Hansson(DHH)がBasecampの開発から抽出して公開しました。
フレームワークとは、アプリケーション開発に必要な基盤(ルーティング、DB接続、テンプレートエンジン、セキュリティ対策など)をあらかじめ用意したものです。開発者はフレームワークの規約に従ってコードを書くことで、共通の基盤部分を自分で作る必要がなくなります。
Railsが重視する設計思想は2つあります。 CoC(Convention over Configuration / 設定より規約) と DRY(Don't Repeat Yourself / 同じことを繰り返さない) です。この2つがRailsの生産性を支えています。
MVCアーキテクチャ
MVC(Model-View-Controller) は、アプリケーションの責務を3つの層に分離するアーキテクチャパターンです。1979年にSmalltalkerのTrygve Reenskaugが考案し、現在ではWebフレームワークの標準的な設計になっています。
Model はアプリケーションのビジネスロジックとデータの永続化を担います。RailsではActiveRecordがこの役割を果たし、DBとのやり取り、バリデーション、データ間の関連付けを行います。
View はユーザーに見せる画面の生成を担います。RailsではERBテンプレートがHTMLを組み立てます。
Controller はリクエストの受付とレスポンスの決定を担います。ユーザーからのリクエストを受け取り、Modelに処理を依頼し、結果をViewに渡して画面を返します。
MVCの核心は 責務の分離 です。各層が自分の仕事だけに集中することで、コードの見通しが良くなり、変更時の影響範囲を限定できます。
🍽️ MVC — レストランの役割分担
第0章で「宅配ピザ」のたとえを使いましたが、MVCの理解をもう一段深めるために、今度は レストラン にたとえます。
3人の役割
| 役割 | MVC | 仕事内容 |
|---|---|---|
| ホールスタッフ | Controller | お客さん(リクエスト)の注文を聞いて、キッチンに伝える。料理が出来たら運ぶ |
| シェフ | Model | 材料(DB)を使って料理(データ加工)する。レシピ(ビジネスロジック)を知っている |
| メニュー表・食器 | View | 料理の見せ方(HTML)を担当。盛り付けや器の選択 |
大事なルール:役割を侵さない
レストランがうまく回るのは、 各自が自分の仕事に集中している からです。
✅ 正しい分担
ホール「注文を受けて、キッチンに伝えます」
シェフ「材料を取り出して、調理します」
メニュー表「料理の見た目を整えます」
❌ ダメな分担(Fat Controller)
ホール「注文を受けて、自分で冷蔵庫を開けて、
料理も自分で作って、盛り付けも自分でやって、
ついでにお会計もやります」
→ ホールスタッフが過労で倒れる
ホールスタッフ(Controller)が料理(ビジネスロジック)まで全部やってしまう状態を Fat Controller と呼びます。これはRailsでよくあるアンチパターンです(→ 第25章で詳しく扱います)。
MVCの流れを具体的に
KnowledgeNoteで「記事一覧を表示する」場合の流れです。
# 1. ルーティング — 注文を受け付ける窓口
# config/routes.rb
resources :articles # GET /articles → articles#index
# 2. コントローラ — ホールスタッフ(注文を受けてキッチンに伝える)
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def index
@articles = Article.published.recent.page(params[:page])
# ↑ Modelに「公開済みの記事を新しい順で持ってきて」と頼む
# ↑ 自分(Controller)は料理しない!
end
end
# 3. モデル — シェフ(データを取得・加工する)
# app/models/article.rb
class Article < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
def author_name
user.display_name
end
end
# 4. ビュー — メニュー表(見た目を整える)
# app/views/articles/index.html.erb
# <% @articles.each do |article| %>
# <h2><%= article.title %></h2>
# <p>by <%= article.author_name %></p>
# <% end %>
この流れが身についてくると、「次のコードはどこに書けばいいか」の判断が自然とできるようになります。
📏 CoC(Convention over Configuration)— 設定より規約
CoC(設定より規約) は、Railsの根幹にある哲学です。「みんなが同じルール(規約)に従えば、設定ファイルをいちいち書かなくて済む」という考え方です。
具体的な規約の例
# ① テーブル名とモデル名の規約
# モデル名(単数形・キャメルケース)→ テーブル名(複数形・スネークケース)
class Article < ApplicationRecord # → テーブル名は「articles」
end
class UserRole < ApplicationRecord # → テーブル名は「user_roles」
end
# ② コントローラ名とビューの規約
# ArticlesController#show → app/views/articles/show.html.erb を自動で探す
# ③ 主キーの規約
# 全テーブルの主キーは「id」(自動採番)
# ④ 外部キーの規約
# 「テーブル名の単数形_id」(例:user_id, article_id)
これらの規約に従っている限り、設定ファイルに「このモデルはこのテーブルに対応する」と書く必要がありません。 Railsが自動で判断してくれます。
規約に従わないとどうなるか
規約から外れることもできますが、その分だけ設定が必要になります。
# 規約に従う場合 — 設定不要
class Article < ApplicationRecord
# テーブル名「articles」、主キー「id」、外部キー「user_id」
# → 全て自動で認識される。何も書かなくていい!
end
# 規約から外れる場合 — 設定が必要
class Article < ApplicationRecord
self.table_name = "blog_posts" # テーブル名が規約と違う
self.primary_key = "article_id" # 主キーが規約と違う
belongs_to :author, class_name: "User", foreign_key: "writer_id"
# 外部キーが規約と違うので明示的に指定が必要
end
上のコードを見れば、規約に従うことでどれだけコードが減るかが一目瞭然です。
Railsの命名規約一覧
| 種類 | 規約 | 例 |
|---|---|---|
| モデル | 単数形・キャメルケース |
Article, UserRole
|
| テーブル | 複数形・スネークケース |
articles, user_roles
|
| コントローラ | 複数形・キャメルケース + Controller | ArticlesController |
| コントローラファイル | 複数形・スネークケース | articles_controller.rb |
| ビュー | app/views/コントローラ名/アクション名.html.erb |
app/views/articles/show.html.erb |
| 主キー | id |
articles.id |
| 外部キー | テーブル名の単数形_id |
articles.user_id |
| マイグレーション | 日時_説明 |
20250215120000_create_articles.rb |
🔄 DRY(Don't Repeat Yourself)— 同じことを繰り返さない
DRY は「同じコードを2箇所以上に書かない」という原則です。Railsはこの原則を強く推奨しています。
DRY違反の例
# ❌ DRY違反 — 同じコードが2箇所にある
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
if @article.user != current_user
redirect_to root_path, alert: "権限がありません"
end
end
def edit
@article = Article.find(params[:id])
if @article.user != current_user
redirect_to root_path, alert: "権限がありません" # ← 同じコード!
end
end
def update
@article = Article.find(params[:id])
if @article.user != current_user
redirect_to root_path, alert: "権限がありません" # ← また同じ!
end
# ...
end
end
DRYに書き直す
# ✅ DRY — before_action で共通処理をまとめる
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
before_action :authorize_user!, only: [:edit, :update, :destroy]
def show
# @articleはbefore_actionでセット済み
end
def edit
# @articleもauthorizeもbefore_actionで済んでいる
end
def update
if @article.update(article_params)
redirect_to @article, notice: "記事を更新しました"
else
render :edit, status: :unprocessable_entity
end
end
private
def set_article
@article = Article.find(params[:id])
end
def authorize_user!
unless @article.user == current_user
redirect_to root_path, alert: "権限がありません"
end
end
def article_params
params.expect(article: [:title, :body, :published])
end
end
before_action で共通処理をまとめることで、同じコードの繰り返しがなくなりました。修正が必要になっても1箇所直すだけで済みます(→ 第14章で詳しく扱います)。
📁 Railsのディレクトリ構造
規約の話をしたところで、Railsプロジェクトのディレクトリ構造を確認します。「どこに何を置くか」も規約で決まっています。
knowledgenote/
├── app/ ← アプリの本体(99%ここを触る)
│ ├── controllers/ ← コントローラ(ホールスタッフ)
│ │ ├── application_controller.rb
│ │ ├── articles_controller.rb
│ │ └── users_controller.rb
│ ├── models/ ← モデル(シェフ)
│ │ ├── application_record.rb
│ │ ├── article.rb
│ │ └── user.rb
│ ├── views/ ← ビュー(メニュー表)
│ │ ├── layouts/
│ │ │ └── application.html.erb
│ │ ├── articles/
│ │ │ ├── index.html.erb
│ │ │ └── show.html.erb
│ │ └── users/
│ ├── helpers/ ← ビュー用のヘルパーメソッド
│ ├── javascript/ ← JavaScript(Stimulus等)
│ └── assets/ ← CSS、画像
│ └── stylesheets/
│
├── config/ ← 設定ファイル
│ ├── routes.rb ← ルーティング(重要!)
│ ├── database.yml ← DB接続設定
│ └── environments/ ← 環境別設定
│
├── db/ ← データベース関連
│ ├── migrate/ ← マイグレーションファイル
│ ├── schema.rb ← 現在のDB構造
│ └── seeds.rb ← 初期データ
│
├── spec/ ← テスト(RSpec)
│ ├── models/
│ ├── controllers/
│ └── factories/
│
├── Gemfile ← Gem一覧(→ [第8章](https://qiita.com/harapeco-mgn/items/8d3f1959402ee69665ee))
├── Gemfile.lock ← Gemバージョンの記録
└── README.md
ファイルの置き場所で迷ったら、この構造に従えば間違いありません。Railsの規約に乗っている限り、場所を間違えることはほとんどありません。
🌐 Rackとリクエストライフサイクル — HTTPリクエストが届くまで
MVCの「Controller → Model → View」の流れを学びましたが、 ブラウザからのリクエストがControllerに届くまでにも、いくつかの層を通っています。
Rackとは
Rack は、RubyのWebサーバとWebフレームワークをつなぐインターフェースの仕様です。「どんなWebサーバ(Puma等)でも、どんなフレームワーク(Rails、Sinatra等)でも、Rackの仕様に従って通信できる」ようにする共通規約です。
リクエストの全体フロー
ブラウザ(GET /articles/1)
↓
Puma(Webサーバ)
│ TCPソケットでHTTP通信を受け付ける
↓
Rack
│ HTTPリクエストを Rails が読める env ハッシュに変換して渡す
↓
ミドルウェアスタック(複数のフィルター層)
│ ・RequestLogger → リクエストをログに記録
│ ・Session → Cookieからセッション情報を復元
│ ・CSRF Protection → トークンを検証
│ ・SSL Enforcement → HTTP → HTTPS リダイレクト
↓
Rails Router(routes.rb)
│ URLとHTTPメソッドからコントローラ・アクションを決定
│ GET /articles/1 → ArticlesController#show
↓
ApplicationController → ArticlesController#show
│ ① before_action を実行(認証チェック等)
│ ② Article.find(params[:id]) でModelに問い合わせ
│ └ Model(ActiveRecord): SELECT * FROM articles WHERE id = 1
│ └ クエリ結果を @article として Controller が受け取る
│ ③ render :show を実行(@article をインスタンス変数でViewに渡す)
↓
View(ERBテンプレート)
│ @article を使ってHTMLを組み立てる
↓
レスポンス(HTML)をブラウザへ返す
ミドルウェアスタックを確認する
rails middleware コマンドで、現在のミドルウェアの一覧が確認できます。
$ rails middleware
use ActionDispatch::HostAuthorization
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ContentSecurityPolicy::Middleware
run KnowledgeNote::Application.routes
上から順にリクエストが通過します。最後の routes に到達するころには、セッション復元・CSRF検証・ロギングが全て完了しています。
before_action とミドルウェアの違い
どちらも「アクションの前に処理を実行する」機能ですが、役割が異なります。
| ミドルウェア | before_action | |
|---|---|---|
| 適用範囲 | アプリ全体(全リクエスト) | コントローラ単位 |
| 書く場所 |
config/application.rb 等 |
コントローラ内 |
| 典型的な用途 | セッション管理、ロギング、SSL強制 | 認証チェック、リソース取得 |
| Railsの変数(@article等) | 使えない | 使える |
コントローラで使う before_action :authenticate_user! は、Rackミドルウェアスタックを通過した後のアプリ層で実行されます。
🛠️ KnowledgeNoteでの具体例
KnowledgeNoteの「記事の作成→保存→表示」の流れで、MVCの連携を確認します。
# config/routes.rb — ルーティング(どの窓口に案内するか)
resources :articles
# POST /articles → ArticlesController#create
# app/controllers/articles_controller.rb — コントローラ(ホールスタッフ)
class ArticlesController < ApplicationController
def new
@article = Article.new # 空のフォーム用オブジェクト
end
def create
@article = current_user.articles.build(article_params)
if @article.save # モデルに「保存して」と頼む
redirect_to @article, notice: "記事を投稿しました"
else
render :new, status: :unprocessable_entity
# バリデーションエラー → 作成フォームを再表示
end
end
private
def article_params
params.expect(article: [:title, :body, :published])
end
end
# app/models/article.rb — モデル(シェフ)
class Article < ApplicationRecord
belongs_to :user # 著者との関連付け
validates :title, presence: true, length: { maximum: 100 }
validates :body, presence: true
scope :published, -> { where(published: true) }
end
<!-- app/views/articles/new.html.erb — ビュー(見た目) -->
<h1 class="text-2xl font-bold mb-6">記事を書く</h1>
<%= form_with(model: @article, class: "space-y-4") do |f| %>
<% if @article.errors.any? %>
<div class="bg-red-50 text-red-700 p-4 rounded">
<ul>
<% @article.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= f.label :title, "タイトル", class: "block font-bold mb-1" %>
<%= f.text_field :title, class: "w-full border rounded p-2" %>
</div>
<div>
<%= f.label :body, "本文", class: "block font-bold mb-1" %>
<%= f.text_area :body, rows: 10, class: "w-full border rounded p-2" %>
</div>
<%= f.submit "投稿する", class: "bg-blue-600 text-white px-6 py-2 rounded" %>
<% end %>
ポイントは、 各ファイルが自分の仕事だけをしている ことです。コントローラは「保存を頼む→結果で分岐」だけ。モデルは「バリデーション+データ管理」だけ。ビューは「画面の見た目」だけ。バリデーションの詳細は(→ 第12章で詳しく扱います)。
💼 面接で聞かれたら?
Q:RailsのCoCとDRYについて説明してください。
「CoCは『設定より規約』という原則で、命名規則やファイルの配置を規約に従って書くことで、設定ファイルを最小限にできる仕組みです。たとえばArticleモデルはarticlesテーブルと自動で紐づきます。DRYは『同じことを繰り返さない』原則で、共通の処理をbefore_actionやヘルパーメソッドにまとめることで、修正が1箇所で済むようにする考え方です。」
深掘りされたら:
- 「MVCの各層の責務は?」→ Modelはデータの取得・加工・バリデーション。Viewは画面の組み立て(HTML生成)。Controllerはリクエストの受付とModelへの指示、レスポンスの決定。
- 「規約に従わないとどうなる?」→ 動かなくなるわけではないが、明示的な設定が必要になる。
self.table_nameやclass_nameの指定など、コード量が増える。
🔗 もっと深く知りたい人へ(1次情報リンク)
- Rails ガイド:Rails をはじめよう — MVC・規約の基礎を手を動かしながら学べる
- Rails ガイド:Rails の命名規約 — テーブル名・モデル名の規約一覧
- Rails API:ApplicationController — コントローラの全メソッド
まとめ
- ✅ Railsは「レールが敷いてある」フレームワーク。規約に従えば最小限のコードでアプリが動く
- ✅ MVCはレストランの役割分担。Controller=ホール、Model=シェフ、View=メニュー表
- ✅ CoC(設定より規約)により、命名やファイル配置を規約に沿わせるだけで設定不要
- ✅ DRY(同じことを繰り返さない)で、共通処理をまとめてメンテナンスしやすくする
- ✅ 「どこに何を書くか」で迷ったら、Railsのディレクトリ規約に従えばOK
- ✅ HTTPリクエストはPuma→Rack→ミドルウェアスタック→Router→Controller→Viewの順で処理される
- ✅ ミドルウェアはアプリ全体に適用、before_actionはコントローラ単位。役割が違う
📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに