Try T.M Engineer Blog

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

【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>

【Ruby on Railsチュートリアル(第4版)】第5章 レイアウトを作成する(演習と解答)

はじめに

このブログ記事は、私(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)

演習と解答

5.1.1 ナビゲーション<演習>
1. Webページと言ったらネコ画像、というぐらいにはWebにはネコ画像が溢れていますよね。リスト 5.4のコマンドを使って、図 5.3のネコ画像をダウンロードしてきましょう。
【解答】以下の通り。

$ curl cdn.learnenough.com/kitten.jpg

2. mvコマンドを使って、ダウンロードしたkitten.jpgファイルを適切なアセットディレクトリに移動してください (参考: 5.2.1)。
【解答】以下の通り。

$ mv kitten.jpg app/assets/images/kitten.jpg

3. image_tagを使って、kitten.jpg画像を表示してみてください (図 5.4)。
【解答】以下の通り。(実機操作は割愛)

<%= link_to image_tag("kitten.jpg", alt: "Kitten Image"),
  'http://rubyonrails.org/' %>

5.1.2 BootstrapとカスタムCSS<演習>
1. リスト 5.10を参考にして、5.1.1.1で使ったネコ画像をコメントアウトしてみてください。また、ブラウザのHTMLインスペクタ機能を使って、コメントアウトするとHTMLのソースからも消えていることを確認してみてください。
【解答】以下の通り。(実機操作は割愛)

<%#= link_to image_tag("kitten.jpg", alt: "Kitten Image"),
  'http://rubyonrails.org/' %>

2. リスト 5.11のコードをcustom.scssに追加し、すべての画像を非表示にしてみてください。うまくいけば、Railsのロゴ画像がHomeページから消えるはずです。先ほどと同様にインスペクタ機能を使って、今度はHTMLのソースコードは残ったままで、画像だけが表示されなくなっていることを確認してみてください。
【解答】以下の通り。

/* app/assets/stylesheets/custom.scss */
img {
  display: none;
}

5.1.3 パーシャル(partial)<演習> 1. Railsがデフォルトで生成するheadタグの部分を、リスト 5.18のようにrenderに置き換えてみてください。ヒント: 単純に削除してしまうと後でパーシャルを1から書き直す必要が出てくるので、削除する前にどこかに退避しておきましょう。
【解答】以下の通り。

### [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">
   <%= yield %>
   <%= render 'layouts/footer' %>
  </div>
</body>
</html>

2. リスト 5.18のようなパーシャルはまだ作っていないので、現時点ではテストは redになっているはずです。実際にテストを実行して確認してみましょう。
【解答】以下の通り。

$rails t
Error:
StaticPagesControllerTest#test_should_get_help:
ActionView::Template::Error: Missing partial layouts/_rails_default with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. 

3. layoutsディレクトリにheadタグ用のパーシャルを作成し、先ほど退避しておいたコードを書き込み、最後にテストが green に戻ることを確認しましょう。
【解答】以下の通り。

### [app/views/layouts/_rails_default.html.erb]
<%= csrf_meta_tags %>
<%= stylesheet_link_tag    'application', media: 'all',
  'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application',
  'data-turbolinks-track': 'reload' %>
$rails t
Finished in 4.254301s, 0.9402 runs/s, 1.8804 assertions/s.
4 runs, 8 assertions, 0 failures, 0 errors, 0 skips

5.2.2 素晴らしい構文を備えたスタイルシート<演習>
1. 5.2.2で提案したように、footerのCSSを手作業で変換してみましょう。具体的には、リスト 5.17の内容を1つずつ変換していき、リスト 5.20のようにしてみてください。
【解答】以下の通り。

/* app/assets/stylesheets/custom.scss */

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $gray-medium-light;
  color: $gray-light;
  a {
    color: $gray;
    &:hover {
      color: $gray-darker;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

5.3.2 RailsのルートURL<演習>
1. 実は名前付きルートは、as:オプションを使って変更することができます。有名なFar Sideの漫画に倣って、Helpページの名前付きルートをhelfに変更してみてください (リスト 5.29)。
【解答】以下の通り。

### [config/routes.rb]
Rails.application.routes.draw do
  root 'static_pages#home'
  get '/help', to:'static_pages#help', as: 'helf'
  get '/about', to:'static_pages#about'
  get '/contact', to:'static_pages#contact'
end

2. 先ほどの変更により、テストが redになっていることを確認してください。リスト 5.28を参考にルーティングを更新して、テストを greenにして見てください。
【解答】以下の通り。

$ rails t
Error:
StaticPagesControllerTest#test_should_get_help:
NameError: undefined local variable or method `help_path' for #<StaticPagesControllerTest:0x00007fca69b1b698>
    test/controllers/static_pages_controller_test.rb:16:in `block in <class:StaticPagesControllerTest>'

3. エディタのUndo機能を使って、今回の演習で行った変更を元に戻して見てください。
【解答】以下の通り。

### [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'
end

5.3.3 名前付きルート<演習>
1. リスト 5.29のようにhelfルーティングを作成し、レイアウトのリンクを更新してみてください。
【解答】以下の通り。

### [config/routes.rb]
Rails.application.routes.draw do
  root 'static_pages#home'
  get '/help', to:'static_pages#help', as: 'helf'
  get '/about', to:'static_pages#about'
  get '/contact', to:'static_pages#contact'
end
$ rails routes
 Prefix Verb URI Pattern        Controller#Action
   root GET  /                  static_pages#home
   helf GET  /help(.:format)    static_pages#help
  about GET  /about(.:format)   static_pages#about
contact GET  /contact(.:format) static_pages#contact
### [app/views/layouts/_header.html.erb]
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home",   root_path %></li>
        <li><%= link_to "Help",   helf_path %></li>
        <li><%= link_to "Log in", '#' %></li>
      </ul>
    </nav>
  </div>
</header>

2. 前回の演習と同様に、エディタのUndo機能を使ってこの演習で行った変更を元に戻してみてください。
【解答】以下の通り。

### [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'
end
$ 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
### [app/views/layouts/_header.html.erb]
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home",   root_path %></li>
        <li><%= link_to "Help",   help_path %></li>
        <li><%= link_to "Log in", '#' %></li>
      </ul>
    </nav>
  </div>
</header>

5.3.4 リンクのテスト<演習>
1. footerパーシャルのabout_pathをcontact_pathに変更してみて、テストが正しくエラーを捕まえてくれるかどうか確認してみてください。
【解答】以下の通り。

### [app/views/layouts/_footer.html.erb]
<footer class="footer">
  <small>
    The <a href="http://railstutoril.jp/">Ruby on Rails Tutorial</a>
    by <a href="http://www.michaelhartl.com/">Michael Hartl</a>
  </small>
  <nav>
    <ul>
      <!-- <li><%#= link_to "About", about_path %></li> -->
      <li><%= link_to "About", contact_path %></li>
      <li><%= link_to "Contact", contact_path %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
    </ul>
  </nav>
</footer>
$ rails t
Failure:
Expected at least 1 element matching "a[href="/about"]", found 0..
Expected 0 to be >= 1.

2. リスト 5.35で示すように、Applicationヘルパーで使っているfull_titleヘルパーを、test環境でも使えるようにすると便利です。こうしておくと、リスト 5.36のようなコードを使って、正しいタイトルをテストすることができます。ただし、これは完璧なテストではありません。例えばベースタイトルに「Ruby on Rails Tutoial」といった誤字があったとしても、このテストでは発見することができないでしょう。この問題を解決するためには、full_titleヘルパーに対するテストを書く必要があります。そこで、Applicationヘルパーをテストするファイルを作成し、リスト 5.37のFILL_INの部分を適切なコードに置き換えてみてください。ヒント: リスト 5.37ではassert_equal <期待される値>, <実際の値>といった形で使っていましたが、内部では==演算子で期待される値と実際の値を比較し、正しいかどうかのテストをしています。
【解答】以下の通り。

### [test/helpers/application_helper_test.rb]
require 'test_helper'

class ApplicationHelperTest < ActionView::TestCase
  test "full title helper" do
    assert_equal full_title,         "Ruby on Rails Tutorial Sample App"
    assert_equal full_title("Help"), "Help|Ruby on Rails Tutorial Sample App"
  end
end

5.4.1 Usersコントローラ<演習>
1. 表 5.1を参考にしながらリスト 5.41を変更し、users_new_urlではなくsignup_pathを使えるようにしてみてください。
【解答】以下の通り。

### [test/controllers/users_controller_test.rb]
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest
  test "should get signup" do
    get signup_url
    assert_response :success
  end
end

2. 先ほどの変更を加えたことにより、テストが redになったことを確認してください。なお、この演習はテスト駆動開発 (コラム 3.3) で説明した red/green のリズムを作ることを目的としています。このテストは次の5.4.2で greenになるよう修正します。
【解答】以下の通り。

$ rails t
Error:
UsersControllerTest#test_should_get_signup:
NameError: undefined local variable or method `signup_url' for #<UsersControllerTest:0x00007ffe7dd35260>
    test/controllers/users_controller_test.rb:5:in `block in <class:UsersControllerTest>'
### [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'
end
$ rails t
Finished in 0.436722s, 16.0285 runs/s, 38.9264 assertions/s.
7 runs, 17 assertions, 0 failures, 0 errors, 0 skips

5.4.2 ユーザー登録用URL<演習>
1. もしまだ5.4.1.1の演習に取り掛かっていなければ、まずはリスト 5.41のように変更し、名前付きルートsignup_pathを使えるようにしてください。また、リスト 5.43で名前付きルートが使えるようになったので、現時点でテストが greenになっていることを確認してください。
前のテストで確認済み

2. 先ほどのテストが正しく動いていることを確認するため、signupルートの部分をコメントアウトし、テスト redになることを確認してください。確認できたら、コメントアウトを解除して 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'
end
$ rails t
Error:
SiteLayoutTest#test_layout_links:
ActionView::Template::Error: undefined local variable or method `signup_path' for #<#<Class:0x00007fa752129e58>:0x00007fa751440ea8>
    app/views/static_pages/home.html.erb:10:in `_app_views_static_pages_home_html_erb__1372035262442598662_70178306610940'
    test/integration/site_layout_test.rb:6:in `block in <class:SiteLayoutTest>'
### [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'
end
$ rails t
Finished in 0.380467s, 18.3984 runs/s, 44.6819 assertions/s.
7 runs, 17 assertions, 0 failures, 0 errors, 0 skips

3. リスト 5.32の統合テストにsignupページにアクセスするコードを追加してください (getメソッドを使います)。コードを追加したら実際にテストを実行し、結果が正しいことを確認してください。ヒント: リスト 5.36で紹介したfull_titleヘルパーを使ってみてください。
【解答】以下の通り。

### [test/integration/site_layout_test.rb]
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest

  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
    get contact_path
    assert_select "title", full_title("Contact")
    get signup_path
  end
end
$ rails test:integration
Finished in 0.453475s, 2.2052 runs/s, 13.2312 assertions/s.
1 runs, 6 assertions, 0 failures, 0 errors, 0 skips

【Ruby on Railsチュートリアル(第4版)】第4章 Rails風味のRuby(演習と解答)

はじめに

このブログ記事は、私(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)

演習と解答

4.2.2 文字列<演習>
1. city変数に適当な市区町村を、prefecture変数に適当な都道府県を代入してください。
【解答】以下の通り。

irb(main):008:0> city = "Tama"
=> "Tama"
irb(main):009:0> prefecture = "Tokyo"
=> "Tokyo"
irb(main):010:0>

2. 先ほど作った変数と式展開を使って、「東京都 新宿区」のような住所の文字列を作ってみましょう。出力にはputsを使ってください。
【解答】以下の通り。

irb(main):005:0> puts "#{prefecture}都 #{city}市"
Tokyo都 Tama市
=> nil
irb(main):006:0>

3. 上記の文字列の間にある半角スペースをタブに置き換えてみてください。(ヒント: 改行文字と同じで、タブも特殊文字です)
【解答】以下の通り。

irb(main):006:0> puts "#{prefecture}都\t#{city}市"
Tokyo都    Tama市
=> nil
irb(main):007:0>

4. タブに置き換えた文字列を、ダブルクォートからシングルクォートに置き換えてみるとどうなるでしょうか?
【解答】以下の通り。

irb(main):007:0> puts '#{prefecture}都\t#{city}市'
#{prefecture}都\t#{city}市
=> nil
irb(main):008:0>

4.2.3 オブジェクトとメッセージ受け渡し<演習>
1. "racecar" の文字列の長さはいくつですか? lengthメソッドを使って調べてみてください。
【解答】以下の通り。

irb(main):013:0> "racecar".length
=> 7
irb(main):014:0>

2. reverseメソッドを使って、"racecar"の文字列を逆から読むとどうなるか調べてみてください。
【解答】以下の通り。

irb(main):014:0> "racecar".reverse
=> "racecar"
irb(main):015:0>

3. 変数sに "racecar" を代入してください。その後、比較演算子 (==) を使って変数sとs.reverseの値が同じであるかどうか、調べてみてください。
【解答】以下の通り。

irb(main):007:0> s = "racecar"
=> "racecar"
irb(main):008:0> if s == s.reverse
irb(main):009:1> "同じ"
irb(main):010:1> else
irb(main):011:1> "違う"
irb(main):012:1> end
=> "同じ"
irb(main):013:0>

4. リスト 4.9を実行すると、どんな結果になるでしょうか? 変数sに "onomatopoeia" という文字列を代入するとどうなるでしょうか? ヒント: 上矢印 (またはCtrl-Pコマンド) を使って以前に使ったコマンドを再利用すると一からコマンドを全部打ち込む必要がなくて便利ですよ。)
【解答】以下の通り。

irb(main):033:0> puts "It's a palindrome!" if s == s.reverse
It's a palindrome!
=> nil
irb(main):034:0> s = "onomatopoeia"
=> "onomatopoeia"
irb(main):035:0> puts "It's a palindrome!" if s == s.reverse
=> nil
irb(main):036:0>

4.2.4 メソッドの定義<演習>
1. リスト 4.10のFILL_INの部分を適切なコードに置き換え、回文かどうかをチェックするメソッドを定義してみてください。ヒント: リスト 4.9の比較方法を参考にしてください。
【解答】以下の通り。

irb(main):049:0> def palindrome_tester(s)
irb(main):050:1>  if s == s.reverse
irb(main):051:2>   puts "It's a palindrome!"
irb(main):052:2>  else
irb(main):053:2>   puts "It's not a palindrome."
irb(main):054:2>  end
irb(main):055:1> end
=> :palindrome_tester
irb(main):056:0>

2. 上で定義したメソッドを使って “racecar” と “onomatopoeia” が回文かどうかを確かめてみてください。1つ目は回文である、2つ目は回文でない、という結果になれば成功です。
【解答】以下の通り。

irb(main):057:0> puts palindrome_tester("racecar")
It's a palindrome!
=> nil
irb(main):058:0> puts palindrome_tester("onomatopoeia")
It's not a palindrome.
=> nil
irb(main):059:0>

3. palindrome_tester("racecar")に対してnil?メソッドを呼び出し、戻り値がnilであるかどうかを確認してみてください (つまりnil?を呼び出した結果がtrueであることを確認してください)。このメソッドチェーンは、nil?メソッドがリスト 4.10の戻り値を受け取り、その結果を返しているという意味になります。
【解答】以下の通り。

irb(main):059:0> puts palindrome_tester("racecar").nil?
It's a palindrome!
true
=> nil
irb(main):060:0>

4.3.1 配列と範囲演算子<演習>
1. 文字列 “A man, a plan, a canal, Panama” を ", " で分割して配列にし、変数aに代入してみてください。
【解答】以下の通り。

irb(main):019:0> a = "A man, a plan, a canal, Panama".split(',')
=> ["A man", " a plan", " a canal", " Panama"]
irb(main):020:0>

2. 今度は、変数aの要素を連結した結果 (文字列) を、変数sに代入してみてください。
【解答】以下の通り。

irb(main):020:0> s = a.join
=> "A man a plan a canal Panama"
irb(main):021:0>

3. 変数sを半角スペースで分割した後、もう一度連結して文字列にしてください (ヒント: メソッドチェーンを使うと1行でもできます)。リスト4.10で使った回文をチェックするメソッドを使って、(現状ではまだ) 変数sが回文ではないことを確認してください。downcaseメソッドを使って、s.downcaseは回文であることを確認してください。
【解答】以下の通り。

irb(main):024:0> s = s.split(' ').join
=> "AmanaplanacanalPanama"
irb(main):025:0>

irb(main):098:0> def palindrome_tester(s)
irb(main):099:1>   if s == s.reverse
irb(main):100:2>     puts "It's a palindrome!"
irb(main):101:2>   else
irb(main):102:2>     puts "It's not a palindrome."
irb(main):103:2>   end
irb(main):104:1> end
=> :palindrome_tester
irb(main):105:0> palindrome_tester(s)
It's a palindrome!
=> nil
irb(main):106:0>

4. aからzまでの範囲オブジェクトを作成し、7番目の要素を取り出してみてください。同様にして、後ろから7番目の要素を取り出してみてください。(ヒント: 範囲オブジェクトを配列に変換するのを忘れないでください)
【解答】以下の通り。

=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
irb(main):006:0> obj[6]
=> "g"
irb(main):007:0> obj[-7]
=> "t"
irb(main):008:0>

4.3.2 ブロック<演習>
1. 範囲オブジェクト0..16を使って、各要素の2乗を出力してください。
【解答】以下の通り。

irb(main):010:0> (0..16).map { |i| i**2 }
=> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256]
irb(main):011:0>

2. yeller (大声で叫ぶ) というメソッドを定義してください。このメソッドは、文字列の要素で構成された配列を受け取り、各要素を連結した後、大文字にして結果を返します。例えばyeller([’o’, ’l’, ’d’])と実行したとき、"OLD"という結果が返ってくれば成功です。ヒント: mapとupcaseとjoinメソッドを使ってみましょう。
【解答】以下の通り。

irb(main):016:0> def yeller(a)
irb(main):017:1>   a.map(&:upcase).join
irb(main):018:1> end
=> :yeller
irb(main):019:0> yeller(['o','l','d'])
=> "OLD"
irb(main):020:0>

3. random_subdomainというメソッドを定義してください。このメソッドはランダムな8文字を生成し、文字列として返します。ヒント: サブドメインを作るときに使ったRubyコードをメソッド化したものです。
【解答】以下の通り。

irb(main):029:0> def random_subdomain
irb(main):030:1>   8.times { print ('A'..'Z').to_a[rand(26)] }
irb(main):031:1> end
=> :random_sobdomain
irb(main):032:0> random_subdomain
XKIANEHV=> 8
irb(main):033:0>

4. リスト 4.12の「?」の部分を、それぞれ適切なメソッドに置き換えてみてください。ヒント:split、shuffle、joinメソッドを組み合わせると、メソッドに渡された文字列 (引数) をシャッフルさせることができます。
【解答】以下の通り。

irb(main):033:0> def string_shuffle(s)
irb(main):034:1>   s.split('').shuffle.join
irb(main):035:1> end
=> :string_shuffle
irb(main):036:0> string_shuffle("foobar")
=> "broofa"
irb(main):037:0>

4.3.3 ハッシュとシンボル<演習>
1. キーが’one’、’two’、’three’となっていて、それぞれの値が’uno’、’dos’、’tres’となっているハッシュを作ってみてください。その後、ハッシュの各要素をみて、それぞれのキーと値を"’#{key}’のスペイン語は’#{value}’"といった形で出力してみてください。
【解答】以下の通り。

irb(main):017:0> h = { one: "uno", two: "dos", three: "tres" }
=> {:one=>"uno", :two=>"dos", :three=>"tres”}
irb(main):018:0> h.each do |key, value|
irb(main):019:1*  puts "#{key}のスペイン語は#{value}"
irb(main):020:1> end
oneのスペイン語はuno
twoのスペイン語はdos
threeのスペイン語はtres
=> {:one=>"uno", :two=>"dos", :three=>"tres"}
irb(main):021:0>

2. person1、person2、person3という3つのハッシュを作成し、それぞれのハッシュに:firstと:lastキーを追加し、適当な値 (名前など) を入力してください。その後、次のようなparamsというハッシュのハッシュを作ってみてください。1.) キーparams[:father]の値にperson1を代入、2). キーparams[:mother]の値にperson2を代入、3). キーparams[:child]の値にperson3を代入。最後に、ハッシュのハッシュを調べていき、正しい値になっているか確かめてみてください。(例えばparams[:father][:first]がperson1[:first]と一致しているか確かめてみてください)
【解答】以下の通り。

irb(main):041:0> person1 = { first:'tanaka', last:'tarou' }
=> {:first=>"tanaka", :last=>"tarou"}
irb(main):042:0> person2 = { first:'yamada', last:'takashi' }
=> {:first=>"yamada", :last=>"takashi"}
irb(main):043:0> person3 = { first:'fukuda', last:'shinya' }
=> {:first=>"fukuda", :last=>"shinya"}
irb(main):044:0> params = { father:person1, mother:person2, child:person3 }
=> {:father=>{:first=>"tanaka", :last=>"tarou"}, :mother=>{:first=>"yamada", :last=>"takashi"}, :child=>{:first=>"fukuda", :last=>"shinya"}}
irb(main):045:0> params[:father][:first]
=> "tanaka"
irb(main):046:0> person1[:first]
=> "tanaka"
irb(main):047:0>

3. userというハッシュを定義してみてください。このハッシュは3つのキー:name、:email、:password_digestを持っていて、それぞれの値にあなたの名前、あなたのメールアドレス、そして16文字からなるランダムな文字列が代入されています。
【解答】以下の通り。

irb(main):001:0> user = { name:"Kodak", email:"Kodak@email.com", password_digest:"0123456789101112" }
=> {:name=>"Kodak", :email=>"Kodak@email.com", :password_digest=>"0123456789101112"}
irb(main):002:0>

4. Ruby API (訳注: もしくはるりまサーチ) を使って、Hashクラスのmergeメソッドについて調べてみてください。次のコードを実行せずに、どのような結果が返ってくるか推測できますか? 推測できたら、実際にコードを実行して推測があっていたか確認してみましょう。
【解答】以下の通り。

irb(main):047:0> { "a"=>100, "b"=>200 }.merge({ "b"=>300 }) #{"a"=>100, "b"=>300}という解が出力されると推測。
=> {"a"=>100, "b"=>300}
irb(main):048:0>

4.4.1 コンストラクタ<演習>
1. 1から10の範囲オブジェクトを生成するリテラルコンストラクタは何でしたか? (復習です)
【解答】以下の通り。

irb(main):006:0> a = 1..10
=> 1..10

2. 今度はRangeクラスとnewメソッドを使って、1から10の範囲オブジェクトを作ってみてください。ヒント: newメソッドに2つの引数を渡す必要があります
【解答】以下の通り。

irb(main):007:0> range1 = Range.new(1, 10)
=> 1..10

3. 比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。
【解答】以下の通り。

irb(main):006:0> a = 1..10
=> 1..10
irb(main):007:0> range1 = Range.new(1, 10)
=> 1..10
irb(main):008:0> range1 == a
=> true
irb(main):009:0>

4.4.2 クラス継承<演習>
1. Rangeクラスの継承階層を調べてみてください。同様にして、HashとSymbolクラスの継承階層も調べてみてください。
【解答】以下の通り。

irb(main):011:0> s = Range.new(1,10)
=> 1..10
irb(main):012:0> s.class
=> Range
irb(main):013:0> s.class.superclass
=> Object
irb(main):014:0> s.class.superclass.superclass
=> BasicObject
irb(main):015:0> s.class.superclass.superclass.superclass
=> nil
irb(main):016:0>

2. リスト 4.15にあるself.reverseのselfを省略し、reverseと書いてもうまく動くことを確認してみてください。 【解答】以下の通り。

irb(main):018:0> class Word < String
irb(main):019:1>   # 文字列が回文であればtrueを返す
irb(main):020:1>   def palindrome?
irb(main):021:2>     self == reverse
irb(main):022:2>   end
irb(main):023:1> end
=> :palindrome?
irb(main):024:0>
irb(main):025:0> b = Word.new("test")
=> "test"
irb(main):026:0> b.palindrome?
=> false
irb(main):027:0> c = Word.new("teet")
=> "teet"
irb(main):028:0> c.palindrome?
=> true
irb(main):029:0>

4.4.3 組み込みクラスの変更<演習>
1. palindrome?メソッドを使って、“racecar”が回文であり、“onomatopoeia”が回文でないことを確認してみてください。南インドの言葉「Malayalam」は回文でしょうか? ヒント: downcaseメソッドで小文字にすることを忘れないで。
【解答】以下の通り。

irb(main):029:0> class String
irb(main):030:1>   # 文字列が回文であればtrueを返す
irb(main):031:1>   def palindrome?
irb(main):032:2>     self == self.reverse
irb(main):033:2>   end
irb(main):034:1> end
=> :palindrome?
irb(main):035:0> "racecar".palindrome?
=> true
irb(main):036:0> "onomatopoeia".palindrome?
=> false
irb(main):037:0> "Malayalam".downcase.palindrome?
=> true
irb(main):038:0>

2. リスト 4.16を参考に、Stringクラスにshuffleメソッドを追加してみてください。ヒント: リスト 4.12も参考になります。
【解答】以下の通り。

irb(main):056:0> class String
irb(main):057:1>   # 文字列をShuffleする
irb(main):058:1>   def shuffle
irb(main):059:2>     self.split('').shuffle.join
irb(main):060:2>   end
irb(main):061:1> end
=> :shuffle
irb(main):062:0> "racecar".shuffle
=> "aarrcce"
irb(main):063:0>

3. リスト 4.16のコードにおいて、self.を削除してもうまく動くことを確認してください。
【解答】以下の通り。

irb(main):063:0> class String
irb(main):064:1>   # 文字列をShuffleする
irb(main):065:1>   def shuffle
irb(main):066:2>     split('').shuffle.join
irb(main):067:2>   end
irb(main):068:1> end
=> :shuffle
irb(main):069:0> "racecar".shuffle
=> "arcacer"
irb(main):070:0>

4.4.4 コントロールクラス<演習>
1. 第2章で作ったToyアプリケーションのディレクトリでRailsコンソールを開き、User.newと実行することでuserオブジェクトが生成できることを確認してみましょう。
【解答】以下の通り。

irb(main):001:0> u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):002:0>

2. 生成したuserオブジェクトのクラスの継承階層を調べてみてください。
【解答】以下の通り。

irb(main):008:0>
irb(main):009:0> u.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
irb(main):010:0> u.class.superclass
=> ApplicationRecord(abstract)
irb(main):011:0> u.class.superclass.superclass
=> ActiveRecord::Base
irb(main):012:0> u.class.superclass.superclass.superclass
=> Object
irb(main):013:0> u.class.superclass.superclass.superclass.superclass
=> BasicObject
irb(main):014:0> u.class.superclass.superclass.superclass.superclass.superclass
=> nil
irb(main):015:0>

4.4.5 ユーザークラス<演習>
1. Userクラスで定義されているname属性を修正して、first_name属性とlast_name属性に分割してみましょう。また、それらの属性を使って "Michael Hartl" といった文字列を返すfull_nameメソッドを定義してみてください。最後に、formatted_emailメソッドのnameの部分を、full_nameに置き換えてみましょう (元々の結果と同じになっていれば成功です)
【解答】以下の通り。

irb(main):001:0> require './example_user'
=> true
irb(main):002:0> example = User.new
=> #<User:0x00007fde00408e78 @first_name=nil, @last_name=nil, @email=nil>
irb(main):003:0> example.first_name
=> nil
irb(main):004:0> example.last_name
=> nil
irb(main):005:0> example.email
=> nil
irb(main):006:0> example.first_name = "Michael"
=> "Michael"
irb(main):007:0> example.last_name = "Hartl"
=> "Hartl"
irb(main):008:0> example.email = "user@example.com"
=> "user@example.com"
irb(main):009:0> example.formatted_email
=> "Michael Hartl <user@example.com>"
irb(main):010:0>

2. "Hartl, Michael" といったフォーマット (苗字と名前がカンマ+半角スペースで区切られている文字列) で返すalphabetical_nameメソッドを定義してみましょう。
【解答】以下の通り。

irb(main):010:0> example.alphabetical_name
=> "Hartl, Michael"
irb(main):011:0>

3. full_name.splitとalphabetical_name.split(’, ’).reverseの結果を比較し、同じ結果になるかどうか確認してみましょう。
【解答】以下の通り。

irb(main):011:0> example.full_name.split
=> ["Michael", "Hartl"]
irb(main):012:0> example.alphabetical_name.split(', ').reverse
=> ["Michael", "Hartl"]
irb(main):013:0>

【Ruby on Railsチュートリアル(第4版)】第3章 ほぼ静的なページの作成(演習と解答)

はじめに

このブログ記事は、私(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)

演習と解答

3.1 セットアップ<演習>
1. BitbucketがMarkdown記法のREADME (リスト 3.3) をHTMLとして正しく描画しているか、確認してみてください。
【解答】以下の通り。
f:id:special-moucom:20180707200309p:plain

2. 本番環境 (Heroku) のルートURLにアクセスして、デプロイが成功したかどうか確かめてみてください。
実機操作のため割愛。


3.2.1 静的なページの生成<演習>
1. Fooというコントローラを生成し、その中にbarとbazアクションを追加してみてください。
【解答】以下の通り。

$ rails g controller Foo bar baz
      create  app/controllers/foo_controller.rb
       route  get 'foo/baz'
       route  get 'foo/bar'
      invoke  erb
      create    app/views/foo
      create    app/views/foo/bar.html.erb
      create    app/views/foo/baz.html.erb
      invoke  test_unit
      create    test/controllers/foo_controller_test.rb
      invoke  helper
      create    app/helpers/foo_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/foo.coffee
      invoke    scss
      create      app/assets/stylesheets/foo.scss
$

2. コラム 3.1で紹介したテクニックを駆使して、Fooコントローラとそれに関連するアクションを削除してみてください。
【解答】以下の通り。

$ rails d controller Foo bar baz
      remove  app/controllers/foo_controller.rb
       route  get 'foo/baz'
       route  get 'foo/bar'
      invoke  erb
      remove    app/views/foo
      remove    app/views/foo/bar.html.erb
      remove    app/views/foo/baz.html.erb
      invoke  test_unit
      remove    test/controllers/foo_controller_test.rb
      invoke  helper
      remove    app/helpers/foo_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      remove      app/assets/javascripts/foo.coffee
      invoke    scss
      remove      app/assets/stylesheets/foo.scss
$

3.4.2 タイトルを追加する(Green)<演習>
1. StaticPagesコントローラのテスト (リスト 3.24) には、いくつか繰り返しがあったことにお気づきでしょうか? 特に「Ruby on Rails Tutorial Sample App」という基本タイトルは、各テストで毎回同じ内容を書いてしまっています。そこで、setupという特別なメソッド (各テストが実行される直前で実行されるメソッド) を使って、この問題を解決したいと思います。まずは、リスト 3.30のテストが green になることを確認してみてください (リスト 3.30では、2.2.2で少し触れたインスタンス変数や文字列の式展開というテクニックを使っています。それぞれ4.4.5と4.2.2で詳しく解説するので、今はわからなくても問題ありません)。
【解答】以下の通り。

### [static_pages_controller_test.rb]

require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest
  # setupメソッドの追加
  def setup
    @base_title = "Ruby on Rails Tutorial Sample App"
  end

  test "should get contact" do
    get static_pages_contact_url
    assert_response :success
    assert_select "title", "Contact|#{@base_title}"
  end
end
### [テストの実行]

$ rails t
/Users/Kodak/00_myfolder/00_devlop/03_Ruby_on_Rails/rails-tutorial/sample_app/db/schema.rb doesn't exist yet. Run `rails db:migrate` to create it, then try again. If you do not intend to use a database, you should instead alter /Users/Kodak/00_myfolder/00_devlop/03_Ruby_on_Rails/rails-tutorial/sample_app/config/application.rb to limit the frameworks that will be loaded.
Run options: --seed 19048

# Running:

Run options: --seed 19048

# Running:

・
・
・

# 正常終了した事を確認
Finished in 0.350625s, 11.4082 runs/s, 22.8164 assertions/s.
4 runs, 8 assertions, 0 failures, 0 errors, 0 skips



Finished in 0.370653s, 10.7918 runs/s, 21.5835 assertions/s.
4 runs, 8 assertions, 0 failures, 0 errors, 0 skips
$

3.4.3 レイアウトと埋め込みRuby(Refactor)<演習>
1. サンプルアプリケーションにContact (問い合わせ先) ページを作成してください (ヒント: まずはリスト 3.15を参考にして、/static_pages/contactというURLのページに「Contact | Ruby on Rails Tutorial Sample App」というタイトルが存在するかどうかを確認するテストを最初に作成しましょう。 次に、3.3.3でAboutページを作ったときのと同じように、Contactページにもリスト 3.40のコンテンツを表示してみましょう。)。
【解答】以下の通り。

### [test/controllers/static_pages_controller_test.rb]
require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  def setup
    @base_title = "Ruby on Rails Tutorial Sample App"
  end
  # contactのテストを作成
  test "should get contact" do
    get static_pages_contact_url
    assert_response :success
    assert_select "title", "Contact|#{@base_title}"
  end

end
<!-- ### [app/views/static_pages/contact.html.erb] -->
<% provide(:title, "Contact") %>
<h1>Contact</h1>
<p>
Contact the Ruby on Rails Tutorial about the sample app at the
<a href="https://railstutorial.jp/contact">contact page</a>.
</p>
### [app/controllers/static_pages_controller.rb]
class StaticPagesController < ApplicationController

  def contact
  end

end
### [config/routes.rb]
Rails.application.routes.draw do
  get 'static_pages/contact'

  get 'static_pages/home'
  get 'static_pages/help'
  get 'static_pages/about'
  root 'application#hello'
end

3.4.4 ルーティングの設定<演習>
1. リスト 3.41にrootルーティングを追加したことで、root_urlというRailsヘルパーが使えるようになりました (以前、static_pages_home_urlが使えるようになったときと同じです)。リスト 3.42のFILL_INと記された部分を置き換えて、rootルーティングのテストを書いてみてください。
【解答】以下の通り。

### [test/controllers/static_pages_controller_test.rb]
require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  def setup
    @base_title = "Ruby on Rails Tutorial Sample App"
  end
  # root_urlに置き換え
  test "should get root" do
    get root_url
    assert_response :success
    assert_select "title", "Contact|#{@base_title}"
  end

end

2. 実はリスト 3.41のコードを書いていたので、先ほどの課題のテストは既に green になっているはずです。このような場合、テストを変更する前から成功していたのか、変更した後に成功するようになったのか、判断が難しいです。リスト 3.41のコードがテスト結果に影響を与えていることを確認するため、リスト 3.43のようにrootルーティングをコメントアウトして見て、 red になるかどうか確かめてみましょう (なおRubyのコメント機能については4.2.1で説明します)。最後に、コメントアウトした箇所を元に戻し (すなわちリスト 3.41に戻し)、テストが green になることを確認してみましょう。
【解答】以下の通り。

### [config/routes.rb]
Rails.application.routes.draw do
  # 以下コメントアウト
  # root 'static_pages#home'
  get 'static_pages/contact'
  get 'static_pages/home'
  get 'static_pages/help'
  get 'static_pages/about'
end
### エラーになった事を確認
Error:
StaticPagesControllerTest#test_should_get_root:
NameError: undefined local variable or method `root_url' for #<StaticPagesControllerTest:0x00007f9717099b00>
    test/controllers/static_pages_controller_test.rb:34:in `block in <class:StaticPagesControllerTest>’
### [config/routes.rb]
Rails.application.routes.draw do
  # コメントアウトをはずす
  root 'static_pages#home'
  get 'static_pages/contact'
  get 'static_pages/home'
  get 'static_pages/help'
  get 'static_pages/about'
end
### 正常終了した事を確認
Finished in 0.402751s, 12.4146 runs/s, 24.8292 assertions/s.
5 runs, 10 assertions, 0 failures, 0 errors, 0 skips