47
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RUNTEQAdvent Calendar 2022

Day 6

Rails初学者必見!作って学ぶRailsのリクエスト/レスポンス

Posted at

はじめに

初学者に絶対押さえておいてほしい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/にアクセスしてこんな画面が出てきたら成功です。

81ab0297f9565e45fcfb2c5f2cb1b384.png

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

作られたファイルを見てみよう

作られたファイルは大きく分けると

  1. モデル関連のファイル
  2. ルーティング関連のファイル
  3. コントローラー関連のファイル
  4. 表示関連のファイル

の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
ですね。

エラー画面が表示されましたか?
大丈夫あなたの画面は正常です。(すでに解決できている人はそのまま進んでください)
次で解説しますね。

e2d424be2eb33ed524477e46dbad1a93.png

データベースを作成する

次のようなエラーが表示されてしまいました。

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
にアクセスしてみてください。
こんな感じの画面が表示されていれば成功です。

765c09248de576af0af9ce9cdd285fb5.png

先ほどはどのような方法でもどんなURLでアクセスすればいいかわかれば100点と言いましたが、どんなURLでアクセス出来るか調べたい時におすすめの方法があります。

Railsサーバーを立ち上げた状態で
http://localhost:3000/rails/info/routes
にアクセスしてみてください。
このような画面が出てくると思います。

c637fbaf19cc760680e9c0b432a8fee0.png

この画面は画面が広く見れるのと、検索が使えるのでわかりやすくおすすめです。

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の部分のことです。
パスは場所のことを指しており、ユーザーがどの情報が欲しいのかはこのパスを見て判断されます。
しかし、ルーティングのページをよく見てみると同じパスが存在していますね。
33d4dce2148394eeb6ecfa77d2e1f8e7.png
同じパスが存在していて困りそうですが、これは同じパスでも先ほど出てきた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のところをルーティングの対応表から探します。
ed54ed816b698d479d99385fbaa9004b.png
そうするとこのリクエストはどのコントローラーのどのアクションで対応すればいいリクエストなのかがわかります。
ここでは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とは少し違うのがわかるかと思います。

29a398b15e58aa473de4f55848d2a260.png

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
にリクエストを送るとメモ一覧画面を見れるようになります。
0e76ec4b9fbda829282e70deb9ce83e2.png

[おまけ]エラーの内容を予想してみよう

Routing Error

f178738936fb9ba06d29bac773f5ebaa.png

どんな原因でしょう?

No route matches [GET] "/memoss"と書いてあることから
GETリクエストでhttp://localhost:3000/memoss
にアクセスが来たけどルーティングの表を見ても対応する項目がないということですね。

このことからルーティングが定義されていないのか、パスやHTTPリクエストメソッドで意図しないものが使われているといったことが推測できますね。

NameError in MemosController#index

99163037754aa388af950c6f28a3d079.png

どんな原因でしょう?

どうやらコントローラーの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

54857879bb334366acf2b105c6b136bf.png

どんな原因でしょう?

MemosController#index is missing a template for request formats: text/htmlと書いてあることから対応するテンプレートファイルがないことがわかるかと思います。

Railsには各アクションでの処理が終わったらそのコントローラーと同じ名前のディレクトリにある、今実行したアクション名と同じ名前を持つHTMLファイルをユーザーに返してあげるとうお約束です。

このことからルーティングはOK、コントローラーに書いてある処理もOK、その後テンプレートファイルに値をセットしようとした時に該当のファイル(ここではapp/views/memos/index.html.erb)が見当たらないということなので、ファイルの作り忘れやファイル名が間違っているなどの原因が推測できますね。

まとめ

Railsのリクエストからレスポンスまでの大枠を掴むとどんなところでエラーが出ているのかの特定がしやすくなったり、大枠を掴むことで詳細の理解が深まったりといいことがたくさんなので意識していきましょう。

47
14
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
47
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?