はじめに
初学者に絶対押さえておいてほしいURLでのアクセス(リクエスト)から表示が返ってくる(レスポンス)までにRailsがどんなことをしているのかの概要のお話です。
これを押さえておくとRailsの大枠を掴んで学習の効率がアップしたり、エラーが起きた時にどこに原因があるかを推測しやすくなったりします。
今回やること
scaffoldコマンドを使って簡単なメモアプリを作成しながら、Railsがどんなことをやっているのかを一つ一つ確認したり、どういうルールがあるのかの解説をします。
こんな人におすすめ
Railsの書籍を1周やってみたけどちゃんと理解できていない気がする...
URLを入力してからユーザーに画面が表示されるまでの流れが説明できない...
エラー画面が出た時にどこをみたらいいのかよくわからない...
前提知識
指定のバージョンのRubyをインストール出来ること
ターミナルの簡単な操作が出来ること
エディタを使ったファイルの閲覧が出来ること
今回作成するアプリケーションの環境
Ruby: 3.1.2
Rails: 7.0.4
まずはrails new
gem install rails -v 7.0.4
rails _7.0.4_ new memo_app --skip-hotwire --skip-test --skip-jbuilder
サーバーの起動確認
cd memo_app
bin/rails s
http://localhost:3000/
にアクセスしてこんな画面が出てきたら成功です。
scaffoldコマンドを使ってmemo機能
便利コマンドを使ってmemo機能を作ってしまいましょう
ここではわかりやすくするためhelper関連のファイルはいらないので--no-helper
を使って飛ばしています
bin/rails g scaffold memo title:string body:text --no-helper
上記のコマンドを打つとこんな感じでたくさんファイルが作られます
❯❯❯bin/rails g scaffold memo title:string body:text --no-helper [main]
invoke active_record
create db/migrate/20221205161342_create_memos.rb
create app/models/memo.rb
invoke resource_route
route resources :memos
invoke scaffold_controller
create app/controllers/memos_controller.rb
invoke erb
create app/views/memos
create app/views/memos/index.html.erb
create app/views/memos/edit.html.erb
create app/views/memos/show.html.erb
create app/views/memos/new.html.erb
create app/views/memos/_form.html.erb
create app/views/memos/_memo.html.erb
invoke resource_route
作られたファイルを見てみよう
作られたファイルは大きく分けると
- モデル関連のファイル
- ルーティング関連のファイル
- コントローラー関連のファイル
- 表示関連のファイル
の4種類のファイルや記述が追加されています。
少し中身をのぞいてみましょう
ファイル名の横の▶︎
をクリックすると詳細がみれますよ
モデル関連のファイル
db/migrate/20221205161342_create_memos.rb
これはデータベースのテーブルを作るためのマイグレーションファイルと呼ばれるものです。
長い数字には作成した時の日時が入っています。
ここの記載に沿ってmemosテーブルが作られます。
class CreateMemos < ActiveRecord::Migration[7.0]
def change
create_table :memos do |t|
t.string :title
t.text :body
t.timestamps
end
end
end
app/models/memo.rb
これはモデルの記述を書くファイルです。
今はそれほど記述はないようですね。
class Memo < ApplicationRecord
end
ルーティング関連のファイル
config/routes.rb
configディレクトリはRails全体の設定を記載するディレクトリです。
routes.rb
はルーティング(リクエストが来た時にどこにいけばいいのかの設定)を記述するファイルです。
何やらmemosに関する記述とコメントアウト(人間には見えるがプログラムによって実行はされない記述)がありますね。
Rails.application.routes.draw do
resources :memos
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Defines the root path route ("/")
# root "articles#index"
end
コントローラー関連のファイル
app/controllers/memos_controller.rb
なにやらたくさん記述してありますね。
class MemosController < ApplicationController
before_action :set_memo, only: %i[ show edit update destroy ]
# GET /memos
def index
@memos = Memo.all
end
# GET /memos/1
def show
end
# GET /memos/new
def new
@memo = Memo.new
end
# GET /memos/1/edit
def edit
end
# POST /memos
def create
@memo = Memo.new(memo_params)
if @memo.save
redirect_to @memo, notice: "Memo was successfully created."
else
render :new, status: :unprocessable_entity
end
end
# PATCH/PUT /memos/1
def update
if @memo.update(memo_params)
redirect_to @memo, notice: "Memo was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /memos/1
def destroy
@memo.destroy
redirect_to memos_url, notice: "Memo was successfully destroyed."
end
private
# Use callbacks to share common setup or constraints between actions.
def set_memo
@memo = Memo.find(params[:id])
end
# Only allow a list of trusted parameters through.
def memo_params
params.require(:memo).permit(:title, :body)
end
end
表示関連のファイル
app/views/memos/index.html.erb
indexはデータの一覧を表示するために使われることが多いです。
<p style="color: green"><%= notice %></p>
<h1>Memos</h1>
<div id="memos">
<% @memos.each do |memo| %>
<%= render memo %>
<p>
<%= link_to "Show this memo", memo %>
</p>
<% end %>
</div>
<%= link_to "New memo", new_memo_path %>
app/views/memos/edit.html.erb
editはデータの編集のためによく使われます。
<h1>Editing memo</h1>
<%= render "form", memo: @memo %>
<br>
<div>
<%= link_to "Show this memo", @memo %> |
<%= link_to "Back to memos", memos_path %>
</div>
app/views/memos/show.html.erb
showは特定のデータの詳細情報を表示するのによく使われます。
<p style="color: green"><%= notice %></p>
<%= render @memo %>
<div>
<%= link_to "Edit this memo", edit_memo_path(@memo) %> |
<%= link_to "Back to memos", memos_path %>
<%= button_to "Destroy this memo", @memo, method: :delete %>
</div>
app/views/memos/new.html.erb
newはデータの新規作成でよく使われます。
<h1>New memo</h1>
<%= render "form", memo: @memo %>
<br>
<div>
<%= link_to "Back to memos", memos_path %>
</div>
app/views/memos/_form.html.erb
_
から始まるファイルは部分テンプレートやパーシャルと呼ばれるファイルでその名の通り、表示の一部分を切り取ったものが書かれ、他のファイルの中で呼び出して使います。
いろいろな表示で共通のものなどをパーシャルをつかって切り出しておくと何度も同じコードを書く手間がなくなったり、コード自体もスッキリしてわかりやすくなったりするのでよく使われます。
このファイルはフォームのコードを切り出していて、newやeditで呼び出して使われています。
<%= form_with(model: memo) do |form| %>
<% if memo.errors.any? %>
<div style="color: red">
<h2><%= pluralize(memo.errors.count, "error") %> prohibited this memo from being saved:</h2>
<ul>
<% memo.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :body, style: "display: block" %>
<%= form.text_area :body %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
app/views/memos/_memo.html.erb
このファイルも_
から始まるのでパーシャルです。
これはmemoの情報を表示するコードが書いてありindexやshowで呼び出されています。
<div id="<%= dom_id memo %>">
<p>
<strong>Title:</strong>
<%= memo.title %>
</p>
<p>
<strong>Body:</strong>
<%= memo.body %>
</p>
</div>
memoの新規作成画面を表示してみよう
bin/rails s
でサーバーを立ち上げていない人は立ち上げましょう。
さて、ここでmemoの新規作成画面にアクセスするためにはどのようなURLでアクセスすればいいか考えてみてください。
どのようにURLを調べましたか?
いろいろな調べ方があると思います- ルーティングファイルを確認しに行った人
- 知識をもとにuserの時はこうだったから...って考えた人
- コマンドを使ってURLを調べた人
ちゃんとアクセス出来ればどれも100点満点です。
正解は
http://localhost:3000/memos/new
ですね。
エラー画面が表示されましたか?
大丈夫あなたの画面は正常です。(すでに解決できている人はそのまま進んでください)
次で解説しますね。
データベースを作成する
次のようなエラーが表示されてしまいました。
ActiveRecord::StatementInvalid in MemosController#new
Could not find table 'memos'
ActiveRecord::StatementInvalid
はデータベースに何か問題があるよというエラーです。
Could not find table 'memos'
memosテーブルが見つかりませんといっていますね。
そういえばテーブルを作成するコマンドを打っていませんでした。
コマンドを使ってマイグレーションファイルで定義したテーブルを作成しましょう。
bin/rails db:migrate
どうやらmemosテーブルが作成されたみたいですね
❯❯❯bin/rails db:migrate +[main]
== 20221205161342 CreateMemos: migrating ======================================
-- create_table(:memos)
-> 0.0009s
== 20221205161342 CreateMemos: migrated (0.0009s) =============================
このコマンドを打った時にこっそり作成されるファイルがあります。
それは、schema.rb
というファイルです。
このファイルにはマイグレーションファイルをもとにRailsが実際に作成したテーブルやカラムの状態が書いてあります。
どんなテーブルやカラムがあるかはこのファイルにまとまっているので覚えておくとどんなテーブルがあるか確認したい時や、自分が書いたマイグレーションファイルが正しく定義出来ていたのかを確認したい時などに役に立つので覚えておきましょう。
db/schema.rb
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_12_05_161342) do
create_table "memos", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
ルーティングについて知る
気を取り直してルーティングの確認に戻りましょう。
再度
http://localhost:3000/memos/new
にアクセスしてみてください。
こんな感じの画面が表示されていれば成功です。
先ほどはどのような方法でもどんなURLでアクセスすればいいかわかれば100点と言いましたが、どんなURLでアクセス出来るか調べたい時におすすめの方法があります。
Railsサーバーを立ち上げた状態で
http://localhost:3000/rails/info/routes
にアクセスしてみてください。
このような画面が出てくると思います。
この画面は画面が広く見れるのと、検索が使えるのでわかりやすくおすすめです。
http://localhost:3000/rails/info/routes
の見方
この表は
- Helper
- HTTP Verb
- Path
- Controller#Action
の4つにわかれています。
Helper
Helperはコントローラーのリダイレクト先の指定(redirect_to memos_path
)やlink_to
のようなメソッドで使えるURLヘルパーというものです。
いちいちhttp://localhost:3000/memos/new
のように記載しなくても短く書くことが出来、定義の変更にも対応しやすい書き方です。
HTTP Verb
HTTP VerbのVerbはHTTPリクエストメソッドのことです。
GETやPOST、PUT、PATCH、DELETEなどですね。
Path
パスはメモの新規作成ページのURLでいうところの
http://localhost:3000/memos/new
の/memos/new
の部分のことです。
パスは場所のことを指しており、ユーザーがどの情報が欲しいのかはこのパスを見て判断されます。
しかし、ルーティングのページをよく見てみると同じパスが存在していますね。
同じパスが存在していて困りそうですが、これは同じパスでも先ほど出てきたHTTPリクエストメソッド(HTTP Verb)が違えば違うリクエストとして解釈されるので
GETリクエストのhttp://localhost:3000/memos/1
と
POSTリクエストのhttp://localhost:3000/memos/1
は明確に区別されるので大丈夫です。
Controller#Action
Controller#Actionにはどのコントローラーのどのアクション(newとかshowとかindexとかcreateとか)で処理するのかが書いてあります。
ルーティングの見方まとめ
GET http://localhost:3000/memos/1
というリクエストが飛んできた時にRailsはこのGETリクエストで/memos/1
というPathへのリクエストがきたらmemos#show
つまり、MemosControllerのshowアクションを実行してあげればいいと解釈してくれるというわけです。
メモ一覧画面が表示されるまでの流れを追ってみよう
メモ一覧画面へのリクエスト
メモ一覧画面はGETのhttp://localhost:3000/memos
でリクエストを送れます。
RailsはGETリクエストでパスが/memos
のところをルーティングの対応表から探します。
そうするとこのリクエストはどのコントローラーのどのアクションで対応すればいいリクエストなのかがわかります。
ここではController#Actionのところにmemos#index
と書いてあるのでMemosControllerのindexアクションでこのリクエスト用の対応するということがわかります。
コントローラーでの対応
app/controllers/memos_controller.rb(indexアクションに関係するものだけ抜粋)
class MemosController < ApplicationController
# GET /memos
def index
@memos = Memo.all
end
end
indexアクションの中には@memos = Memo.all
と書いてあります。
ここではモデルとやりとりして全てのメモデータを@memos
というインスタンス変数に代入しています。
データを取得したり、代入していることはわかったけど、ここから先にどうやったらユーザーに画面を見せることができるのかよくわかりませんね。
実はこの先の動きはRailsで決まっているお約束に沿って行われます。
それは各アクションでの処理が終わったらそのコントローラーと同じ名前のディレクトリにある、今実行したアクション名と同じ名前を持つHTMLファイルをユーザーに返してあげるとうお約束です。
このコントローラーはMemosControllerなのでapp/views/memos
ディレクトリを探します。
そしてアクション名はindexなのでindex.html.erb
ファイルを探して来ます。
つまり、なにも書いてはいないけどルールに沿ってapp/views/memes/index.html.erb
を探してくれるというわけです。
app/views/memos/index.html.erb
<p style="color: green"><%= notice %></p>
<h1>Memos</h1>
<div id="memos">
<% @memos.each do |memo| %>
<%= render memo %>
<p>
<%= link_to "Show this memo", memo %>
</p>
<% end %>
</div>
<%= link_to "New memo", new_memo_path %>
表示用のテンプレートにモデルからもらった値などをセットする
検証ツールを使って実際に表示されているHTMLを見てみるとlink_to
と書いてあるところが
<a href="/memos/new">New memo</a>
のようになっていたりとindex.html.erb
とは少し違うのがわかるかと思います。
index.html.erb
ファイルはテンプレートというもので、Railsはこのファイルをそのままユーザーに渡しているわけではなく、インスタンス変数の値を埋め込んだり、Railsの記法やRubyのコードなどを実行した結果をHTMLに変換してそれを返しています。
コントローラーのところで出てきたインスタンス変数はこういったテンプレートファイルに値をセットするために定義することが多いです。
値をセットするなどして作成したHTMLファイルをブラウザに渡す
最後にRailsは作成したHTMLファイルをブラウザに渡すことでユーザーはメモ一覧画面をみることが出来ます。
実際に生成されたHTML
<html>
<script type="text/javascript">window["_gaUserPrefs"] = { ioo : function() { return true; } }</script>
<head>
<title>MemoApp</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="csrf-param" content="authenticity_token">
<meta name="csrf-token" content="QH_kuXKR-beYiKhZBEvQDR-dESiU2sdwMsrE7-appT7mAqo0F7Kj52AfXBRlKA7xS6lIV2etAGyRJdLg8krC7g">
<link rel="stylesheet" href="/assets/application-e0cf9d8fcb18bf7f909d8d91a5e78499f82ac29523d475bf3a9ab265d5e2b451.css">
<script type="importmap" data-turbo-track="reload">{
"imports": {
"application": "/assets/application-3897b39d0f7fe7e947af9b84a1e1304bb30eb1dadb983104797d0a5e26a08736.js"
}
}</script>
<link rel="modulepreload" href="/assets/application-3897b39d0f7fe7e947af9b84a1e1304bb30eb1dadb983104797d0a5e26a08736.js">
<script src="/assets/es-module-shims.min-d89e73202ec09dede55fb74115af9c5f9f2bb965433de1c2446e1faa6dac2470.js" async="async" data-turbo-track="reload"></script>
<script type="module">import "application"</script>
</head>
<body>
<p style="color: green"></p>
<h1>Memos</h1>
<div id="memos">
</div>
<a href="/memos/new">New memo</a>
</body>
</html>
index.html.erbに書いてあること以外にもいろいろ書いてありますね。
Railsは対象のファイルだけを返すのではなく、レイアウトと呼ばれるファイルに書いてある
<%= yield %>
にindex.html.erbなどのファイルの中身を入れ込んで丸っとHTMLに変換したものを返しています。
app/views/layouts/application.html.erb
<%= yield %>
と書いてあるところがありますね
<!DOCTYPE html>
<html>
<head>
<title>MemoApp</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application" %>
<%= javascript_importmap_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
最後にブラウザがレスポンスで返されたHTMLを人間に見やすいように表示することで
ユーザーは
GETリクエストでhttp://localhost:3000/memos
にリクエストを送るとメモ一覧画面を見れるようになります。
[おまけ]エラーの内容を予想してみよう
Routing Error
どんな原因でしょう?
No route matches [GET] "/memoss"
と書いてあることから
GETリクエストでhttp://localhost:3000/memoss
にアクセスが来たけどルーティングの表を見ても対応する項目がないということですね。
このことからルーティングが定義されていないのか、パスやHTTPリクエストメソッドで意図しないものが使われているといったことが推測できますね。
NameError in MemosController#index
どんな原因でしょう?
どうやらコントローラーのindexアクションの途中の処理でエラーが起きているようですね。
ということはルーティングまではOKということがわかりますね。
それと@memos = Memos.all
このコードでやりたいことはモデルとやりとりして全てのメモデータを@memos
というインスタンス変数に代入することでした。
メモモデルはapp/models/memo.rbに定義されています。
class Memo < ApplicationRecord
end
よく見てみるとmemo.rbファイルに定義してあるのはMemo
で、コントローラーで使っているのはMemos
になっていますね。
モデルのクラスを呼び出しているので@memos = Memo.all
にしてあげるとよさそうなことが推測できます。
No template for interactive request
どんな原因でしょう?
MemosController#index is missing a template for request formats: text/html
と書いてあることから対応するテンプレートファイルがないことがわかるかと思います。
Railsには各アクションでの処理が終わったらそのコントローラーと同じ名前のディレクトリにある、今実行したアクション名と同じ名前を持つHTMLファイルをユーザーに返してあげるとうお約束です。
このことからルーティングはOK、コントローラーに書いてある処理もOK、その後テンプレートファイルに値をセットしようとした時に該当のファイル(ここではapp/views/memos/index.html.erb
)が見当たらないということなので、ファイルの作り忘れやファイル名が間違っているなどの原因が推測できますね。
まとめ
Railsのリクエストからレスポンスまでの大枠を掴むとどんなところでエラーが出ているのかの特定がしやすくなったり、大枠を掴むことで詳細の理解が深まったりといいことがたくさんなので意識していきましょう。