dhtakeuti’s thoughts

主に開発やPCについて考えたこと、感じたことの記録

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

f:id:dhtakeuti:20181109110131p:plain
Ruby on Rails Tutorial

Ruby on Rails チュートリアル の2周目、第2章の User モデル追加を行った。演習でなんとなく MVC の図で分かったつもりだったが不十分だったので、もう少し突っ込んでみた。

シーケンス図にしてみる

第2章の「2.2.2 MVCの挙動」のような図はよく見かけるもので、なんとなくは理解していたが、実際のソースコードとのマッピングが自分の中で十分できていなかった。そこで、シーケンス図を描いてみてより理解を深めることにした。下図はチュートリアル内の MVC を説明する図である。

図 2.11: RailsにおけるMVC

実際に Rails を動かし、ユーザーの一覧表示、新規登録、ユーザー情報編集、削除のオペレーションを行って、新たに追加されたソースコードがどのように使用されているかを確認した。結果は次のようになった。

ユーザーの一覧表示

[sequence]index

ユーザーの新規登録

[sequence]edit

ユーザー情報編集

[sequence]create

ユーザーの削除

[sequence]destroy

解析の方法

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

以上。