Ruby on Rails チュートリアル第2章 MVC を理解する (2周目)

Ruby on Rails チュートリアル の2周目、第2章の User モデル追加を行った。演習でなんとなく MVC の図で分かったつもりだったが不十分だったので、もう少し突っ込んでみた。
シーケンス図にしてみる
第2章の「2.2.2 MVCの挙動」のような図はよく見かけるもので、なんとなくは理解していたが、実際のソースコードとのマッピングが自分の中で十分できていなかった。そこで、シーケンス図を描いてみてより理解を深めることにした。下図はチュートリアル内の MVC を説明する図である。

実際に Rails を動かし、ユーザーの一覧表示、新規登録、ユーザー情報編集、削除のオペレーションを行って、新たに追加されたソースコードがどのように使用されているかを確認した。結果は次のようになった。
ユーザーの一覧表示
ユーザーの新規登録
ユーザー情報編集
ユーザーの削除
解析の方法
最初は、byebug を使って少しづつ動作してみたが、効率が悪い。それぞれのソースコードに記述されたクラスのメソッドで、呼び出しと return の箇所に puts でメッセージを標準出力させるようにした。いわゆる print debugging である。これでブラウザーで操作をしては、Rails のメッセージ出力を読むことで呼び出し順を確認した。
メッセージを出力するのに苦労したのは、モデルの user.rb である。scaffold で生成されたソースコードは下記の通り。
class User < ApplicationRecord end
使用されているメソッドとメッセージ出力を加えたソースコードは下記の通り。
class User < ApplicationRecord
def User.hello
puts "-------------------------------- user.rb#hello ------"
end
def User.all
puts "-------------------------------- user.rb#all Enter --"
@users = super
puts "-------------------------------- user.rb#all Exit ---"
return @users
end
def initialize(*)
puts "-------------------------------- user.rb#new Enter --"
@user = super
puts "-------------------------------- user.rb#new Exit ---"
end
def User.find(args)
puts "-------------------------------- user.rb#find Enter --"
@user = super(args)
puts "-------------------------------- user.rb#find Exit ---"
return @user
end
end
また、ビューの index.html.erb は試行錯誤した。修正後のソースコードは下記の通り。他のビューのソースコードも同様に修正した。
<p id="notice"><%= notice %></p>
<% puts "-------------------------------- index.html.erb Enter --" %>
<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 %>
<% puts "-------------------------------- index.html.erb Exit ---" %>
コントローラーは、各メソッドの最初と最後にメッセージ出力を追加した。修正後のソースコードは下記の通り。
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
# GET /users.json
def index
puts "------------------------- UserController#index Enter --"
#User.hello # => method not found error
@users = User.all
puts "------------------------- UserController#index Exit ---"
end
# GET /users/1
# GET /users/1.json
def show
end
# GET /users/new
def new
puts "------------------------- UserController#new Enter --"
#@user = User.new
@user = User.new()
puts "------------------------- UserController#new Exit ---"
end
# GET /users/1/edit
def edit
end
# POST /users
# POST /users.json
def create
puts "------------------------- UserController#create Enter --"
@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
puts "------------------------- UserController#create Exit ---"
end
# PATCH/PUT /users/1
# PATCH/PUT /users/1.json
def update
puts "------------------------- UserController#update Enter --"
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
puts "------------------------- UserController#update Exit ---"
end
# DELETE /users/1
# DELETE /users/1.json
def destroy
puts "------------------------- UserController#destroy Enter --"
@user.destroy
respond_to do |format|
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
puts "------------------------- UserController#destroy Exit ---"
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
puts "------------------------- UserController#set_user Enter --"
@user = User.find(params[:id])
puts "------------------------- UserController#set_user Exit ---"
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
シーケンス図は PlantText を使用して作成した。ユーザーの追加の操作の PlantUML の記述は下記のようにした。他の図も同様に作成した。
@startuml title Ruby on Rails チュートリアル 2.2.2 MVCの挙動 (POST /users createアクション) end title hide footbox actor ブラウザー as user box "Control" participant users_contoller.rb as control end box box "View" participant new.html.erb as vnew participant show.html.erb as vshow end box box "Model" participant user.rb as model participant ActiveRecord as ar end box database SQLite3 as db user -> control : GET /users/new activate control control --> model : new activate model model --> ar : new model -> control deactivate model control -> vnew activate vnew deactivate control vnew -> user note left ユーザー登録 画面の表示 end note deactivate vnew ==== user -> control : POST /users activate control note left 新規ユーザー の登録 end note control -> model : create(1) activate model model -> ar : create(1) ar -> db : SQL note right INSERT INTO users (name, email, created_at, updated_at) VALUES (?, ?, ?, ?) end note model -> control : 成功 deactivate model deactivate control control -> control activate control note right : Redirect GET /users/1 control -> model : find(1) activate model model -> ar : find(1) ar -> db : SQL note right SELECT users.* FROM users WHERE users.id=1 LIMIT 1 end note db -> ar : user (1) ar -> model : user (1) model -> control : user (1) deactivate model control -> vshow : user (1) activate vshow deactivate control vshow -> user : user (1) note left ユーザー情報 画面の表示 end note deactivate vshow @enduml
まとめ
- シーケンス図を書いてみたら理解が深まった。
- モデル クラスでは、ActiveRecord が大活躍。
- Ruby のメソッドのオーバーライドの書き方がわかった。
- byebug の使い方がわかった。
- PlantUML でのシーケンス図の書き方がわかった。
以上。
![[sequence]index](https://cdn-ak.f.st-hatena.com/images/fotolife/d/dhtakeuti/20181219/20181219165252.png)
![[sequence]edit](https://cdn-ak.f.st-hatena.com/images/fotolife/d/dhtakeuti/20181219/20181219165248.png)
![[sequence]create](https://cdn-ak.f.st-hatena.com/images/fotolife/d/dhtakeuti/20181219/20181219165244.png)
![[sequence]destroy](https://cdn-ak.f.st-hatena.com/images/fotolife/d/dhtakeuti/20181219/20181219165236.png)