ぞえの技術めも

Ruby on Rails勉強中

【135日目】【1日20分のRailsチュートリアル】【第10章】パスワード再設定用メイラーメソッドのテストを書く

Ruby on Railsチュートリアル(第3版)

今日は「10.2.3 PasswordResetsメイラーメソッド」のテストを書くところから。

10.2.3 PasswordResetsメイラーメソッド

アカウント有効化メイラーメソッドのテスト (リスト10.18) の場合と同様、パスワード再設定用メイラーメソッドのテストを書くことにします (リスト10.47)。

アカウント有効化のときと同じようなテストを作成する。

test/mailers/user_mailer_test.rb

  test "password_reset" do
    user = users(:michael)
    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
    assert_match CGI::escape(user.email), mail.body.encoded
  end

テスト実行して問題ないことを確認。

$ bundle exec rake test
44 tests, 192 assertions, 0 failures, 0 errors, 0 skips

リスト10.43、リスト10.44、リスト10.45のコードを使用すると、正しいメールアドレスを送信したときの画面は図10.16のようになります。このメールはサーバーログではリスト10.49のように表示されます。

正しいメールアドレス送信してみよう。

サーバーを起動して

$ rails server -b $IP -p $PORT

パスワード再設定画面から有効なメールアドレスを送信。

f:id:kt_zoe:20170419123705p:plain

リダイレクト後の画面にflashメッセージが表示されることを確認。

パスワード再設定メールもサーバーログに出力されてました。

Sent mail to <メールアドレス> (16.9ms)
Date: Wed, 19 Apr 2017 01:58:18 +0000
From: noreply@example.com
To: <メールアドレス>
Message-ID: <58f6c43a2f077_6c13f9569dca45481827@kt-zoe-rails-tutorial-3478208.mail>
Subject: Password reset
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_58f6c43a2b791_6c13f9569dca4548172a";
 charset=UTF-8
Content-Transfer-Encoding: 7bit


----==_mimepart_58f6c43a2b791_6c13f9569dca4548172a
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

To reset your password click the link below:

http://rails-tutorial-kt-zoe.c9users.io//password_resets/DQVXIK-zO7OtQwQfF7VaWQ/edit?email=<メールアドレス>

This link will expire in two hours. 

If you did not request your password to be reset, please ignore this email and
your password will stay as it is.

----==_mimepart_58f6c43a2b791_6c13f9569dca4548172a
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

<html>
  <body>
    <h1>Password reset</h1>

<p>To reset your password click the link below:</p>

<a href="http://rails-tutorial-kt-zoe.c9users.io//password_resets/DQVXIK-zO7OtQwQfF7VaWQ/edit?email=<メールアドレス>">Reset password</a>

<p>This link will expire in two hours.</p>

<p>
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
</p>
  </body>
</html>

----==_mimepart_58f6c43a2b791_6c13f9569dca4548172a--

(<メールアドレス>の部分は実際にはフォームに入れたメールアドレスが表示される)

メールに記載されているURLが動作するにはまだ実装が必要らしい。もうちょいかな。

今日の学習時間は【17分】

次は「10.2.4 パスワードを再設定する」から。

【134日目】【1日20分のRailsチュートリアル】【第10章】パスワードリセットのメールプレビュー機能を実装する

Ruby on Railsチュートリアル(第3版)

今日は「10.2.3 PasswordResetsメイラーメソッド」から。

10.2.3 PasswordResetsメイラーメソッド

最初にユーザーメイラーにpassword_resetメソッドを作成し (リスト10.43)、続いてテキストメールのビューテンプレート (リスト10.44) と HTMLメールのビューテンプレート (リスト10.45) をそれぞれ定義します。

アカウント有効化のときと同じようにメソッドとビュー追加。

app/mailers/user_mailer.rb

  def password_reset(user)
    @user = user
    mail to: user.email, subject: "Password reset"
  end

app/views/user_mailer/password_reset.text.erb

To reset your password click the link below:

<%= edit_password_reset_url(@user.reset_token, email: @user.email) %>

This link will expire in two hours. 

If you did not request your password to be reset, please ignore this email and
your password will stay as it is.

app/views/user_mailer/password_reset.html.erb

<h1>Password reset</h1>

<p>To reset your password click the link below:</p>

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

<p>This link will expire in two hours.</p>

<p>
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
</p>

アカウント有効化メールの場合 (10.1.2) と同様、Railsのメールプレビュー機能でパスワード再設定のメールをプレビューしましょう。

メールプレビュー機能を実装する。

test/mailers/previews/user_mailer_preview.rb

  # Preview this email at 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

ここまで実装できたら動作確認。

サーバーを起動して

$ rails server -b $IP -p $PORT

<ローカルアドレス>/rails/mailers/user_mailer/password_resetにアクセス。

f:id:kt_zoe:20170417123833p:plain

f:id:kt_zoe:20170417123845p:plain

パスワードリセットのメールプレビューが確認できました。

今日の学習時間は【17分】

次は「10.2.3 PasswordResetsメイラーメソッド」のテストを書くところから。

【133日目】【1日20分のRailsチュートリアル】【第10章】パスワード再設定画面でメールアドレスを送信したときの処理

Ruby on Railsチュートリアル(第3版)

今日は「10.2.2 PasswordResetsコントローラとフォーム」のフォームからメールアドレスを送信するところから。

10.2.2 PasswordResetsコントローラとフォーム

図10.12のフォームから送信を行なった後、メールアドレスをキーとしてユーザーをデータベースから見つけ、パスワード再設定用トークンと送信時のタイムスタンプでデータベースの属性を更新する必要があります。
(中略) 送信が無効の場合は、ログイン (リスト8.9) と同様にnewページを出力してflash.nowメッセージを表示します。

パスワード再設定フォームからメールアドレスが送信された後の処理をcreateアクションとして追加する。

app/controllers/password_resets_controller.rb

  def create
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    if @user
      @user.create_reset_digest
      @user.send_password_reset_email
      flash[:info] = "Email sent with password reset instructions"
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end

Userモデル内のコードは、before_createコールバック (リスト10.3) 内で使用されるcreate_activation_digestメソッドと似ています(リスト10.42)。

上で追加したcreate_reset_digestメソッドとかあったっけ、と思ってたらここで追加するのか。
アカウント有効化の処理と似てますね。

app/models/user.rb

  attr_accessor :remember_token, :activation_token, :reset_token
    :
  # パスワード再設定の属性を設定する
  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
    :

図10.13に示すように、この時点でのアプリケーションは、無効なメールアドレスを入力した場合に正常に動作します。

サーバーを起動して

$ rails server -b $IP -p $PORT

パスワード再設定画面で適当なメールアドレス(test01@example.com)を入れて送信してみる。

f:id:kt_zoe:20170413122631p:plain

エラーのfalshメッセージ表示されました。

正しいメールアドレス送信が正常に動作するにはメイラーメソッドの定義が必要らしい。まだか。

今日の学習時間は【26分】

次は「10.2.3 PasswordResetsメイラーメソッド」から。

【132日目】【1日20分のRailsチュートリアル】【第10章】パスワード再設定フォームを追加する

Ruby on Railsチュートリアル(第3版)

今日は「10.2.2 PasswordResetsコントローラとフォーム」から。

10.2.2 PasswordResetsコントローラとフォーム

ログインフォームを参考に、パスワード再設定フォームのビューを実装する。

app/views/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_for(:password_reset, url: password_resets_path) do |f| %>
      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

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

サーバーを起動して

$ rails server -b $IP -p $PORT

ログインフォームの「forgot password」から
<ローカルアドレス>/password_resets/newにアクセス。

f:id:kt_zoe:20170412124235p:plain

パスワード再設定フォームが表示されました。

今日はかなり短いけどここまで。。。

今日の学習時間は【7分】

次は「10.2.2 PasswordResetsコントローラとフォーム」のフォームからメールアドレスを送信するところから。

【131日目】【1日20分のRailsチュートリアル】【第10章】ログイン画面にパスワード再設定画面へのリンクを追加する

Ruby on Railsチュートリアル(第3版)

今日は「10.2 パスワードの再設定」から。

10.2 パスワードの再設定

パスワード再設定の仕組みは、アカウント有効化と似ている部分が多く、10.1で学んだ手法の多くをここでも適用できます。

1passwordを導入してからパスワード忘れること減ったけど、それより前にサービス登録したやつなんか結構パスワード再発行(再設定)したりする。。。

アカウント有効化の際と似ていて、PasswordResetsリソースを作成して、再設定用のトークンとそれに対応するダイジェストを保存するのが今回の目的となります。

ふーむ、パスワード再設定フォーム表示するまでの処理は似てる、ってことかな。

10.2.1 PasswordResetsリソース

アカウント有効化 (10.1.1) の場合と同様、最初に新しいリソースで使用するコントローラを生成します。

下記コマンドでコントローラを生成。

$ rails generate controller PasswordResets new edit --no-test-framework
      create  app/controllers/password_resets_controller.rb
       route  get 'password_resets/edit'
       route  get 'password_resets/new'
      invoke  erb
      create    app/views/password_resets
      create    app/views/password_resets/new.html.erb
      create    app/views/password_resets/edit.html.erb
      invoke  helper
      create    app/helpers/password_resets_helper.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/password_resets.coffee
      invoke    scss
      create      app/assets/stylesheets/password_resets.scss

新しいパスワードを再設定するためのフォーム (図10.8) と、Userモデル内のパスワードを変更するためのフォーム (図10.9) が両方必要になるので、今回はnew、create、edit、updateのルーティングも必要になります。

deleteは要らないからnew、create、edit、updateを指定、ってことなのかな…。

config/routes.rb

  resources :password_resets,     only: [:new, :create, :edit, :update]

ログイン画面のパスワード入力フォームの上にパスワード再設定画面へのリンクを追加。

app/views/sessions/new.html.erb

      <%= link_to "(forgot password)", new_password_reset_path %>

ちゃんとリンク追加できたか見た目確認してみよう。

サーバーを起動して

$ rails server -b $IP -p $PORT

<ローカルアドレス>/login

にアクセス。

f:id:kt_zoe:20170410130325p:plain

テキストリンク追加されてます。

f:id:kt_zoe:20170410130336p:plain

パスワード再設定画面はまだViewいじってないのでデフォルトのまま。

セキュリティ上の注意点をもうひとつ。再設定用のリンクはなるべく短時間 (数時間以内) で期限切れになるようにしなければなりません。そのために、再設定メールの送信時刻も記録する必要があります。

パスワード再設定は期限切れ設定するのか。まぁそうだよね…!

以下を実行して、マイグレーション図10.11の属性を追加します。

パスワード再設定処理向けにUserモデルにreset_digest属性とreset_sent_at属性を追加する。

$ rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime
      invoke  active_record
      create    db/migrate/20170410023324_add_reset_to_users.rb
$ bundle exec rake db:migrate
-- add_column(:users, :reset_digest, :string)
   -> 0.0011s
-- add_column(:users, :reset_sent_at, :datetime)
   -> 0.0004s

追加できたー。

今日の学習時間は【27分】

次は「10.2.2 PasswordResetsコントローラとフォーム」から。

【130日目】【1日20分のRailsチュートリアル】【第10章】アカウント有効化処理のリファクタリング

Ruby on Railsチュートリアル(第3版)

今日は「10.1.4 有効化のテストとリファクタリング」のリファクタリングするところから。

10.1.4 有効化のテストとリファクタリング

今日はリファクタリング

activateメソッドを作成してユーザーの有効化属性を更新し、send_activation_emailメソッドを作成して有効化メールを送信します。

activateメソッド欲しいなー、と思ってました。ソースの可読性上がるよね。

Userモデルにメソッド2つ追加して

app/models/user.rb

  # アカウントを有効にする
  def activate
    update_attribute(:activated,    true)
    update_attribute(:activated_at, Time.zone.now)
  end

  # 有効化用のメールを送信する
  def send_activation_email
    UserMailer.account_activation(self).deliver_now
  end

  private
    :

Userコントローラーのメール送信処理をsend_activation_emailメソッドに置き換えて

app/controllers/users_controller.rb

  def create
    @user = User.new(user_params)
    if @user.save
      @user.send_activation_email
      :

アカウント有効化コントローラーのアカウント有効化処理をactivateメソッドに置き換えた。

app/controllers/account_activations_controller.rb

  def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
      user.activate
      :

リスト10.33ではuser.という記法を使用していないことにご注目ください。Userモデルにはそのような変数はないので、これがあるとエラーになります。

置き換え前はuser.という記法使ってたけど、Userモデルに移動させたことでuser→selfになるからいらなくなるよ、ってことか…。
処理コピペすると変更するの忘れそう。。。

リファクタリングできたのでテスト実行。問題なし。

$ bundle exec rake test
43 tests, 185 assertions, 0 failures, 0 errors, 0 skips

ついにアカウントの有効化を実装できました。きりのよい所でGitにコミットしておきましょう。

コミットしました。

$ git add -A
$ git commit -m "Add account activations"

今日の学習時間は【18分】

次は「10.2 パスワードの再設定」から。

【129日目】【1日20分のRailsチュートリアル】【第10章】アカウント有効化の統合テストを追加する

Ruby on Railsチュートリアル(第3版)

今日は「10.1.4 有効化のテストとリファクタリング」から。

10.1.4 有効化のテストとリファクタリング

この節では、アカウント有効化の統合テストを追加します。
正しい情報でユーザー登録を行った場合のテスト (リスト7.26) は既にあるので、7.4.4で開発したテストに若干手を加えることにします。

setupメソッドを追加して、
“invalid signup information” はそのまま?(でも警告表示のテストが抜けてたので追加した)
“valid signup information with account activation"は後半新規追加。

test/integration/users_signup_test.rb

class UsersSignupTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear
  end
  :
  test "valid signup information with account activation" do
       :
       post users_path, user: { name:  "Example User",
       :
    assert_equal 1, ActionMailer::Base.deliveries.size
    user = assigns(:user)
    assert_not user.activated?
    # 有効化していない状態でログインしてみる
    log_in_as(user)
    assert_not is_logged_in?
    # 有効化トークンが不正な場合
    get edit_account_activation_path("invalid token")
    assert_not is_logged_in?
    # トークンは正しいがメールアドレスが無効な場合
    get edit_account_activation_path(user.activation_token, email: 'wrong')
    assert_not is_logged_in?
    # 有効化トークンが正しい場合
    get edit_account_activation_path(user.activation_token, email: user.email)
    assert user.reload.activated?
    follow_redirect!
    assert_template 'users/show'
    assert is_logged_in?
  end

有効化トークンに関するテスト、この順番が重要なんだろうな。正しい場合からテストすると期待してる動作にはならないもんね。たぶん

リスト10.31のassignsメソッドは本チュートリアル初登場です。
第8章の演習 (8.6) で説明したように、assignsメソッドを使用すると、対応するアクション内にあるインスタンス変数にアクセスできるようになります。

Usersコントローラのcreateアクションでは@userというインスタンス変数が定義されていて、そのインスタンス変数にアクセスできる、ってことらしい。
うーん、難しいな…。

テストは通ることを確認。

$ bundle exec rake test
43 tests, 185 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【23分】

次は「10.1.4 有効化のテストとリファクタリング」のリファクタリングするところから。