13.2 マイクロポストの表示
Web経由でマイクロポストを作成する方法は現時点ではないが、マイクロポストを表示することと、テストすることならできる。
ここでは、Twitterのような独立したマイクロポストのindex
ページを作らず、次のモックアップのように、ユーザーのshow
ページで直接マイクロポストを表示させることにする。
ユーザープロフィールにマイクロポストを表示させるため、最初に極めてシンプルなERbテンプレートを作成する。
次に、10.3.2のサンプルデータ生成タスクにマイクロポストのサンプルを追加して、画面にサンプルデータが表示されるようにしてみる。
13.2.1 マイクロポストの描画
ここでは、ユーザーのプロフィールの画面(show.html.erb
)で、そのユーザーのマイクロポストを表示させたり、これまでに投稿した総数も表示させたりしていく。(10.3で実装したユーザーを表示する部分と似ている。)
一度DBをリセットし、サンプルデーターを再生成
$ rails db:migrate:reset $ rails db:seed
(今回使うのはビューだけで、Micropostsコントローラは13.3から使う。)
$ rails g controller Microposts
ユーザーごとにすべてのマイクロポストを描画
10.3.5で見た次のコードでは、
<ul class="users"> <%= render @users %> </ul>
@users
変数内のそれぞれのユーザーを出力していた。これを参考に、
_micropost.html.erb
パーシャルを使ってマイクロポストのコレクションを表示する<ol class="microposts"> #① <%= render @microposts %> </ol>
ul
タグではなく、順序付きリストのol
タグを使っている点に注目!マイクロポストが特定の順序(新しい→古い)に依存しているため。
1つのマイクロポストを表示するパーシャル
<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 classs="timestamp"> Posted <%= time_ago_in_words(micropost.created_at ) %> ago. #①投稿時間 </span> </li>
①:time_ago_in_words
というヘルパーメソッドで「3分前に投稿」といった文字列を出力
②:各マイクロポストに対してCSSのidを割り振っている。これは一般的に良いとされる慣習とのこと
ページネーションを使っての表示
一度にすべてのマイクロポストが表示されてしまうことを防ぐため。ユーザー一覧ページと同じ方法でwill_paginate
メソッドを使う。
<%= will_paginate @microposts %>
ユーザー一覧画面のコードと比較すると、少し違っている。以前は次のようなコードだった。
<%= will_paginate %>
実は、上のコードは引数なしで動作していた。これはwill_paginate
が、Usersコントローラのコンテキストにおいて、@users
インスタンス変数が存在していることを前提としているため。
@users
インスタンス変数は、10.3.3でも述べたようにActiveRecord: :Relation
クラスのインスタンス。
しかし、今回の場合はUsersコントローラのコンテキストからマイクロポストをページネーションしたいため(つまりコンテキストが異なるため)、明示的に@microposts
変数をwill_paginate
に渡す必要がある。なるほど!
したがって、そのようなインスタンス変数をUsersコントローラのshow
アクションで定義しなければならない。
: def show @user = User.find(params[:id]) @microposts = @user.microposts.paginate(page: params[:page]) #追加① end : end
paginate
メソッドは、マイクロポストの関連付けを経由してmicropostテーブルに到達し、必要なマイクロポストのページを引き出してくれる。
マイクロポストの投稿数を表示
関連付けをとおしてcount
メソッドを呼び出すことができる。
user.microposts.count
重要なことは、count
メソッドではDB上のマイクロポストを全部読みだしてから結果の配列に対してlength
を呼ぶ、といった無駄な処理はしていないという点。そんなことをしたら、マイクロポストの数が増加するにつれて効率が低下してしまう。
そうではなく、(DB内での計算は高度に最適化されているので)DBに代わりに計算してもらい、特定のuser_id
に紐付いたマイクロポストの数をDBに問い合わせている。
すべての要素が揃ったので実装
プロフィール画面にマイクロポストを表示させてみる。(このとき、if @user.microposts.any?
を使って、ユーザーのマイクロポストが1つもない場合には空のリストを表示させていない点にも注目!)
<% 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>
次でマイクロポストのサンプルを作成。
13.2.2 マイクロポストのサンプル
マイクロポスのサンプルデータを追加して、表示を確認できるようにしたい。
すべてのユーザーにマイクロポストを追加すると時間が掛かり過ぎる。
よって、take
メソッドを使って最初の6人だけに追加
User.order(:created_at).take(6)
orderメソッド
上のコードではorder
メソッドを経由することで、明示的に最初の(IDが小さい順に)6人を呼び出すようにしている。
pikawaka.com
Faker gemでサンプルを追加
Faker gemにLorem.sentence
というメソッドを使って、サンプルを追加する。
6人については、1ページの表示限界数(30)を越えさせるために、それぞれ50個分のマイクロポストを追加するようにしている。
下の①で50.times.do
のuser.each
を後に書いてる理由は、ユーザーごとに50個分のマイクロポストを作成してしまうと、ステータスフィードに表示される投稿がすべて同じユーザーになってしまい、視覚的な見栄えが悪くなってしまうから。
: users = User.order(:created_at).take(6) 50.times do content = Faker::Lorem.sentence(5) users.each { |user| user.microposts.create!(content: content) } #① end
DBに反映
$ rails db:migrate:reset $ rails db:seed
生成し終わったら、Railsサーバーを一度落として、起動し直す。次の図
マイクロポスト固有のスタイルが与えられていないので、CSSを追加
:
/* micrposts */
.microposts {
list-styles: 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;
}
}
time_ago_in_words
メソッドによるもの。数分待ってからページを再度読み込むと、このテキストは自動的に新しい時間に基づいて更新される。
13.2.3 プロフィール画面のマイクロポストをテストする
アカウントを有効化したばかりのユーザーはプロフィール画面にリダイレクトされるので、そのプロフィール画面が正しく描画されてることは、単体テストで確認済み。
ここでは、プロフィール画面で表示されるマイクロポストに対して、統合テストを書いていく。まずは、プロフィール画面用の統合テストを生成してみる。
テストの準備
$ rails g integration_test users_profile Running via Spring preloader in process 18157 invoke test_unit create test/integration/users_profile_test.rb
orange: content: "I just ate an orange!" created_at: <%= 10.minutes.ago %> user: michael #①
①:user
にmichael
という値を渡すと、Railsはfixtureファイル内の対応するユーザーを探し出して、(もし見つかれば)マイクロポストに関連付けてくれる。
michael: name: Michael Example email: michael@example.com :
また、マイクロポストのページネーションをテストするためには、埋め込みRubyを使い、マイクロポスト用のfixtureにテストデータを追加
<% 30.times do |n| %> micropost_<%= n %> #埋め込みRuby content: <%= Faker::Lorem.sentence(5) %> # created_at: <%= 42.days.ago %> # user: michael <% end %>
これらのコードを1つにまとめると、マイクロポスト用のfixtureファイルは次のようになる。
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/PKffm2ul4dk" 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 %>
テストデータを実装
今回のテストでは、プロフィール画面にアクセスした後に、ページタイトルとユーザー名、Gravatar、マイクロポストの投稿数、ページ分割されたマイクロポスト、といった順でテストしていく。
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' @user.microposts.paginate(page: 1).each do |micropost| assert_match micropost.content, response.body #③ end end end
①②:Applicationヘルパーを読み込んだことでfull_title
ヘルパーが利用できている点に注目!
response.body
③:マイクロポストの投稿数をチェックするために、12章の演習で使ったresponse.body
を使用。
response.body
にはそのページの完全なHTMLが含まれている(HTMLのbodyタグだけではない)。
bodyの内容だけでなくて、ページ内のすべてのHTMLが含まれるということ。
したがって、そのページのどこかしらにマイクロポストの投稿数が存在するのであれば、次のように探し出してマッチできるはず。
assert_match @user.microposts.count.to_s response.body
これはassert_select
よりもずっと抽象的なメソッド。特に、assert_select
ではどのHTMLタグを探すのか伝える必要があるが、assert_match
メソッドではその必要がない点が違う。
④:assert_select
の引数では、ネストした文法を使っている点にも注目。このように書くことで、h1
タグの内側にある、garavatar
クラス付きのimg
タグがあるかどうかチェックできる。
これでテストは成功する。