【142日目】【1日20分のRailsチュートリアル】【第10章】演習の2.の/users/:idの統合テストを作成する
今日は「10.5 演習」の2.の/users/:idの統合テストを作成するところから。
10.5 演習
応用問題: /usersと/users/:id両方の統合テストを作成してください。
昨日の続き。/users/:idの統合テスト作成について考えます。
とりあえずテストが通ったコードはコチラ。
テスト追加するファイルがコレジャナイ感が拭えない。。。
test/integration/users_index_test.rb
test "user_path as non-activated" do log_in_as(@admin) # 無効なユーザー @admin.toggle!(:activated) get user_path(@admin) assert_redirected_to root_path @admin.toggle!(:activated) end
$ bundle exec rake test 48 tests, 217 assertions, 0 failures, 0 errors, 0 skips
あーでもないこーでもないと考えてたら時間切れ。
今日の学習時間は【18分】。
次は「10.5 演習」の3.から。
【141日目】【1日20分のRailsチュートリアル】【第10章】演習の2.
今日は「10.5 演習」の2.から。
10.5 演習
2.現在は、/usersのユーザーインデックスページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。
しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト10.58のテンプレートに記入して、この動作を変更してください。
なお、ここで使用するActive Recordのwhereメソッドについては、11.3.3でもう少し詳しく説明します。応用問題: /usersと/users/:id両方の統合テストを作成してください。
whereメソッドはよく分からないけど、SQLでSELECT文使うときのWHERE句みたいなものかな。。。また後で。
穴埋めするとこんな感じかなぁ。。。
app/controllers/users_controller.rb
def index @users = User.where(activated: true).paginate(page: params[:page]) end def show @user = User.find(params[:id]) redirect_to root_url and return unless @user.activated? end
応用問題: /usersと/users/:id両方の統合テストを作成してください。
なんて難しい。。。。
/usersについて統合テスト作成してみたけどこんなのでいいのだろうか。。。ファイルもこれでいいのかよく分かってない…。
とりあえずテストは通った。
まぁまずは自分で書いてみるのも勉強だよね。答え合わせはまたいつか。
test/integration/users_index_test.rb
def setup @admin = users(:michael) @non_admin = users(:archer) end : test "index as non-activated" do log_in_as(@admin) # 無効なユーザー @admin.toggle!(:activated) get users_path assert_template 'users/index' assert_select 'a', text: @admin.name, count: 0 @admin.toggle!(:activated) end
$ bundle exec rake test 47 tests, 215 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【35分】。
次は「10.5 演習」の2.の/users/:idの統合テストを作成するところから。
【140日目】【1日20分のRailsチュートリアル】【第10章】10章のまとめと演習の1.
前回からだいぶ間が空いてしまったけど復活。
今日は「10.4 最後に」から。
10.4 最後に
アカウント有効化機能とパスワード再設定機能が追加されたことで、ついにサンプルアプリケーションの登録、ログイン、ログアウト機能がすべて本格的に実装完了しました。
アプリケーションの本機能ではないけど、アカウント管理には必須な機能がこれで実装完了したのか…!
10.4.1 本章のまとめ
そういえばGitへのコミットは…!?って思いかけたけど、前回してました。そうだった。
10.5 演習
演習とチュートリアル本編の食い違いを避ける方法については、3.6のトピックブランチの演習に追加したメモをご覧ください。
演習用にブランチ作成しておきます。
$ git checkout account-activation-password-reset $ git checkout -b account-activation-password-reset-exercises
1.リスト10.57のテンプレートを埋めて、期限切れのパスワード再設定のブランチ (リスト10.52) の統合テストを作成してください (10.57 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。
期限切れのテスト方法はさまざまですが、リスト10.57でおすすめした手法 (大文字小文字は区別されません) を使えば、レスポンスの本文に「expired」という語があるかどうかをチェックできます。
"Password reset has expired."
っていうエラーメッセージの全文でなくてもいいのかな。。。まぁ一部でいいか。
リスト10.57をほぼコピペして一部埋めてみた。
assert_match /expired/i, response.body
でIDEのワーニング出てるんだよね…合ってると思うんだけどな。。。
正規表現の/i
については下記記事が詳しかった。/i
が大文字小文字区別されないための記法か。
test/integration/password_resets_test.rb
test "expired token" do get new_password_reset_path post password_resets_path, password_reset: { email: @user.email } @user = assigns(:user) @user.update_attribute(:reset_sent_at, 3.hours.ago) patch password_reset_path(@user.reset_token), email: @user.email, user: { password: "foobar", password_confirmation: "foobar" } assert_response :redirect follow_redirect! assert_match /expired/i, response.body end
テストは通ったのでワーニングは無視します。気になるけど…
$ bundle exec rake test 46 tests, 213 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【25分】。
次は「10.5 演習」の2.から。
【139日目】【1日20分のRailsチュートリアル】【第10章】本番環境でのメール送信を試そうとして諦めた
今日は「10.3 本番環境でのメール」から。
10.3 本番環境でのメール
アカウント有効化とパスワード復旧の最大の山場であるこのセクションでは、いよいよproduction (本番) 環境でアプリケーションからメールを送信します。
最初に無料のサービスを利用してメールを送信し、続いてアプリケーションの設定とデプロイを行います。
いよいよメール送信!
production環境からメール送信するために、「SendGrid」というHerokuアドオンを使用してアカウントを検証します (このアドオンを使用するにはHerokuアカウントにクレジットカードを設定する必要がありますが、アカウント検証では料金は発生しません)。
ん?Herokuアカウントにクレジットカードの登録がいるの…???
試しにコマンド打ってみたけどやっぱりクレジットカードの登録を希望された。
$ heroku addons:create sendgrid:starter ! Please verify your account to install this add-on plan (please enter a credit card) For more information, see https://devcenter.heroku.com/categories/billing Verify now at https://heroku.com/verify
学習時点でそこまではしたくないので一旦パス。必須になったら考えよう。
この時点で、Gitのトピックブランチをmasterにマージしておきましょう。
本番環境でのメール送信はできないけどマージしておこう。
$ bundle exec rake test $ git add -A $ git commit -m "Add password resets & email configuration" $ git checkout master $ git merge account-activation-password-reset
続いてリモートリポジトリにプッシュし、Herokuにデプロイします。
メール送信が動かない状態でHerokuにデプロイしても意味ないからリモートリポジトリへのプッシュまでしておくかな。
$ bundle exec rake test
$ git push
リモートリポジトリにpushできたことを確認。
今日の学習時間は【15分】。
次は「10.4 最後に」から。わー!10章の終わりが近づいてきたー!
【138日目】【1日20分のRailsチュートリアル】【第10章】パスワード再設定のテストを作成する
今日は「10.2.5 パスワードの再設定をテストする」から。
10.2.5 パスワードの再設定をテストする
まずはパスワード再設定のテストファイルを生成しましょう。
生成しました。
$ rails generate integration_test password_resets invoke test_unit create test/integration/password_resets_test.rb
テストの冒頭部分には次のような違いがあります: 最初に「forgot password」フォームを表示して無効なメールアドレスを送信し、次はそのフォームで有効なメールアドレスを送信します。
後者ではパスワード再設定用トークンが作成され、再設定用メールが送信されます。続いて、メールのリンクを開いて無効な情報を送信し、次にそのリンクから有効な情報を送信して、それぞれが期待どおりに動作することを確認します。
長い…!うっかりどれかのテスト書くの忘れそう。。
test/integration/password_resets_test.rb
require 'test_helper' class PasswordResetsTest < ActionDispatch::IntegrationTest def setup ActionMailer::Base.deliveries.clear @user = users(:michael) end test "password resets" do get new_password_reset_path assert_template 'password_resets/new' # メールアドレスが無効 post password_resets_path, password_reset: { email: "" } assert_not flash.empty? assert_template 'password_resets/new' # メールアドレスが有効 post password_resets_path, password_reset: { email: @user.email } assert_not_equal @user.reset_digest, @user.reload.reset_digest assert_equal 1, ActionMailer::Base.deliveries.size assert_not flash.empty? assert_redirected_to root_url # パスワード再設定用フォーム user = assigns(:user) # メールアドレスが無効 get edit_password_reset_path(user.reset_token, email: "") assert_redirected_to root_url # 無効なユーザー user.toggle!(:activated) get edit_password_reset_path(user.reset_token, email: user.email) assert_redirected_to root_url user.toggle!(:activated) # メールアドレスが正しく、トークンが無効 get edit_password_reset_path('wrong token', email: user.email) assert_redirected_to root_url # メールアドレスもトークンも有効 get edit_password_reset_path(user.reset_token, email: user.email) assert_template 'password_resets/edit' assert_select "input[name=email][type=hidden][value=?]", user.email # 無効なパスワードと確認 patch password_reset_path(user.reset_token), email: user.email, user: { password: "foobaz", password_confirmation: "barquux" } assert_select 'div#error_explanation' # パスワードが空 patch password_reset_path(user.reset_token), email: user.email, user: { password: "", password_confirmation: "" } assert_select 'div#error_explanation' # 有効なパスワードと確認 patch password_reset_path(user.reset_token), email: user.email, user: { password: "foobaz", password_confirmation: "foobaz" } assert is_logged_in? assert_not flash.empty? assert_redirected_to user end end
今回の新しい要素はinputタグぐらいでしょう
assert_select "input[name=email][type=hidden][value=?]", user.email
こんな書き方でinputタグのテストができるのかー。
user.toggle!(:activated)
って出てきたことあったっけ、と思ってたらあったわ。
引数に:activatedを渡すことでアカウント有効化の切り替えができるんだね。
テストで問題ないことを確認。
$ bundle exec rake test 45 tests, 210 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【23分】。
次は「10.3 本番環境でのメール」から。
【137日目】【1日20分のRailsチュートリアル】【第10章】パスワードリセットのupdateアクションを実装する
今日は「10.2.4 パスワードを再設定する」のupdateアクションを定義するところから。
10.2.4 パスワードを再設定する
リスト10.51のeditアクションに対応するupdateアクションを定義するには、4通りの場合分けに対応する必要があります:
パスワード再設定の期限が切れている場合、更新に成功した場合、更新が失敗した場合 (パスワードが正しくないなど)、更新が失敗した場合 (一見更新が成功したように見えるがパスワードが2つとも空欄) です。
いっぱい確認しなきゃいけないことあるんだな…。
app/controllers/password_resets_controller.rb
: before_action :check_expiration, only: [:edit, :update] : def update if params[:user][:password].empty? @user.errors.add(:password, "can't be empty") render 'edit' elsif @user.update_attributes(user_params) log_in @user flash[:success] = "Password has been reset." redirect_to @user else render 'edit' end end private def user_params params.require(:user).permit(:password, :password_confirmation) end : # 再設定用トークンが期限切れかどうかを確認する def check_expiration if @user.password_reset_expired? flash[:danger] = "Password reset has expired." redirect_to new_password_reset_url end end end
上のコードが動作するには、このpassword_reset_expired?メソッドを定義する必要があります。
(中略)
reset_sent_at < 2.hours.ago
この「<」記号を「〜より少ない」と読んでしまうと、「パスワード再設定メール送信時から経過した時間が、2時間より少ない場合」となってしまい、ここで行おうとしていることと反対の意味になってしまいます。
えー!難しいな…。
2.hours.ago
は「現在時刻より2時間前の時間」を取得できるので、2時間前の時間がreset_sent_at
(パスワード再設定メールを送信した時間)より大きい場合は2時間経過していることになり、trueを返す、ってことか。
こう考えると別におかしくないな。
app/models/user.rb
# パスワード再設定の期限が切れている場合はtrueを返す def password_reset_expired? reset_sent_at < 2.hours.ago end
リスト10.53のコードを使用すると、リスト10.52のupdateアクションが動作するようになります。
動作見てみよう。
前回起動できなかったサーバーは何もしてないのに起動できるようになってた。
何事もなかったかのようにサーバーを起動して、
$ rails server -b $IP -p $PORT
試しに以前パスワードリセットしてみたときのURLにアクセスしてみる。
当然パスワード再設定の期限は切れているのでエラーメッセージが表示されました。
パスワード再設定メールを再度送信して、今度は空のパスワードを再設定しようとしてみる。
エラーメッセージが表示されました。
エラーにならないパスワードを再設定するとパスワード再設定に成功した旨のメッセージが表示され、プロフィール画面が表示されました。
問題なさそう。
今日の学習時間は【25分】。
次は「10.2.5 パスワードの再設定をテストする」から。
【136日目】【1日20分のRailsチュートリアル】【第10章】パスワード再設定フォームを実装する
今日は「10.2.4 パスワードを再設定する」から。
10.2.4 パスワードを再設定する
フォームリンクが動作するためには、パスワード再設定のフォームが必要です。
この作業はユーザーのeditビューでユーザーを更新する (リスト9.2) のと似ていますが、今回はパスワード入力フィールドと確認用フィールドだけを使います。 (中略) このメールアドレスの最適な保存方法は、隠しフィールドとしてページ内に保存することです。
画面にはパスワード入力フィールドとパスワード確認用フィールドのみが表示されるけど、隠しフィールドでメールアドレスも送るようにするのか…!
app/views/password_resets/edit.html.erb
<% provide(:title, 'Reset password') %> <h1>Reset password</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %> <%= render 'shared/error_messages' %> <%= hidden_field_tag :email, @user.email %> <%= 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 "Update password", class: "btn btn-primary" %> <% end %> </div> </div>
再設定用のリンクをクリックすると、前者ではメールアドレスがparams[:email]に保存されますが、後者を使用するとparams[:user][:email]に保存されてしまうからです。
どっちでも良くない?なんて思ってしまったけど、取り出すときに後者だとできないのかな…。
このフォームを出力 (レンダリング) するためにPasswordResetsコントローラのeditアクション内で@userインスタンス変数を定義する必要があります。
before_action
で@userインスタンス変数を定義して正当なアカウントか確認する。
app/controllers/password_resets_controller.rb
before_action :get_user, only: [:edit, :update] before_action :valid_user, only: [:edit, :update] : def edit end private def get_user @user = User.find_by(email: params[:email]) end # 正しいユーザーを確認する def valid_user unless (@user && @user.activated? && @user.authenticated?(:reset, params[:id])) redirect_to root_url end end
サーバー起動して画面見てみようとしたらサーバー起動しない…!
$ rails server -b $IP -p $PORT : A server is already running. Check /home/ubuntu/workspace/sample_app/tmp/pids/server.pid. Exiting
次のとき調べよう…psコマンドでそれらしいプロセスないんだけどな…。
今日の学習時間は【20分】。
次は「10.2.4 パスワードを再設定する」のupdateアクションを定義するところから。
でもその前にサーバー起動できるようにするところから。。