おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

30歳まで残り2年の僕は人生を変えるためにRailsチュートリアルを始めようと思う(第8章)

こんにちは。opiyoです。

今回は、第8章をやっていきます。

第8章は画面からユーザーを登録する方法です。

8.1.1 演習 Sessionsコントローラ

8.1.1.1

<問題>GET login_pathとPOST login_pathとの違いを説明できますか? 少し考えてみましょう。

<回答>

       login GET    /login(.:format)          sessions#new
             POST   /login(.:format)          sessions#create
  • GET login_path:'/login'にアクセスした時 → sessionコントローラー、newアクション
  • POST login_path:'/login'で、入力した値を送信した時 → sessionコントローラー、createアクション

8.1.1.2

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

<回答>

$ rails routes | grep users
      signup GET    /signup(.:format)         users#new
       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
$ 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 演習 ログインフォーム

8.1.2.1

<問題>リスト 8.4で定義したフォームで送信すると、Sessionsコントローラのcreateアクションに到達します。Railsはこれをどうやって実現しているでしょうか? 考えてみてください。ヒント:表 8.1とリスト 8.5の1行目に注目してください。

<回答>

       login GET    /login(.:format)          sessions#new
             POST   /login(.:format)          sessions#create

form_for(:session, url: login_path)のurlに指定されたプレフィックスを見て判断している。 httpメソッドが、GET or POSTかはmethodオプションで変えれるはずだけどデフォルトはpostになる。 よってSessionsコントローラのcreateアクションに到達する!

8.1.3 演習 ユーザーの検索と認証

8.1.3.1

<問題>Railsコンソールを使って、表 8.2のそれぞれの式が合っているか確かめてみましょう. まずはuser = nilの場合を、次にuser = User.firstとした場合を確かめてみてください。ヒント: 必ず論理値オブジェクトとなるように、4.2.3で紹介した!!のテクニックを使ってみましょう。例: !!(user && user.authenticate(’foobar’))

<回答>

■User:存在しない && Password:合致しない → false

> user = nil
=> nil
irb(main):002:0> user && user.authenticate("hoge")
=> nil

■User:存在する && Password:合致しない → false

> user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "haku", email: "haku@co.jp", created_at: "2017-08-17 02:41:54", updated_at: "2017-08-17 02:41:54", password_digest: "$2a$10$zcrCZj1hnh3cp.zZDmYD6OV/73uzwUzbWTqyceJZWEz...">
> !!(user && user.authenticate("hoge"))
=> false

■User:存在する && Password:合致する → true

> !!(user && user.authenticate("hakuhaku"))
=> true

8.1.5 演習 フラッシュのテスト

8.1.5.1

<問題>8.1.4の処理の流れが正しく動いているかどうか、ブラウザで確認してみてください。特に、flashがうまく機能しているかどうか、フラッシュメッセージの表示後に違うページに移動することを忘れないでください。

<回答>

  1. “/login"を開く
  2. EmailとPasswordに適当な値を入力しログインボタンを押下する
  3. flashメッセージが表示されることを確認
  4. “/"を開いた時にflashメッセージが表示されないことを確認

8.2.1 log_inメソッド

8.2.1.1

<問題>有効なユーザーで実際にログインし、ブラウザからcookiesの情報を調べてみてください。このとき、sessionの値はどうなっているでしょうか? ヒント: ブラウザでcookiesを調べる方法が分からない? 今こそググってみるときです! (コラム 1.1)

<回答> Chromeの場合はディベロッパーツールのApplicationタブから確認できます。(右クリックして「検証」すれば出てくる!) f:id:opiyotan:20170817122354p:plain

8.2.1.2

<問題>先ほどの演習課題と同様に、Expiresの値について調べてみてください。

<回答> 有効期限の意味。 上の画像で「Session」ってなってるやつは、ブラウザの再起動で無くなる。 年月日があるものは、それが有効期限になる。

8.2.2 演習 現在のユーザー

8.2.2.1

<問題>Railsコンソールを使って、User.find_by(id: …)で対応するユーザーが検索に引っかからなかったとき、nilを返すことを確認してみましょう。

<回答>

> User.find_by(id: 99)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 99], ["LIMIT", 1]]
=> nil

<問題>先ほどと同様に、今度は:user_idキーを持つsessionハッシュを作成してみましょう。リスト 8.17に記したステップにしたがって、||=演算子がうまく動くことも確認してみましょう。

<回答>

> session = {}
=> {}
irb(main):002:0> session[:user_id] = nil
=> nil
irb(main):003:0> @current_user ||= User.find_by(id: session[:user_id])
  User Load (2.9ms)  SELECT  "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ?  [["LIMIT", 1]]
=> nil
irb(main):004: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):005:0> @current_user ||= User.find_by(id: session[:user_id])
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "haku", email: "haku@co.jp", created_at: "2017-08-17 02:41:54", updated_at: "2017-08-17 02:41:54", password_digest: "$2a$10$zcrCZj1hnh3cp.zZDmYD6OV/73uzwUzbWTqyceJZWEz...">

8.2.3 演習 レイアウトリンクを変更する

8.2.3.1

<問題>ブラウザのcookieインスペクタ機能を使って (8.2.1.1)、セッション用のcookieを削除してみてください。ヘッダー部分にあるリンクは非ログイン状態のものになっているでしょうか? 確認してみましょう。

<回答> 非ログイン状態になる。

8.2.3.2

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

<回答> 非ログイン状態になる。

8.2.4 演習 レイアウトの変更をテストする

8.2.4.1

<問題>試しにSessionヘルパーのlogged_in?メソッドから!を削除してみて、リスト 8.23が redになることを確認してみましょう。

<回答>

$ rails test
FAIL["test_login_with_valid_information_followed_by_logout", UsersLoginTest, 0.940030867999667]
 test_login_with_valid_information_followed_by_logout#UsersLoginTest (0.94s)
        Expected exactly 0 elements matching "a[href="/login"]", found 1..
        Expected: 0
          Actual: 1
        test/integration/users_login_test.rb:27:in `block in <class:UsersLoginTest>'

8.2.4.2

<問題>先ほど削除した部分 (!) を元に戻して、テストが greenに戻ることを確認してみましょう。

<回答>

$ rails test
Running via Spring preloader in process 4789
Started with run options --seed 35483

  21/21: [==========================================================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.37352s
21 tests, 54 assertions, 0 failures, 0 errors, 0 skips

8.2.5 演習 レイアウトの変更をテストする

8.2.5.1

<問題>リスト 8.25のlog_inの行をコメントアウトすると、テストスイートは red になるでしょうか? それとも green になるでしょうか? 確認してみましょう。

<回答> テストスイートは redになる。

 FAIL["test_valid_signup_information", UsersSignupTest, 2.6715680599991174]
 test_valid_signup_information#UsersSignupTest (2.67s)
        Expected false to be truthy.
        test/integration/users_signup_test.rb:15:in `block in <class:UsersSignupTest>'

8.2.5.2

<問題>現在使っているテキストエディタの機能を使って、リスト 8.25をまとめてコメントアウトできないか調べてみましょう。また、コメントアウトの前後でテストスイートを実行し、コメントアウトすると red に、コメントアウトを元に戻すと green になることを確認してみましょう。ヒント: コメントアウト後にファイルを保存することを忘れないようにしましょう。また、テキストエディタのコメントアウト機能については Test Editor Tutorial の Commenting Out (英語) などを参照してみてください。

<回答> コメントアウトは、コメントアウトしたい行を全て選択して「Command + /」(エディタ:Atom)

8.3 演習 ログアウト

8.3.1

<問題>ブラウザから [Log out] リンクをクリックし、どんな変化が起こるか確認してみましょう。また、リスト 8.31で定義した3つのステップを実行してみて、うまく動いているかどうか確認してみましょう。

<回答> - トップページが表示される - dropdownメニューがLog inになる - ProfileSettingsのメニューが非表示になっている

8.3.2

<問題>cookiesの内容を調べてみて、ログアウト後にはsessionが正常に削除されていることを確認してみましょう。

<回答>

   18:   def destroy
   19:     log_out if logged_in?
   20:     byebug
=> 21:     redirect_to root_url
   22:   end

(byebug) session[:user_id]
nil

メモ

  • httpはステートレスなプロトコル。つまり状態がない
  • リクエストのひとつひとつは、その前のリクエスト情報を知らない
  • リクエストが終わると何もかも忘れて次回最初からやり直す健忘症的なプロトコル
  • ブラウザのあるページから別のページに移動したときに、ユーザーのIDを保持しておく手段がHTTPプロトコル内「には」まったくあり
  • セッション (Session) と呼ばれる半永続的な接続をコンピュータ間 (ユーザーのパソコンのWebブラウザとRailsサーバーなど) に別途設定
  • セッションを実装する方法として最も一般的なのは、cookies
  • sessionメソッドで作成した一時cookiesは自動的に暗号化され
  • user.authenticate(password)は、引数に渡された文字列 (パスワード) をハッシュ化した値と、データベース内にあるpassword_digestカラムの値を比較する
  • 「!!」(「バンバン (bang bang)」と読みます) という演算子を使うと、そのオブジェクトを2回否定することになるので、どんなオブジェクトも強制的に論理値に変換できる
> 0
=> 0
irb(main):017:0> !0
=> false
irb(main):019:0* !!0
=> true

> user && user.authenticate("hakuhaku")
=> #<User id: 1, name: "haku", email: "haku@co.jp", created_at: "2017-08-17 02:41:54", updated_at: "2017-08-17 02:41:54", password_digest: "$2a$10$zcrCZj1hnh3cp.zZDmYD6OV/73uzwUzbWTqyceJZWEz...">
irb(main):023:0> !!(user && user.authenticate("hakuhaku"))
=> true
  • flashのメッセージとは異なり、flash.nowのメッセージはその後リクエストが発生したときに消滅します
  • sessionメソッドで作成した一時cookiesは自動的に暗号化される
  • Railsのapplication.jsファイルを通して、Bootstrapに同梱されているJavaScriptライブラリを読み込むよう、アセットパイプラインに指示する
    • Rails 5.1 からjQueryもデフォルトで読み込まれなくなったので、jQueryも追加する必要があります

30歳まで残り2年の僕は人生を変えるためにRailsチュートリアルを始めようと思う(第7章)

こんにちは。opiyoです。

今回は、第7章をやっていきます。

第7章は画面からユーザーを登録する方法です。

7.1.1 演習 デバッグとRails環境

7.1.1.1

<問題> ブラウザから /about にアクセスし、デバッグ情報が表示されていることを確認してください。このページを表示するとき、どのコントローラとアクションが使われていたでしょうか? paramsの内容から確認してみましょう。

<回答>

--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: static_pages
  action: about
permitted: false

7.1.1.2

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

<回答>

> user = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-07-03 07:19:07", updated_at: "2017-07-03 07:40:01", password_digest: "$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5...">
> user.attributes.class
=> Hash
> user.attributes
=> {"id"=>1, "name"=>"Example User", "email"=>"example@railstutorial.org", "created_at"=>Mon, 03 Jul 2017 07:19:07 UTC +00:00, "updated_at"=>Mon, 03 Jul 2017 07:40:01 UTC +00:00, "password_digest"=>"$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5oPa9JV.taW"}

irb(main):002:0> puts user.attributes.to_yaml # user.attributeでHashが返ってくるので、それをyml化させてる
---
id: 1
name: Example User
email: example@railstutorial.org
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2017-07-03 07:19:07.610288000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2017-07-03 07:40:01.853612000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5oPa9JV.taW"
=> nil

irb(main):004:0* y user.attributes # yメソッドはyml形式でオブジェクトの中身を表示させる。pメソッドだと<>が表示されてエスケープする必要があるがそれがいらないから利用するなんてことがあるらしい
---
id: 1
name: Example User
email: example@railstutorial.org
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2017-07-03 07:19:07.610288000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2017-07-03 07:40:01.853612000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5oPa9JV.taW"
=> nil

出力される結果は同じになる。

7.1.2 演習 Usersリソース

7.1.2.1

<問題>埋め込みRubyを使って、マジックカラム (created_atとupdated_at) の値をshowページに表示してみましょう (リスト 7.4)。

<回答>

<%= @user.created_at %>
<%= @user.updated_at %>

7.1.2.2

<問題>埋め込みRubyを使って、Time.nowの結果をshowページに表示してみましょう。ページを更新すると、その結果はどう変わっていますか? 確認してみてください。

<回答>

<%= Time.now %>
<%= "現在の時刻は#{Time.now.hour}#{Time.now.min}#{Time.now.sec}秒です。" %> # 時分秒だけを表示することもできる

f:id:opiyotan:20170816103859p:plain

7.1.3 演習 debuggerメソッド

7.1.3.1

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

(byebug) puts params.to_yaml
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: users
  action: show
  id: '2'
permitted: false

7.1.3.2

<問題>newアクションの中にdebuggerを差し込み、/users/new にアクセスしてみましょう。@userの内容はどのようになっているでしょうか? 確認してみてください。

<回答>

(byebug) @user
#<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil>

7.1.4 演習 Gravatar画像とサイドバー

7.1.4.1

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

<回答> f:id:opiyotan:20170816110346p:plain

7.1.4.2

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

<回答>

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

7.1.4.3

<問題>オプション引数は今でもRubyコミュニティで一般的に使われていますが、Ruby 2.0から導入された新機能「キーワード引数 (Keyword Arguments)」でも実現することができます。先ほど変更したリスト 7.12を、リスト 7.13のように置き換えてもうまく動くことを確認してみましょう。この2つの実装方法はどういった違いがあるのでしょうか? 考えてみてください。

<回答>

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 演習 ユーザー登録フォーム

7.2.1.1

<問題>試しに、リスト 7.15にある:nameを:nomeに置き換えてみましょう。どんなエラーメッセージが表示されるようになりますか?

<回答>

Unknown action
The action 'index' could not be found for UsersController

7.2.1.2

<問題>試しに、ブロックの変数fをすべてfoobarに置き換えてみて、結果が変わらないことを確認してみてください。確かに結果は変わりませんが、変数名をfoobarとするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。

<回答> 結果は変わらないが、なぜダメなのか? - 変数名が長くなり見通しが悪い - formfだからこそ、意味が通じるのがfoobarだと違う意味があるように思えてしまう。混乱する。

7.2.2 演習 フォームHTML

7.2.2.1

<問題> ごめんなさい。分かりません。 教えてください!!

7.3.2 演習 Strong Parameters

7.3.2.1

<問題>/users/new?admin=1 にアクセスし、paramsの中にadmin属性が含まれていることをデバッグ情報から確認してみましょう。

<回答>

--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  admin: '1'
  controller: users
  action: new
permitted: false

7.3.3 演習 エラーメッセージ

7.3.3.1

<問題>最小文字数を5に変更すると、エラーメッセージも自動的に更新されることを確かめてみましょう。

<回答> f:id:opiyotan:20170816124511p:plain

7.3.3.2

<問題>未送信のユーザー登録フォーム (図 7.12) のURLと、送信済みのユーザー登録フォーム (図 7.18) のURLを比べてみましょう。なぜURLは違っているのでしょうか? 考えてみてください。

<回答> - 未送信:/signup - 送信済:/users 正しく保存できなかった場合は、render ‘new'する処理となっているため。

7.3.4 演習 失敗時のテスト

7.3.4.1

<問題>リスト 7.20で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.25にテンプレートを用意しておいたので、参考にしてください。

<回答>

# test/integration/users_signup_test.rb
  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

7.3.4.2

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

<回答>

Rails.application.routes.draw do
  root 'static_pages#home'
(略)
  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| %> # ここを修正・・・明示的にurlを指定する(users#create)
(略)
      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>
# rails routes
   signup GET    /signup(.:format)         users#new
          POST   /signup(.:format)         users#create
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create

見て分かるように、変更前はuserspostアクションを利用してusers#createの処理となっていた。 変更後は、signuppostアクションをしたusers#createの処理となっている。 アクション先がどちらの書き方でも変わらないため、テストを修正しなくても成功する。

7.3.4.3

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

<回答>

  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

7.3.4.4

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

<回答>

# test/integration/users_signup_test.rb
  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 "form[action=?]", "/signup" # ここを追加
    assert_select 'div#error_explanation'
    assert_select 'div.alert-danger'
  end

7.4.1 演習 登録フォームの完成

7.4.1.1

<問題>有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。

<回答>

> User.last
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 5, name: "toma", email: "toma@co.jp", created_at: "2017-08-16 04:51:43", updated_at: "2017-08-16 04:51:43", password_digest: "$2a$10$3NSYCM0YE340GHAaaEJXlOChlsFABFemcj6CiT4yd9V...">

<問題>リスト 7.28を更新し、redirect_to user_url(@user)とredirect_to @userが同じ結果になることを確認してみましょう。

<回答>

# redirect_to @userの時
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: users
  action: show
  id: '5'
permitted: false
# redirect_to user_url(@user)の時
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: users
  action: show
  id: '6'
permitted: false

コントローラー、アクションが変わらない事を確認しました。

7.4.2 演習 flash

7.4.2.1

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

<回答>

> "#{:success}"
=> "success"

<問題>先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。

<回答>

> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}
> "#{flash[:success]}"
=> "It worked!"
irb(main):012:0> "#{flash[:danger]}"
=> "It failed."

7.4.3 演習 実際のユーザー登録

7.4.3.1

<問題>Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。

<回答>

> User.last
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 3, name: "toma", email: "toma@co.jp", created_at: "2017-08-16 08:19:23", updated_at: "2017-08-16 08:19:23", password_digest: "$2a$10$sYvSxyb8klPNf2D6XMhxDOu1SGev2X0lH58lZVrgLij...">
irb(ma

問題なし

<問題>自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。

<回答> 確認できた!

7.4.4 演習 実際のユーザー登録

7.4.4.1

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

<回答>

  test "valid signup information" do
    get signup_path
(略)
    assert_template 'users/show'
    assert_not flash.empty?
  end

7.4.4.2

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

<回答>

<!DOCTYPE html>
<html>
      .
      <% flash.each do |message_type, message| %>
        <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
      <% end %>
      .
</html>

7.4.4.3

<問題>リスト 7.28のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。

<回答>

 rails test
Running via Spring preloader in process 7332
Started with run options --seed 36041

ERROR["test_valid_signup_information", UsersSignupTest, 2.10871023800064]
 test_valid_signup_information#UsersSignupTest (2.11s)
RuntimeError:         RuntimeError: not a redirect! 204 No Content
            test/integration/users_signup_test.rb:27:in `block in <class:UsersSignupTest>'

  19/19: [==========================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.45401s
19 tests, 40 assertions, 0 failures, 1 errors, 0 skips

7.4.4.4

<問題>リスト 7.28で、@user.saveの部分をfalseに置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。

<回答>

$ rails test
Running via Spring preloader in process 7468
Started with run options --seed 62890

 FAIL["test_valid_signup_information", UsersSignupTest, 1.01124269200227]
 test_valid_signup_information#UsersSignupTest (1.01s)
        "User.count" didn't change by 1.
        Expected: 1
          Actual: 0
        test/integration/users_signup_test.rb:21:in `block in <class:UsersSignupTest>'

 FAIL["test_invalid_signup_information", UsersSignupTest, 1.0781876540022495]
 test_invalid_signup_information#UsersSignupTest (1.08s)
        Expected at least 1 element matching "div#error_explanation", found 0..
        Expected 0 to be >= 1.
        test/integration/users_signup_test.rb:15:in `block in <class:UsersSignupTest>'

  19/19: [==========================================================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.28741s
19 tests, 39 assertions, 2 failures, 0 errors, 0 skips
  • 登録されたユーザーの件数が増えないことで、assert_differenceの結果がNGになる。

7.5.3 演習 本番環境へのデプロイ

7.5.3.1

<問題>ブラウザから本番環境 (Heroku) にアクセスし、SSLの鍵マークがかかっているか、URLがhttpsになっているかどうかを確認してみましょう。

<回答> f:id:opiyotan:20170816180959p:plain

<問題>本番環境でユーザーを作成してみましょう。Gravatarの画像は正しく表示されているでしょうか?

<回答> f:id:opiyotan:20170816181057p:plain

メモ

  • Railsにはテスト環境 (test)、開発環境 (development)、そして本番環境 (production) の3つの環境がデフォルトで装備されている
  $ rails console
  Loading development environment
  >> Rails.env
  => "development"
  >> Rails.env.development?
  => true
  >> Rails.env.test?
  => false

# rails db:migrateを本番環境で実行する
$ rails db:migrate RAILS_ENV=production
  • Sassのミックスイン機能を使うことで、CSSルールのグループをパッケージ化できる
  • resources :usersの1行を追加すると、ユーザーのURLを生成するための多数の名前付きルート (5.3.3) と共に、RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになる
  • Digestライブラリのhexdigestメソッドを使うと、MD5のハッシュ化が実現できま
>> email = "MHARTL@example.COM"
>> Digest::MD5::hexdigest(email.downcase)
=> "1fda4469bcbec3badf5418269ffc5968"
  • formの構造について
<%= f.label :name %>
<%= f.text_field :name %>
↓ #変換されると?
<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />
  • 空の判定
>> user.errors.empty?
=> false # 値が何かしらある。空ならtrue。
>> user.errors.any?
=> true # 値が何かしらある。空ならfalse
  • 引数に整数が与えられると、それに基づいて2番目の引数の英単語を複数形に変更したものを返す
>> helper.pluralize(2, "woman")
=> "2 women"
>> helper.pluralize(3, "erratum")
=> "3 errata"
  • redirect_toの仕組み
redirect_to @user
↓
redirect_to user_url(@user)
 # user_urlはプレフィックスなので、具体的に言うと`users#show`

はまった

key must be 32 bytes

ArgumentError (key must be 32 bytes):
Application Trace | Framework Trace | Full Trace
activesupport (5.0.0.1) lib/active_support/message_encryptor.rb:72:in `key='
activesupport (5.0.0.1) lib/active_support/message_encryptor.rb:72:in `_encrypt'
activesupport (5.0.0.1) lib/active_support/message_encryptor.rb:58:in `encrypt_and_sign'
actionpack (5.0.0.1) lib/action_dispatch/middleware/cookies.rb:592:in `commit'
actionpack (5.0.0.1) lib/action_dispatch/middleware/cookies.rb:465:in `[]='
actionpack (5.0.0.1) lib/action_dispatch/middleware/session/cookie_store.rb:117:in `set_cookie'
rack (2.0.3) lib/rack/session/abstract/id.rb:363:in `commit_session'
rack (2.0.3) lib/rack/session/abstract/id.rb:234:in `context'
rack (2.0.3) lib/rack/session/abstract/id.rb:226:in `call'

解決方法は、rubyのバージョンを変えると上手くいった。 変更方法はこちらから。 今回、私の場合は2.4.1から2.3.4に変更し上手くいったがバージョン下げるってのは何だか嫌だな。

30歳まで残り2年の僕は人生を変えるためにRailsチュートリアルを始めようと思う(第6章)

こんにちは。opiyoです。

今回は、第6章をやっていきます。

第6章はユーザーのモデルを作っていきます。

6.1.1 演習 データベースの移行

6.1.1.1

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

<回答>

#db/schema.rb
ActiveRecord::Schema.define(version: 20170630074441) 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
# db/migrate/20170629005430_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

6.1.1.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 マイグレーション」を参照してください。

<回答>

ActiveRecord::Schema.define(version: 0) do

end

6.1.1.3

<問題>もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。

<回答>

ActiveRecord::Schema.define(version: 20170630074441) 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ファイル

6.1.2.1

<問題>Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください (ヒント: 4.4.4で紹介したテクニックを使ってみてください)。

<回答>

> u = User.new
> u.class.superclass
=> ApplicationRecord(abstract)

6.1.2.2

<問題>同様にして、ApplicationRecordがActiveRecord::Baseを継承していることについて確認してみてください。

<回答>

> u.class.superclass.superclass
=> ActiveRecord::Base

6.1.3 演習 ユーザーオブジェクトを作成する

6.1.3.1

<問題>user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。

<回答>

* 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, password_digest: nil>
irb(main):019:0> user.name.class
=> String
irb(main):020:0> user.email.class
=> String

6.1.3.2

<問題>created_atとupdated_atは、どのクラスのインスタンスでしょうか?

<回答>

* user.created_at.class
=> NilClass
> user.updated_at.class
=> NilClass

6.1.4 演習 ユーザーオブジェクトを検索する

6.1.4.1

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

<回答>

* User.find_by(name: "Michael Hartl")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "Michael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-14 00:50:04", updated_at: "2017-08-14 00:50:04", password_digest: "$2a$10$vp5fiCtIdDwU/AdskPGeKeZ35UdjtkZbPWnV3i7g0Rq...">
irb(main):030:0> User.find_by_name("Michael Hartl")
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "Michael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-14 00:50:04", updated_at: "2017-08-14 00:50:04", password_digest: "$2a$10$vp5fiCtIdDwU/AdskPGeKeZ35UdjtkZbPWnV3i7g0Rq...">

6.1.4.2

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

<回答>

* User.all.class
=> User::ActiveRecord_Relation

6.1.4.3

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

<回答>

 User.all.length
  User Load (0.5ms)  SELECT "users".* FROM "users"
=> 2

6.1.5 演習 ユーザーオブジェクトを更新する

6.1.5.1

<問題>userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。

<回答>

> User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-14 00:50:04", updated_at: "2017-08-14 00:50:04">
irb(main):002:0> u = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-14 00:50:04", updated_at: "2017-08-14 00:50:04">
irb(main):003:0> u.name
=> "Michael Hartl"
irb(main):004:0> u.name = "Haku"
=> "Haku"
irb(main):005:0> u.save
   (0.1ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "mhartl@example.com"], ["id", 1], ["LIMIT", 1]]
  SQL (0.3ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Haku"], ["updated_at", 2017-08-14 01:06:07 UTC], ["id", 1]]
   (1.3ms)  commit transaction
=> true

6.1.5.2

<問題>今度はupdate_attributesを使って、email属性を更新および保存してみてください。

<回答>

> u.update_attributes(email: "haku@example.com")
   (0.1ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "haku@example.com"], ["id", 1], ["LIMIT", 1]]
  SQL (0.3ms)  UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["email", "haku@example.com"], ["updated_at", 2017-08-14 01:10:15 UTC], ["id", 1]]
   (1.6ms)  commit transaction
=> true

6.1.5.3

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

<回答>

* u.update_attribute(:created_at, 1.year.ago)
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "users" SET "created_at" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["created_at", 2016-08-14 01:14:57 UTC], ["updated_at", 2017-08-14 01:14:57 UTC], ["id", 1]]
   (1.6ms)  commit transaction
=> true

6.2.1 演習 有効性を検証する

6.2.1.1

<問題>コンソールから、新しく生成したuserオブジェクトが有効 (valid) であることを確認してみましょう。

<回答>

* 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):009:0> user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "user@example.com"], ["LIMIT", 1]]
=> true

6.2.1.2

<問題>6.1.3で生成したuserオブジェクトも有効であるかどうか、確認してみましょう。

<回答>

* 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):013:0> user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "mhartl@example.com"], ["LIMIT", 1]]
=> true

6.2.2 演習 存在性を検証する

6.2.2.1

<問題>新しいユーザーuを作成し、作成した時点では有効ではない (invalid) ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。

<回答>

* u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):008:0> u.invalid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
=> true
irb(main):009:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
=> false
irb(main):010:0> u.errors.full_messages
=> ["Name can't be blank", "Email can't be blank", "Email is invalid"]

6.2.2.2

<問題>u.errors.messagesを実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?

<回答>

> errors = u.errors.messages # `errors.messages`でハッシュになる
=> {:name=>["can't be blank"], :email=>["can't be blank", "is invalid"]}
irb(main):023:0* errors[:email]
=> ["can't be blank", "is invalid"]

6.2.3 演習 長さを検証する

6.2.3.1

<問題>長すぎるnameとemail属性を持ったuserオブジェクトを生成し、有効でないことを確認してみましょう。

<回答>

* u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):046:0> u.name = "a" * 51
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
irb(main):047:0> u.name.size
=> 51
irb(main):048:0> u.email = "a" * 255 + "@co.jp"
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@co.jp"
irb(main):049:0> u.email.size
=> 261
irb(main):050:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@co.jp"], ["LIMIT", 1]]
=> false
irb(main):051:0> u.error
u.error_on_ignored_order_or_limit u.errors

irb(main):051:0> u.error
u.error_on_ignored_order_or_limit u.errors

6.2.3.2

<問題>長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。

<回答>

> u.errors.full_messages
=> ["Name is too long (maximum is 50 characters)", "Email is too long (maximum is 255 characters)"]

6.2.4 演習 フォーマットを検証する

6.2.4.1

<問題>リスト 6.18にある有効なメールアドレスのリストと、リスト 6.19にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、リスト 6.21の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。

<回答> f:id:opiyotan:20170815131537p:plain

6.2.4.2

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

<回答>

# app/models/user.rb
 class User < ApplicationRecord
  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 }
end
# test/models/user_test.rb
  test "email validation should reject invalid addresses" do
    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
                           foo@bar_baz.com foo@bar+baz.com foo@bar..com]
    invalid_addresses.each do |invalid_address|
      @user.email = invalid_address
      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
    end
  end

6.2.4.3

<問題>foo@bar..comをRubularのメールアドレスのリストに追加し、リスト 6.23の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。

<回答> f:id:opiyotan:20170815132436p:plain

6.2.5 演習 一意性を検証する

6.2.5.1

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

<回答> 問題なし

# before_saveをコメントアウトすると大文字、小文字が合致せずエラーになる
$ rails test
Running via Spring preloader in process 5880
Started with run options --seed 39690

 FAIL["test_email_addresses_should_be_saved_as_lower-case", UserTest, 1.0127649949990882]
 test_email_addresses_should_be_saved_as_lower-case#UserTest (1.01s)
        Expected: "foo@example.com"
          Actual: "Foo@ExAMPle.CoM"

6.2.5.2

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

<回答>

before_save { self.email = email.downcase }
↓
before_save { email.downcase! }
  • 気になること - self.email.downcase! じゃなくていいの? emailをはどう参照されるのだろうか。 selfがあれば、そのオブジェクト自身ってわかる気がするけどそれを勝手にやってくれるってことなのかな…

6.3.2 演習 ユーザーがセキュアなパスワードを持っている

6.3.2.1

<問題>この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。

<回答>

* u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil>
irb(main):016:0> u.name = "Haku"
=> "Haku"
irb(main):017:0> u.email = "haku@co.jp"
=> "haku@co.jp"
irb(main):018:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "haku@co.jp"], ["LIMIT", 1]]
=> false

6.3.2.2

<問題> なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください。

<回答>

irb(main):019:0> u.errors.full_messages
=> ["Password can't be blank"]

6.3.3 演習 パスワードの最小文字数

6.3.3.1

<問題>有効な名前とメールアドレスでも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。

<回答>

* u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil>
irb(main):007:0> u.name = "Haku"
=> "Haku"
irb(main):008:0> u.email = "haku@co.jp"
=> "haku@co.jp"
irb(main):009:0> u.password = u.password_confirmation = "haku"
=> "haku"
irb(main):010:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "haku@co.jp"], ["LIMIT", 1]]
=> false

6.3.3.2

<問題> 上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう。

<回答>

> u.errors.full_messages
=> ["Password is too short (minimum is 6 characters)"]

6.3.4 演習 ユーザーの作成と認証

6.3.4.1

<問題>コンソールを一度再起動して (userオブジェクトを消去して)、このセクションで作ったuserオブジェクトを検索してみてください。

<回答>

$ rails c
Running via Spring preloader in process 6789
Loading development environment (Rails 5.0.0.1)
No entry for terminal type "ake/version";
using dumb terminal settings.
irb(main):001:0> User.find_by(email: "mhartl@example.com")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "mhartl@example.com"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-15 06:49:16", updated_at: "2017-08-15 06:49:16", password_digest: "$2a$10$ZDicArmstR/XhkV.IKJVu.hMWDn5oh.P3pilUSBlJOi...">

6.3.4.2

<問題>オブジェクトが検索できたら、名前を新しい文字列に置き換え、saveメソッドで更新してみてください。うまくいきませんね…、なぜうまくいかなかったのでしょうか?

<回答>

> user = User.find_by(email: "mhartl@example.com")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "mhartl@example.com"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-15 06:49:16", updated_at: "2017-08-15 06:49:16", password_digest: "$2a$10$ZDicArmstR/XhkV.IKJVu.hMWDn5oh.P3pilUSBlJOi...">
irb(main):003:0> user.name = "Haku"
=> "Haku"
irb(main):004:0> user.save
   (0.2ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "mhartl@example.com"], ["id", 1], ["LIMIT", 1]]
   (0.1ms)  rollback transaction
=> false
irb(main):005:0> user.errors.full_messages
=> ["Password can't be blank", "Password is too short (minimum is 6 characters)"] # 

nameだけ変更してsaveしているが、passwordも保存することを要求されるので「password」が無いよってエラーになる。 nameだけ明示的に変更する場合は、6.3.4.3の方法を利用する。

6.3.4.3

<問題>今度は6.1.5で紹介したテクニックを使って、userの名前を更新してみてください。

<回答>

* user.update_attribute(:name, "Haku")
   (0.1ms)  begin transaction
  SQL (0.4ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Haku"], ["updated_at", 2017-08-15 06:59:37 UTC], ["id", 1]]
   (1.9ms)  commit transaction
=> true
irb(main):010:0> user.name
=> "Haku"

メモ

  • マイグレーション
  • SQLのDDL
  • モデリング
  • モデル名は単数系
  • コントローラーは複数形
  • dbのロールバック
$ rails db:rollback
  • dbのロールバックには、up downメソッドを使う場合もある
  • rails c のサンドボックスモードで実行
$ rails c --sandbox
Running via Spring preloader in process 3062
Loading development environment in sandbox (Rails 5.0.0.1)
Any modifications you make will be rolled back on exit
  • モデルだけのテスト実行
$ rails test:models
  1/1: [============================================================================================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.03454s
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
  • オブジェクトが有効かどうかはvalid?メソッドを使う
  • ハッシュ化とは元に戻せない不可逆なデータに処理する

  • !!でそのオブジェクトが対応する論理値オブジェクトに変換できる

  • モデルの作成方法

$ rails generate model User name:string email:string
      invoke  active_record
      create    db/migrate/20160523010738_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
  • コントローラー名は複数形
  • モデル名は単数形
  • マイグレーションファイル
    • データベースに与える変更を定義したもの
    • rails db:migrateで反映する
    • SQLiteの場合はdevelopment.sqlite3 というファイルが出来上がり、これが実体になる
    • 元に戻す場合は rails db:rollback
  • update_attributesメソッドは属性のハッシュを受け取り、成功時には更新と保存を続けて同時に行います (保存に成功した場合はtrueを返します)。 ただし、検証に1つでも失敗すると、 update_attributesの呼び出しは失敗します。
  • 特定の属性のみを更新したい場合は、次のようにupdate_attributeを使います。このupdate_attributeには、検証を回避するといった効果もあります。
> u.update_attribute(name: "El Duderino") # この書き方ではダメ
ArgumentError: wrong number of arguments (given 1, expected 2)

>> user.update_attribute(:name, "El Duderino") # カンマが区切ってキーと値で渡す
=> true
>> user.name
=> "El Duderino"

会社での目標じゃなくて自分の目標を立てた方が人生は変わる

8月も1/3が終わり8末になると期が変わるって会社も多いのではないだろうか。

期が変わるってなると面談があったり色々と変化があるだろう。

私の会社も8末で期が変わるので色々面談したり目標に対する成果をまとめた資料作ったりが発生するのだが非常に面倒だ。

この目標とそれの達成について今日は思ったことを書こうと思う。


今期の振り返りとして

やったー万歳! 目標達成だー!

ってなったのだけど本当に凄いことだし良い事だと思う。

来期の目標を立てるときは、今期の目標と結果に対して会社全体の目標がまず決まる。

それを達成するためにチームの目標があり、個人の目標だある。

会社で立てる目標ってのは会社の目標を叶えるための目標であって実は自分の目標に直結しないこともあると思う。

この会社で生きるための力を付けることがゴールだから当たり前っちゃ当たり前なのかな。



評価制度が変わるよって話もあったのだけどどうやら組織が大きくなってきたから昔と同じように評価することは難しいって事らしい。

ここ数年人数の増減は無いし大きくなった感覚は無いのだが今までの評価については何だったのだろうか。まーそれはおいておく。ここは変わらないけど、ここが変わる。それによってこんな風になるのよ。素敵でしょ。って感じなのだが皆が知りたいのは結局「どんだけ上がるんだ」って話。これだけ。


今まではどうなったらどんだけ上がるってのが無かったから、それが明確になるなら良いなとは思う。

僕が会社から期待されている役割は具体的にこうだ!ってのがふんわりしてたからそれが具体的になるのはよいことだ。

だけどこれも結局、会社のための力であって外に出たときに通用するのかってのは全く別問題だ。



ここ数記事の中でも同じこと書いているが大事な事だから言うけど40年続く会社は3社らしい。

つまり一生同じ会社で働くってのは今の時代、もー不可能なのだ。

だからこそ何処行っても通用する力を若いうちにつけておく必要が絶対にある。

だから会社がどうなって欲しいかという視点だけじゃなく自分がどうなりたいのか。世の中はどんな人が必要とされているのかをしっかり確認することは絶対に大事だ!

最後に

目標の達成は大事。

会社と自分が重なっている人は幸せだ。

だけどそんな人ほとんどいないはず。

だからこそ自分はどうしたいのかっていう目標を自分のために立てよう。

そうすればきっと人生変わるはずだ。

人生に可能性なんて存在しない。あるのは目の前の現実だけ

僕は今日新幹線の中でこれを書いている。

周りにはスーツの人もいっぱいて、お酒飲んだりスマホでゲームしたりPC開いて仕事したりしている。


最近本当にこれからの人生について色々考えるのですが僕の周りにいる人達に悩みは無いのだろうか?


サラリーマンの平均年収は400~500万

独り身であれば楽勝で生活できる額でしょうが、家族がいる人、今後結婚する人にとっては、これだけでは生きていけない数字です。


皆どうやって生きているんだ?



うちもそうだが僕の周りは専業主婦家族ばかり。

確かにじいじ、ばあばと過ごしている人も多いし都会に比べたら地方なので色々安いのかもしれない。

でも、その安さも家賃くらいなもので生活していくコストは変わらない

車無いと生活できないし田舎は田舎で金がかかる。


僕は28歳。結婚してるし子供も二人いる。

どこにでもあるような普通の家族だが子供たちが育っていくことを考えたら色々考えておかなければ人生詰むだろう。


当たり前が当たり前じゃない時代にきっとなるし、もー既になっているのだろう。

この事実に気づけてない人はきっと多いだろうが、僕は知れた。

だからこそ手遅れになる前に、30歳になる前に将来に備えておくためには何かを変えなければならない。


そんな中流れてきた目に留まった記事の中にスゲー良いことが書いてあって思わず呟いてしまいました。


もーこの文章のままなのですが、僕は今まで何をしてきたんだと。

行動はしている。人より高い意識を持って日々情報を集めている。そして行動している。

だけど大きな何かをやり忘れている。何かのステップを飛んでしまっている。


そーそれは夢を叶える為の行動だ。

想いなんて誰でも持てる。夢だって語れる。だけどそれを実現する行動が伴っていなければ信用なんて出来ない。

これが吹っ飛んでるから僕は前に進めないのだ。

最後に

これはついさっきまでの僕ですが自分の人生に可能性なんて無いので辞めましょう。

あるのは目の前の現実だけ。

この現実を変えるに、どうなりたいのかを考え具体的なアクションに落とし込みそれを今やるだけです。

手遅れ何てことは絶対にないはず。

だから、動き出そう。手を動かそう。足を動かそう。

いつだって、誰だって人生は変えれるはずだ!