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 でのシーケンス図の書き方がわかった。
以上。