1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

おっさん初学者のRailsチュートリアル学習記録(12章)

Last updated at Posted at 2021-04-11

プログラミング年少組のど素人が、アウトプット用に素人目線の解説を書いていきます。
自分が自分に教えていることを想像しながら進めていきます。
下記と同じような境遇の方であれば参考になるかも

  • まっさらなプログラミング初学者目線
  • オール独学
  • 勉強は得意じゃない
  • 社会人のため、1日に勉強できる量は限られる。

電子書籍のwebテキスト(第6版)を購入して使用
Progateの「Web開発パス」完走済
ドットインストールのプレミアム会員卒業
Railsチュートリアル解説動画を見ながら学習中
#12章 パスワードの再設定#
この章ではユーザーがパスワードを忘れてしまった場合に、再度設定する機能を追加します。
サイトにログインしようとした際に、小さく「パスワードを忘れてしまった場合はこちら」みたいな感じでよくあるやつです。
実装の内容と致しましてはほぼ11章のアカウント有効化と同じような感じみたいです。
共通しているのが、メールの送信を利用した機能ということです。

違いといえば、パスワード再設定のためのページを新たに作るなどがあります。
とは言え新しい概念は出てこないみたいですので11章みたいにもがき苦しむ必要はなさそうです。

有効化にはAccountActivationリソースを作りました
パスワードの再設定にはPasswordResetsリソースを作ります
再設定ようのトークンとダイジェストが登場します・・・が、これはちょっとだけ「authenticated?メソッド」で紹介していたような気がします
「ホニャララ_token」「ホニャララ_digest」みたいなやつ
実際は「reset_token」と「reset_digest」となります

それではどういう手順でパスワード再設定機能が動くか見ていくきましょう

【ユーザー】
ログインしようとるすもパスワードを忘れてしまってログインできない!「パスワードを忘れてしまったら?リンク」をクリックする
【ユーザー → サーバー】
パスワード再設定用のページが開くのでそこで自分のemailを入力し送信(createアクションへのPOSTリクエスト)
要求するためのフォーム(リンク?)とそれを受け取るためのcreateアクションが必要
【サーバー】
データベースの中から該当するユーザーを見つけたら、トークンを発行し、ハッシュ化してデータベースの「reset_digest」に保存
【サーバー → ユーザー】
登録情報にあったemailアドレスに、先程発行したハッシュ化する前のトークン及びemail情報のついてリンクをメールで送る
【ユーザー → サーバー】
email記載のリンクをクリックすると、サーバーにユーザーの「email」情報と「ハッシュ化する前のトークン」の情報が送られる
【サーバー】
emailで送られてきた情報「email」と「有効化する前のトークン(をハッシュ化したもの)」
データベースに保存していた情報「email」と「ハッシュ化された有効化トークン」
この2つを比較して認証に使い、情報が合致したら晴れて「パスワード変更用のフォーム」がユーザーに表示される
最終的には既存のパスワード情報を更新するためeditフォーム+updateが必要
また、パスワード再設定については有効期限を設けて、認証後何時間までなら変更可能とするかを決める。認証が通った状態がいつまでも続いていたら、何のために認証かけるのか分かりませんからね。

11章で使ったものをコピペしつつ修正して使ってますが、流れは似ているっちゃあ似ています
#12.1 PasswordResetsリソース#
セッションの時はSessionsリソースを作りました
アカウント有効化の時はAccountActivationsリソースを作りました
パスワード再設定ではPasswordResetsリソースを作ります

前回同様にUserモデルにカラムを追加する形になります
有効化の時は有効化が初期値ではfalseになっているのをeditアクションでtrueに変えるだけでした。
今回はeditアクションだけではなく、ビューを描画するnewアクションも登場します

まずはお決まりのトピックブランチの作成から取り掛かりましょう

$ git checkout -b password-reset

##12.1.1 PasswordResetsコントローラー##
まずはコントローラーの設定からですいつものように「rails generate controller ~」から始めましょう

$ rails generate controller PasswordResets new edit --no-test-framework

先ほど書いたように、editアクションだけではなくて、newアクションも作成しています
それよりも気になるのが**「--no-test-framework」**です
これを入れることによって、わざわざ単体テストを入れない設定にしているそうです

今回の実装では2種類の入力フォーム(ビューページ)を使用します
1.パスワードを忘れた際に開く、emailを入力ページ(newで使う)
2.新しいパスワードを入れるページ(create、edit、updateで使う)

もう書いちゃってますが、ここでは4つのアクションを使うことになります
「rails generate」でnewとeditだけ書いてたのは、ビューが必要なのがこの2つだけだったからです。

話がそれましたがコントローラーの次はルーティングをしましょうか
有効化の時と同様に、7つのアクションに対応した「resources」を使います。もちろん使うのは4つだけなので、そこんとこは「only: ~」で対応します

config/routes.rb
  resources :password_resets, only: [:new, :create, :edit, :update]

「パスワードを忘れた時は?」があるのはログイン画面ですので
「sessions/new.html.erb」にパスワード再設定画面へのリンクを貼っときましょう
##12.1.2 新しいパスワードの設定##
今回搭載するパスワード再設定機能もセキュリティレベルを高めにしておかないといけません
パスワードに関わるところは肝ですから、ハッシュ化はマストです
もう一つの注意点が、期限切れまでの時間を短くすることです。そのため、再設定メールの送信時刻も残しておく必要が出てきます
この2点の注意点からUserモデルには2つの属性を追加します
「reset_digest」「reset_sent_at」
この2つを含むカラムを「rails generate migration」で追加します

$ rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime

からの

$ rails db:migrate

次は「パスワードを忘れた人はこちら」をクリックすると開くページのビューを作成します
「password_resets/new.html.erb」

password_resets/new.html.erb
<% provide(:title, "Forgot password") %>
<h1>Forgot password</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(url: password_resets_path, scope: :password_reset,
                    local: true) do |f| %>
      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.submit "Submit", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

もう当たり前になってきてますが、「form_with」ヘルパーや「Bootstrap」が使われています
##12.1.3 createアクションでパスワード再設定##
先ほど「form_with」からcreateアクションへPOSTリクエストが送られましたが、肝心のPOSTリクエストがないので作りましょう

controllers/password_resets_controller.rb
  def create
    # form_withから送られてきたアドレスをデータベースから探します
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    # もしデータベースの中にユーザーがいたら
    if @user
      # reset_digestを作成します
      @user.create_reset_digest
      # password_reset_emailを送信します
      @user.send_password_reset_email
      # フラッシュを表示させて
      flash[:info] = "Email sent with password reset instructions"
      # HOME画面に飛ばします
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end

定義してないメソッドがバンバン出てきてこれだけではワケわかりません
なぜか後付けで定義されています
今回もトークンを一時発行しますが、平文のトークンはデータベースには保存しないのでattr_accessrを使います

models/user.rb
 attr_accessor :remember_token, :activation_token, :reset_token

で、後付け作成の「create_reset_digest」と「send_password_reset_email」を作成していきます

models/user.rb
  # パスワード再設定の属性を設定する
  def create_reset_digest
    self.reset_token = User.new_token
    update_attribute(:reset_digest,  User.digest(reset_token))
    update_attribute(:reset_sent_at, Time.zone.now)
  end

  # パスワード再設定のメールを送信する
  def send_password_reset_email
    UserMailer.password_reset(self).deliver_now
  end

「create_reset_digest」メソッドについては、有効化時に作成した「create_activation_digest」メソッドに似ています
「send_@assword_reset_email」メソッドは、「send_activation_email」に似ています

ちょっと「password_reset」なんてメソッドいつの間に定義してたっけ?と思いましたが、前の章まで見返していくと

$ rails generate mailer UserMailer account_activation password_reset

なんてコマンドを流して、すでに作ってました。
疑問点はそれくらいですかね。コード的には見て分かる内容でした。
#12.2 パスワード再設定のメール送信#
パスワード再設定の骨組みがあらかたできてきましたので、送付するメールの内容を作っていきます。
送る内容は11章のアカウントの有効化ほぼ同じ内容です。
メールの文面にリンクが付いていて、そのリンクにはemail情報とトークン情報が含まれています。
先ほど書いた通り、メイラーも「rails generate UserMailer」した時に作成してます
##12.2.1 パスワード再設定のメールとテンプレート##
ちょうど気になっていたこの部分はまだ使える状態になってません。

   UserMailer.password_reset(self).deliver_now

書いた通り「rails generate UserMailer」した際にできたものではありますが、デフォルトではこうなっていて引数も取っていません

maileres/user_mailer.rb
  def password_reset
    @greeting = "Hi"

    mail to: "to@example.org"
  end

変更したものがこちら

maileres/user_mailer.rb
  def password_reset(user)
    @user = user

    mail      to: user.email,
         subject: "Password reset"
  end

繰り返しですが、アカウント有効化時とほぼ同じ内容です。
インスタンス変数作って、宛先と件名を指定するだけです

宛先と件名が決まったので、次は文面です。これもアカウント有効化と同様に「HTML」と「テキスト」の2パターンあります

password_reset.html.erb
<%= link_to "Reset password", edit_password_reset_url(@user.reset_token,
                                                      email: @user.email) %>

重要なのはやはりリンクでしょう。
「edit_password_reset_url」(パスワードを編集するアクションにGETリクエストを送るURL)
「@user.reset_token」(仮保存したトークン)
「email: @user.email」(email情報)
役者は揃ってます。

メールのプレビューができるようにする設定もありました。
完全に忘れてましたが、testディレクトリの中に作ってました。

test/mailers/previews/user_mailer_preview.rb
  # http://localhost:3000/rails/mailers/user_mailer/password_reset
  def password_reset
    user = User.first
    user.reset_token = User.new_token
    UserMailer.password_reset(user)
  end

idが1のユーザーでとりあえずやってますね
最終的に「deliver.now」がついてないので送ってはないんでしょうね
記載の通り、メインページ開いてURLのお尻に「/rails/mailers/user_mailer/password_reset」を付けたらプレビューに飛べます。
テキストタイプにすれば分かりやすいですが、しっかりトークンとemail情報が載ってます。
##12.2.2 送信メールのテスト##
メイラーメソッドのテストを書いていきます
ユーザーよりパスワード再設定の依頼を受けて、本人確認をするために送るメールの内容を確認します。

test/mailers/user_mailer_test.rb
  test "password_reset" do
    # 具体的にfixtureにいるまいけるさんでテストします
    user = users(:michael)
    # リンクに含めるトークンを作成してattr_accessorに入れる
    user.reset_token = User.new_token
    # パスワードリセットのメールオブジェクトをまいけるさんで作成
    mail = UserMailer.password_reset(user)
    # 件名
    assert_equal "Password reset", mail.subject
    # 宛先
    assert_equal [user.email], mail.to
    # 送り主
    assert_equal ["noreply@example.com"], mail.from
    # メール本文にトークンは入っているか
    assert_match user.reset_token, mail.body.encoded
    # メール本文にemailは入っているか
    assert_match CGI.escape(user.email), mail.body.encoded
  end

メール本文のことをテストしているとして見ると何だそんなことかーといった内容です。が、またCGI.escapeが登場しました。
メールで送付するテキストの文面では「@」が表現できないとかで、変わりの文字が表示されます。CGI.escapeを付けることで、変換後の文字列を表現することができるので、emailの中に表現できない文字列が含まれていたとしてもクリアすることができます。
少なくとも「@」は絶対含まれていますので、1つは確定しています。
#12.3 パスワードを再設定する#
パスワード再設定もだいぶ進んできましたので全体の流れをもう一度確認します。

【ユーザー】
ログインしようとるすもパスワードを忘れてしまってログインできない!「パスワードを忘れてしまったら?リンク」をクリックする
【ユーザー → サーバー】
パスワード再設定用のページが開くのでそこで自分のemailを入力し送信(createアクションへのPOSTリクエスト)
【サーバー】
データベースの中から該当するユーザーを見つけたら、トークンを発行し、ハッシュ化してデータベースの「reset_digest」に保存
【サーバー → ユーザー】
登録情報にあったemailアドレスに、先程発行したハッシュ化する前のトークン及びemail情報のついてリンクをメールで送る
【ユーザー → サーバー】
email記載のリンクをクリックすると、サーバーにユーザーの「email」情報と「ハッシュ化する前のトークン」の情報が送られる
☝️ここまでが終了しました。あとひと息です

【サーバー】
emailで送られてきた情報「email」と「有効化する前のトークン(をハッシュ化したもの)」
データベースに保存していた情報「email」と「ハッシュ化された有効化トークン」
この2つを比較して認証に使い、情報が合致したら晴れて「パスワード変更用のフォーム」がユーザーに表示される
最終的には既存のパスワード情報を更新するためeditフォーム+updateが必要
また、パスワード再設定については有効期限を設けて、認証後何時間までなら変更可能とするかを決める。認証が通った状態がいつまでも続いていたら、何のために認証かけるのか分かりませんからね。

ユーザーから送られたメールのリンクがこれです(あくまで例題ですが)

https://example.com/password_resets/3BdBrXeQZSWqFIDRN8cxHA/edit?email=fu%40bar.com

これをユーザーがポチると「GETリクエストをpassword_resetのeditアクションに飛ばします」
リクエストを受けたeditアクションはパスワード編集用のページを表示します(これから作成します)

内容は省略して、テキストで説明されてる部分はここ

<%= hidden_field_tag :email, @user.email %>

editアクションがメールの送り主をアドレスから指定してページを表示したのですが、そのメールアドレス、、、次の手順であるupdateアクションでも使いたいんですけどー。となります。
基本、editアクションが使ったらアドレスの情報は消えてしまうので、どこかに保存しとかなきゃ。
ということで、見えない形にしてページの中に保存しています。
ユーザーには見えないところで、情報を使いまわしているワケですよ。なんてことだ。

今度はeditアクションについて見ていきます

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
end

editアクションは編集ページを表示するだけの機能しかないのですが、
**「誰を」編集するページかはハッキリさせとかないといけません。
何せ、パスワードを変更するページですから。
と、いうわけでページを開く
前に(editアクションを発動させる前)本人であるかを確認してからアクションを動かすようにします
そこで登場するのが、「users_controller」で使った
「before_action」**です。
まあ、もってこいの機能ですよね。

まずは「get_userメソッド」で@userして特定の個人を指定
「valid_userメソッド」で指定した@userが正しいユーザーかどうかを判定
これが確定したらパスワード編集ページを表示させますよ。ということです。

一方で、「account_activations_controller」のeditアクションでは、アクションが動いてからユーザーの特定や認証を行っていました。表示させるページなどありませんでしたからね。

今回のコードに戻りましょう。
「valid_userメソッド」をよく見てみると・・・
しれっと11章で苦しめられた抽象化された「authenticated?」メソッドが使われてます。

@user.authenticated?(:reset, params[:id])

はい、出た出たこのパターン。
authenticated?メソッドの第二引数は「トークン」じゃなかったの?でだいぶ苦しめられましたが、今回もこれです。
ユーザーがクリックしたリンクの中に含まれているトークン情報がGETリクエストで送られて、いつの間にかparams[:id]の中に入ってるということですので、へーそーゆーことなんだー。と、しときます。

それともう一つあります

password_resets_controller.rb
  before_action :get_user,   only: [:edit, :update]
  before_action :valid_user, only: [:edit, :update]

アクションが2つついてる!
編集ページを介さないでパスワード更新するPATCHリクエストが送られるケースもあるわけです
仕組み上できなくもないことですので、PATCHリクエストを受け付ける前にも本人確認をやっておきます

サーバーでとりあえず一連の動作をやってみましょう
1.Log inを開く
2.「forget password」をクリックする
3.Emailを入力して「Submit」をクリックする
4.「rails server」したターミナルから編集ページに飛ぶURLを探す
5.URLをクリックする
6.「Reset password」ページが開く

おお〜うまくいってますね。
・・・!?
ここでなんとなく気づいたのですが、「id」にトークンが入ってる!!
「email」というキーには当然「email」が入ります
「トークン」というキーはありませんので、ユーザーから送られた「トークン」はどこに入るのか
それが「id」というキーに入った!ということだったのでしょうか。。
でもここでやっとトークンがparams[:id]の中に入ってるということが理解できました。
##12.3.2 パスワードを更新する##
いよいよパスワードを更新します。
editアクションが編集ページを表示した時は、updateアクションでデータ更新です。
と、いうことでupdateアクションを組み立てていきます。
updateアクションには4つの機能を盛り込みます

1.パスワードの有効期限確認
2.パスワードのバリデーションもバッチリ確認(失敗した理由をフラッシュで表示)
3.空文字で入力されていないか確認
4.もちろん正しい入力値であれば更新する

1から見ていきましょう
期限か・・・。全く頭になかったです。
確かremember_meやった時にもありましね。

cookies[:remember_token] = { value: remember_token, expires: 20.years.from_now.utc }

これはクッキーを20年間保持するやつでした。今回これは全く関係ありませんが・・・
今回はこれで対応するそうです

password_resets_controller.rb
before_action :check_expiration, only: [:edit, :update]

もちろん「check_expiration」なんてメソッドはまだどこにも出てきてないので、定義しないといけません。見るからに「期限をチェックする」的なネーミングです

password_resets_controller.rb
private

# トークンが期限切れかどうか確認する
def check_expiration
  # パスワードリセットの期限切れてますか?
  if @user.password_reset_expired?
    # YES!ならいかに続く
    flash[:danger] = "Password reset has expired."
    redirect_to new_password_reset_url
  end
end

って今「check_expiration」っていう新しいメソッド定義したばかりなのに、その中にまた見たこともない「password_reset_expire」なんてメソッドが入り込んどる・・・
インスタンスについてるインスタンスメソッドですね。
当然、このメソッドも定義しないと使えません。。
しかも定義するのは「models/user.rb」ですって・・・ところで何でいつもmodelで定義するんやろ・・・

今回は2時間以上たったら期限切れ〜〜ということで指定するようにします

models/users.rb
  def password_reset_expired?
    # リセット申請を送った時刻が2時間前よりさらに前だった〜
    reset_sent_at < 2.hours.ago
  end

「check_expiration」がtrueの時は時間切れ〜〜判定がされる。ということでした。
パスワードの再設定を要求した時点でデータベースに保存された「reset_sent_at(パスワード送信時刻)」が「2.hours.ago(2時間前)」より小さかったら時間切れ〜〜
あれ?日本語おかしいな
2時間より短いなら時間切れ〜〜かな?それなら意味違うしな。。
捉え方が間違ってるみたいです。
答え書いちゃってますが、「reset_sent_at」が「2.hours.ago」より小さかったらではなく**「早かったら(前だったら)」**と見るべきだったそうです。。なんと紛らわしいのでしょうか
時間の「<」はこんな感じで表現されます。

3.hours.ago < 2.hours.ago < 1.hour.ago

これでしっくりきます。

reset_sent_at < 2.hours.ago

「パスワード再設定依頼送信時刻」が「2時間前」より前だったらtrue = 期限が切れてたらtrue
「パスワード再設定依頼送信時刻」が「2時間前」より後だったらfalse = 期限内だったらfalse
期限が切れてる方がtrueになってると、少し混乱してしまいそうですけどね。

ようやくこれで1つ目をクリアです。

お次は「2.パスワードのバリデーションもバッチリ確認」です
バリデーションについてはUserモデルでルール決めは完了してるので、初めからできているはず。
エラーの表示は「password_resets/edit.html.erb」内で

 <%= render 'shared/error_messages' %>

としてるのですでにできています。
あとは無効なデータを入力した場合は「password_resets/edit.html.erb」を再表示させるようにすればいいだけ

password_resets_controllers.rb
    render 'edit' 

これだけ!!

問題は次の「3.空文字で入力されていないか確認」となります
引っかかる理由はユーザーの編集をやっていた際に入れていたこのコード

models/user.rb
validates :password, presence: true, length: {minimum: 6}, allow_nil: true

**「allow_nil: true」**です。
登録の時に関しては「presence: true」が優先されて空白を許可しませんでしたが、編集の場合は「allow_nil: true」が優先されて、空白でもOK〜。となっていました。
今回も言わば編集みたいなもんですから「allow_nil: true」が優先されます。
パスワードを変更する際にパスワード無しで登録できちゃいます。
ということで、パスワード再設定時のルールをこんな感じで決めます。

controllers/password_resets_controller.rb
  # パスワードが空白だったら?
  if params[:user][:password].empty?
    # エラー判定になって「空白はいかんよ」メッセージを表示します
    @user.errors.add(:password, :blank)
    # 再設定ページに飛ばします
    render 'edit'

「errors.add」メソッドに関しては詳しく突っ込んでる様子はありません。何故でしょうか。。。
どこから出てきて、どんな機能があるか。またどこかで定義するのか。。。
自分で調べましょう
こんな感じで使います。

# 「ベースにするもの」が「エラー判定する条件」を満たしたらエラーにする
errors.add(:ベースにするもの, :エラー判定する条件 )
=> errors.add(:password, :blank)

また、4章で出てきた組み込みヘルパーみたいに改めて定義する必要はなさそうです。
手軽にエラーの条件を付与したい場合に使えそうです。。
今回はパスワードが空白だったらエラー判定することを手作業で追加した感じです

そして最後の「4.もちろん正しい入力値であれば更新する」です

controllers/password_resets_controller.rb
  # 受け取った情報に更新する
  @user.update(user_params)
  # そのままログインする
  log_in @user
  # パスワードが更新された旨を提示する
  flash[:success] = "Password has been reset."
  # ユーザー詳細ページに飛ばす
  redirect_to @user

  def user_params
    params.require(:user).permit(:password, :password_confirmation)
  end

受け取る情報を限定させるために「user_params」使ってますね。確か・・・勝手にadmin属性変更したりできないようにするためだったっけ。
**「ストロングパラメーター」**なんて名前がついてました。

最終的にはupdateアクションはこうなります

password_resets_controller.rb
  def update
    if params[:user][:password].empty?                  
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update(user_params)                    
      log_in @user
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'                                
    end
  end

とりあえずこれで1〜4が決まりましたので「update」アクションが完成しました。
と、言うことは・・・パスワード再設定機能完成です!!
##12.3.3 パスワードの再設定をテストする##
機能の搭載が完了したら当然テストします。
今回は統合テストで流れを見ていきますので、ファイルから作りますか

$ rails generate integration_test password_resets

これまでの流れを一気に見ていくので、これまでで最長のテストになります・・・
心してかかりましょう!

test/integration/password_resets_test.rb
  def setup
    # メールは一度も送ってない状態にする
    ActionMailer::Base.deliveries.clear
    user = users(:michael)
  end

  test "password resets" do
    # もちろん「パスワード忘れた人はこちら」をクリックしたところから始めます
    get new_password_reset_path
    # メールアドレスだけを入力する「Forgot password」ページが表示されますよね?
    assert_template 'password_resets/new'
    # 指定するHTMLが表示されますよね?
    assert_select 'input[name=?]', 'password_reset[email]'
    # メールアドレスを空白にしてPOST送信
    post password_resets_path, params: { password_reset: { email: "" } }
    # フラッシュ表示されてますよね?
    assert_not flash.empty?
    # 再度「Forgot password」ページが表示されますよね?
    assert_template 'password_resets/new'
    # 有効なメールアドレス入れてPOST送信
    post password_resets_path,
         params: { password_reset: { email: @user.email } }
    # password_resetトークンが発行されるので、reset_ditestに値が入ってますよね?
    assert_not_equal @user.reset_digest, @user.reload.reset_digest
    # メールオブジェクトが1つ作成されてますよね?
    assert_equal 1, ActionMailer::Base.deliveries.size
    # フラッシュが表示されますよね?(Email sent with password reset instruction)
    assert_not flash.empty?
    # HOME画面に飛ばされますよね?
    assert_redirected_to root_url

    # 〜ここからは「Reset passwordページ」のテスト(まずはページを表示するかどうかから)〜

    # assignsメソッドを使ってインスタンス変数にアクセス
    user = assigns(:user)
    # 「Forgot password」ページで入力したemailとは違うアドレスでGETリクエストが来たら
    get edit_password_reset_path(user.reset_token, email: "")
    # HOMEページに飛ばします
    assert_redirected_to root_url
    # 有効化されているユーザーを無効化に反転して保存してやってみる
    user.toggle!(:activated)
    # リンクから正しい情報でGETリクエストが送信される
    get edit_password_reset_path(user.reset_token, email: user.email)
    # HOMEページに飛ばします
    assert_redirected_to root_url
    # 無効化に反転したユーザー情報を有効化に反転して保存して元に戻す
    user.toggle!(:activated)
    # メールアドレスが有効で、トークンが無効
    get edit_password_reset_path('wrong token', email: user.email)
    # HOMEページに飛ばします
    assert_redirected_to root_url
    # メールアドレスもトークンも有効。ここでやっと通過する。
    get edit_password_reset_path(user.reset_token, email: user.email)
    # 「Reset passwordページ」が表示されますよね?
    assert_template 'password_resets/edit'
    # 隠しemailのHTMLが含まれてますよね?
    assert_select "input[name=email][type=hidden][value=?]", user.email
    # パスワードとパスワード確認が違う
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "barquux" } }
    # エラーメッセージ出てますか?
    assert_select 'div#error_explanation'
    # パスワードが空白で登録しようとした
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "",
                            password_confirmation: "" } }
    # エラーメッセージ出てますか?
    assert_select 'div#error_explanation'
    # 有効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "foobaz" } }
    # ログインしてますか?
    assert is_logged_in?
    # flash表示されてますか?
    assert_not flash.empty?
    # ユーザー詳細ページは表示されてますか?
    assert_redirected_to user
  end

ヤバイっす。長いっす。
搭載した機能を最初から見てるわけですからそりゃ長くもなりますが
ゆーて、あまり初登場的なものはなさそうです

ん?となったところ。
・いつもリンクが含まれるか確認する「assert_select」を、HTML要素含まれるかで使ってる
・9章で登場した「assigns」メソッドを使ってインスタンス変数にアクセスする
・toggle!メソッドって何?
 👉booleanの属性を反転し保存する
・assert_select 'div#error_explanation'って何?
 👉エラーメッセージ入ってますか?のテスト
・assert_select "input[name=email][type=hidden][value=?]", user.email
 👉updateアクションで使う隠しemailはHTMLに含まれているか確認するテスト
#12.4 本番環境でのメール送信(再掲)#
パスワード再設定の実装が完了しましたので、メール送信などの機能も含めて本番環境でも動くようにしていきます
と、言いたいところですが、実はもう11章で同じことをやっていました(結局うまく動きませんでしたが・・・)
とりあえずいつもの流れ

$ rails test
$ git add -A
$ git commit -m "Add account activation"
$ git checkout master
$ git merge password-reset

ここまで済んだらgitにプッシュできます

$ rails test
$ git push
$ git push heroku
$ heroku run rails db:migrate

これをやってからメールの送信が動くかどうか試しに自分のアドレスに送信してやってみると・・・なんと!・・・届いてました!!
リンクからクリックすると無事にサイトに飛びますが、有効化ができないので編集ページに飛ぶことはできませんでした。。。開発環境ではうまくいくのに何ででしょうね。。。
しかもパスワード再設定依頼のメールはちゃんと届いていたので、メール送信機能自体は本番環境でもちゃんと動くみたいです。
データベースです。絶対にデータベースです。何とかしたい・・・でもできない・・・

※この問題は13章終えたところで解決しました。問題はやはりデータベースでした。

$ heroku pg:reset DATABASE

これをやることで、pgのデータベースをいったんスッキリさせることができます。
サービスのリリース後は決してやるべきではないですが・・・これで、本番環境でも晴れてログインすることができるようになりました

#12章の要約をようやく作り終えての感想#
11章の繰り返し的なことが多くて動画もかなり早足で進みました。
最終的に実装した機能も動いてくれたので11章みたいにモヤっとはしませんでした。
統合テストをまとめてぶっ込んできたのには驚きましたが、最後に12章でやったことをまとめて振り返ることができたのは良かったです。どういう機能を搭載してきて、それがどういう風に動くことを想定していたか、ということを再確認できるし、加えて動作のテストもできるので改めて統合テストは素晴らしいと感じました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?