はじめに
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版の 13章 13.2 マイクロポストを表示するの演習まとめ&回答例です。
個人の解答例なので、誤りがあればご指摘ください。
動作環境
- 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分前に投稿」というような文字列を返すらしい。
<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変数を定義する。
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】ユーザープロフィール画面にマイクロポストを表示する
<% 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サーバを再起動して、ブラウザで確認。
【scss】マイクロポストのCSSを追加
/* 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件のマイクロポストが表示され、ページネーションも動くことを確認。
演習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が出た時の対処法
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内に存在するかチェック
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の該当箇所のタグをコメントアウトする。
<!-- <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度のみ表示されていることをテストする。
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対応) が出てしまったので、9月中に14章まで完了できるようにがんばります!