1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsチュートリアル 第13章 ユーザーのマイクロポスト - マイクロポストを表示する

Posted at

前提

マイクロポストの表示は、「ユーザーのshowページで直接マイクロポストを表示する」という形式で実装していくことになります。

Railsチュートリアル本文では、図 13.4というモックアップが示されています。

本項目では、以下の実装を行っていきます。

  1. ユーザープロフィールにマイクロポストを表示させるためのERbテンプレートを作成する
  2. サンプルデータ生成タスクにマイクロポストのサンプルを追加し、画面にサンプルデータが表示されるようにする

マイクロポストの描画

実装内容としては、Railsチュートリアル本文第10章の10.3 すべてのユーザーを表示するに類似している、とのことです。

長くなりましたので、別記事で解説します。

演習 - マイクロポストの描画

1. 7.3.3で軽く説明したように、今回ヘルパーメソッドとして使ったtime_ago_in_wordsメソッドは、Railsコンソールのhelperオブジェクトから呼び出すことができます。このhelperオブジェクトのtime_ago_in_wordsメソッドを使って、3.weeks.ago6.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クラスの継承関係は以下のようになっていますね。

Class.png

マイクロポストのサンプル

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 IpsumChuck 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に変わることを確認してみてください。

以下のようにソースコードを変更します。

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 %>
+         <%# <%= 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行目には以下のコードが記述されています。

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の内容を以下のように変更します。

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の内容を以下のように改変したらどうなるでしょうか。

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

テストの動作は想定どおりであるようです。

1
0
0

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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?