「問題の切り分け」と「仮説→検証」の重要性
上手くアプリが動かない時に、闇雲にコードを修正するのではなく、原因となりうる箇所を細かく切り分けながら解決していくことが重要だと感じます。
そこで今回は、そのような手順でについて、いかに記していきます。
【今回の問題点】掲示板を編集し、更新ボタンを押しても内容が変更されない。
掲示板の投稿文を編集し、更新ボタンを押しても、編集前の投稿文から何も内容が変わらないという問題が発生しました。
ソースコード
<% content_for(:title, t('.title')) %>
<div class="container">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<h1><%= t ('.title') %></h1>
<%= render 'form', { board: @board } %>
</div>
</div>
</div>
<%= form_with model: board, local: true do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :body %>
<%= f.text_area :body, class: 'form-control', rows: "10" %>
</div>
<div class="form_group, mb-2">
<%= f.label :board_image %><br>
<%= f.file_field :board_image, onchange: 'previewFileWithId(preview)', class: 'form-control mb-3', accept: 'image/*' %>
<%= f.hidden_field :board_image_cache, class: 'form-control' %>
</div>
<div class="form_group, mb-2">
<%= image_tag @board.board_image.url, id: 'preview' %>
</div>
<%= f.submit class: "btn btn-primary" %>
<% end %>
def edit
@board = Board.find(params[:id])
end
def update
board = Board.find(params[:id])
board.save
redirect_to board_path(board.id), success: '掲示板を更新しました'
end
private
def board_params
params.require(:board).permit(:title, :body, :board_image, :board_image_cache)
end
end
検証ツールでHTMLコード、HTTP通信を確認する
- 検証ツールを使ったところ、form_withはHTML上で以下の様に出力されている。
- 3行目のinputタグで、キーがmethod、 値がpatchになっているので、PATCHによるHTTPリクエストを送信できるようになっている。
- formタグのmethod属性で指定できるのはGETかPOSTのどちらかなので、PATCHによるHTTPリクエストを行うにはこのような書き方になる。
<form enctype="multipart/form-data" action="/boards/85" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓">
<input type="hidden" name="_method" value="patch">
・
・
・
- 検証ツールのネットワークで、更新ボタンを押した時のHTTP通信を確認してみると、PATCHメソッドが使われているし、問題なさそうに見える。
ターミナルのログを確認する
- 次にターミナルのログを確認する。
begin transaction〜commit transaction
の間でDBに対する更新処理が行われるが、テーブルからデータを選択するSELECT文
しか発行されていない。
Started PATCH "/boards/85" for ::1 at 2020-04-07 16:52:13 +0900
Processing by BoardsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"hazM2i+iApBPBweO69FzHOCoYwrRcQNkG/ruvxb3qLNjE1o1/5b5s2oUm0L/DHJ0NoYz5b9fksTJLZyXG0VasQ==", "board"=>{"title"=>"おはよう", "body"=>"lll", "board_image_cache"=>""}, "commit"=>"更新する", "id"=>"85"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 122], ["LIMIT", 1]]
↳ vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
Board Load (1.4ms) SELECT "boards".* FROM "boards" WHERE "boards"."id" = ? LIMIT ? [["id", 85], ["LIMIT", 1]]
↳ app/controllers/boards_controller.rb:40
(0.1ms) begin transaction
↳ app/controllers/boards_controller.rb:41
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 122], ["LIMIT", 1]]
↳ app/controllers/boards_controller.rb:41
(0.1ms) commit transaction
↳ app/controllers/boards_controller.rb:41
Redirected to http://localhost:3000/boards/85
Completed 302 Found in 22ms (ActiveRecord: 2.1ms)
(このSELECT文のSQLは、Boardモデルのbelongs_to :user
の設定により、user_idの存在性を検証するpresense: true
のvalidationが設定されることで、外部キー制約の整合性をモデルレベルでチェックするために発行されているみたい。)
デバッグ処理をする
- また、デバッグ処理を行ってみると、変数
board
には掲示板を編集する前のデータが格納されていることが分かった。つまり、その編集前の内容でboard.save
されているということが分かった。
一方、form_with
で送信されてきたパラメーターを、params
やboard_params
によって取得することはできている(繰り返しになるが、そのパラメーターの内容を変数board
に格納することができてない)。
39: def update
40: board = Board.find(params[:id])
41: binding.pry
=> 42: board.save
43: redirect_to board_path(board.id), success: '掲示板を更新しました'
44: end
[1] pry(#<BoardsController>)>
[2] pry(#<BoardsController>)> board
=> #<Board:0x00007fbec0615d60
id: 85,
title: "おはよう",
body: "lll",
created_at: Tue, 07 Apr 2020 16:27:16 JST +09:00,
updated_at: Tue, 07 Apr 2020 16:27:16 JST +09:00,
user_id: 122,
board_image: nil>
[3] pry(#<BoardsController>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"L9166LF35+SCHBI/tVIlFC+sfkK4cxEUOayTF8uWpabJYuwHYUMcx6cPjvOhjyR8+YIurdZdgLTre+E/xiRXpA==", "board"=>{"title"=>"おはよ", "body"=>"lll", "board_image_cache"=>""}, "commit"=>"更新する", "controller"=>"boards", "action"=>"update", "id"=>"85"} permitted: false>
[4] pry(#<BoardsController>)> board_params
=> <ActionController::Parameters {"title"=>"おはよ", "body"=>"lll", "board_image_cache"=>""} permitt
仮説立て
以上から、editアクションや編集ページのフォーム等には問題無い一方、updateアクションのコードに誤りがあるのでは?と仮説を立てた。それを踏まえて、以下の内容を試してみた。
正しく挙動するアプリの動きと比較してみる
- 正しく更新処理ができるアプリで、更新ボタンを押した時のターミナルログを確認すると、以下の様に、UPDATE文が実行されていることが分かった。
- また、当該アプリのコントローラ内のupdateアクションでは、
update
メソッドが使用されていた。
# 別アプリのターミナルログ
Started PATCH "/users/2" for ::1 at 2020-04-07 17:41:58 +0900
Processing by UsersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"93Zt5AM5oz72MWSUh1RG5Wxu6Q3YA/FFb0vinQJTe5BF7gvphzMRdHm2D2K/73n8vk6/eF4wsLyECPoHleIqqA==", "user"=>{"name"=>"山田太郎", "email"=>"tarou@gmail.com"}, "commit"=>"Update User", "id"=>"2"}
User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
(0.2ms) begin transaction
SQL (2.3ms) UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "山田太郎"], ["updated_at", "2020-04-07 08:41:58.073312"], ["id", 2]]
(3.2ms) commit transaction
Redirected to http://localhost:3000/users/2
Completed 302 Found in 22ms (ActiveRecord: 6.6ms)
原因を発見
- その点から 自分の開発アプリでは、アクション内でupdateメソッドによる更新処理を行えていなかったことが分かった。以下の様に修正すると、掲示板の編集内容が上手く更新された。
def update
board = Board.find(params[:id])
board.update(board_params)
redirect_to board_path(board.id), success: '掲示板を更新しました'
end
すでにテーブルに保存されているデータを、新しい情報に更新するメソッド。
- #####updateメソッドの使い方
-
更新したいレコードを変数に代入する。
-
その変数に対してupdateメソッドを使用する(updateメソッドの引数に変更後のデータをハッシュ形式で与える)。
-
params.require(:board).permit(:title, :body, :board_image, :board_image_cache)
は、{"title"=>"おはようです。", "body"=>"朝だね", "board_image_cache"=>""}
というハッシュ形式でデータを取得している。
-
- ちなみに、
board.update
は以下に代替可能(基本的にupdateメソッドを使えばOK)。
# board.update(arg)は、以下の2行と同じ
board.title = arg
board.save
# 複数の項目を更新したい場合はassign_attributesメソッドが使用できる
board.assign_attributes(title: 'おはよう', body: 'ございます')
board.save
- 修正後に更新ボタンを押した時のターミナルのログを確認すると、updateメソッドを実行したことで、UPDATE文のSQLが発行されている。
[1] pry(#<BoardsController>)> quit
(0.2ms) begin transaction
↳ app/controllers/boards_controller.rb:42
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 122], ["LIMIT", 1]]
↳ app/controllers/boards_controller.rb:42
Board Update (0.7ms) UPDATE "boards" SET "title" = ?, "body" = ?, "updated_at" = ? WHERE "boards"."id" = ? [["title", "おはようです。"], ["body", "朝だね。"], ["updated_at", "2020-04-07 19:21:18.893427"], ["id", 85]]
↳ app/controllers/boards_controller.rb:42
(1.2ms) commit transaction
↳ app/controllers/boards_controller.rb:42
Redirected to http://localhost:3000/boards/85
Completed 302 Found in 14690ms (ActiveRecord: 2.7ms)
おわりに
以上の様に、自分なりに仮説を立てながら原因を探り、問題解決してみました!
そのためにはターミナルのログ、検証ツール、デバッグを活かすことが重要だな〜と再認識しました👀
あとは正しく挙動するアプリケーションとコードの内容やログの内容を比較することで、「自分のコードはどこに問題があるのか」「模範となるアプリではどのような動きをしているのか」を確認できたので、コレもいい方法だと感じました。
これからも問題の切り分け、「仮説→検証」の繰り返しを行いながら、実力を付けていきたいです💪
参照先
【Rails】updateメソッドの使い方を徹底解説!
formタグのaction,method属性の使い方と特性〜html,slimでの記述方法〜