第7章Railsチュートリアル演習問題と解答まとめ

Ruby on Railsチュートリアルの演習問題と解答をまとめる。

第7章 ユーザー登録 - Railsチュートリアル

f:id:yukitoku_sw:20191019165508p:plain

アウトプットすることで、より自分の理解を深めることを目的としています。 自分なりに調べて考えた回答のため、記載内容に誤りがある場合はコメントいただけると幸いです。


演習7.1.1 デバッグRails環境

問題1

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

解答

controller: static_pages
actin: about


問題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: "Kitaro", email: "mhartl@example.com", created_at: "2019-10-21 13:25:29", updated_at: "2019-10-21 13:37:24", password_digest: "$2a$10$7NLTw29m6mN8OY5aghw.hOW7UQIMuo5VnruWHtE61ky...">

>> puts user.attributes.to_yaml
---
id: 1
name: Kitaro
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2019-10-21 13:25:29.045255000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2019-10-21 13:37:24.179944000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$7NLTw29m6mN8OY5aghw.hOW7UQIMuo5VnruWHtE61kyf9eLSZIcru"
=> nil

>> y user.attributes
---
id: 1
name: Kitaro
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2019-10-21 13:25:29.045255000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2019-10-21 13:37:24.179944000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$7NLTw29m6mN8OY5aghw.hOW7UQIMuo5VnruWHtE61kyf9eLSZIcru"
=> nil

puts user.attributes.to_yamly user.attributes の実行結果は同じ。

後者の方が文字数が少ないから楽だね〜


演習7.1.2 Usersリソース

問題1

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

解答

[app/views/show.html.erb]

<%= @user.name %>, <%= @user.email %>,
<%= @user.created_at %>, <%= @user.updated_at %>


問題2

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

解答

<%= @user.name %>, <%= @user.email %>,
<%= @user.created_at %>, <%= @user.updated_at %>

<p>
  Time.now: <%= Time.now %>
</p>

表示結果が見にくかったので

タグで囲って、タイトル的なものを付けてみた。


演習7.1.3 debuggerメソッド

問題1

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

解答

.to_yamlをつけることでyaml形式に変換してくれる

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

(byebug) puts params
{"controller"=>"users", "action"=>"show", "id"=>"1"}
nil
(byebug) 


問題2

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

解答

@user は nil

   1: class UsersController < ApplicationController
   2: 
   3:   def show
   4:     @user = User.find(params[:id])
   5:   end
   6:   def new
   7:     debugger
=> 8:   end
   9: end
(byebug) puts @user

nil


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

問題1

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

解答

省略


問題2

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

解答

リスト7.12参照


問題3

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

解答

リスト7.13参照
キーワード引数の方が簡潔で無駄がない。


演習7.2.1 form_forを使用する

問題1

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

解答

NoMethodError in Users#new

f:id:yukitoku_sw:20191022100233p:plain


問題2

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

解答

置き換えるだけなので省略

単純にめんどくさい。 視認性が悪い。 ぱっと見で変数を使っているとわかる。


演習7.2.2 フォームHTML

問題1

Learn Enough HTML to Be DangerousではHTMLをすべて手動で書き起こしていますが、なぜformタグを使わなかったのでしょうか? 理由を考えてみてください。

解答

Learn Enough HTML to Be Dangerousをやっていないので調べてみた。

formタグってのは入力・送信フォームを作成する時に使うタグですね。 Learn Enough HTML to Be Dangerousの中では入力・送信フォームを作成していないですね。
なので、解答としては入力・送信フォームを作成していないから。 ということで良いかと思います。
引用: https://kojimanotech.com/2018/07/06/81/#outline__6


演習7.3.2 Strong Parameters

問題1

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

解答

f:id:yukitoku_sw:20191022104906p:plain


演習7.3.3 エラーメッセージ

問題1

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

解答

f:id:yukitoku_sw:20191022110814p:plain

最小文字数を5に変更

f:id:yukitoku_sw:20191022110943p:plain

エラーメッセージも自動的に更新された!


問題2

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

解答

  1. 送信するとPOST(createアクション)が実行される。
  2. しかし、POST(createアクション)が失敗し、"/users" で止まってしまった。
  3. POSTはGETではないので何かを表示することができない。
  4. その時は render で "new.html.erb" を表示しましょう。

的な感じ?


演習7.3.4 失敗時のテスト

問題1

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

解答

assert_selectが謎だったので調べた。わかりやすかった記事

assert_selectでHTMLを検証する - Rails RecipeBookはてな版 - rails-recipebookグループ

<% if @user.errors.any? %>
  <div id="error_explanation">       # 1. ここを調べる
    <div class="alert alert-danger">  # 2. ここを調べる
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    (中略)
  </div>
<% end %>
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest
  
  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      (中略)
    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'     # 1. id="error_explanationが存在するか
    assert_select 'div.alert'                            # 2.1. class="alert"が存在するか
    assert_select 'div.alert-danger'              # 2.2. class="alert-danger"が存在するか
  end
end

CSSセレクタと同じように
id は #
class は .
を使う。


問題2

ユーザー登録フォームのURLは /signup ですが、無効なユーザー登録データを送付するとURLが /users に変わってしまいます。これはリスト 5.43で追加した名前付きルート (/signup) と、RESTfulなルーティング (リスト 7.3) のデフォルト設定との差異によって生じた結果です。リスト 7.26とリスト 7.27の内容を参考に、この問題を解決してみてください。うまくいけばどちらのURLも /signup になるはずです。あれ、でもテストは greenのままになっていますね...、なぜでしょうか? (考えてみてください)

解答

リスト7.26とリスト7.27を追加修正することで、どちらのURLも/signupになる。 テストがGREENのママなのは、 resources :usersの中に/users/createが残っているから。


問題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'
    assert_select 'div.alert-danger'
  end
end

users_pathをsignup_pathへ変更
テストも通ります。


問題4

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

解答

リスト7.20の状態に戻す。
テストGREEN
テストにassert_select 'form[action="/signup"]' を追加する。
テストRED
リスト7.27に戻す。
テストGREEN

assert_select 'form[action="/signup"]'は
site_layout_testで行なった。 assert_select "a[href=?]", help_path
の応用編だと思いまうす。


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

問題1

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

解答

>> user = User.last
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]=> #<User id: 2, name: "Baki Hanma", email: "bhanma@example.com", created_at: "2019-10-22 11:09:22", updated_at: "2019-10-22 11:09:22", password_digest: "$2a$10$e76.Gx0p3mmm.Dt3Cf6R0eUiKP3W27B504liPO1Iwdl...">
>>   

User.lastで一番最近作ったuserを探せる。


問題2

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

解答

動作確認なので省略


演習7.4.2 flash

問題1

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

解答

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


問題2

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

解答

valueのみが返ってくる


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

問題1

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

解答

動作確認のみなので省略


問題2

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

解答

動作確認のみなので省略


演習7.4.4 成功時のテスト

問題1

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

解答

[test/integration/users_signup_test.rb]

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.empty?
  end
end


問題2

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

解答

リスト7.35参照


問題3

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

解答

[test/integration/users_signup_test.rb]

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.empty?
  end
end

コメントアウトするとテストがREDに!


問題4

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

解答

assert_difference "User.count", 1 do
(中略)
end

doの時点のUserの数を記憶し、 endの時点でUserが1人増えているであろう。 というテストなので1人増えていれば true それ以外は flase


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

問題1

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

解答

動作確認なので省略


問題2

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

解答

動作確認なので省略


おわり


yukitoku-sw.hatenablog.com