エラー
form_tagで渡したユーザー情報がredirect先のブラウザに表示されないエラー。
View
form_tagで入力した情報をuserコントローラーのupdateアクションへ渡す。
<%= form_tag("/users/#{@user.id}/update", {multipart: true}) do %>
<p>Name</p>
<input name="name" value="<%= @user.name %>">
<p>Image</p>
<input name="image" type="file">
<p>Email</p>
<input name="email" value="<%= @user.email %>">
<input type="submit" value="Update">
<% end %>
Route
form_tagで渡された情報が post 'users/:id/update', to:'users#update'を通る。
Rails.application.routes.draw do
post 'users/:id/update', to:'users#update'
get 'users/:id/edit', to: 'users#edit'
end
Controller
updateアクションで、URLから検索したidを@userに代入する。
@userのnameとemailに、form_tagで渡ってきたnameと、emailを代入する。
@user.saveされたら、/users/#{@user.id}のURLを通って、user/showページに画面推移。されなかったら、render("users/edit")。
def edit
@user = User.find_by(id: params[:id])
end
def update
@user = User.find_by(id: params[:id])
@user.name = params[:name]
@user.email = params[:email]
if params[:image]
@user.image_name = "#{@user.id}.jpg"
image = params[:image]
File.binwrite("public/user_images/#{@user.image_name}", image.read)
end
if @user.save
flash[:notice] = "Updated your information."
redirect_to("/users/#{@user.id}")
else
render("users/edit")
end
end
form_tag("/users/#{@user.id}/update", {multipart: true}) doでsubmit後のログ
Started POST "/users/1/update" for 127.0.0.1 at 2019-03-20 23:39:07 +0900
Processing by UsersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"teAfeEXI8IMrtjWSRdq9DD9a3l+chPishnWRIT18+r5/gGYyRU3Y1vomGpK4tA2aI06/yV12ZkBHPSXlOiBlCg==", "name"=>"あああ", "ema=>"uuu@u.com", "id"=>"1"}
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
(0.2ms) BEGIN
User Exists (1.1ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'uuu@u.com' AND (`users`.`id` != 1) LIMIT 1
(0.3ms) ROLLBACK
Rendering users/edit.html.erb within layouts/application
Rendered users/edit.html.erb within layouts/application (2.0ms)
Completed 200 OK in 68ms (Views: 54.9ms | ActiveRecord: 2.7ms)
推定原因
form_tagで渡したユーザー情報がDBで保存されていない。
保存されていないため、Update後のユーザー情報がリダイレクト先のブラウザに表示されない。
Update後のユーザー情報がリダイレクト先のブラウザに表示するには、updateアクションの結果をDBに保存する必要がある。
なぜ、updateアクションの結果がDBで保存されていないかわからない。なので、再度、updateアクションを確認。
def update
@user = User.find_by(id: params[:id])
@user.name = params[:name]
@user.email = params[:email]
if params[:image]
@user.image_name = "#{@user.id}.jpg"
image = params[:image]
File.binwrite("public/user_images/#{@user.image_name}", image.read)
end
if @user.save
flash[:notice] = "Updated your information."
redirect_to("/users/#{@user.id}")
else
render("users/edit")
end
end
ユーザーモデルも確認
class User < ApplicationRecord
has_secure_password
validates :email, {presence: true, uniqueness: true}
validates :password, {presence: true}
validates :password, confirmation: true
validates :name, presence: true, length: { maximum: 50 }
end
ユーザー編集時に、ユーザー情報がDBで保存されない原因
validates :password, confirmation: trueのパスワードのバリデーションにありました。
この1行を消したら、form_tagで送信した情報がデータベースに保存されて、リダイレクト先のブラウザで変更内容を出力できました。
問題
ユーザー編集時に、データベースに保存される場合は、
validates :password, {presence: true} validates :password, confirmation: true
このバリデーションのコードを消せば、ユーザーupdateが実施され、データベースにデータが保存されました。パスワードが空の場合は、データベースに保存しないバリデーションを外したからです。
しかし、ユーザー編集時とは違い、ユーザー登録時には、パスワードが空の場合、データベースに保存されないようにするために、バリデーションが必要になります。
なので、以下の2つの条件を満たす、バリデーションが必要になることがわかりました。
①ユーザー登録の際に、パスワードが空の場合は、新規ユーザーが作成されないけど、②ユーザー情報の更新時に、パスワードが空の場合でも、データベースにユーザー情報が保存されるバリデーション
パスワードのバリデーションに対して、空だったときの例外処理(条件①、②を両立させるバリデーション)
allow_nil: true
class User < ApplicationRecord
has_secure_password
validates :password, presence: true, allow_nil: true
end
allow_nil: trueによって、ユーザー登録の際に、パスワードが空の場合でも、新規ユーザーが作成されるようになってしまうのでは?
大丈夫だった。has_secure_passwordによって、オブジェクトの生成時(User.newの時)に、オブジェクトの存在性(presence)を検証するようになっている。そのため、ユーザー登録の際に、空のパスワードだと、新規ユーザーが作成されないようになってる。だから、安心。
「リスト 10.13によって、新規ユーザー登録時に空のパスワードが有効になってしまうのかと心配になるかもしれませんが、安心してください。6.3.3で説明したように、has_secure_passwordでは (追加したバリデーションとは別に) オブジェクト生成時に存在性を検証するようになっているため、空のパスワード (nil) が新規ユーザー登録時に有効になることはありません。」
has_secure_passwordの性質
has_secure_passwordには、validationを追加しなくても、User.newで生成される、例えば、name,email,password,image_nameのそれぞれが、presenceかnilかを検証してくれる性質があると理解した。
def create
@user = User.new(
name: params[:name],
email: params[:email],
image_name: "default_user.jpg",
password: params[:password]
)
if @user.save
session[:user_id] = @user.id
flash[:notice] = "ユーザー登録が完了しました"
redirect_to("/users/#{@user.id}")
else
render("users/new")
end
end
引用元:https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#sec-updating_users
補足:存在性の検証とは
参考資料:https://railsguides.jp/active_record_validations.html
class Person < ApplicationRecord
validates :name, presence: true
end
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false
メモ
エラーの原因を考えるときは、view,route,controllerだけではなく、model,databaseまで視野に入れる
MVCでエラーの原因を考えるとき、view,route,controllerまで確認できていたけど、modelまでは確認できていなかった。なので、エラーの原因がmodelにあると気づくのに時間がかかった。