おぴよの気まぐれ日記

おぴよの気まぐれ日記

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

Ruby on Rails チュートリアルで30歳までに人生を変える(第7章)

こんにちは。opiyoです。

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

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

railsチュートリアル7章の学んだこと

  • 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`

railsチュートリアル7章ではまった

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に変更し上手くいったがバージョン下げるってのは何だか嫌だな。

railsチュートリアル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