###概要
この記事は私の知識をより確実なものにするためにRailsチュートリアル解説記事を書くことで理解を深め
勉強の一環としています。稀にとんでもない内容や間違えた内容が書いてあるかもしれませんので
ご了承ください。
できればそれとなく教えてくれますと幸いです・・・
###この章でやること
ユーザーの作成、ログイン、ログイン情報の記憶が実装できたので
次はユーザーリソースで放置していた更新、表示、削除機能を作成する。
###ユーザーを更新する
ユーザーを更新するにはeditアクションを編集する。
今迄に実装してきたsessionsコントローラのnewアクションや
Usersコントローラのnewアクションのようにフォームを用意し、
フォームの入力値をupdateアクションに送るという動作を実装すればいい。
もちろん編集が可能なのはユーザー本人だがこれまでに実装した認証を使って
アクセス制御を実装していく。
####編集フォーム
editページはURLに対象のユーザーのIDが含まれる
ex) users/1/edit
これを利用してURLのIDからユーザーを取り出しインスタンス変数に保存しておく。
def edit
@user = User.find(id: params[:id])
end
こうすることで次に作成するフォームでモデルオブジェクトに@user
オブジェクトを指定する。
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @user, local: true) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="https://gravatar.com/emails" target="_blank">change</a>
</div>
</div>
</div>
フォームに無効な値を入力した際のエラーメッセージを表示するために_error_messagesパーシャルを再利用している。
またgravatarのリンク部にtarget="_blank"
とあるが、このように記述することで新しいタブでリンク先を表示できる。
さらにフォームの入力欄には@user変数に現在入っている値が自動入力される。
Rails側で自動的に保存されている属性情報を引っ張ってきて表示してくれるらしい。ぐう有能。
このerbから生成される実際のHTMLをのぞくと
<input type="hidden" name="_method" value="patch">
このような記述がある。Webブラウザは更新のリクエストであるPATCHリクエストを送信できないためRailsが
隠しinputフィールドにpatchを指定して、疑似的にPATCHリクエストとして偽造している。
もう一つ覚えておきたい事項としてnewアクションとeditアクションでほぼ変わらないerbコードを使っているのに
なぜRailsが新規のユーザーか既存のユーザーか判別できるかだが
ActiveRecordのnew_record?
メソッドで新規か既存か判別できるからである。
>> new_user = User.new
(1.3ms) SELECT sqlite_version(*)
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_digest: nil>
>> user1 = User.first
User Load (0.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "take", email: "take.webengineer@gmail.com", created_at: "2020-06-14 02:57:10", updated_at: "2020-06-20 03:53:57", password_digest: [FILTERED], remember_digest: "$2a$12$tYO.HIfYezXpTk2zRp9s6uqJY4wUkPM28NfYuJ7vxq/...">
>> new_user.new_record?
=> true
>> user1.new_record?
=> false
>>
実際のerbではform_withを使った際モデルオブジェクトに対して
new_record?```の結果を見て
postかpatchかを判定する。
最後にナビゲーションバーのeditアクションへのリンクを設定する。
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
#####演習
1.興味がわいたので具体的にどのような脆弱性があるか調べた結果、以下の記事が参考になったので置いておく。
https://webegins.com/target-blank/
"noopener"超重要!
2.newビューとeditビューではフォームの部分がほぼ共通で、違う点といったら
submitボタンのテキストぐらいである。
そのため、provideメソッドを使ってsubmitボタンのテキストコンテンツを変更するようにし、
パーシャルにまとめてリファクタリングする。
<%= form_with(model: @user, local: true) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit yield(:btn_text), class: "btn btn-primary" %>
<% end %>
<% provide(:title, 'Sign up') %>
<% provide(:btn_text, "Create my account") %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
</div>
</div>
<% provide(:title, "Edit user") %>
<% provide(:btn_text, "Save changes") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a>
</div>
</div>
</div>
####編集の失敗
ユーザー登録と同じく、無効な値で更新しようとした際の編集の失敗に関して実装していく。
updateアクションを実装していくがcreateアクションでparamsを使ってユーザーを作成したのと同じく
editアクションから送信されたparamsを使って更新する。
構造はかなり似ている。
もちろんparamsでDBを直接更新するのは危険なため今回もStrongParameter(以前定義したuser_paramsメソッド)を使う。
def update
@user = User.find(params[:id])
if @user.update(user_params)
else
render 'edit'
end
end
現時点でUserモデルのバリデーションと_error_messagesパーシャルが存在するため
無効な値にはエラーを返してくれるようになっている。
####編集失敗時のテスト
ユーザーの編集関連の統合テストを作成する。
今回は題目通り編集失敗時のテストを書いていく。
rails g integration_test user_edit
require 'test_helper'
class UserEditTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "unsuccessful edit" do
get edit_user_path(@user)
assert_template 'users/edit'
patch user_path(@user) , params:{user:{name: "",
email: "foo@bar",
password: "foo",
password_confirmation: "bar"}}
assert_template 'users/edit'
end
end
1.editページにGETリクエスト、editページが描画されているか確認
2.updateアクションにpatchリクエスト、無効な値を送信
3.editページが再描画されているか確認。
の順のテストになる。
#####演習
1. assert_select 'div.alert', "The form contains 4 errors."
####TDDで編集を成功させる
今度は成功時の動作を実装していく。
ユーザ⁻画像はGravatarで実装しているためすでに動作する。
name,email,passwordなどほかの属性の編集の成功を実装していく。
機能を実装する前に統合テストを書き、機能を実装し終わったときその機能が受け入れ可能な状態かどうか決めるテスト
を「受け入れテスト」と呼ぶ。
実際にTDDで編集成功を実装してみる。
先程実装した失敗時のテストを参考に実装していくとわかりやすい。(もちろん有効なデータを送信するが)
test "successful edit" do
get edit_user_path(@user)
assert_template 'users/edit'
name = "foo"
email = "foo@bar.com"
patch user_path(@user) , params:{user:{name: name,
email: email,
password: "",
password_confirmation: ""}}
assert_not flash.empty?
assert_redirected_to @user
@user.reload
assert_equal name, @user.name
assert_equal email, @user.email
end
り確実なものにするためにRailsチュートリアル解説記事を書くことで理解を深め
勉強の一環としています。稀にとんでもない内容や間違えた内容が書いてあるかもしれませんので
ご了承ください。
できればそれとなく教えてくれますと幸いです・・・
###この章でやること
ユーザーの作成、ログイン、ログイン情報の記憶が実装できたので
次はユーザーリソースで放置していた更新、表示、削除機能を作成する。
###ユーザーを更新する
ユーザーを更新するにはeditアクションを編集する。
今迄に実装してきたsessionsコントローラのnewアクションや
Usersコントローラのnewアクションのようにフォームを用意し、
フォームの入力値をupdateアクションに送るという動作を実装すればいい。
もちろん編集が可能なのはユーザー本人だがこれまでに実装した認証を使って
アクセス制御を実装していく。
####編集フォーム
editページはURLに対象のユーザーのIDが含まれる
ex) users/1/edit
これを利用してURLのIDからユーザーを取り出しインスタンス変数に保存しておく。
def edit
@user = User.find(id: params[:id])
end
こうすることで次に作成するフォームでモデルオブジェクトに@user
オブジェクトを指定する。
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @user, local: true) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="https://gravatar.com/emails" target="_blank">change</a>
</div>
</div>
</div>
フォームに無効な値を入力した際のエラーメッセージを表示するために_error_messagesパーシャルを再利用している。
またgravatarのリンク部にtarget="_blank"
とあるが、このように記述することで新しいタブでリンク先を表示できる。
さらにフォームの入力欄には@user変数に現在入っている値が自動入力される。
Rails側で自動的に保存されている属性情報を引っ張ってきて表示してくれるらしい。ぐう有能。
このerbから生成される実際のHTMLをのぞくと
<input type="hidden" name="_method" value="patch">
このような記述がある。Webブラウザは更新のリクエストであるPATCHリクエストを送信できないためRailsが
隠しinputフィールドにpatchを指定して、疑似的にPATCHリクエストとして偽造している。
もう一つ覚えておきたい事項としてnewアクションとeditアクションでほぼ変わらないerbコードを使っているのに
なぜRailsが新規のユーザーか既存のユーザーか判別できるかだが
ActiveRecordのnew_record?
メソッドで新規か既存か判別できるからである。
>> new_user = User.new
(1.3ms) SELECT sqlite_version(*)
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_digest: nil>
>> user1 = User.first
User Load (0.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "take", email: "take.webengineer@gmail.com", created_at: "2020-06-14 02:57:10", updated_at: "2020-06-20 03:53:57", password_digest: [FILTERED], remember_digest: "$2a$12$tYO.HIfYezXpTk2zRp9s6uqJY4wUkPM28NfYuJ7vxq/...">
>> new_user.new_record?
=> true
>> user1.new_record?
=> false
>>
実際のerbではform_withを使った際モデルオブジェクトに対して
new_record?```の結果を見て
postかpatchかを判定する。
最後にナビゲーションバーのeditアクションへのリンクを設定する。
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
#####演習
1.興味がわいたので具体的にどのような脆弱性があるか調べた結果、以下の記事が参考になったので置いておく。
https://webegins.com/target-blank/
"noopener"超重要!
2.newビューとeditビューではフォームの部分がほぼ共通で、違う点といったら
submitボタンのテキストぐらいである。
そのため、provideメソッドを使ってsubmitボタンのテキストコンテンツを変更するようにし、
パーシャルにまとめてリファクタリングする。
<%= form_with(model: @user, local: true) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit yield(:btn_text), class: "btn btn-primary" %>
<% end %>
<% provide(:title, 'Sign up') %>
<% provide(:btn_text, "Create my account") %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
</div>
</div>
<% provide(:title, "Edit user") %>
<% provide(:btn_text, "Save changes") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a>
</div>
</div>
</div>
####編集の失敗
ユーザー登録と同じく、無効な値で更新しようとした際の編集の失敗に関して実装していく。
updateアクションを実装していくがcreateアクションでparamsを使ってユーザーを作成したのと同じく
editアクションから送信されたparamsを使って更新する。
構造はかなり似ている。
もちろんparamsでDBを直接更新するのは危険なため今回もStrongParameter(以前定義したuser_paramsメソッド)を使う。
def update
@user = User.find(params[:id])
if @user.update(user_params)
else
render 'edit'
end
end
現時点でUserモデルのバリデーションと_error_messagesパーシャルが存在するため
無効な値にはエラーを返してくれるようになっている。
####編集失敗時のテスト
ユーザーの編集関連の統合テストを作成する。
今回は題目通り編集失敗時のテストを書いていく。
rails g integration_test user_edit
require 'test_helper'
class UserEditTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "unsuccessful edit" do
get edit_user_path(@user)
assert_template 'users/edit'
patch user_path(@user) , params:{user:{name: "",
email: "foo@bar",
password: "foo",
password_confirmation: "bar"}}
assert_template 'users/edit'
end
end
1.editページにGETリクエスト、editページが描画されているか確認
2.updateアクションにpatchリクエスト、無効な値を送信
3.editページが再描画されているか確認。
の順のテストになる。
#####演習
1. assert_select 'div.alert', "The form contains 4 errors."
####TDDで編集を成功させる
今度は成功時の動作を実装していく。
ユーザ⁻画像はGravatarで実装しているためすでに動作する。
name,email,passwordなどほかの属性の編集の成功を実装していく。
機能を実装する前に統合テストを書き、機能を実装し終わったときその機能が受け入れ可能な状態かどうか決めるテスト
を「受け入れテスト」と呼ぶ。
実際にTDDで編集成功を実装してみる。
先程実装した失敗時のテストを参考に実装していくとわかりやすい。(もちろん有効なデータを送信するが)
test "successful edit" do
get edit_user_path(@user)
assert_template 'users/edit'
name = "foo"
email = "foo@bar.com"
patch user_path(@user) , params:{user:{name: name,
email: email,
password: "",
password_confirmation: ""}}
assert_not flash.empty?
assert_redirected_to @user
@user.reload
assert_equal name, @user.name
assert_equal email, @user.email
end
もちろんテストは失敗する。
まず、flashメッセージを実装していないこと。リダイレクトの指定をしていないこと。この二つが引っかかる。
そして一番大切な部分。パスワードの値を空にしているため、バリデーションに引っかかり、正常に更新できない。
前者の二つはこの行で実装する。
def update
@user = User.find(params[:id])
if @user.update(user_params)
flash[:success] = "Profile updated"
redirect_to @user
else
render 'edit'
end
end
この時点では@user.updateはパスワードが空欄でバリデーションに引っかかり、else文に分岐するため、
テストもうまく動かない。
この対策としてパスワードが空の時の例外処理を加える。
このような際にはallow_nil: true
というオプションを使うと便利。
これで空欄でもバリデーションにひっかからなくなる。
このオプションで存在性の検証を通過してしまうが、has_secure_password
メソッド側のオブジェクト生成時の
存在性のバリデーションが働くため、新規作成時はnilをはじき、更新時はnilならパスワードを変更しないという
動作を実現できる。
さらにこのallow_nil: true
オプションを追加したことでモデルに定義したバリデーションと
has_secure_password
メソッドのバリデーションが重複して同じエラーメッセージが表示される不具合も解決する。
###認可
Webアプリにおける認証はユーザーを識別すること。認可はユーザーの実行可能な操作範囲を管理すること。
今まで実装してきたupdate,editアクションでは大きな欠陥があり、
現在の状態ではどのユーザーがログインしていようとすべてのユーザーを編集できてしまう。
ナビゲーションバーのSettingリンクはログインしているユーザーのeditページを表示するが
直接URLに様々なユーザーのeditアクションを指定してしまえばアクセス可能だし、更新もできてしまう。
これはまずいので動作を正しいものに変える。
具体的には
未ログイン時はログインページに転送する+メッセージを表示。
ログイン済みだが別のユーザーにアクセスしようとしている場合はルートURLに転送する。
####ユーザーにログインを要求する
Usersコントローラでbeforeフィルターを使ってedit,updateアクションが実行される前に
必ずログインを強制するよう実装していく。
before_action :logged_in_user, only:[:edit,update]
.
.
.
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url
end
end
このように実装することでedit,updateアクションの実行前に必ずlogged_in_userメソッドが実行され
ログインしていない際にはフラッシュメッセージでログインを促すメッセージを表示し、
ログインページにリダイレクトする。
そして今の段階ではログインしていない状態でeditビューにアクセスするとログインページに
飛ばされてしまうようになったためテストは失敗する。
テストが通るようにuser_edit_test.rbではeditアクションにアクセスする前にログインするようにする。
log_in_asメソッドをテスト用に定義しているためそれをつかう。
これでテストはパスするようになる。だがbefore_actionの行をコメントアウトしてもテストではじかれない。
これは重大なセキュリティホールでテストではじかれなければまずいので
しっかりテストではじくよう修正していく。
test "should redirect edit when not logged in" do
get edit_user_path(@user)
assert_not flash.empty?
assert_redirected_to login_path
end
test "should redirect update when not logged in" do
patch user_path(@user), params:{ user: {name: @user.name,
email: @user.email }}
assert_not flash.empty?
assert_redirected_to login_path
end
このようにテストを追加していくことで
edit,updateアクションを実行する前にlog_in_userが実行されているかを必ずテストするため
セキュリティホールをテストではじいてくれるようになる。
#####演習
1.newページやsignupアクションが実行できなくなりエラーとなる。
FAIL["test_should_get_new", #<Minitest::Reporters::Suite:0x00007f1d1cf4dab8 @name="UsersControllerTest">, 0.06502773099964543]
test_should_get_new#UsersControllerTest (0.07s)
Expected response to be a <2XX: success>, but was a <302: Found> redirect to <http://www.example.com/login>
Response body: <html><body>You are being <a href="http://www.example.com/login">redirected</a>.</body></html>
test/controllers/users_controller_test.rb:10:in `block in <class:UsersControllerTest>'
FAIL["test_invalid_signup_information", #<Minitest::Reporters::Suite:0x00007f1d1cff74c8 @name="UsersSignupTest">, 0.08553676799965615]
test_invalid_signup_information#UsersSignupTest (0.09s)
expecting <"users/new"> but rendering with <[]>
test/integration/users_signup_test.rb:12:in `block in <class:UsersSignupTest>'
FAIL["test_valid_signup_information", #<Minitest::Reporters::Suite:0x00007f1d1d0494d0 @name="UsersSignupTest">, 0.09624041300003228]
test_valid_signup_information#UsersSignupTest (0.10s)
"User.count" didn't change by 1.
Expected: 2
Actual: 1
test/integration/users_signup_test.rb:20:in `block in <class:UsersSignupTest>'
9/9: [================================================================] 100% Time: 00:00:01, Time: 00:00:01
####正しいユーザーを要求する
次はログインしていても本人でなければ編集できないようにしていく。
TDDで進めていく。
まずは別のユーザーでログインする状況を作るためにfixtureに2人目のユーザーを追加する。
archer:
name: Sterling Archer
email: duchess@example.gov
password_digest: <%= User.digest('password') %>
続いて、テストで@other_userとしてログインし、
@userの更新を行うテストを書く。
test "should redirect edit when logged in as wrong user" do
log_in_as(@other_user)
get edit_user_path(@user)
assert flash.empty?
assert_redirected_to root_path
end
test "should redirect update when logged in as wrong user" do
log_in_as(@other_user)
patch user_path(@user), params:{ user: { name: @user.name,
email: @user.email}}
assert flash.empty?
assert_redirected_to root_path
end
flashメッセージは特に表示せずルートURLに飛ばすだけなのでこのようなテストになる。
テストを書き、もちろんパスしないので
テストをパスさせるようにコードを書く。
具体的にはcorrect_user
メソッドを作成し、edit,updateアクション実行前にユーザーがあっていなかったら
ルートURLに飛ばす処理を書く。
before_action :correct_user, only:[:edit,:update]
private
def correct_user
@user = User.find(params[:id])
redirect_to root_url unless @user == current_user
end
これでテストがパスするようになる。
最後にcurrent_user?
メソッドを定義し、先ほど定義したcorrect_user
メソッドに組み込む
def correct_user
@user = User.find(params[:id])
redirect_to root_url unless current_user?(@user)
end
def current_user?(user)
user && user == current_user
end
#####演習
1.updateアクションを保護しなかった場合、editアクション(editページ)を経由せずcurlコマンドなどで直接
値を送った場合に更新できてしまうから。
2.editアクションのほうが簡単にテストできる。(実際にログインして別のユーザのeditパスを表示すればいい)
####フレンドリーフォワーディング
さらにこの更新の機能を便利にする。具体的には
logged_in_user
メソッドで未ログインのユーザーのeditページへのアクセスをはじいてログインページに飛ばした際
そのままログインすると問答無用でユーザー詳細ページ(show)に飛ばされてしまうが、
editページにアクセスしたくてログインしたのにshowページが表示されてしまうのは少々不便である。
これを改良しログインしたらeditページに飛ばしてくれるようにする(フレンドリーフォワーディング)
テストもこの通り実装すればいいので
未ログインでeditページにアクセスし、ログインしたらeditページにリダイレクトすることをチェックする。
test "successful edit with friendly forwarding" do
get edit_user_path(@user)
log_in_as(@user)
assert_redirected_to edit_user_url(@user)
name = "foo"
email = "foo@bar.com"
patch user_path(@user) , params:{user:{name: name,
email: email,
password: "",
password_confirmation: ""}}
assert_not flash.empty?
assert_redirected_to @user
@user.reload
assert_equal name, @user.name
assert_equal email, @user.email
end
現時点で失敗するテストが書けたのでつぎはこのテストがパスするようにコードを書いていく。
リクエスト時のページを保存しておき、ログイン時にそこへリダイレクトする処理を書く。
sessions_helperにメソッドを定義する。
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
def store_location
session[:forwarding_url] = request.original_url if request.get?
end
store_location
では
一時セッションにリクエスト先のURLを保存する処理を書いている。
この時GETリクエストだけを保存するようにしないと
万が一ログインしてフォームページににアクセスし、意図的に保存されたログイン情報のcookiesを削除した上で
フォームの内容を送信するとpost,patchなどのURLが保存されてしまう。
その状態でredirect_back_or
メソッドを使うとリダイレクトでpost,patchなどを期待するURLに対してGETリクエストが
送られてしまい、エラーが発生する可能性が高い。
GETリクエストに絞ることでこういったリスクを回避できる。
logged_in_user
メソッドにstore_location
メソッドを入れて、リクエスト先URLを保存し、
sessions_controllerのcreateアクションにredirect_back_orメソッド
を入れることで、
ログイン時、もしセッションに保存されたURLがあればそちらにリダイレクトするようにする。
def create
@user = User.find_by(email: params[:session][:email].downcase)
if @user&.authenticate(params[:session][:password])
log_in(@user)
params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
redirect_back_or @user
else
flash.now[:danger] = "Invalid email/password combination"
render 'new'
end
end
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
ちなみにreturnやメソッドの最終行の直接呼出しがない限りは
リダイレクトはメソッドの最後で行われる。
上記の内容でテストはパスする。
#####演習
1.ログインしてeditページにリダイレクトした時点で保存したURLが消えていることを確認すればいい。
test "successful edit with friendly forwarding" do
get edit_user_path(@user)
log_in_as(@user)
assert_nil session[:forwarding_url] #ここを追加
assert_redirected_to edit_user_url(@user)
name = "foo"
email = "foo@bar.com"
patch user_path(@user) , params:{user:{name: name,
email: email,
password: "",
password_confirmation: ""}}
assert_not flash.empty?
assert_redirected_to @user
@user.reload
assert_equal name, @user.name
assert_equal email, @user.email
end
(byebug) session[:forwarding_url]
"https://12b7e3b6aec94b45960b81560e233372.vfs.cloud9.us-east-2.amazonaws.com/users/1/edit"
(byebug) request.get?
true
###最後に
だんだん章の最後にやることが増えてきたので
とりあえずまとめておく。
rails t
git add -A
git commit -m "Finish user edit, update, index and destroy actions"
git co master
git merge updating-users
git push
rails t
git push heroku
heroku pg:reset DATABASE
heroku run rails db:migrate
heroku run rails db:seed
本番用DBはpg:reset DATABASE
で行う。なお、間違いを防ぐためにDBをリセットするアプリ名の
入力を求められるので入力してリセット
それか--confirmオプションを使って
heroku pg:reset DATABASE -c アプリ名
としてもいい。
あとはheroku上でマイグレートとサンプルの追加を行って終わり。