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

# 前提

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

Railsチュートリアル本文では、[図 13.4](https://railstutorial.jp/chapters/user_microposts?version=5.1#fig-user_microposts_mockup)というモックアップが示されています。

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

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

# マイクロポストの描画

実装内容としては、Railsチュートリアル本文第10章の[10.3 すべてのユーザーを表示する](https://railstutorial.jp/chapters/updating_and_deleting_users?version=5.1#sec-showing_all_users)に類似している、とのことです。

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

* [Railsチュートリアル 第13章 ユーザーのマイクロポスト - マイクロポストの描画](https://qiita.com/rapidliner00/items/be6bb6a2d56af0496fe2)

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

## 1. [7.3.3](https://railstutorial.jp/chapters/sign_up?version=5.1#sec-signup_error_messages)で軽く説明したように、今回ヘルパーメソッドとして使った`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](https://railstutorial.jp/chapters/user_microposts?version=5.1#code-user_show_microposts_instance)内のコードにあるように、まずは`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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/cfdaa56b-0e9a-9bfc-3b03-fe93095f30ba.png)

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

RDB上にマイクロポストが何もなくては、「ユーザーのプロフィール画面にマイクロポストがが表示される」という状況も実現できません。ここでマイクロポストのサンプルを追加していきましょう。

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

* [Railsチュートリアル 第13章 ユーザーのマイクロポスト - マイクロポストのサンプル](https://qiita.com/rapidliner00/items/89443b700ba1c2ba2ec6)

# 演習 - マイクロポストのサンプル

## 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](https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/take.html)の解説には、以下のような記述があります。

> Enumerable オブジェクトの先頭から n 要素を配列として返します。

「配列として返します」というのがポイントですね。

## 3. Fakerはlorem ipsum以外にも、非常に多種多様の事例に対応しています。Fakerのドキュメント (英語) を眺めながら画面に出力する方法を学び、実際に[大学名](https://github.com/stympy/faker#fakeruniversity)や[電話番号](https://github.com/stympy/faker#fakerphonenumber)、[Hipster Ipsum](https://github.com/stympy/faker#fakerhipster)や[Chuck Norris facts](https://github.com/stympy/faker#fakerchucknorris) (参考: [チャック・ノリスの真実](https://ja.wikipedia.org/wiki/%E3%83%81%E3%83%A3%E3%83%83%E3%82%AF%E3%83%BB%E3%83%8E%E3%83%AA%E3%82%B9%E3%83%BB%E3%83%95%E3%82%A1%E3%82%AF%E3%83%88)) を画面に出力してみましょう。

> (訳注: もちろん日本語にも対応していて、例えば沖縄らしい用語を出力する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."
```

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

実装そのものをテスト駆動で行っていったので、この項の内容についても先に終えてしまっていました。以下の別記事にて、実装とともに言及しています。

* [Railsチュートリアル 第13章 ユーザーのマイクロポスト - 「プロフィール画面へのマイクロポストの表示」を、テスト駆動で実装していく](https://qiita.com/rapidliner00/items/5b79d26efce4fa13b48a)

# 演習 - プロフィール画面のマイクロポストをテストする

## 1. [リスト 13.28](https://railstutorial.jp/chapters/user_microposts?version=5.1#code-user_profile_test)にある２つの'`h1`'のテストが正しいか確かめるため、該当するアプリケーション側のコードをコメントアウトしてみましょう。テストが`green`から`red`に変わることを確認してみてください。

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

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

```ruby:test/integration/users_profile_test.rb（14行目）
assert_select 'h1', text: @user.name
```

## 2. [リスト 13.28](https://railstutorial.jp/chapters/user_microposts?version=5.1#code-user_profile_test)にあるテストを変更して、`will_paginate`が１度のみ表示されていることをテストしてみましょう。

> ヒント: [表 5.2](https://railstutorial.jp/chapters/filling_in_the_layout?version=5.1#table-assert_select)を参考にしてください。

`test/integration/users_profile_test.rb`の内容を以下のように変更します。

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

```diff: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行目は以下のコードが記述されています。

```ruby:
assert_select 'div.pagination', count: 1
```

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