LoginSignup
1

More than 5 years have passed since last update.

35歳だけどRailsチュートリアルやってみた。[第4版 13章 13.2 マイクロポストを表示する まとめ&解答例]

Last updated at Posted at 2017-08-16

はじめに

最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。

このままだとプログラムがまったく書けない人になってしまう危機感(迫り来る35歳定年説)と、
新しいことに挑戦したいという思いから、
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版を学習中です。
業務で使うのはもっぱらJavaなのですが、Rails楽しいですね。

これまでEvernoteに記録していましたが、ソースコードの貼付けに限界を感じたため、
Qiitaで自分が学習した結果をアウトプットしていきます。

個人の解答例なので、誤りがあればご指摘ください。

動作環境

  • cloud9
  • ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
  • Rails 5.0.0.1

13.2.1 マイクロポストの描画

本章での学び

【事前準備】サンプルデータのリセット

yokoyan:~/workspace/sample_app (user-microposts) $ rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20170417215343 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0019s
== 20170417215343 CreateUsers: migrated (0.0021s) =============================

== 20170423125906 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
   -> 0.0011s
== 20170423125906 AddIndexToUsersEmail: migrated (0.0017s) ====================

== 20170424041610 AddPasswordDigestToUsers: migrating =========================
-- add_column(:users, :password_digest, :string)
   -> 0.0008s
== 20170424041610 AddPasswordDigestToUsers: migrated (0.0008s) ================

== 20170514215307 AddRememberDigestToUsers: migrating =========================
-- add_column(:users, :remember_digest, :string)
   -> 0.0006s
== 20170514215307 AddRememberDigestToUsers: migrated (0.0007s) ================

== 20170614215124 AddAdminToUsers: migrating ==================================
-- add_column(:users, :admin, :boolean, {:default=>false})
   -> 0.0016s
== 20170614215124 AddAdminToUsers: migrated (0.0017s) =========================

== 20170618221238 AddActivationToUsers: migrating =============================
-- add_column(:users, :activation_digest, :string)
   -> 0.0007s
-- add_column(:users, :activated, :boolean, {:dafault=>false})
   -> 0.0004s
-- add_column(:users, :activated_at, :datetime)
   -> 0.0005s
== 20170618221238 AddActivationToUsers: migrated (0.0023s) ====================

== 20170703042006 AddResetToUsers: migrating ==================================
-- add_column(:users, :reset_digest, :string)
   -> 0.0007s
-- add_column(:users, :reset_sent_at, :datetime)
   -> 0.0003s
== 20170703042006 AddResetToUsers: migrated (0.0016s) =========================

== 20170719214241 CreateMicroposts: migrating =================================
-- create_table(:microposts)
   -> 0.0016s
-- add_index(:microposts, [:user_id, :created_at])
   -> 0.0009s
== 20170719214241 CreateMicroposts: migrated (0.0032s) ========================
yokoyan:~/workspace/sample_app (user-microposts) $ rails db:seed

【事前準備】Micropostsコントローラの生成

yokoyan:~/workspace/sample_app (user-microposts) $ rails generate controller Microposts
Running via Spring preloader in process 2577
Expected string default value for '--jbuilder'; got true (boolean)
Expected string default value for '--helper'; got true (boolean)
Expected string default value for '--assets'; got true (boolean)
      create  app/controllers/microposts_controller.rb
      invoke  erb
      create    app/views/microposts
      invoke  test_unit
      create    test/controllers/microposts_controller_test.rb
      invoke  helper
      create    app/helpers/microposts_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/microposts.coffee
      invoke    scss
      create      app/assets/stylesheets/microposts.scss

【事前準備】micropostパーシャルの作成

yokoyan:~/workspace/sample_app (user-microposts) $ pwd
/home/ubuntu/workspace/sample_app
yokoyan:~/workspace/sample_app (user-microposts) $ touch app/views/microposts/_micropost.html.erb

【View】micropostパーシャルの実装

マイクロポストの内容を一覧表示するパーシャルを作成する。
将来的な拡張を見越して、マイクロポスト毎にidを割り振っている。
はじめて出てきた、time_ago_in_wordsは、ActionViewのヘルパーメソッド
「3分前に投稿」というような文字列を返すらしい。

/sample_app/app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  </span>
</li>

【controller】ページング処理の実装のための事前準備

Usersコントローラから、ユーザのページングを行うのであれば、
ユーザープロフィール画面(View)のwill_paginateの引数に@usersを明示的に渡す必要はない。

今回は、Usersコントローラから、マイクロポストのページング処理を行うため、
同画面(View)のwill_paginateの引数に@microposts変数をwill_paginateに明示的に渡す必要がある。
Usersコントローラにて、@microposts変数を定義する。

/sample_app/app/controllers/users_controller.rb
  def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page])
    redirect_to root_url and return unless @user.activated?
    #debugger
  end

【View】ユーザープロフィール画面にマイクロポストを表示する

/sample_app/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 %>
        <%= @user.name %>
      </h1>
    </section>
  </aside>
  <div class="col-md-8">
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>

演習1

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

>> helper.time_ago_in_words(3.weeks.ago)
=> "21 days"
>> 
?> helper.time_ago_in_words(6.month.ago)
=> "6 months"

演習2

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

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

演習3

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

?> user = User.first
  User Load (0.4ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-07-25 21:45:26", updated_at: "2017-07-25 21:45:26", password_digest: "$2a$10$G03Oh9yI0VMHnGF38MA3iOkoHiLtJ2qnuQ8whUZ8lEp...", remember_digest: nil, admin: true, activation_digest: "$2a$10$M9ShPLhv8mtQE6Yb4.BDMeTjvoWnGI1xfeEAvka1I7Z...", activated: true, activated_at: "2017-07-25 21:45:26", reset_digest: nil, reset_sent_at: nil>
>> 
?> microposts = user.microposts.paginate(page: nil)
  Micropost Load (0.2ms)  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 マイクロポストのサンプル

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

本章での学び

【事前準備】サンプルデータにマイクロポストを追加する

サンプルユーザのうち、IDが小さい順に6人呼び出して、50件のマイクロポストを登録する。

ちなみに、lorem ipsumとは、意味のない典型的なダミーテキストのこと。
wiki:lorem ipsum

【事前準備】DBのリセット

yokoyan:~/workspace/sample_app (user-microposts) $ rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20170417215343 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0014s
== 20170417215343 CreateUsers: migrated (0.0015s) =============================

== 20170423125906 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
   -> 0.0010s
== 20170423125906 AddIndexToUsersEmail: migrated (0.0011s) ====================

== 20170424041610 AddPasswordDigestToUsers: migrating =========================
-- add_column(:users, :password_digest, :string)
   -> 0.0008s
== 20170424041610 AddPasswordDigestToUsers: migrated (0.0013s) ================

== 20170514215307 AddRememberDigestToUsers: migrating =========================
-- add_column(:users, :remember_digest, :string)
   -> 0.0007s
== 20170514215307 AddRememberDigestToUsers: migrated (0.0008s) ================

== 20170614215124 AddAdminToUsers: migrating ==================================
-- add_column(:users, :admin, :boolean, {:default=>false})
   -> 0.0013s
== 20170614215124 AddAdminToUsers: migrated (0.0014s) =========================

== 20170618221238 AddActivationToUsers: migrating =============================
-- add_column(:users, :activation_digest, :string)
   -> 0.0011s
-- add_column(:users, :activated, :boolean, {:dafault=>false})
   -> 0.0006s
-- add_column(:users, :activated_at, :datetime)
   -> 0.0005s
== 20170618221238 AddActivationToUsers: migrated (0.0024s) ====================

== 20170703042006 AddResetToUsers: migrating ==================================
-- add_column(:users, :reset_digest, :string)
   -> 0.0010s
-- add_column(:users, :reset_sent_at, :datetime)
   -> 0.0006s
== 20170703042006 AddResetToUsers: migrated (0.0023s) =========================

== 20170719214241 CreateMicroposts: migrating =================================
-- create_table(:microposts)
   -> 0.0019s
-- add_index(:microposts, [:user_id, :created_at])
   -> 0.0010s
== 20170719214241 CreateMicroposts: migrated (0.0035s) ========================

【事前準備】DBにサンプルデータを登録する

yokoyan:~/workspace/sample_app (user-microposts) $ rails db:seed
yokoyan:~/workspace/sample_app (user-microposts) $ 

【動作確認】マイクロポストの表示確認

登録が終わったら、Railsサーバを再起動して、ブラウザで確認。

image

【scss】マイクロポストのCSSを追加

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

.microposts {
  list-style: none;
  padding: 0;
  li {
    padding: 10px 0;
    border-top: 1px solid #e8e8e8;
  }
  .user {
    margin-top: 5em;
    padding-top: 0;
  }
  .content {
    display: block;
    margin-left: 60px;
    img {
      display: block;
      padding: 5px 0;
    }
  }
  .timestamp {
    color: $gray-light;
    display: block;
    margin-left: 60px;
  }
  .gravatar {
    float: left;
    margin-right: 10px;
    margin-top: 5px;
  }
}

aside {
  textarea {
    height: 100px;
    margin-bottom: 5px;
  }
}

span.picture {
  margin-top: 10px;
  input {
    border: 0;
  }
}

【動作確認】マイクロポストの表示確認2

ブラウザで確認。50件のマイクロポストが表示され、ページネーションも動くことを確認。

image

演習1

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

1から10までのうち先頭6件を表示。

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

演習2

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

演習1と同じ結果となる。

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

演習3

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

Fakerで大学名を表示する。

?> Faker::University.name
=> "Volkman Institute"
>> 

Fakerで電話番号を表示する。

?> Faker::PhoneNumber.phone_number
=> "1-782-129-1939"
>> 

Hipster(ジャズ?)のテキストを表示する。

?> Faker::Hipster.sentence
=> "Street forage tousled asymmetrical."

チャック・ノリスの真実(ジョーク)を表示する。
量子暗号はチャック・ノリスでは機能しませんwww

?> Faker::ChuckNorris.fact
=> "Quantum cryptography does not work on Chuck Norris. When something is being observed by Chuck it stays in the same state until he's finished. "
>> 

faker-okinawaを試す。

【事前準備】faker-okinawaのインストール

gemfileを修正する。
※fakerと同時に入れるとエラーになったため、一旦fakerをコメントアウトする。

# gem 'faker',  '1.6.6'
gem 'faker-okinawa'

バンドルする。

yokoyan:~/workspace/sample_app (user-microposts) $ bundle
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/.........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.
Resolving dependencies...

・・・略・・・

Installing faker-okinawa 0.1.0
Bundle complete! 26 Gemfile dependencies, 84 gems now installed.
Gems in the group production were not installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

gemをインストール。

yokoyan:~/workspace/sample_app (user-microposts) $ gem install faker-okinawa
Successfully installed faker-okinawa-0.1.0
1 gem installed
【動作確認】faker-okinawaを試してみる。

泡盛の名前を取得。

?> Faker::Okinawa::Awamori.name
=> "かねやま"

設定を戻す

動作確認が完了したら、fakerを元に戻す。

Gemfileを戻す。

gem 'faker',  '1.6.6'
#gem 'faker-okinawa'

bundleする。

yokoyan:~/workspace/sample_app (user-microposts) $ bundle

fakerの再インストール。

yokoyan:~/workspace/sample_app (user-microposts) $ gem install faker
Fetching: faker-1.8.4.gem (100%)
Successfully installed faker-1.8.4
1 gem installed

13.2.3 プロフィール画面のマイクロポストをテストする

本章での学び

【事前準備】統合テストの作成

プロフィール画面用の統合テストを生成する。

yokoyan:~/workspace/sample_app (user-microposts) $ rails generate integration_test users_profile
Running via Spring preloader in process 1595
Expected string default value for '--jbuilder'; got true (boolean)
      invoke  test_unit
      create    test/integration/users_profile_test.rb

【事前準備】テストデータの生成

マイクロポスト用のfixtureファイルを修正する。
各マイクロポストデータに、user: michaelを追記すると、michaelユーザの投稿として関連付けされる。また、マイクロポストのページャーのテストを行うために、30回マイクロポストを自動生成する。

なお、ymlの各属性の後ろに:をつけないと、エラーが発生するので注意。
詳細は以下参照。

Railsチュートリアル中にcould not find expected ':' while scanning a simple keyが出た時の対処法

/sample_app/test/fixtures/microposts.yml
orange:
  content: "I just ate an orange!"
  created_at: <%= 10.minutes.ago %>
  user: michael

tau_manifesto:
  content: "Check out the @tauday site by @mhartl: http://tauday.com"
  created_at: <%= 3.years.ago %>
  user: michael

cat_video:
  content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
  created_at: <%= 2.hours.ago %>
  user: michael

most_recent:
  content: "Writing a short test"
  created_at: <%= Time.zone.now %>
  user: michael

<% 30.times do |n| %>
micropost_<%= n %>:
  content: <%= Faker::Lorem.sentence(5) %>
  created_at: <%= 42.days.ago %>
  user: michael
<% end %>

【test】統合テストの実装

以下の仕様で実装する。

  • GETリクエスト送信(ユーザープロフィールページ)
  • ユーザープロフィールページ(users/show)のテンプレートが表示されること
  • HTMLのtitle属性にユーザーの名前が表示されること
  • HTMLのh1タグにユーザーの名前が表示されること
  • HTMLのh1タグの下に、imgタグ(gravatorクラス付き)が表示されること
  • ユーザーのマイクロポスト数をカウントして文字列に変換する。レスポンスのHTML内に存在するかチェック
  • HTMLにdivタグ(paginationクラス付き)が表示されること
  • ユーザーのマイクロポストから、paginateを使って1ページ目を取得する。取得した結果を繰り返す
    • マイクロポストのcontent属性が、レスポンスのHTML内に存在するかチェック
/sample_app/test/integration/users_profile_test.rb
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.gravator'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination'
    @user.microposts.paginate(page: 1) do |micropost|
      assert_match micropost.content, response.body
    end
  end
end

【test】テスト実行

テスト結果がgreenになることを確認。

yokoyan:~/workspace/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 1230
Started with run options --seed 10439

  54/54: [================================================================================================================================================================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.15487s
54 tests, 238 assertions, 0 failures, 0 errors, 0 skips

演習1

リスト 13.28にある2つの’h1’のテストが正しいか確かめるため、該当するアプリケーション側のコードをコメントアウトしてみましょう。テストが green から redに変わることを確認してみてください。

viewの該当箇所のタグをコメントアウトする。

/sample_app/app/views/users/show.html.erb
      <!-- <h1> -->
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>

テストがredになることを確認。
(確認後はコメントアウトを戻すこと)

yokoyan:~/workspace/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 1915
Started with run options --seed 50720

 FAIL["test_profile_display", UsersProfileTest, 1.6931300410069525]
 test_profile_display#UsersProfileTest (1.69s)
        Expected at least 1 element matching "h1", found 0..
        Expected 0 to be >= 1.
        test/integration/users_profile_test.rb:14:in `block in <class:UsersProfileTest>'

  54/54: [================================================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.94467s
54 tests, 234 assertions, 1 failures, 0 errors, 0 skips

演習2

リスト 13.28にあるテストを変更して、will_paginateが1度のみ表示されていることをテストしてみましょう。ヒント: 表 5.2を参考にしてください。

will_paginateに該当する、div class=paginationが1度のみ表示されていることをテストする。

image.png

/sample_app/test/integration/users_profile_test.rb
  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.gravator'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination'
    @user.microposts.paginate(page: 1) do |micropost|
      assert_match micropost.content, response.body
    end
    assert_select 'div.pagination', count:1 #追加
  end

テストがgreenになることを確認。

yokoyan:~/workspace/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 2471
Started with run options --seed 42382

  54/54: [================================================================================================================================================================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.10291s
54 tests, 239 assertions, 0 failures, 0 errors, 0 skips

おわりに

お盆休みをはさんだため、久々の更新です。
マイクロポストのテストデータを作成し、テストコードの実装が完了しました。
そろそろ最新版 (Rails 5.1対応) が出そうなので、8月中に14章まで完了できるようにがんばります!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1