前提
Railsチュートリアル 第2章 - Toyアプリケーション - Usersリソースの挙動を変更するからの続きです。
ホストは以下の状態であることを前提としています。
>>> pwd
~/docker/rails_tutorial_test/toy_app
Rails用のDockerコンテナが実行されており、以下の状態であることを前提としています。
# pwd
/var/www/toy_app
# rails server
=> Booting Puma
=> Rails 5.1.6 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.9.1 (ruby 2.5.1-p57), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
{この時点の状態}
Usersコントローラとは
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
# GET /users.json
def index
@users = User.all
end
# GET /users/1
# GET /users/1.json
def show
end
# GET /users/new
def new
@user = User.new
end
# GET /users/1/edit
def edit
end
# POST /users
# POST /users.json
def create
@user = User.new(user_params)
respond_to do |format|
if @user.save
format.html { redirect_to @user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: @user }
else
format.html { render :new }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /users/1
# PATCH/PUT /users/1.json
def update
respond_to do |format|
if @user.update(user_params)
format.html { redirect_to @user, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: @user }
else
format.html { render :edit }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.json
def destroy
@user.destroy
respond_to do |format|
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
@user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:name, :email)
end
end
scaffoldで生成されたUsersコントローラの実体は、pp/controllers/users_controller.rb
というRubyのソースコードです。以下の事柄が重要であろうと思います。
- index, show, new, edit, create, update, destroyというアクションが定義されている
- 各アクションは、RESTアーキテクチャに基づいて定義されている
- コメント文を見るに、HTTPの各リクエストの使い方もRESTアーキテクチャに基づいている
-
@users
や@user
といった変数が登場する-
@users
は、Userモデルからすべてのユーザーの一覧を取り出したものである -
@user
は、Userモデルから単一のユーザーを取り出したものである
-
関係するドキュメントとして、RailsチュートリアルにRailsにおけるRESTアーキテクチャを構成するすべてのアクションの一覧という表が公開されています。
Usersモデルとは
もう一度app/controllers/users_controller.rb
、とりわけGET /users
に関する部分を見てみましょう。
# GET /users
# GET /users.json
def index
@users = User.all
end
@users = User.all
というのは、「Userモデルからすべてのユーザーの一覧を取り出す」という意味の記述です。というわけで、今度はUserモデルの定義であるapp/models/user.rb
を見てみましょう。
class User < ApplicationRecord
end
「ApplicationRecord
というクラスを継承している」ということ以外何も書かれていない、驚くほどシンプルな定義ですね。ApplicationRecord
の定義は、app/models/application_record.rb
に書かれています。
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
「ActiveRecord
というクラスを継承している」という記述に到達しました。ActiveRecord
クラスに関して記述された文書としては、最初に見つかったものでは@penguin_noteさんのRails: ActiveRecord::Baseメソッドのまとめ - Qiitaがあります。
index.html.erb
ビュー
Toyアプリケーションの「/」にアクセスした際、rails server
は以下のようなメッセージを返します。
Started GET "/" for 172.17.0.1 at 2019-08-01 07:57:08 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#index as HTML
Rendering users/index.html.erb within layouts/application
User Load (6.7ms) SELECT "users".* FROM "users"
Rendered users/index.html.erb within layouts/application (54.8ms)
Completed 200 OK in 632ms (Views: 472.4ms | ActiveRecord: 29.5ms)
今回注目するのは以下の記述です。
Rendering users/index.html.erb within layouts/application
Rendered users/index.html.erb within layouts/application
users/index.html.erb
というリソース名が出てきますね。users/index.html.erb
の実体は、app/views
内で以下のように定義されています。
<p id="notice"><%= notice %></p>
<h1>Users</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New User', new_user_path %>
内容についての解説はRailsチュートリアルでもなされていません。しかし、以下の事実は、Railsチュートリアルでも言及されている通りに重要です。
-
@users
が、Rubyのインスタンス変数であるということ-
@users
が、少なくとも「Usersコントローラとindex.html.erb
ビューで共通に使える」というスコープを持つ変数であるということ
-
-
@users
が、Usersコントローラ内でもindex.html.erb
ビュー内でも使われているということ -
@users
が、Usersコントローラとindex.html.erb
ビューで共通のものを指しているということ
演習
1. 図 2.11を参考にしながら、/users/1/edit というURLにアクセスしたときの振る舞いについて図を書いてみてください。
前提として、Toyアプリケーションの「/users/1/edit」にアクセスした際、rails server
は以下のようなメッセージを返します。
Started GET "/users/1/edit" for 172.17.0.1 at 2019-08-03 04:24:05 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#edit as HTML
Parameters: {"id"=>"1"}
User Load (16.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Rendering users/edit.html.erb within layouts/application
Rendered users/_form.html.erb (29.8ms)
Rendered users/edit.html.erb within layouts/application (82.8ms)
Completed 200 OK in 531ms (Views: 414.0ms | ActiveRecord: 16.1ms)
このときの振る舞いを図にすると、以下のようになるはずです。
特筆すべき点は以下であろうと思います。
-
UsersController#edit
は、show
,edit
,update
,destroy
の各処理共通で事前に呼ばれるprivateメソッドであるset_user
以外は何もしていない - モデルとして
User.all
ではなく、User.find
が呼ばれている - モデルはビューに
@users
ではなく@user
を渡している -
index.html.erb
ではなく、edit.html.erb
が呼ばれている -
edit.html.erb
から、さらに_forms.html.erb
が呼ばれ、_forms.html.erb
がedit.html.erb
にHTMLを返している
2. 図示した振る舞いを見ながら、Scaffoldで生成されたコードの中でデータベースからユーザー情報を取得しているコードを探してみてください。
UsersController
でいえば、User.find
というメソッドが「データベースからユーザー情報を取得しているコード」ということになります。ToyアプリケーションにおけるUser
モデルのクラス継承関係はUser < ApplicationRecord < ActiveRecord::Base
というものなので、最終的にはActiveRecord::Base
というクラスのfind
メソッドが呼ばれることになるはずです。
ActiveRecordそのものの説明は本筋から外れるので割愛します。興味がありましたら、@penguin_noteさんのRails: ActiveRecord::Baseメソッドのまとめ - Qiita等を読んでみてください。
3. ユーザーの情報を編集するページのファイル名は何でしょうか?
app/views/users/edit.html.erb
ですね。同ファイルは、さらに内部でapp/views/users/_form.html.erb
というビューを呼び出しています。各々ソースは以下のとおりです。
<h1>Editing User</h1>
<%= render 'form', user: @user %>
<%= link_to 'Show', @user %> |
<%= link_to 'Back', users_path %>
<%= form_with(model: user, local: true) do |form| %>
<% if user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name, id: :user_name %>
</div>
<div class="field">
<%= form.label :email %>
<%= form.text_field :email, id: :user_email %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>