Try T.M Engineer Blog

多摩市で生息するエンジニアが「アウトプットする事は大事だ」と思って始めたブログ

【Ruby on Railsチュートリアル(第4版)】第9章 発展的なログイン機構(演習と解答)

はじめに

このブログ記事は、私(Kodak)自身のRailsの勉強記録として書いています。
Ruby on Railsチュートリアル』の演習と解答をもくもくと書いているだけの記事なので、興味の無い方は軽くスルーしてあげてください。他にも、まだ『Railsチュートリアル』の演習に挑戦していない方、これから『Railsチュートリアル』をやるぞ!という方は(演習の解答の)ネタバレになりますので、スルーしてください。

Ruby on Rails チュートリアルとは?

Ruby業界でRailsを使い始めるなら、まず最初に初めるRails入門サイトです。
電子書籍版は有料ですが、Webサイトにあるオンライン版は無料なので、誰でも読む(Railsにチャレンジする)事ができます。
railstutorial.jp
全部で14章あり、かなりのボリュームですが、これを読む事でRailsの基礎を学ぶ事ができ、ちょっとしたWebアプリケーションを作れるレベルにはなれる?とのこと。
各章毎に演習問題が複数あり、これを解いていく事でRailsへの理解を深めていく事ができる様になっています。

環境について

Ruby 2.5.0-dev
Rails 5.1.4
・バージョン管理ツール:GitHub(https://github.com/Kodak4400)

演習と解答

9.1.1 記憶トークンと暗号化<演習>
1. コンソールを開き、データベースにある最初のユーザーを変数userに代入してください。その後、そのuserオブジェクトからrememberメソッドがうまく動くかどうか確認してみましょう。また、remember_tokenとremember_digestの違いも確認してみてください。
【解答】以下の通り。

$ rails console --sandbox
Loading development environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
irb(main):001:0> user = User.first
  User Load (0.4ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-08-12 15:23:47", updated_at: "2018-08-12 15:23:47", password_digest: "$2a$10$j8YTAFtYA/b8uo0q0gqNX.9Zox3VCDNwhYkjMd6ET87...", remember_digest: nil>
irb(main):002:0> user
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-08-12 15:23:47", updated_at: "2018-08-12 15:23:47", password_digest: "$2a$10$j8YTAFtYA/b8uo0q0gqNX.9Zox3VCDNwhYkjMd6ET87...", remember_digest: nil>
irb(main):003:0> user.remember
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.3ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", "2018-08-28 12:14:56.339679"], ["remember_digest", "$2a$10$JXbVWHZy9Y649zF9V5UgDOwueqLWMDsiPdoEUw2SLR3kN5qr/4aHC"], ["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
irb(main):004:0> user.remember_token
=> "nCgSjbDAyJkTft85xVTCCA"
irb(main):005:0> user.remember_digest
=> "$2a$10$JXbVWHZy9Y649zF9V5UgDOwueqLWMDsiPdoEUw2SLR3kN5qr/4aHC"
irb(main):006:0>

2. リスト 9.3では、明示的にUserクラスを呼び出すことで、新しいトークンやダイジェスト用のクラスメソッドを定義しました。実際、User.new_tokenやUser.digestを使って呼び出せるようになったので、おそらく最も明確なクラスメソッドの定義方法であると言えるでしょう。しかし実は、より「Ruby的に正しい」クラスメソッドの定義方法が2通りあります。1つはややわかりにくく、もう1つは非常に混乱するでしょう。テストスイートを実行して、リスト 9.4 (ややわかりにくい) や、リスト 9.5 (非常に混乱する) の実装でも、正しく動くことを確認してみてください。ヒント: selfは、通常の文脈ではUser「モデル」、つまりユーザーオブジェクトのインスタンスを指しますが、リスト 9.4やリスト 9.5の文脈では、selfはUser「クラス」を指すことにご注意ください。わかりにくさの原因の一部はこの点にあります。
実機操作のため割愛。

9.1.2 ログイン状態の保持<演習>
1. ブラウザのcookieを調べ、ログイン後のブラウザではremember_tokenと暗号化されたuser_idがあることを確認してみましょう。
【解答】以下の通り。
f:id:special-moucom:20180830092424p:plain

2. コンソールを開き、リスト 9.6のauthenticated?メソッドがうまく動くかどうか確かめてみましょう。
【解答】以下の通り。

$ rails console --sandbox
Loading development environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
irb(main):001:0> user = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-08-12 15:23:47", updated_at: "2018-08-28 14:19:43", password_digest: "$2a$10$j8YTAFtYA/b8uo0q0gqNX.9Zox3VCDNwhYkjMd6ET87...", remember_digest: "$2a$10$ngv5/yMYwIknzAmT.xNE/eEcplZiu2PVr7HscyaiAau...">
irb(main):002:0> user
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-08-12 15:23:47", updated_at: "2018-08-28 14:19:43", password_digest: "$2a$10$j8YTAFtYA/b8uo0q0gqNX.9Zox3VCDNwhYkjMd6ET87...", remember_digest: "$2a$10$ngv5/yMYwIknzAmT.xNE/eEcplZiu2PVr7HscyaiAau...">
irb(main):003:0> user.remember
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.4ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", "2018-08-28 14:25:05.418184"], ["remember_digest", "$2a$10$g6KH9a8pP4rNF6n.aIFZ..HM/ShRzGLe0Fb.pkoUxtKn6u0m5isZ6"], ["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
irb(main):004:0> user.remember_token
=> "8SqCVjnPAg7x8tGVE0UwHQ"
irb(main):005:0> user.authenticated?(user.remember_token)
=> true
irb(main):006:0>

9.1.3 ユーザーを忘れる<演習>
1. ログアウトした後に、ブラウザの対応するcookiesが削除されていることを確認してみましょう。
実機操作のため割愛。

9.1.4 2つの目立たないバグ<演習>
1. リスト 9.16で修正した行をコメントアウトし、2つのログイン済みのタブによるバグを実際に確かめてみましょう。まず片方のタブでログアウトし、その後、もう1つのタブで再度ログアウトを試してみてください。
【解答】実機操作のため割愛。

2. リスト 9.19で修正した行をコメントアウトし、2つのログイン済みのブラウザによるバグを実際に確かめてみましょう。まず片方のブラウザでログアウトし、もう一方のブラウザを再起動してサンプルアプリケーションにアクセスしてみてください。
【解答】実機操作のため割愛。

3. 上のコードでコメントアウトした部分を元に戻し、テストスイートが red から greenになることを確認しましょう。
【解答】以下の通り。

### app/models/user.rb
class User < ApplicationRecord

  # 渡されたトークンがダイジェストと一致したらtrueを返す
  def authenticated?(remember_token)
    return false if remember_digest.nil?
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

  # ユーザーのログイン情報を破棄する
  def forget
    update_attribute(:remember_digest, nil)
  end
end
### app/controllers/sessions_controller.rb 
class SessionsController < ApplicationController

  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end
$ rails test
Finished in 0.754003s, 33.1564 runs/s, 91.5116 assertions/s.
25 runs, 69 assertions, 0 failures, 0 errors, 0 skips
$

9.2 [Remember me]チェックボックス<演習>
1. ブラウザでcookies情報を調べ、[remember me] をチェックしたときに意図した結果になっているかどうかを確認してみましょう。
実機操作のため割愛。

2. コンソールを開き、三項演算子を使った実例を考えてみてください (コラム 9.2)。
【解答】以下の通り。

$ rails console --sandbox
Loading development environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
irb(main):001:0> def your_name?(name)
irb(main):002:1>  name == 'Kodak' ? "yes" : "no"
irb(main):003:1> end
=> :your_name?
irb(main):004:0> your_name?("Kodak")
=> "yes"
irb(main):005:0> your_name?("Magikarp")
=> "no"
irb(main):006:0>

9.3.1 [Remember me]チェックボックスをテストする<演習>
1. リスト 9.25の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使います。このメソッドにはインスタンス変数に対応するシンボルを渡します。例えばcreateアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userを (インスタンス変数ではない) 通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。このアイデアに従ってリスト 9.27とリスト 9.28の不足分を埋め (ヒントとして?やFILL_INを目印に置いてあります)、[remember me] チェックボックスのテストを改良してみてください。
【解答】以下の通り。

### app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def create
    @user = User.find_by(email:params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報ページにリダイレクトする
      log_in @user
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_to @user
    else
      # エラーメッセージを作成する
      flash.now[:danger] = 'Invalid email/password combination' #本当は正しくない
      render 'new'
    end
  end

end
class UsersLoginTest < ActionDispatch::IntegrationTest

  test "login with remembering" do
    log_in_as(@user, remember_me:'1')
    assert_equal cookies['remember_token'], assigns(:user).remember_token
  end

end
$ rails test
Finished in 0.706577s, 38.2124 runs/s, 101.8997 assertions/s.

27 runs, 72 assertions, 0 failures, 0 errors, 0 skips

9.3.2 [Remember me]をテストする<演習> 1. リスト 9.33にあるauthenticated?の式を削除すると、リスト 9.31の2つ目のテストで失敗することを確かめてみましょう (このテストが正しい対象をテストしていることを確認してみましょう)。 【解答】以下の通り。

### app/helpers/sessions_helper.rb
module SessionsHelper

  def current_user
    if(user_id = session[:user_id])
      @current_user ||= User.find_by(id:user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id:user_id)
  #   if user && user.authenticated?(cookies[:remember_token])
      if user
        log_in user
        @current_user = user
      end
    end
  end
Failure:
SessionsHelperTest#test_current_user_returns_nil_when_remember_digest_is_wrong [rails-tutorial/sample_app/test/helpers/sessions_helper_test.rb:17]:
Expected #<User id: 762146111, name: "Michael Example", email: "michael@example.com", created_at: "2018-08-29 13:39:31", updated_at: "2018-08-29 13:39:31", password_digest: "$2a$04$m6OP7WuMes8ezEStQDXfJenCq3N8XncZGmN4sgJ8Php...", remember_digest: "$2a$04$ZqqQuHUiHVdszcZNt2ZTDekJjOVrZgtmAWNJ5JjTQl0..."> to be nil.

「HTML5/CSS3モダンコーディング」はちょっと難しいけど、モチベーションが保てて良い本だった件

はじめに

最近、RailsチュートリアルJavaサーブレット等、サーバーサイド側の勉強が続いていたので、ここいらでフロントエンド側も勉強したいと思い、この本を手にしました。
手を動かしてコードを書いて学習するスタイルで、とりあえず一周終えたので感想を書いていきたいと思います。

「HTML/CSS3モダンコーディング」の感想

正直な感想を言うと、僕がこの本を読むのは少し早かったかな・・・と感じるところがありました。
しかし、本書がスタンダード・グリッド・シングルページレイアウトにポイントを絞って書かれていた事もあり、最低限これだけ知ってれば、こんなWebページを作れるよ。という大枠を知る事ができたので、結果的に購入して良かったです。
また、本書の解説で使用しているWebページは以下の通りデザインも素敵なので、多少難しくてもモチベーションが保てたところがとても良かったです。
f:id:special-moucom:20180827220318p:plain f:id:special-moucom:20180827220345p:plain f:id:special-moucom:20180827220305p:plain

PART0:イントロダクションがCSS初心者にとって大変勉強になった

CSSにあまり触れてこなかった僕自身としては、このPART0:イントロダクションは凄くためになりました。 特に「要素名にスタイルを指定しない」「classとidの違い」「リセットHTML」等の初心者が気をつけるべきポイントや気になる部分を丁寧に解説されていて、僕自身初めて知る事ばかりだったので大変勉強になりました。

PART1:スタンダードレイアウトの学習がとにかく大変だった

CSSにあまり触れてこなかった人は、PART1:スタンダードレイアウトを読むのが一番大変なのではないでしょうか。僕は凄く大変に感じました。
いきなりclearで回り込みを解除する。とか言われても全然頭に入ってこなかったので、このあたりが「あ、この本を読むのは少し早かったかな」と感じた部分だったかもしれません。
しかし、「そもそも回り込みとは何なのか。」Googleで調べて実際にコーディングする事を繰り返し、理解していきました。
一応、誤解を与えないように言うと、本書ではclearやmargin等の初心者がつまづきやすい箇所は、ちゃんと別枠を設けて説明が記載されています。なので、こちらを読めばGoogleで調べなくても理解できる人は理解できるはずです。ただ、僕は本書に書かれている内容だけではいまいち理解できない部分があったので、そこをGoogleで調べて理解していきました。

PART1:スタンダードレイアウトさえ理解できれば、PART2と3はそこまで難しくない

上記の通り、一番の難所はPART1:スタンダードレイアウトだと感じました。 PART1を読み終えた後のPART2と3はするすると読めたので、そこまで難しいと感じませんでした。 これは本書がWebページのレイアウト作成に重点を置いているからで、本書で使用されているCSSの種類が少ないからなのだと思います。
それでも、これだけ素敵なデザインのWebページを作ることができるので、凄いなと感じました。

本書を読み終えれば素敵なデザインのWebページが作れる様になるのか?

ならないです。(きっぱり!)
あくまで本書を読めば、HTMLとCSSの書き方やWebページのレイアウトの作り方が理解できるまでです。 素敵なデザインのWebページが作れる様になるには、やはりそれなりの経験(Webページを作る数)が必要なのだと感じました。
しかし、HTMLやCSSの書き方を学べた事で、この先やろうと考えていたjQueryの本はするすると読めそうです。 というわけで、次回はjQueryの本を読んでいきたいと思います。

【Ruby on Railsチュートリアル(第4版)】第8章 基本的なログイン機構(演習と解答)

はじめに

このブログ記事は、私(Kodak)自身のRailsの勉強記録として書いています。
Ruby on Railsチュートリアル』の演習と解答をもくもくと書いているだけの記事なので、興味の無い方は軽くスルーしてあげてください。他にも、まだ『Railsチュートリアル』の演習に挑戦していない方、これから『Railsチュートリアル』をやるぞ!という方は(演習の解答の)ネタバレになりますので、スルーしてください。

Ruby on Rails チュートリアルとは?

Ruby業界でRailsを使い始めるなら、まず最初に初めるRails入門サイトです。
電子書籍版は有料ですが、Webサイトにあるオンライン版は無料なので、誰でも読む(Railsにチャレンジする)事ができます。
railstutorial.jp
全部で14章あり、かなりのボリュームですが、これを読む事でRailsの基礎を学ぶ事ができ、ちょっとしたWebアプリケーションを作れるレベルにはなれる?とのこと。
各章毎に演習問題が複数あり、これを解いていく事でRailsへの理解を深めていく事ができる様になっています。

環境について

Ruby 2.5.0-dev
Rails 5.1.4
・バージョン管理ツール:GitHub(https://github.com/Kodak4400)

演習と解答

8.1.1 Sessionsコントローラ<演習>
1. GET login_pathとPOST login_pathとの違いを説明できますか? 少し考えてみましょう。
【解答】GET login_pathは、データを取得したい時に使用する。 POST login_pathはデータを送信したい時に使用する。

2. ターミナルのパイプ機能を使ってrails routesの実行結果とgrepコマンドを繋ぐことで、Usersリソースに関するルーティングだけを表示させることができます。同様にして、Sessionsリソースに関する結果だけを表示させてみましょう。現在、いくつのSessionsリソースがあるでしょうか? ヒント: パイプやgrepの使い方が分からない場合は Learn Enough Command Line to Be Dangerousの Section on Grep (英語) を参考にしてみてください。
【解答】以下の通り。

$ rails routes | grep sessions
sessions_new GET    /sessions/new(.:format)   sessions#new
       login GET    /login(.:format)          sessions#new
             POST   /login(.:format)          sessions#create
      logout DELETE /logout(.:format)         sessions#destroy

8.1.2 ログインフォーム<演習>
1. リスト 8.4で定義したフォームで送信すると、Sessionsコントローラのcreateアクションに到達します。Railsはこれをどうやって実現しているでしょうか? 考えてみてください。ヒント:表 8.1とリスト 8.5の1行目に注目してください。
【解答】"Sign up now!"のSubmitボタンの遷移先がsignup_pathになっているため。signup_pathは、Post users#createのコントローラアクションと紐付いている。

8.1.3 ユーザーの検索と認証<演習>
1. Railsコンソールを使って、表 8.2のそれぞれの式が合っているか確かめてみましょう. まずはuser = nilの場合を、次にuser = User.firstとした場合を確かめてみてください。ヒント: 必ず論理値オブジェクトとなるように、4.2.3で紹介した!!のテクニックを使ってみましょう。例: !!(user && user.authenticate(’foobar’))
【解答】以下の通り。

$ rails console --sandbox
Loading development environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
# 存在しない(何でもよい)の確認
irb(main):001:0> user = nil
=> nil
irb(main):002:0> !!(user && user.authenticate('foobar'))
=> false
# 有効なユーザー(誤ったパスワード)の確認
irb(main):003:0> user = User.first
  User Load (0.4ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-08-12 15:23:47", updated_at: "2018-08-12 15:23:47", password_digest: "$2a$10$j8YTAFtYA/b8uo0q0gqNX.9Zox3VCDNwhYkjMd6ET87...">
irb(main):004:0> !!(user && user.authenticate('aaaaa'))
=> false
# 有効なユーザー(正しいパスワード)の確認
irb(main):005:0> !!(user && user.authenticate('foobar'))
=> true
irb(main):006:0>

8.1.4 フラッシュメッセージを表示する<演習>
1. 8.1.4の処理の流れが正しく動いているかどうか、ブラウザで確認してみてください。特に、flashがうまく機能しているかどうか、フラッシュメッセージの表示後に違うページに移動することを忘れないでください。
【解答】実機操作のため割愛。

8.2.1 log_inメソッド<演習>
1. 有効なユーザーで実際にログインし、ブラウザからcookiesの情報を調べてみてください。このとき、sessionの値はどうなっているでしょうか? ヒント: ブラウザでcookiesを調べる方法が分からない? 今こそググってみるときです! (コラム 1.1)
【解答】以下の通り。
f:id:special-moucom:20180819161644p:plain

2. 先ほどの演習課題と同様に、Expiresの値について調べてみてください。
【解答】上記画像参照。ブラウザセッション終了時

8.2.2 現在のユーザー<演習>
1. Railsコンソールを使って、User.find_by(id: ...)で対応するユーザーが検索に引っかからなかったとき、nilを返すことを確認してみましょう。
【解答】以下の通り。

$ rails console --sandbox
Loading development environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
## 検索に引っかかる場合
irb(main):001:0> user = User.find_by(email: "example@railstutorial.org")
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "example@railstutorial.org"], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-08-12 15:23:47", updated_at: "2018-08-12 15:23:47", password_digest: "$2a$10$j8YTAFtYA/b8uo0q0gqNX.9Zox3VCDNwhYkjMd6ET87...">
## 検索に引っかからない場合
irb(main):002:0> user = User.find_by(email: "test@test")
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "test@test"], ["LIMIT", 1]]
=> nil

2. 先ほどと同様に、今度は:user_idキーを持つsessionハッシュを作成してみましょう。リスト 8.17に記したステップに従って、||=演算子がうまく動くことも確認してみましょう。
【解答】以下の通り。

irb(main):005:0> session = {}
=> {}
irb(main):006:0> session[:user_id] = nil
=> nil
irb(main):007:0> @current_user ||= User.find_by(id: session[:user_id])
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ?  [["LIMIT", 1]]
=> nil
irb(main):008:0> session[:user_id]= User.first.id
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> 1
irb(main):009:0> @current_user ||= User.find_by(id: session[:user_id])
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-08-12 15:23:47", updated_at: "2018-08-12 15:23:47", password_digest: "$2a$10$j8YTAFtYA/b8uo0q0gqNX.9Zox3VCDNwhYkjMd6ET87...">
irb(main):010:0> @current_user ||= User.find_by(id: session[:user_id])
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-08-12 15:23:47", updated_at: "2018-08-12 15:23:47", password_digest: "$2a$10$j8YTAFtYA/b8uo0q0gqNX.9Zox3VCDNwhYkjMd6ET87...">
irb(main):011:0>

8.2.3 レイアウトリンクを変更する<演習>
1. ブラウザのcookieインスペクタ機能を使って (8.2.1.1)、セッション用のcookieを削除してみてください。ヘッダー部分にあるリンクは非ログイン状態のものになっているでしょうか? 確認してみましょう。
【解答】実機操作のため割愛。

2. もう一度ログインしてみて、ヘッダーのレイアウトが変わったことを確認してみましょう。その後、ブラウザを再起動させ、再び非ログイン状態に戻ったことも確認してみてください。注意: もしブラウザの [閉じたときの状態に戻す] 機能をオンにしていると、セッション情報も復元される可能性があります。もしその機能をオンにしている場合、忘れずにオフにしておきましょう (コラム 1.1)。
【解答】実機操作のため割愛。

8.2.4 レイアウトの変更をテストする<演習>
1. 試しにSessionヘルパーのlogged_in?メソッドから!を削除してみて、リスト 8.23が redになることを確認してみましょう。
【解答】以下の通り。

### app/helpers/sessions_helper.rb
module SessionsHelper

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    current_user.nil?
  end
end
$ rails test test/integration/users_login_test.rb
Failure:
Expected exactly 0 elements matching "a[href="/login"]", found 1..
Expected: 0
  Actual: 1

2. 先ほど削除した部分 (!) を元に戻して、テストが greenに戻ることを確認してみましょう。
【解答】以下の通り。

### app/helpers/sessions_helper.rb 
module SessionsHelper

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end
end
$ rails test test/integration/users_login_test.rb
Finished in 0.537099s, 3.7237 runs/s, 18.6185 assertions/s.
2 runs, 10 assertions, 0 failures, 0 errors, 0 skips
$

8.2.5 ユーザー登録時にログイン<演習>
1. リスト 8.25のlog_inの行をコメントアウトすると、テストスイートは red になるでしょうか? それとも green になるでしょうか? 確認してみましょう。
【解答】以下の通り。ユーザー作成後にログインしていない状態となるため、テストではREDになる。

### app/controllers/users_controller.rb
class UsersController < ApplicationController

  def create
    @user = User.new(user_params)
    if @user.save
      # log_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end

end
$ rails test
Failure:
Expected false to be truthy.

2. 現在使っているテキストエディタの機能を使って、リスト 8.25をまとめてコメントアウトできないか調べてみましょう。また、コメントアウトの前後でテストスイートを実行し、コメントアウトすると red に、コメントアウトを元に戻すと green になることを確認してみましょう。ヒント: コメントアウト後にファイルを保存することを忘れないようにしましょう。また、テキストエディタコメントアウト機能については Test Editor Tutorial の Commenting Out (英語) などを参照してみてください。
【解答】実機操作のため割愛。(参考)私が使用してるテキストエディタVimですが、コメントアウトは、Ctl-v → G → I → "#"を入力 → esc で可能。

8.3.4 失敗時のテスト<演習>
1. ブラウザから [Log out] リンクをクリックし、どんな変化が起こるか確認してみましょう。また、リスト 8.31で定義した3つのステップを実行してみて、うまく動いているかどうか確認してみましょう。
【解答】実機操作のため割愛。

2. cookiesの内容を調べてみて、ログアウト後にはsessionが正常に削除されていることを確認してみましょう。
【解答】実機操作のため割愛。

【Ruby on Railsチュートリアル(第4版)】第7章 ユーザー登録(演習と解答)

はじめに

このブログ記事は、私(Kodak)自身のRailsの勉強記録として書いています。
Ruby on Railsチュートリアル』の演習と解答をもくもくと書いているだけの記事なので、興味の無い方は軽くスルーしてあげてください。他にも、まだ『Railsチュートリアル』の演習に挑戦していない方、これから『Railsチュートリアル』をやるぞ!という方は(演習の解答の)ネタバレになりますので、スルーしてください。

Ruby on Rails チュートリアルとは?

Ruby業界でRailsを使い始めるなら、まず最初に初めるRails入門サイトです。
電子書籍版は有料ですが、Webサイトにあるオンライン版は無料なので、誰でも読む(Railsにチャレンジする)事ができます。
railstutorial.jp
全部で14章あり、かなりのボリュームですが、これを読む事でRailsの基礎を学ぶ事ができ、ちょっとしたWebアプリケーションを作れるレベルにはなれる?とのこと。
各章毎に演習問題が複数あり、これを解いていく事でRailsへの理解を深めていく事ができる様になっています。

環境について

Ruby 2.5.0-dev
Rails 5.1.4
・バージョン管理ツール:GitHub(https://github.com/Kodak4400)

演習と解答

7.1.1 デバックとRails環境<演習>
1. ブラウザから /about にアクセスし、デバッグ情報が表示されていることを確認してください。このページを表示するとき、どのコントローラとアクションが使われていたでしょうか? paramsの内容から確認してみましょう。
【解答】以下の通り。

 controller: static_pages
 action: about

2. Railsコンソールを開き、データベースから最初のユーザー情報を取得し、変数userに格納してください。その後、puts user.attributes.to_yamlを実行すると何が表示されますか? ここで表示された結果と、yメソッドを使ったy user.attributesの実行結果を比較してみましょう。
【解答】以下の通り。

$ rails console --sandbox
Loading development environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
irb(main):001:0> user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2018-08-05 15:04:19", updated_at: "2018-08-05 15:04:19", password_digest: "$2a$10$RYgKGzk99xGmlvFo5SG6eeGYPmTug89rApFF0i.hq/v...">
irb(main):002:0> puts user.attributes.to_yaml
---
id: 1
name: Michael Hartl
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2018-08-05 15:04:19.200613000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2018-08-05 15:04:19.200613000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$RYgKGzk99xGmlvFo5SG6eeGYPmTug89rApFF0i.hq/vlxdh2d5ooG"
=> nil
irb(main):003:0> y user.attributes
---
id: 1
name: Michael Hartl
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2018-08-05 15:04:19.200613000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2018-08-05 15:04:19.200613000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$RYgKGzk99xGmlvFo5SG6eeGYPmTug89rApFF0i.hq/vlxdh2d5ooG"
=> nil
irb(main):004:0>

7.1.2 Usersリソース<演習>
1. 埋め込みRubyを使って、マジックカラム (created_atとupdated_at) の値をshowページに表示してみましょう (リスト 7.4)。
【解答】以下の通り。

### app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %>, <%= @user.created_at %>, <%= @user.updated_at %>

2. 埋め込みRubyを使って、Time.nowの結果をshowページに表示してみましょう。ページを更新すると、その結果はどう変わっていますか? 確認してみてください。
【解答】以下の通り。

### app/views/users/show.html.erb
<%= Time.now => |
<%= @user.name %>, <%= @user.email %>, <%= @user.created_at %>, <%= @user.updated_at %>

7.1.3 debuggerメソッド<演習>
1. showアクションの中にdebuggerを差し込み (リスト 7.6)、ブラウザから /users/1 にアクセスしてみましょう。その後コンソールに移り、putsメソッドを使ってparamsハッシュの中身をYAML形式で表示してみましょう。ヒント: 7.1.1.1の演習を参考にしてください。その演習ではdebugメソッドで表示したデバッグ情報を、どのようにしてYAML形式で表示していたでしょうか?
【解答】以下の通り。

(byebug) puts @user.attributes.to_yaml
---
id: 1
name: Example User
email: example@railstutorial.org
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2018-08-05 15:04:19.200613000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2018-08-06 15:37:45.025375000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$72.Qvt0xhg4Ceay1EGQ.weaTRki26bZanGeBqxNRSxim1KGbqGBNu"
nil
(byebug)

2. newアクションの中にdebuggerを差し込み、/users/new にアクセスしてみましょう。@userの内容はどのようになっているでしょうか? 確認してみてください。
【解答】以下の通り。

(byebug) @user
nil
(byebug)

7.1.4 Gravatar画像とサイドバー<演習>
1. (任意) Gravatar上にアカウントを作成し、あなたのメールアドレスと適当な画像を紐付けてみてください。メールアドレスをMD5ハッシュ化して、紐付けた画像がちゃんと表示されるかどうか試してみましょう。
実機操作のため割愛。

2. 7.1.4で定義したgravatar_forヘルパーをリスト 7.12のように変更して、sizeをオプション引数として受け取れるようにしてみましょう。うまく変更できると、gravatar_for user, size: 50といった呼び出し方ができるようになります。重要: この改善したヘルパーは10.3.1で実際に使います。忘れずに実装しておきましょう。
【解答】以下の通り。

### app/helpers/users_helper.rb
module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user, options = { size: 80 })
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    size = options[:size]
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end
### app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <h1>
        <%= gravatar_for @user, size: 50 %>
        <%= @user.name %>
      </h1>
    </section>
  </aside>
</div>

3. オプション引数は今でもRubyコミュニティで一般的に使われていますが、Ruby 2.0から導入された新機能「キーワード引数 (Keyword Arguments)」でも実現することができます。先ほど変更したリスト 7.12を、リスト 7.13のように置き換えてもうまく動くことを確認してみましょう。この2つの実装方法はどういった違いがあるのでしょうか? 考えてみてください。
【解答】受け取り側でハッシュ値を取り出し、再設定する必要がない。

### app/helpers/users_helper.rb
module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user, size: 80 )
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end

7.2.1 form_forを利用する<演習>
1. 試しに、リスト 7.15にある:nameを:nomeに置き換えてみましょう。どんなエラーメッセージが表示されるようになりますか?
【解答】以下の通り。

ActionView::Template::Error (undefined method `nome' for #<User:0x00007f90fe06b828>
Did you mean?  name):
     5:   <div class="col-md-6 col-md-offset-3">
     6:     <%= form_for(@user) do |f| %>
     7:       <%= f.label :nome %>
     8:       <%= f.text_field :nome %>
     9:
    10:       <%= f.label :email %>
    11:       <%= f.email_field :email %>

app/views/users/new.html.erb:8:in `block in _app_views_users_new_html_erb___3366810708409065086_70130356933080'
app/views/users/new.html.erb:6:in `_app_views_users_new_html_erb___3366810708409065086_70130356933080'

2. 試しに、ブロックの変数fをすべてfoobarに置き換えてみて、結果が変わらないことを確認してみてください。確かに結果は変わりませんが、変数名をfoobarとするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。
【解答】form_forで使用する変数名として、fであれば変数名から「一時的に使用するブロック変数であること」「form_forの接頭辞を使用していること」が連想でる。しかし、foobarだと変数名からこれらが連想できないため、あまり良い変更ではない。

### app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |foobar| %>
      <%= foobar.label :name %>
      <%= foobar.text_field :name %>

      <%= foobar.label :email %>
      <%= foobar.email_field :email %>

      <%= foobar.label :password %>
      <%= foobar.password_field :password %>

      <%= foobar.label :password_confirmation, "Confirmation" %>
      <%= foobar.password_field :password_confirmation %>

      <%= foobar.submit "Create my account", class:"btn btn-primary" %>
    <% end %>
  </div>
</div>

7.2.2 フォームHTML<演習>
1. Learn Enough HTML to Be DangerousではHTMLをすべて手動で書き起こしていますが、なぜformタグを使わなかったのでしょうか? 理由を考えてみてください。
【解答】入力フォーム作成時に、formタグを使用する。Learn Enough HTMLには、入力フォームが無いため、HTMLをすべて手動で書き起こしている。

7.3.2 Strong Parameters<演習>
1. /signup?admin=1 にアクセスし、paramsの中にadmin属性が含まれていることをデバッグ情報から確認してみましょう。
【解答】以下の通り。

parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  admin: '1'
  controller: users
  action: new

7.3.3 エラーメッセージ<演習>
1. 最小文字数を5に変更すると、エラーメッセージも自動的に更新されることを確かめてみましょう。
【解答】以下の通り。

### app/models/user.rb
class User < ApplicationRecord
  before_save { email.downcase! }
  validates :name, presence: true, length:{ maximum: 50 }
  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: 5 }
end

f:id:special-moucom:20180813104317p:plain

2. 未送信のユーザー登録フォーム (図 7.12) のURLと、送信済みのユーザー登録フォーム (図 7.18) のURLを比べてみましょう。なぜURLは違っているのでしょうか? 考えてみてください。
【解答】signupパスからコントローラアクションusers#createが呼ばれて、usersパスに遷移したため。
未送信のユーザー登録フォームのURL: http://localhost:3000/signup
送信済みのユーザー登録フォームのURL: http://localhost:3000/users

7.3.4 失敗時のテスト<演習>
1. リスト 7.20で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.25にテンプレートを用意しておいたので、参考にしてください。
【解答】以下の通り。

### test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path,params:{ user:{ name: "",
                                      email: "user@invalid",
                                      password: "foo",
                                      password_confirmation: "bar" }}

    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.alert-danger'
  end
end

2. 未送信のユーザー登録フォームと送信直後のURLは、それぞれ /signup と /users になり、URLが異なっています。これは、リスト 5.43で追加した名前付きルートと、デフォルトのRESTfulなルーティング (リスト 7.3) を設定したことによって生じた差異です。リスト 7.26とリスト 7.27の内容を追加し、この問題を解決してみてください。うまくいけば、いずれのURLも /signup となるはずです。あれ、でもテストは greenのままになっていますね...、なぜでしょうか? (考えてみてください)
【解答】以下の通り。

### config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
  get '/help', to:'static_pages#help'
  get '/about', to:'static_pages#about'
  get '/contact', to:'static_pages#contact'
  get '/signup', to:'users#new'
  post '/signup', to:'users#create'
  resources :users
end
### app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user, url:signup_path) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class:'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class:'form-control' %>

      <%= 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 "Create my account", class:"btn btn-primary" %>
    <% end %>
  </div>
</div>
### signupパスにコントローラアクション:users#createが追加された。
### 加えて、formの呼び出し先のurlをsignupの上記コントローラアクションに変更しているため、urlがsignupから変わらなくなったもの。
$ rails routes
   Prefix Verb   URI Pattern               Controller#Action
     root GET    /                         static_pages#home
     help GET    /help(.:format)           static_pages#help
    about GET    /about(.:format)          static_pages#about
  contact GET    /contact(.:format)        static_pages#contact
   signup GET    /signup(.:format)         users#new
          POST   /signup(.:format)         users#create
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy
$

3. リスト 7.25のpost部分を変更して、先ほどの演習課題で作った新しいURL (/signup) に合わせてみましょう。また、テストが greenのままになっている点も確認してください。
【解答】以下の通り。

### test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post signup_path,params:{ user:{ name: "",
                                      email: "user@invalid",
                                      password: "foo",
                                      password_confirmation: "bar" }}

    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.alert-danger'
  end
end
$ rails test
Finished in 0.570018s, 33.3323 runs/s, 71.9276 assertions/s.
19 runs, 41 assertions, 0 failures, 0 errors, 0 skips

4. リスト 7.27のフォームを以前の状態 (リスト 7.20) に戻してみて、テストがやはり greenになっていることを確認してください。これは問題です! なぜなら、現在postが送信されているURLは正しくないのですから。assert_selectを使ったテストをリスト 7.25に追加し、このバグを検知できるようにしてみましょう (テストを追加して redになれば成功です)。その後、変更後のフォーム (リスト 7.27) に戻してみて、テストが green になることを確認してみましょう。ヒント: フォームから送信してテストするのではなく、’form[action="/signup"]’という部分が存在するかどうかに着目してテストしてみましょう。
【解答】以下の通り。

### app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class:'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class:'form-control' %>

      <%= 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 "Create my account", class:"btn btn-primary" %>
    <% end %>
  </div>
</div>
$ rails test
Finished in 0.598132s, 31.7656 runs/s, 68.5467 assertions/s.
19 runs, 41 assertions, 0 failures, 0 errors, 0 skips
### test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post signup_path,params:{ user:{ name: "",
                                      email: "user@invalid",
                                      password: "foo",
                                      password_confirmation: "bar" }}

    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.alert-danger'
    assert_select "form[action=?]", signup_path
  end
end
$ rails test
Failure:
Expected at least 1 element matching "form[action="/signup"]", found 0..
Expected 0 to be >= 1.
### app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user, url:signup_path) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class:'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class:'form-control' %>

      <%= 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 "Create my account", class:"btn btn-primary" %>
    <% end %>
  </div>
</div>
$ rails test
Finished in 0.780902s, 24.3308 runs/s, 53.7840 assertions/s.
19 runs, 42 assertions, 0 failures, 0 errors, 0 skips

7.4.1 登録フォーム完成<演習>
1. 有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。
実機操作のため割愛。

2. リスト 7.28を更新し、redirect_to user_url(@user)とredirect_to @userが同じ結果になることを確認してみましょう。
【解答】以下の通り。

### app/controllers/users_controller.rb
class UsersController < ApplicationController

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params
     params.require(:user).permit(:name, :email, :password,
                                  :password_confirmation)
    end
end
### app/controllers/users_controller.rb
class UsersController < ApplicationController

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to user_url(@user)
    else
      render 'new'
    end
  end

  private

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

7.4.2 flash<演習>
1. コンソールに移り、文字列内の式展開 (4.2.2) でシンボルを呼び出してみましょう。例えば"#{:success}"といったコードを実行すると、どんな値が返ってきますか? 確認してみてください。
【解答】以下の通り。

irb(main):004:0> "#{:success}"
=> "success"
irb(main):005:0>

2. 先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。
【解答】classは、"alert alert-success"となり、ハッシュ値のmessageが表示される。

7.4.3 実際のユーザー登録<演習>
1. Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。
【解答】以下の通り。

$ rails console
Loading development environment (Rails 5.1.4)
irb(main):001:0> User.find_by(email: "example@railstutorial.org")
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "example@railstutorial.org"], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-08-12 15:23:47", updated_at: "2018-08-12 15:23:47", password_digest: "$2a$10$j8YTAFtYA/b8uo0q0gqNX.9Zox3VCDNwhYkjMd6ET87...">
irb(main):002:0>

2. 自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。
実機操作のため割愛。

7.4.3 実際のユーザー登録<演習>
1. 7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.34に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。ちなみに、テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです。
【解答】以下の通り。

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "valid signup information" do
    get signup_path
    assert_difference 'User.count',1 do
      post users_path,params:{ user:{ name: "Example User",
                                      email: "user@example.com",
                                      password: "password",
                                      password_confirmation: "password" }}

    end
    follow_redirect!
    assert_template 'users/show'
    assert_not flash.nil?
  end
end

2. 本文中でも指摘しましたが、flash用のHTML (リスト 7.31) は読みにくいです。より読みやすくしたリスト 7.35のコードに変更してみましょう。変更が終わったらテストスイートを実行し、正常に動作することを確認してください。なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。
【解答】以下の通り。

<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= render 'layouts/rails_default' %>
    <%= render 'layouts/shim' %>
</head>
<body>
  <%= render 'layouts/header' %>
  <div class="container">
   <% flash.each do |message_type, message| %>
     <%= content_tag(:div, message, class:"alert alert-#{message_type}") %>
   <% end %>
   <%= yield %>
   <%= render 'layouts/footer' %>
   <%= debug(params) if Rails.env.development? %>
  </div>
</body>
</html>
$ rails test
Finished in 0.638803s, 31.3086 runs/s, 70.4443 assertions/s.
20 runs, 45 assertions, 0 failures, 0 errors, 0 skips

3. リスト 7.28のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。
【解答】以下の通り。

require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "valid signup information" do
    get signup_path
    assert_difference 'User.count',1 do
      post users_path,params:{ user:{ name: "Example User",
                                      email: "user@example.com",
                                      password: "password",
                                      password_confirmation: "password" }}

    end
    # follow_redirect!
    assert_template 'users/show'
    assert_not flash.nil?
  end
end
$ rails test
Failure:
expecting <"users/show"> but rendering with <[]>

4. リスト 7.28で、@user.saveの部分をfalseに置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。
【解答】DBへの保存が失敗するため、assert_difference 'User.count'でエラーとなる。

$ rails test
Failure:
Expected at least 1 element matching "div#error_explanation", found 0..
Expected 0 to be >= 1.

7.5.3 本番環境へのデプロイ<演習>
1. ブラウザから本番環境 (Heroku) にアクセスし、SSLの鍵マークがかかっているか、URLがhttpsになっているかどうかを確認してみましょう。
実機操作のため割愛。

2. 本番環境でユーザーを作成してみましょう。Gravatarの画像は正しく表示されているでしょうか?
実機操作のため割愛。

【Ruby on Railsチュートリアル(第4版)】第6章 ユーザーのモデルを作成する(演習と解答)

はじめに

このブログ記事は、私(Kodak)自身のRailsの勉強記録として書いています。
Ruby on Railsチュートリアル』の演習と解答をもくもくと書いているだけの記事なので、興味の無い方は軽くスルーしてあげてください。他にも、まだ『Railsチュートリアル』の演習に挑戦していない方、これから『Railsチュートリアル』をやるぞ!という方は(演習の解答の)ネタバレになりますので、スルーしてください。

Ruby on Rails チュートリアルとは?

Ruby業界でRailsを使い始めるなら、まず最初に初めるRails入門サイトです。
電子書籍版は有料ですが、Webサイトにあるオンライン版は無料なので、誰でも読む(Railsにチャレンジする)事ができます。
railstutorial.jp
全部で14章あり、かなりのボリュームですが、これを読む事でRailsの基礎を学ぶ事ができ、ちょっとしたWebアプリケーションを作れるレベルにはなれる?とのこと。
各章毎に演習問題が複数あり、これを解いていく事でRailsへの理解を深めていく事ができる様になっています。

環境について

Ruby 2.5.0-dev
Rails 5.1.4
・バージョン管理ツール:GitHub(https://github.com/Kodak4400)

演習と解答

6.1.1 データベースの移行<演習>
1. Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはデータベースの構造 (スキーマ (schema) と呼びます) を追跡するために使われます。さて、あなたの環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル (リスト 6.2) の内容を比べてみてください。
【解答】以下の通り。

$ ls -l db/schema.rb
-rw-r--r--  1 Kodak  staff  961  7 21 18:20 db/schema.rb
$ cat db/schema.rb
ActiveRecord::Schema.define(version: 20180721091744) do

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.string "email"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end
$

2. ほぼすべてのマイグレーションは、元に戻すことが可能です (少なくとも本チュートリアルにおいてはすべてのマイグレーションを元に戻すことができます)。元に戻すことを「ロールバック (rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。 $ rails db:rollback 上のコマンドを実行後、db/schema.rbの内容を調べてみて、ロールバックが成功したかどうか確認してみてください (コラム 3.1ではマイグレーションに関する他のテクニックもまとめているので、参考にしてみてください)。上のコマンドでは、データベースからusersテーブルを削除するためにdrop_tableコマンドを内部で呼び出しています。これがうまくいくのは、drop_tableとcreate_tableがそれぞれ対応していることをchangeメソッドが知っているからです。この対応関係を知っているため、ロールバック用の逆方向のマイグレーションを簡単に実現することができるのです。なお、あるカラムを削除するような不可逆なマイグレーションの場合は、changeメソッドの代わりに、upとdownのメソッドを別々に定義する必要があります。詳細については、Railsガイドの「Active Record マイグレーション」を参照してください。
【解答】以下の通り。

$ rails db:migrate
== 20180721091744 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0008s
== 20180721091744 CreateUsers: migrated (0.0009s) =============================
$ rails db:rollback
== 20180721091744 CreateUsers: reverting ======================================
-- drop_table(:users)
   -> 0.0008s
== 20180721091744 CreateUsers: reverted (0.0051s) =============================

$
$ ls -l db/schema.rb
-rw-r--r--  1 Kodak  staff  771  7 21 18:30 db/schema.rb
$ cat db/schema.rb
ActiveRecord::Schema.define(version: 0) do

end
$

3. もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。
【解答】以下の通り。

$ rails db:migrate
== 20180721091744 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0011s
== 20180721091744 CreateUsers: migrated (0.0012s) =============================

$ ls -l db/schema.rb
-rw-r--r--  1 Kodak  staff  961  7 21 18:31 db/schema.rb
$ cat db/schema.rb
ActiveRecord::Schema.define(version: 20180721091744) do

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.string "email"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end
$

6.1.2 modelファイル<演習>
1. Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください (ヒント: 4.4.4で紹介したテクニックを使ってみてください)。
【解答】以下の通り。

irb(main):001:0> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):002:0> user.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
irb(main):003:0> user.class.superclass
=> ApplicationRecord(abstract)

2. 同様にして、ApplicationRecordがActiveRecord::Baseを継承していることについて確認してみてください。
【解答】以下の通り。

irb(main):003:0> user.class.superclass
=> ApplicationRecord(abstract)
irb(main):004:0> user.class.superclass.superclass
=> ActiveRecord::Base
irb(main):005:0> user.class.superclass.superclass.superclass

6.1.3 ユーザーオブジェクトを作成する<演習>
1. user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。
【解答】以下の通り。

irb(main):022:0> user.name.class
=> String
irb(main):023:0> user.email.class
=> String
irb(main):024:0>

2. created_atとupdated_atは、どのクラスのインスタンスでしょうか?
【解答】以下の通り。

irb(main):024:0> user.created_at.class
=> ActiveSupport::TimeWithZone
irb(main):025:0> user.updated_at.class
=> ActiveSupport::TimeWithZone
irb(main):026:0>

6.1.4 ユーザーオブジェクトを検索する<演習>
1. nameを使ってユーザーオブジェクトを検索してみてください。また、 find_by_nameメソッドが使えることも確認してみてください (古いRailsアプリケーションでは、古いタイプのfind_byをよく見かけることでしょう)。
【解答】以下の通り。

irb(main):039:0> User.find_by(name:"Mishael Hartl")
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "Mishael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Mishael Hartl", email: "mhartl@example.com", created_at: "2018-07-21 11:57:36", updated_at: "2018-07-21 11:57:36">
irb(main):040:0> User.find_by_name("Mishael Hartl")
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "Mishael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Mishael Hartl", email: "mhartl@example.com", created_at: "2018-07-21 11:57:36", updated_at: "2018-07-21 11:57:36">
irb(main):041:0>

2. 実用的な目的のため、User.allはまるで配列のように扱うことができますが、実際には配列ではありません。User.allで生成されるオブジェクトを調べ、ArrayクラスではなくUser::ActiveRecord_Relationクラスであることを確認してみてください。
【解答】以下の通り。

irb(main):041:0> User.all.class
=> User::ActiveRecord_Relation
irb(main):042:0>

3. User.allに対してlengthメソッドを呼び出すと、その長さを求められることを確認してみてください (4.2.3)。Rubyの性質として、そのクラスを詳しく知らなくてもなんとなくオブジェクトをどう扱えば良いかわかる、という性質があります。これをダックタイピング (duck typing) と呼び、よく次のような格言で言い表されています「もしアヒルのような容姿で、アヒルのように鳴くのであれば、それはもうアヒルだろう」。(訳注: そういえばRubyKaigi 2016の基調講演で、Ruby作者のMatzがダックタイピングについて説明していました。2〜3分の短くて分かりやすい説明なので、ぜひ視聴してみてください!)
【解答】以下の通り。

irb(main):042:0> User.all.length
  User Load (0.1ms)  SELECT "users".* FROM "users"
=> 2
irb(main):043:0>

6.1.5 ユーザーオブジェクトを更新する<演習>
1. userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。
【解答】以下の通り。

irb(main):070:0> user
=> #<User id: 1, name: "El Duderino", email: "dude@abides.org", created_at: "2018-07-21 11:57:36", updated_at: "2018-07-21 12:21:52">
irb(main):071:0> user.name = "Kodak"
=> "Kodak"
irb(main):072:0> user.save
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.1ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Kodak"], ["updated_at", "2018-07-21 12:25:47.913562"], ["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
irb(main):073:0> user
=> #<User id: 1, name: "Kodak", email: "dude@abides.org", created_at: "2018-07-21 11:57:36", updated_at: "2018-07-21 12:25:47">
irb(main):074:0>

2. 今度はupdate_attributesを使って、email属性を更新および保存してみてください。
【解答】以下の通り。

irb(main):074:0> user
=> #<User id: 1, name: "Kodak", email: "dude@abides.org", created_at: "2018-07-21 11:57:36", updated_at: "2018-07-21 12:25:47">
irb(main):075:0> user.update_attribute(:email, "Kodak@example.com")
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.1ms)  UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["email", "Kodak@example.com"], ["updated_at", "2018-07-21 12:27:12.787655"], ["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
irb(main):076:0> user.save
   (0.1ms)  SAVEPOINT active_record_1
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
irb(main):077:0> user
=> #<User id: 1, name: "Kodak", email: "Kodak@example.com", created_at: "2018-07-21 11:57:36", updated_at: "2018-07-21 12:27:12">
irb(main):078:0>

3. 同様にして、マジックカラムであるcreated_atも直接更新できることを確認してみてください。ヒント: 更新するときは「1.year.ago」を使うと便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の時間を算出してくれます。
【解答】以下の通り。

irb(main):079:0> user
=> #<User id: 1, name: "Kodak", email: "Kodak@example.com", created_at: "2018-07-21 11:57:36", updated_at: "2018-07-21 12:27:12">
irb(main):080:0> 1.year.ago
=> Fri, 21 Jul 2017 12:30:16 UTC +00:00
irb(main):081:0> user.update_attribute(:updated_at, 1.year.ago)
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.1ms)  UPDATE "users" SET "updated_at" = ? WHERE "users"."id" = ?  [["updated_at", "2017-07-21 12:30:54.388296"], ["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
irb(main):082:0> user.save
   (0.1ms)  SAVEPOINT active_record_1
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
irb(main):083:0> user
=> #<User id: 1, name: "Kodak", email: "Kodak@example.com", created_at: "2018-07-21 11:57:36", updated_at: "2017-07-21 12:30:54">
irb(main):084:0>

6.2.1 有効性を検証する<演習>
1. コンソールから、新しく生成したuserオブジェクトが有効 (valid) であることを確認してみましょう。
【解答】以下の通り。

$ rails console --sandbox
Loading test environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
irb(main):001:0> user = User.new(name: "Example User", email: "user@example.com")
=> #<User id: nil, name: "Example User", email: "user@example.com", created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
=> true
irb(main):003:0>

2. 6.1.3で生成したuserオブジェクトも有効であるかどうか、確認してみましょう。
【解答】以下の通り。

irb(main):006:0> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com", created_at: nil, updated_at: nil>
irb(main):007:0> user.valid?
=> true
irb(main):008:0>

6.2.2 存在性を検証する<演習>
1. 新しいユーザーuを作成し、作成した時点では有効ではない (invalid) ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。
【解答】以下の通り。

$ rails console --sandbox
Loading test environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
irb(main):001:0> u = User.new(name:"", email:"")
=> #<User id: nil, name: "", email: "", created_at: nil, updated_at: nil>
irb(main):002:0> u.valid?
=> false
irb(main):003:0> u.errors.full_messages
=> ["Name can't be blank", "Email can't be blank"]
irb(main):004:0>

2. u.errors.messagesを実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?
【解答】以下の通り。

irb(main):023:0> u.errors.messages
=> {:name=>["can't be blank"], :email=>["can't be blank"]}
irb(main):024:0> u.errors.messages[:email]
=> ["can't be blank"]
irb(main):025:0>

6.2.3 長さを検証する<演習> 1. 長すぎるnameとemail属性を持ったuserオブジェクトを生成し、有効でないことを確認してみましょう。
【解答】以下の通り。

irb(main):001:0> user = User.new(name:"a"*51, email:"a"*244 + "@example.com")
=> #<User id: nil, name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", email: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
=> false
irb(main):003:0>

2. 長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。
【解答】以下の通り。

irb(main):003:0> user.errors.messages
=> {:name=>["is too long (maximum is 50 characters)"], :email=>["is too long (maximum is 255 characters)"]}
irb(main):004:0> user.errors.full_messages
=> ["Name is too long (maximum is 50 characters)", "Email is too long (maximum is 255 characters)"]
irb(main):005:0>

6.2.4 フォーマットを検証する<演習>
1. リスト 6.18にある有効なメールアドレスのリストと、リスト 6.19にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、リスト 6.21の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
【解答】以下の通り。
f:id:special-moucom:20180804222240p:plain

2. 先ほど触れたように、リスト 6.21のメールアドレスチェックする正規表現は、foo@bar..comのようにドットが連続した無効なメールアドレスを許容してしまいます。まずは、このメールアドレスをリスト 6.19の無効なメールアドレスリストに追加し、これによってテストが失敗することを確認してください。次に、リスト 6.23で示した、少し複雑な正規表現を使ってこのテストがパスすることを確認してください。 【解答】以下の通り。

#### test/models/user_test.rb  
 
   test "email validation should reject invalid addresses" do
     valid_addresses = %w[user@example,com user_at_foo.org user.name@example.
                                foo@bar_baz.com foo@bar+baz.com foo@bar..com]
     valid_addresses.each do |valid_address|
       @user.email = valid_address
       assert_not @user.valid?, "#{valid_address.inspect} should be invalid"
     end
   end
$ rails test:models
Failure:
"foo@bar..com" should be invalid
class User < ApplicationRecord
  validates :name, presence: true, length:{ maximum: 50 }
  # VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  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 }
end
$ rails test:models
Finished in 0.042992s, 162.8210 runs/s, 372.1623 assertions/s.
7 runs, 16 assertions, 0 failures, 0 errors, 0 skips

3. foo@bar..comをRubularのメールアドレスのリストに追加し、リスト 6.23の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
【解答】以下の通り。
f:id:special-moucom:20180804222250p:plain

6.2.5 一意性を検証する<演習>
1. リスト 6.33を参考に、メールアドレスを小文字にするテストをリスト 6.32に追加してみましょう。ちなみに追加するテストコードでは、データベースの値に合わせて更新するreloadメソッドと、値が一致しているかどうか確認するassert_equalメソッドを使っています。リスト 6.33のテストがうまく動いているか確認するためにも、before_saveの行をコメントアウトして redになることを、また、コメントアウトを解除すると greenになることを確認してみましょう。
【解答】以下の通り。

### app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name, presence: true, length:{ maximum: 50 }
  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 }
end
### test/models/user_test.rb
  test "email addresses should be saved as lower-case" do
    mixed_case_email = "Foo@ExAPle.CoM"
    @user.email = mixed_case_email
    @user.save
    assert_equal mixed_case_email.downcase, @user.reload.email
  end
end
$ rails t
Finished in 0.516900s, 30.9538 runs/s, 67.7114 assertions/s.
16 runs, 35 assertions, 0 failures, 0 errors, 0 skips
class User < ApplicationRecord
  # before_save { self.email = email.downcase }
  validates :name, presence: true, length:{ maximum: 50 }
  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 }
end
$ rails t
Failure:
Expected: "foo@exaple.com"
  Actual: "Foo@ExAPle.CoM"

2. テストスイートの実行結果を確認しながら、before_saveコールバックをemail.downcase!に書き換えてみましょう。ヒント: メソッドの末尾に!を付け足すことにより、email属性を直接変更できるようになります (リスト 6.34)。
【解答】以下の通り。

### app/models/user.rb
class User < ApplicationRecord
  before_save { email.downcase! }
  validates :name, presence: true, length:{ maximum: 50 }
  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 }
end
$ rails t
Finished in 0.516124s, 31.0003 runs/s, 67.8132 assertions/s.
16 runs, 35 assertions, 0 failures, 0 errors, 0 skips

6.2.5 一意性を検証する<演習>
1. リスト 6.33を参考に、メールアドレスを小文字にするテストをリスト 6.32に追加してみましょう。ちなみに追加するテストコードでは、データベースの値に合わせて更新するreloadメソッドと、値が一致しているかどうか確認するassert_equalメソッドを使っています。リスト 6.33のテストがうまく動いているか確認するためにも、before_saveの行をコメントアウトして redになることを、また、コメントアウトを解除すると greenになることを確認してみましょう。
【解答】以下の通り。

### test/models/user_test.rb
  test "email addresses should be saved as lower-case" do
    mixed_case_email = "Foo@ExAPle.CoM"
    @user.email = mixed_case_email
    @user.save
    assert_equal mixed_case_email.downcase, @user.reload.email
  end
### app/models/user.rb
class User < ApplicationRecord
  # before_save { self.email = email.downcase }
  validates :name, presence: true, length:{ maximum: 50 }
  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 }
end
$ rails t
Failure:
Expected: "foo@exaple.com"
  Actual: "Foo@ExAPle.CoM"

2. テストスイートの実行結果を確認しながら、before_saveコールバックをemail.downcase!に書き換えてみましょう。ヒント: メソッドの末尾に!を付け足すことにより、email属性を直接変更できるようになります (リスト 6.34)。
【解答】以下の通り。

### app/models/user.rb
class User < ApplicationRecord
  before_save { email.downcase! }
  validates :name, presence: true, length:{ maximum: 50 }
  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 }
end
$ rails t
Finished in 0.631721s, 28.4936 runs/s, 58.5702 assertions/s.
18 runs, 37 assertions, 0 failures, 0 errors, 0 skips

6.3.2 ユーザーがセキュアなパスワードを持っている<演習>
1. この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。
【解答】以下の通り。

$ rails console --sandbox
Loading test environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
irb(main):001:0> u = User.new(name:"Kodak", email:"kodak@example.com")
=> #<User id: nil, name: "Kodak", email: "kodak@example.com", created_at: nil, updated_at: nil, password_digest: nil>
irb(main):002:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "kodak@example.com"], ["LIMIT", 1]]
=> false

2. なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください。
【解答】以下の通り。

### Passwordがブランクだから失敗する。
irb(main):003:0> u.errors.full_messages
=> ["Password can't be blank"]
irb(main):004:0>

6.3.3 パスワードの最小文字数<演習>
1. 有効な名前とメールアドレスでも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。
【解答】以下の通り。

$ rails console --sandbox
Loading test environment in sandbox (Rails 5.1.4)
Any modifications you make will be rolled back on exit
irb(main):001:0> u = User.new(name:"Kodak", email:"kodak@example.com", password:"test", password_confirmation:"test")
=> #<User id: nil, name: "Kodak", email: "kodak@example.com", created_at: nil, updated_at: nil, password_digest: "$2a$04$KRwXzohH1GsunyCijvFyJOEQYRctJ7HPl0ntTxVCCWR...">
irb(main):002:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "kodak@example.com"], ["LIMIT", 1]]
=> false

2. 上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう。
【解答】以下の通り。

irb(main):003:0> u.errors.full_messages
=> ["Password is too short (minimum is 6 characters)"]
irb(main):004:0>

6.3.4 ユーザー作成と認証<演習>
1. コンソールを一度再起動して (userオブジェクトを消去して)、このセクションで作ったuserオブジェクトを検索してみてください。
【解答】以下の通り。

$ rails console --sandbox
irb(main):001:0> user = User.find_by(email:"mhartl@example.com")
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "mhartl@example.com"], ["LIMIT", 1]]
=> #<User id: 980190963, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2018-07-29 08:38:46", updated_at: "2018-07-29 08:38:46", password_digest: "$2a$04$9hMkUoiUj89BCHD9zXO1su3v.fv572laOPFjg9i7cUY...">
irb(main):002:0> user
=> #<User id: 980190963, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2018-07-29 08:38:46", updated_at: "2018-07-29 08:38:46", password_digest: "$2a$04$9hMkUoiUj89BCHD9zXO1su3v.fv572laOPFjg9i7cUY...">
irb(main):003:0>

2. オブジェクトが検索できたら、名前を新しい文字列に置き換え、saveメソッドで更新してみてください。うまくいきませんね...、なぜうまくいかなかったのでしょうか?
【解答】以下の通り。

### save時にパスワードの保存を要求するため、検証で失敗している
irb(main):005:0> user.name = "Kodak Hartl"
=> "Kodak Hartl"
irb(main):006:0> user.save
   (0.1ms)  SAVEPOINT active_record_1
  User Exists (0.1ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "mhartl@example.com"], ["id", 980190963], ["LIMIT", 1]]
   (0.1ms)  ROLLBACK TO SAVEPOINT active_record_1
=> false
irb(main):007:0>

3. 今度は6.1.5で紹介したテクニックを使って、userの名前を更新してみてください。
【解答】以下の通り。

### update_attributeなら検証を回避できる
irb(main):009:0> user.update_attribute(:name, "Kodak Hartl")
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.4ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Kodak Hartl"], ["updated_at", "2018-07-29 09:06:05.264126"], ["id", 980190963]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
irb(main):010:0>