今日やること

  • Rails Tutorial 10.1.2~10章最後まで

注意点

  • ひたすら迅速に、かける時間の短縮を最大限意識する
  • 学習の為の学習にならない
  • このログ記録にかける時間・記述量も減らす
  • 基本手戻りをしない(前章のリンク先などに戻らない)
  • 学習していても頭の中が常にマルチスレッド状態になりがちで眼の前の教材学習が頭に入りにくいのは別の問題として要対策
  • 逆にここ暫くやってない坐禅とか瞑想とかヨガとか再開するといいかも?

Tips

10.1.2 ユーザー情報編集失敗時の処理
  • マスアサインメントの脆弱性
    • ユーザーが入力したままのparamsハッシュをそのまま渡すことはセキュリティ上非常に危険
    • Rails 4.0以降ではコントローラ層でStrong Parametersというテクニックを使うことが推奨されている
    • 同時に現在はユーザー入力のパラメータを丸ごと渡すとエラーになるようになっている

以上の理由から、この場合paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外を許可しないよう指定

params.require(:user).permit(:name, :email, :password, :password_confirmation)  

上記をUsersコントローラ内だけで使用可能な(Web経由のユーザー側からはアクセスできない)private外部メソッドとして定義

# 以下がprivateメソッドであることを明示する為にネストを一段深くする  
private        # 対応するendやカッコ囲みは不要  

    def user_params  
      params.require(:user).permit(:name, :email, :password, :password_confirmation)  
    end  
10.1.3 編集失敗時のテスト

10.1.2でブラウザから無効な編集情報を送信した際にエラーメッセージと共に編集画面が再表示される挙動は確認できた。

今度はこれを統合テストとしてチェックを自動化する
まず統合テストを生成

$ rails generate integration_test users_edit  

まずは簡単な編集失敗時のテストを記述

  • 画面遷移・描画成功の確認
  • 全ての項目が無効な編集データを送信(POSTでなくPATCHである点に注意)
  • その後に再度編集画面が再描画されること

及び演習問題にあるエラーの数を確認するテスト行(assert_select)を1行追加

"test/integration/users_edit_test.rb"
編集の失敗に対するテスト

require 'test_helper'  

class UsersEditTest < ActionDispatch::IntegrationTest  

  def setup  
    @user = users(:michael)  
  end  

  # 全てのパラメータが無効な("4 errors"となる)編集データを設定  
  test "unsuccessful edit" do  
    get edit_user_path(@user)  
    assert_template 'users/edit'  
    patch user_path(@user), params: { user: { name:  "",  
                                              email: "foo@invalid",  
                                              password:              "foo",  
                                              password_confirmation: "bar" } }  

    assert_template 'users/edit'  
    # (演習問題) 無効な編集データ送信により再描画された編集画面に表示されるエラーメッセージの数を確認  
    assert_select 'div.alert', text: 'The form contains 4 errors.'  
  end  
end  

演習問題の解き方

  1. Chromeのデベロッパーツールを利用し[Elements]パネルでタグの要素とスタイルを検証(図1)
  2. エラーメッセージ表示部分に該当するHTMLのdivタグを確認
  3. assert_selectメソッドを用い、alertクラスのdivタグを探し出し"The form contains 4 errors."というテキストを精査
  4. assert_selectの使用例(下記表5.2参照)に従い3.を実行
図1.Chromeデベロッパーツールによる検証

上記デベロッパーツール[Elements]パネル内エラーメッセージ該当部分



<div id="error_explanation">  
    <div class="alert alert-danger">  
      The form contains 4 errors.  
    </div>  
    <ul>  
      <li>Name can't be blank</li>  
      <li>Email is invalid</li>  
      <li>Password confirmation doesn't match Password</li>  
      <li>Password is too short (minimum is 6 characters)</li>  
    </ul>  
  </div>  

表 5.2: assert_selectのいくつかの使用例

Code マッチするHTML
assert_select "div" <div>foobar</div>
assert_select "div", "foobar" <div>foobar</div>
assert_select "div.nav" <div class="nav">foobar</div>
assert_select "div#profile" <div id="profile">foobar</div>
assert_select "div[name=yo]" <div name="yo">hey</div>
assert_select "a[href=?]", ’/’, count: 1 <a href="/">foo</a>
assert_select "a[href=?]", ’/’, text: "foo" <a href="/">foo</a>

なお、上記に従い 

assert_select 'div.alert',  text: 'The form contains 4 errors.'  
assert_select "div.alert", "The form contains 4 errors."  

どちらでもテスト成功(GREEN)となる。
当然ながら、ここのエラーの数だけを本来の4 errorsから3や5に変えるとテスト失敗する。
(逆にそれでもテストが成功してしまわないかまで確認した方がいいと思われる)


10.1.4 TDD(テスト駆動開発)で編集(成功)機能を実装する


www プロフィール画像編集はGravatarが提供する機能として既に機能している

今回はそれ以外の残りの機能をテスト駆動開発を使ってユーザーの編集機能を実装する

受け入れテスト (Acceptance Tests)」ある機能の実装が完了し、受け入れ可能な状態になったかどうかを決めるテスト

  1. ユーザー情報を更新する正しい振る舞いをテストで定義 (有効な情報を送信)
  2. flashメッセージが空でないかどうかのチェック
  3. プロフィールページにリダイレクトされるかどうかをチェック
  4. データベース内のユーザー情報が正しく変更されたかどうかも検証

上記を反映したコードを追記
また、その際ユーザーの名前やメールアドレスを変更する編集作業に変更の必要ないパスワード・再確認用パスワードを毎回入力させるのは不便なので、パスワード入力無しで更新できるようにする。

"test/integration/users_edit_test.rb"

require 'test_helper'  

class UsersEditTest < ActionDispatch::IntegrationTest  

  def setup  
    @user = users(:michael)  
  end  
︙  
  # ユーザー編集の成功に対するテスト(機能未実装なので現状ではRED)  
  test "successful edit" do  
    get edit_user_path(@user)  
    assert_template 'users/edit'  
    name  = "Foo Bar"  
    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  
end  

本来はバリデーションによりパスワード及び確認用パスワード入力に空白は認められないが、こういった場合の例外処理に便利なallow_nil: trueというオプションを追記する。

"app/models/user.rb"
パスワードが空のままでも更新できるようにする

class User < ApplicationRecord  

  attr_accessor :remember_token  
  before_save { self.email = email.downcase }  
  validates :name, presence: true, length: { maximum: 50 }  
  # VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i  
  # より厳密なメールアドレス用正規表現(上記ではxxx@yyy..comが通ってしまう)  
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i  
  validates :email, presence: true, length: { maximum: 255 },  
                    format: { with: VALID_EMAIL_REGEX },  
                    uniqueness: { case_sensitive: false }  
  has_secure_password  
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true  
 ︙  
end  

上記のオプションによって新規ユーザー登録時にも空のパスワードが有効になってしまう心配はない。

(理由)
直上のhas_secure_passwordでは(追加したバリデーションとは別に)オブジェクト生成時に存在性を検証するようになっているため、空のパスワード(nil)が新規ユーザー登録時に有効になることはない。

同時に、以前未解決のままであった「空のパスワードを入力すると存在性のバリデーション(presence: trueによる制約)とhas_secure_passwordによるバリデーションがそれぞれ実行され、2つの同じエラーメッセージが表示される」というバグ(7.3.3)が残っていたが、これで解決できた。

今日やったこと

  • 10.1.2~10.1.完了までで所要時間およそ3時間

反省点

やはり他の人の平均所要時間(演習に取り組んでいるのか飛ばしているのかは不明ながら)と比較すると、尋常ならざる遅さと言わざるを得ない。

相当な意識改革・環境整備が急務と思われる。

だがしかし

今日のところは学習を終え、風呂入ってリラックスし寝る前には一番いい気分で床に就こう。
くれぐれも睡眠時間削って夜通し学習して少しでも追い付こう、とかはもう無し。たぶん悪循環