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}秒です。" %> # 時分秒だけを表示することもできる
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ハッシュ化して、紐付けた画像がちゃんと表示されるかどうか試してみましょう。
<回答>
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とするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。
<回答>
結果は変わらないが、なぜダメなのか?
- 変数名が長くなり見通しが悪い
- form
のf
だからこそ、意味が通じるのが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に変更すると、エラーメッセージも自動的に更新されることを確かめてみましょう。
<回答>
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
見て分かるように、変更前はusers
のpost
アクションを利用してusers#create
の処理となっていた。
変更後は、signup
のpost
アクションをした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になっているかどうかを確認してみましょう。
<回答>
<問題>本番環境でユーザーを作成してみましょう。Gravatarの画像は正しく表示されているでしょうか?
<回答>