新米パパの育児留学

新米パパの育児留学

未経験からエンジニアへの転職体験談など "リアル" な情報を発信

IT/WEBエンジニアの転職(未経験/30代可あり)エージェント, サービス比較(p.s.侍の炎上を見て)
全くの未経験からIT/Webエンジニアに転職した私(30代)のロードマップ
30代未経験からIT / Webエンジニアへのリアルな転職体験談4 ”2度目の転職活動から入社へ”
本当に使えるものだけ!出産準備品・ベビー用品で実際に買ってよかったおすすめ10選
クロスバイク  LIG(リグ) MOVE 700Cの組み立て手順まとめ
Ruby初心者におすすめの学習方法「プロを目指す人のためのRuby入門」
Ruby on Rails チュートリアル 完全攻略 概要と演習解答総まとめ

【第13章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ

Ruby on Rails Tutorial最新版の演習と解答です。

GEEKLYのIT・WEB・ソーシャルゲーム業界への転職支援サービス

目的

Ruby on Rails チュートリアル 5.0(第4版)を学習中です。

学習を進める中で演習問題の解答がなかった(*1,2)ので、自分なりにまとめていくこととしました。

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

(*1)著者による有償版の解答はあるようです。正式な解答をご希望の方はこちらを参照ください。

Learn Web Development with Rails: Michael Hartl's Ruby on Rails Tutorial | Softcover.io

(*2)旧版に関する解答はありましたが、最新版 5.0(第4版)に関しては検索しても出てきませんでした。

演習問題と解答

演習13.1.1

演習13.1.1.1

<問題> RailsコンソールでMicropost.newを実行し、インスタンスを変数micropostに代入してください。その後、user_idに最初のユーザーのidを、contentに "Lorem ipsum" をそれぞれ代入してみてください。この時点では、 micropostオブジェクトのマジックカラム (created_atとupdated_at) には何が入っているでしょうか?

<解答>

>> micropost=Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>

>> micropost.user_id=1
=> 1
>> micropost.content="Lorem ipsum"
=> "Lorem ipsum"

>> micropost
=> #<Micropost id: nil, content: "Lorem ipsum", user_id: 1, created_at: nil, updated_at: nil>

マジックカラムは"nil"

演習13.1.1.2

<問題> 先ほど作ったオブジェクトを使って、micropost.userを実行してみましょう。どのような結果が返ってくるでしょうか? また、micropost.user.nameを実行した場合の結果はどうなるでしょうか?

<解答>

>> micropost.user
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-02-20 10:41:54", updated_at: "2017-02-20 10:41:54", password_digest: "$2a$10$/kQ3M3tCRo9fdhf4dvYBBezX19bWZk2SJeSkovfkJgB...", remember_digest: nil, admin: true, activation_digest: "$2a$10$gxTIacN4NX5iNR0/H3WayuLkrh7ZGJzsoJG3hSu1WAt...", activated: true, activated_at: "2017-02-20 10:41:54", reset_digest: nil, reset_sent_at: nil>

>> micropost.user.name
=> "Example User"

演習13.1.1.3

<問題> 先ほど作ったmicropostオブジェクトをデータベースに保存してみましょう。この時点でもう一度マジックカラムの内容を調べてみましょう。今度はどのような値が入っているでしょうか?

<解答>

>> micropost.save
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", 2017-02-23 07:52:35 UTC], ["updated_at", 2017-02-23 07:52:35 UTC]]
   (13.0ms)  commit transaction
=> true

マジックカラムには上記の実行日時が入る。

演習13.1.2

演習13.1.2.1

<問題> Railsコンソールを開き、user_idとcontentが空になっているmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?

<解答> 動作確認のみなので省略。

演習13.1.2.2

<問題> コンソールを開き、今度はuser_idが空でcontentが141文字以上のmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?

<解答> 動作確認のみなので省略。

演習13.1.3

演習13.1.3.1

<問題> データベースにいる最初のユーザーを変数userに代入してください。そのuserオブジェクトを使ってmicropost = user.microposts.create(content: "Lorem ipsum")を実行すると、どのような結果が得られるでしょうか?

<解答>

>> user=User.first
  User Load (1.2ms)  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-02-20 10:41:54", updated_at: "2017-02-20 10:41:54", password_digest: "$2a$10$/kQ3M3tCRo9fdhf4dvYBBezX19bWZk2SJeSkovfkJgB...", remember_digest: nil, admin: true, activation_digest: "$2a$10$gxTIacN4NX5iNR0/H3WayuLkrh7ZGJzsoJG3hSu1WAt...", activated: true, activated_at: "2017-02-20 10:41:54", reset_digest: nil, reset_sent_at: nil>

>> micropost = user.microposts.create(content: "Lorem ipsum")
   (0.1ms)  begin transaction
  SQL (2.1ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", 2017-02-23 08:21:53 UTC], ["updated_at", 2017-02-23 08:21:53 UTC]]
   (13.0ms)  commit transaction
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2017-02-23 08:21:53", updated_at: "2017-02-23 08:21:53">

演習13.1.3.2

<問題> 先ほどの演習課題で、データベース上に新しいマイクロポストが追加されたはずです。 user.microposts.find(micropost.id)を実行して、本当に追加されたのかを確かめてみましょう。また、先ほど実行したmicropost.idの部分をmicropostに変更すると、結果はどうなるでしょうか?

<解答>

>> user.microposts.find(micropost.id)
  Micropost Load (0.3ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? LIMIT ?  [["user_id", 1], ["id", 2], ["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2017-02-23 08:21:53", updated_at: "2017-02-23 08:21:53">

>> user.microposts.find(micropost)
DEPRECATION WARNING: You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`. (called from irb_binding at (irb):4)
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? LIMIT ?  [["user_id", 1], ["id", 2], ["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2017-02-23 08:21:53", updated_at: "2017-02-23 08:21:53">

micropost.idの部分をmicropostに変更するとidを指定するよう要求される

演習13.1.3.3

<問題> user == micropost.userを実行した結果はどうなるでしょうか? また、user.microposts.first == micropost を実行した結果はどうなるでしょうか? それぞれ確認してみてください。

>> user == micropost.user
=> true

>> user.microposts.first == micropost
  Micropost Load (0.4ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> false

micropost id:2なので、user.microposts.first != micropost

演習13.1.4

演習13.1.4.1

<問題> Micropost.first.created_atの実行結果と、Micropost.last.created_atの実行結果を比べてみましょう。

<解答>

>> Micropost.first.created_at
  Micropost Load (1.7ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
=> Thu, 23 Feb 2017 08:21:53 UTC +00:00

>> Micropost.last.created_at
  Micropost Load (0.4ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> Thu, 23 Feb 2017 07:52:35 UTC +00:00

演習13.1.4.2

<問題> Micropost.firstを実行したときに発行されるSQL文はどうなっているでしょうか? 同様にして、Micropost.lastの場合はどうなっているでしょうか? ヒント: それぞれをコンソール上で実行したときに表示される文字列が、SQL文になります。

<解答>

>> Micropost.first
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2017-02-23 08:21:53", updated_at: "2017-02-23 08:21:53">

>> Micropost.last
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Micropost id: 1, content: "Lorem ipsum", user_id: 1, created_at: "2017-02-23 07:52:35", updated_at: "2017-02-23 07:52:35">

Micropost.firstはDESC Micropost.lastはASC

演習13.1.4.3

<問題> データベース上の最初のユーザーを変数userに代入してください。そのuserオブジェクトが最初に投稿したマイクロポストのidはいくつでしょうか? 次に、destroyメソッドを使ってそのuserオブジェクトを削除してみてください。削除すると、そのuserに紐付いていたマイクロポストも削除されていることをMicropost.findで確認してみましょう。

<解答> 動作確認のみなので省略。

演習13.2.1

演習13.2.1.1

<問題> 7.3.3で軽く説明したように、今回ヘルパーメソッドとして使ったtime_ago_in_wordsメソッドは、Railsコンソールのhelperオブジェクトから呼び出すことができます。このhelperオブジェクトのtime_ago_in_wordsメソッドを使って、3.weeks.agoや6.months.agoを実行してみましょう。

<解答> 動作確認のみなので省略。

演習13.2.1.2

<問題> helper.time_ago_in_words(1.year.ago)と実行すると、どういった結果が返ってくるでしょうか?

<解答>

>> helper.time_ago_in_words(1.year.ago)
=> "about 1 year"

演習13.2.1.3

<問題> micropostsオブジェクトのクラスは何でしょうか? ヒント: リスト 13.23内のコードににあるように、まずはpaginateメソッド (引数はpage: nil) でオブジェクトを取得し、その後classメソッドを呼び出してみましょう。

<解答>

>> user=User.first
  User Load (0.3ms)  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-02-23 11:51:52", updated_at: "2017-02-23 11:51:52", password_digest: "$2a$10$ZN5cjKOsES4dYHA/QRijX.4wJG.cFLhUkciP2cYhROy...", remember_digest: nil, admin: true, activation_digest: "$2a$10$e7ScgLHWk1IcYzvOXRt08.KOV.YZ/z.h9QI1imwG3Ez...", activated: true, activated_at: "2017-02-23 11:51:52", reset_digest: nil, reset_sent_at: nil>

>> microposts = user.microposts.paginate(page: nil)
  Micropost Load (0.3ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ? OFFSET ?  [["user_id", 1], ["LIMIT", 30], ["OFFSET", 0]]
=> #<ActiveRecord::AssociationRelation []>

>> microposts.class
=> Micropost::ActiveRecord_AssociationRelation

演習13.2.2

演習13.2.2.1

<問題> (1..10).to_a.take(6)というコードの実行結果を推測できますか? 推測した値が合っているかどうか、実際にコンソールを使って確認してみましょう。

<解答>

>> (1..10).to_a.take(6)
=> [1, 2, 3, 4, 5, 6]

演習13.2.2.1

<問題> 先ほどの演習にあったto_aメソッドの部分は本当に必要でしょうか? 確かめてみてください。

<解答> 不要。

演習13.2.2.1

<問題> Fakerはlorem ipsum以外にも、非常に多種多様の事例に対応しています。Fakerのドキュメント (英語) を眺めながら画面に出力する方法を学び、実際に大学名や電話番号、Hipster IpsumやChuck Norris facts (参考: チャック・ノリスの真実) を画面に出力してみましょう。(訳注: もちろん日本語にも対応していて、例えば沖縄らしい用語を出力するfaker-okinawaもあります。ぜひ遊んでみてください。)

<解答> 動作確認のみなので省略。

演習13.2.3

演習13.2.3.1

<問題> リスト 13.28にある2つの’h1’行をコメントアウトしてみて、テストが green から redに変わることを確認してみましょう。

<解答> 動作確認のみなので省略。 ※実際にコメントアウトするのは"リスト 13.24"の'h1'の中の2行。

演習13.2.3.2

<問題> リスト 13.28にあるテストを変更して、will_paginateが少なくとも1度(※)は表示されていることをテストしてみましょう。ヒント: 表 5.2を参考にしてください。

※英文版を確認してみると"only once"となっており、"1度のみ"でした。おそらく誤訳かと思われます。 原文は以下。 https://www.railstutorial.org/book/user_microposts#sec-profile_micropost_tests Update Listing 13.28 to test that will_paginate appears only once. Hint: Refer to Table 5.2.

<解答>

[users_profile_test.rb]

require 'test_helper'

class UsersProfileTest < ActionDispatch::IntegrationTest
  include ApplicationHelper

  def setup
    @user = users(:michael)
  end

  test "profile display" do
    get user_path(@user)
    assert_template 'users/show'
    assert_select 'title', full_title(@user.name)
    assert_select 'h1', text: @user.name
    assert_select 'h1>img.gravatar'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination', count:1   <---注目
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end
end

演習13.3.1

演習13.3.1.1

<問題> なぜUsersコントローラ内にあるlogged_in_userフィルターを残したままにするとマズイのでしょうか? 考えてみてください。

<解答> コードが重複するため。

演習13.3.2

演習13.3.2.1

<問題> Homeページをリファクタリングして、if-else文の分岐のそれぞれに対してパーシャルを作ってみましょう。

<解答>

[home.html.erb]

<% if logged_in? %>
  <%= render 'static_pages/user_logged_in' %>
<% else %>
  <%= render 'static_pages/user_not_logged_in' %>
<% end %>

[_user_logged_in.html.erb]

  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>

[_user_not_logged_in.html.erb]

  <div class="center jumbotron">
    <h1>Welcome to the Sample App</h1>

    <h2>
      This is the home page for the
      <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </h2>

    <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
  </div>

  <%= link_to image_tag("rails.png", alt: "Rails logo"),
              'http://rubyonrails.org/' %>

演習13.3.3

演習13.3.3.1

<問題> 新しく実装したマイクロポストの投稿フォームを使って、実際にマイクロポストを投稿してみましょう。Railsサーバーのログ内にあるINSERT文では、どういった内容をデータベースに送っているでしょうか? 確認してみてください。

<解答>

 INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "aaa"], ["user_id", 1], ["created_at", 2017-02-24 01:41:51 UTC], ["updated_at", 2017-02-24 01:41:51 UTC]]

演習13.3.3.2

<問題> コンソールを開き、user変数にデータベース上の最初のユーザーを代入してみましょう。その後、Micropost.where("user_id = ?", user.id)とuser.microposts、そしてuser.feedをそれぞれ実行してみて、実行結果がすべて同じであることを確認してみてください。ヒント: ==で比較すると結果が同じかどうか簡単に判別できます。

<解答> 動作確認のみなので省略。

演習13.3.4

演習13.3.4.1

<問題> マイクロポストを作成し、その後、作成したマイクロポストを削除してみましょう。次に、Railsサーバーのログを見てみて、DELETE文の内容を確認してみてください。

<解答> 動作確認のみなので省略。

演習13.3.4.2

<問題> redirect_to request.referrer || root_urlの行をredirect_back(fallback_location: root_url)と置き換えてもうまく動くことを、ブラウザを使って確認してみましょう (このメソッドはRails 5から新たに導入されました)。

<解答> 動作確認のみなので省略。

演習13.3.5

演習13.3.5.1

<問題> リスト 13.55で示した4つのコメント (「無効な送信」など) のそれぞれに対して、テストが正しく動いているか確認してみましょう。具体的には、対応するアプリケーション側のコードをコメントアウトし、テストが redになることを確認し、元に戻すと greenになることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習13.3.5.2

<問題> サイドバーにあるマイクロポストの合計投稿数をテストしてみましょう。このとき、単数形 (micropost) と複数形 (microposts) が正しく表示されているかどうかもテストしてください。ヒント: リスト 13.57を参考にしてみてください。

<解答>

[microposts_interface_test.rb]

require 'test_helper'

class MicropostsInterfaceTest < ActionDispatch::IntegrationTest

(中略)

  test "micropost sidebar count" do
    log_in_as(@user)
    get root_path
    assert_match "#{@user.microposts.count} microposts", response.body   <---注目
    # まだマイクロポストを投稿していないユーザー
    other_user = users(:malory)
    log_in_as(other_user)
    get root_path
    assert_match "0 microposts", response.body
    other_user.microposts.create!(content: "A micropost")
    get root_path
    assert_match "1 micropost", response.body   <---注目
  end
end

演習13.4.1

演習13.4.1.1

<問題> 画像付きのマイクロポストを投稿してみましょう。もしかして、大きすぎる画像が表示されてしまいましたか? (心配しないでください、次の13.4.3でこの問題を直します)。

<解答> 動作確認のみなので省略。

演習13.4.1.2

<問題> リスト 13.63に示すテンプレートを参考に、13.4で実装した画像アップローダーをテストしてください。テストの準備として、まずはサンプル画像をfixtureディレクトリに追加してください (コマンド例: cp app/assets/images/rails.png test/fixtures/)。リスト 13.63で追加したテストでは、Homeページにあるファイルアップロードと、投稿に成功した時に画像が表示されているかどうかをチェックしています。なお、テスト内にあるfixture_file_uploadというメソッドは、fixtureで定義されたファイルをアップロードする特別なメソッドです16。ヒント: picture属性が有効かどうかを確かめるときは、11.3.3で紹介したassignsメソッドを使ってください。このメソッドを使うと、投稿に成功した後にcreateアクション内のマイクロポストにアクセスするようになります。

<解答>

[microposts_interface_test.rb]

require 'test_helper'

class MicropostsInterfaceTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'div.pagination'
    assert_select 'input[type="file"]'   <---注目
    # 無効な送信
    post microposts_path, micropost: { content: "" }
    assert_select 'div#error_explanation'
    # 有効な送信
    content = "This micropost really ties the room together"
    picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
    assert_difference 'Micropost.count', 1 do
      post microposts_path, micropost: { content: content, picture: picture }   <---注目
    end
    assert assigns(:micropost).picture?   <---注目
    follow_redirect!
    assert_match content, response.body
    # 投稿を削除する
    assert_select 'a', text: 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end
    # 違うユーザーのプロフィールにアクセス (削除リンクがないことを確認)
    get user_path(users(:archer))
    assert_select 'a', text: 'delete', count: 0
  end

(中略)

end

演習13.4.2

演習13.4.2.1

<問題> 5MB以上の画像ファイルを送信しようとした場合、どうなりますか?

<解答> エラーが出る。

演習13.4.2.2

<問題> 無効な拡張子のファイルを送信しようとした場合、どうなりますか?

<解答> エラーが出る。

演習13.4.3

演習13.4.3.1

<問題> 解像度の高い画像をアップロードし、リサイズされているかどうか確認してみましょう。画像が長方形だった場合、リサイズはうまく行われているでしょうか?

<解答> 確認は省略。長方形の場合は、長い辺が400pxとなるようにリサイズされる。

演習13.4.3.2

<問題> 既にリスト 13.63のテストを追加していた場合、この時点でテストスイートを走らせるとエラーメッセージが表示されるようになるはずです。このエラーを取り除いてみましょう。ヒント: リスト 13.68にある設定ファイルを修正し、テスト時はCarrierWaveに画像のリサイズをさせないようにしてみましょう。

<解答> リスト 13.68参照。

演習13.4.4

演習13.4.4.1

<問題> 本番環境で解像度の高い画像をアップロードし、適切にリサイズされているか確認してみましょう。長方形の画像であっても、適切にリサイズされていますか?

<解答> 動作確認のみなので省略。

GEEKLYのIT・WEB・ソーシャルゲーム業界への転職支援サービス

あわせて読みたい記事

mochikichi.hatenablog.com mochikichi.hatenablog.com mochikichi.hatenablog.com

おすすめの本

この本は、初心者にもわかりやすいとエンジニア界隈ではかなり著名な"伊藤 淳一"さんの本です。 私もいつもQiitaやブログでお世話になっており、わかりやすい解説に信頼を寄せていたので購入しました。

期待通り、いや、期待以上にわかりやすく丁寧な解説でしたのでかなり理解が深まったと思います。 私は参考書を読むのは退屈で頭に入ってこないタイプなのですが、わかりやすい解説に加えて実際に手を動かして理解を深められる例題が用意されているので楽しくサクサク進めていけます。

Rubyの基礎はProgateやドットインストールなどで理解した上で読まれると更に理解が深まると思います。 RailsスキルをレベルアップしていくためにもRubyの理解を深めることは非常に効果的です。 Railsチュートリアルを完了した方や、進めている途中の方で"Rubyのステップアップとしての次の一冊"をお探しの方におすすめです。