前提
マイクロポストの表示は、「ユーザーのshowページで直接マイクロポストを表示する」という形式で実装していくことになります。
Railsチュートリアル本文では、図 13.4というモックアップが示されています。
本項目では、以下の実装を行っていきます。
- ユーザープロフィールにマイクロポストを表示させるためのERbテンプレートを作成する
- サンプルデータ生成タスクにマイクロポストのサンプルを追加し、画面にサンプルデータが表示されるようにする
マイクロポストの描画
実装内容としては、Railsチュートリアル本文第10章の10.3 すべてのユーザーを表示するに類似している、とのことです。
長くなりましたので、別記事で解説します。
演習 - マイクロポストの描画
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.months.ago)
=> "6 months"
3.weeks.ago
だと日数が返ってきていますね。6.months.ago
だと月数が返ってきます。
>> helper.time_ago_in_words(30.days.ago)
=> "about 1 month"
30.days.ago
だと「約1ヶ月」という結果が返ってきます。
2. helper.time_ago_in_words(1.year.ago)
と実行すると、どういった結果が返ってくるでしょうか?
>> helper.time_ago_in_words(1.year.ago)
=> "about 1 year"
「約1年」という結果が返ってきます。
>> helper.time_ago_in_words(30.months.ago)
=> "over 2 years"
なお、30.months.ago
だと「2年超」という結果が返ってきます。
3. micropostsオブジェクトのクラスは何でしょうか?
ヒント: リスト 13.23内のコードにあるように、まずは
paginate
メソッド (引数はpage: nil
) でオブジェクトを取得し、その後class
メソッドを呼び出してみましょう。
>> microposts = User.find(1).microposts.paginate(page: nil)
...略
=> #<ActiveRecord::AssociationRelation []>
>> microposts.class
=> Micropost::ActiveRecord_AssociationRelation
クラスはMicropost::ActiveRecord_AssociationRelation
です。「Micropost
というモデル名そのものがクラス名に含まれる」というのは、以前に学習した「メタプログラミング」のなせる業でしょうか。
というか、User.find(1).microposts.paginate(page: nil)
の時点でクラス名がコンソールに返ってきていますね。
なお、micropostsオブジェクトのクラスのスーパークラスをたどっていくと、以下のような結果になります。
>> microposts.class
=> Micropost::ActiveRecord_AssociationRelation
>> microposts.class.superclass
=> ActiveRecord::AssociationRelation
>> microposts.class.superclass.superclass
=> ActiveRecord::Relation
>> microposts.class.superclass.superclass.superclass
=> Object
Micropost::ActiveRecord_AssociationRelation
クラスの継承関係は以下のようになっていますね。
マイクロポストのサンプル
RDB上にマイクロポストが何もなくては、「ユーザーのプロフィール画面にマイクロポストがが表示される」という状況も実現できません。ここでマイクロポストのサンプルを追加していきましょう。
長くなりましたので、別記事で解説します。
演習 - マイクロポストのサンプル
1. (1..10).to_a.take(6)
というコードの実行結果を推測できますか? 推測した値が合っているかどうか、実際にコンソールを使って確認してみましょう。
take(6)
メソッドを呼び出さない場合、実行結果は以下のようになります。
>> (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
そこから先頭6個の要素を取り出すとなると…
>> (1..10).to_a.take(6)
=> [1, 2, 3, 4, 5, 6]
上記のような実行結果になります。
2. 先ほどの演習にあったto_a
メソッドの部分は本当に必要でしょうか?確かめてみてください。
>> (1..10).take(6)
=> [1, 2, 3, 4, 5, 6]
to_a
がなくても、to_a
がある場合と同じ結果を返してきています。
余談 - take
メソッドの言語的背景
Array
クラス(配列)にせよRange
クラス(範囲)にせよ、いずれもEnumerable
クラスを継承しています。よって、Enumerable
クラスで定義されているtake
メソッドは、どちらのクラスでも使うことができるのです。
RubyリファレンスマニュアルのEnumerable#takeの解説には、以下のような記述があります。
Enumerable オブジェクトの先頭から n 要素を配列として返します。
「配列として返します」というのがポイントですね。
3. Fakerはlorem ipsum以外にも、非常に多種多様の事例に対応しています。Fakerのドキュメント (英語) を眺めながら画面に出力する方法を学び、実際に大学名や電話番号、Hipster IpsumやChuck Norris facts (参考: チャック・ノリスの真実) を画面に出力してみましょう。
(訳注: もちろん日本語にも対応していて、例えば沖縄らしい用語を出力するfaker-okinawaもあります。ぜひ遊んでみてください。)
>> Faker::University.name
=> "North Virginia University"
>> Faker::University.name
=> "The Witting"
>> Faker::University.suffix
=> "Institute"
>> Faker::University.suffix
=> "Academy"
>> Faker::PhoneNumber.phone_number
=> "(181) 784-5325 x48037"
>> Faker::PhoneNumber.phone_number
=> "810.184.4321"
>> Faker::PhoneNumber.cell_phone
=> "(385) 397-3478"
>>
>> Faker::PhoneNumber.cell_phone
=> "242-717-7440"
>> Faker::Hipster.words
=> ["portland", "keffiyeh", "selvage"]
>> Faker::Hipster.words
=> ["gluten-free", "tattooed", "retro"]
>> Faker::Hipster.sentence
=> "Wayfarers migas yr fingerstache."
>> Faker::Hipster.sentence
=> "Wayfarers shoreditch cred hammock."
>> Faker::ChuckNorris.fact
=> "The programs that Chuck Norris writes don't have version numbers because he only writes them once. If a user reports a bug or has a feature request they don't live to see the sun set."
>> Faker::ChuckNorris.fact
=> "Chuck Norris doesn't bug hunt, as that signifies a probability of failure. He goes bug killing."
プロフィール画面のマイクロポストをテストする
実装そのものをテスト駆動で行っていったので、この項の内容についても先に終えてしまっていました。以下の別記事にて、実装とともに言及しています。
演習 - プロフィール画面のマイクロポストをテストする
1. リスト 13.28にある2つの'h1
'のテストが正しいか確かめるため、該当するアプリケーション側のコードをコメントアウトしてみましょう。テストがgreen
からred
に変わることを確認してみてください。
以下のようにソースコードを変更します。
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
- <%= gravatar_for @user %>
+ <%# <%= gravatar_for @user %> %>
- <%= @user.name %>
+ <%# <%= @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>
テストの結果はどうなるでしょうか。
# rails test test/integration/users_profile_test.rb
Running via Spring preloader in process 1994
Started with run options --seed 54579
FAIL["test_profile_display", UsersProfileTest, 11.107052200008184]
test_profile_display#UsersProfileTest (11.11s)
<Reimu Hakurei> expected but was
<%>
%>>..
Expected 0 to be >= 1.
test/integration/users_profile_test.rb:14:in `block in <class:UsersProfileTest>'
1/1: [===================================] 100% Time: 00:00:11, Time: 00:00:11
Finished in 11.10913s
1 tests, 3 assertions, 1 failures, 0 errors, 0 skips
私の環境では、test/integration/users_profile_test.rb
の14行目には以下のコードが記述されています。
assert_select 'h1', text: @user.name
2. リスト 13.28にあるテストを変更して、will_paginate
が1度のみ表示されていることをテストしてみましょう。
ヒント: 表 5.2を参考にしてください。
test/integration/users_profile_test.rb
の内容を以下のように変更します。
require 'test_helper'
class UsersProfileTest < ActionDispatch::IntegrationTest
include ApplicationHelper
def setup
@user = users(:rhakurei)
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'
+ assert_select 'div.pagination', count: 1
@user.microposts.paginate(page: 1).each do |micropost|
assert_match micropost.content, response.body
end
end
end
test/integration/users_profile_test.rb
に対するテストの結果は以下のようになります。
# rails test test/integration/users_profile_test.rb
Running via Spring preloader in process 2059
Started with run options --seed 602
1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.65851s
1 tests, 67 assertions, 0 failures, 0 errors, 0 skips
現時点でテストは無事成功します。
ではどういう場合にテストが失敗するのか
例えば、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>
+ <%= will_paginate @microposts %>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
# rails test test/integration/users_profile_test.rb
Running via Spring preloader in process 2072
Started with run options --seed 43019
FAIL["test_profile_display", UsersProfileTest, 2.532113199995365]
test_profile_display#UsersProfileTest (2.53s)
Expected exactly 1 element matching "div.pagination", found 2..
Expected: 1
Actual: 2
test/integration/users_profile_test.rb:17:in `block in <class:UsersProfileTest>'
1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.54357s
1 tests, 7 assertions, 1 failures, 0 errors, 0 skips
「pagination
クラスを持つdiv
要素が2つ存在する」という趣旨のメッセージを出力して、テストが失敗しています。
私の環境では、test/integration/users_profile_test.rb
の17行目は以下のコードが記述されています。
assert_select 'div.pagination', count: 1
テストの動作は想定どおりであるようです。