コミュ障だから明日が僕らをよんだって返事もろくにしなかった

何かを創る人に憧れたからブログをはじめたんだと思うよ

社会のボトムズがRailsに手を出す #12

これまでのあらすじ

「このRailsチュートリアルが終わったら俺結婚するんだ」その言葉を残して、Railsチュートリアルを更新することは二度となかった。

~HAPPY END


はい、ということでRailsチュートリアル第十二章をやっていきます。前回はアカウント有効化という主にメール周りの機能をいじったりしました。第十二章も第十一章とほぼほぼ同じ内容らしいのでサクッと終わらせていきたいところ。

前回記事
inujini.hatenablog.com


なにはともあれガワをつくります
$ rails generate controller PasswordResets new edit --no-test-framework
$ rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime

今回はテスト生成しないオプションを使うとのこと。オプション系はRailsガイド見てもどこに何かいてるかよくわかんないからrails generate controller --helpで確認するのが手っ取り早いのかもしれない。

追記箇所など 説明
config/routes.rb パスワード再設定用リソースを追加
app/views/sessions/new.html.erb パスワード再設定画面のリンク追加
app/views/password_resets/new.html.erb パスワード再設定画面
app/views/password_resets/edit.html.erb パスワード再設定のフォーム
app/views/user_mailer/password_reset.text.erb パスワード再設定のテンプレート (テキスト)
app/views/user_mailer/password_reset.html.erb パスワード再設定のテンプレート (HTML)
app/controllers/password_resets_controller.rb パスワード再設定のcreateアクション追加
パスワード再設定のeditアクション追加
パスワード再設定のupdateアクション追加
app/models/user.rb パスワード再設定用メソッドを追加
app/mailers/user_mailer.rb パスワード再設定のリンクをメール送信
test/mailers/previews/user_mailer_preview.rb パスワード再設定のプレビューメソッド

それでこんなん作れます。

f:id:andron:20190121214535p:plainf:id:andron:20190121214544p:plain
コード載せると長くなるし、ここまでの内容は基本的に11章に同じなんで省略で…。


テストをやっていきます

最初につくったやつは結合テストで補うということなのでテストを書いていきます。

$ rails generate integration_test password_resets

そんでこう。

# test/integration/password_resets_test.rb
require 'test_helper'
class PasswordResetsTest < ActionDispatch::IntegrationTest
  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:michael)
  end

  test "パスワードリセット" do
    get new_password_reset_path
    assert_template 'password_resets/new'
    # メールアドレスが無効
    post password_resets_path, params: { password_reset: { email: "" } }
    assert_not flash.empty?
    assert_template 'password_resets/new'
    # メールアドレスが有効
    post password_resets_path, params: { 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), 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?
    assert_not flash.empty?
    assert_redirected_to user
    # パスワード再設定が成功したらnil
    assert_nil user.reload['reset_digest'] 
  end
end

すごいごちゃごちゃしてて切り分けたいけどこうするのが一般的なのですかね?


演習の方は以下。

# app/models/user.rb
class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token, :reset_token
  before_save   :downcase_email
  before_create :create_activation_digest
### 略 ###
  # パスワード再設定の属性を設定する
  def create_reset_digest
    self.reset_token = User.new_token
    update_columns(reset_digest:  User.digest(reset_token), reset_sent_at: Time.zone.now)
  end
end
# test/integration/password_resets_test.rb
require 'test_helper'
class PasswordResetsTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:michael)
  end

  test "トークンの期限切れ" do
    get new_password_reset_path
    post password_resets_path,
         params: { password_reset: { email: @user.email } }

    @user = assigns(:user)
    @user.update_attribute(:reset_sent_at, 3.hours.ago)
    patch password_reset_path(@user.reset_token),
          params: { email: @user.email,
                    user: { password:              "foobar",
                            password_confirmation: "foobar" } }
    assert_response :redirect
    follow_redirect!
    assert_match "期限が切れました", response.body
  end
end

大体日本語化させてるせいでassert_matchのヒントがヒントになっていない感じになってしまった。


内容としてはそんな感じ。特に詰まる内容はないと思われ。


第十二章の感想

ほぼ第十一章を踏襲した息抜き回(?)だった。新しい学びがあるとしたら期限切れメソッドの作り方とかでしょうか。

# app/models/user.rb
class User < ApplicationRecord
  ### 略 ###
  # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end
end

これ。


そろそろ終わりが見えてきました。次回第十三章なんですが割と内容が長いから前後編にしようかどうか悩み中です。ネット見てて思うんですけど、初心者の学習法「progate→dotinstall→railsチュートリアル」みたいなのがここにきてもよくわかんない。挫折するから二週目、三週目が必要と言うアドバイスもよくわからないんですよね。逆引き用のサンプルとして使うにしてもあのサイトごちゃごちゃしてて見づらいし、うーんって感じです。これを完走したらそこら辺の分からないっていう分からなさが理解できるようになるんでしょうか?

次回へ続く……。