Railsチュートリアル 第13章 ユーザーのマイクロポスト - マイクロポストの描画

# 開発環境のデータベースのリセット

演習等で生成したマイクロポストの影響を排除するため、一旦開発環境のデータベースをリセットします。

```
# rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'

== [timestamp] CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0186s
== [timestamp] CreateUsers: migrated (0.0202s) =============================

...略

== [timestamp] CreateMicroposts: migrating =================================
-- create_table(:microposts)
   -> 0.0163s
-- add_index(:microposts, [:user_id, :created_at])
   -> 0.0035s
== [timestamp] CreateMicroposts: migrated (0.0206s) ========================

# rails db:seed
```

`rails db:seed`は、正常に完了した場合、何のメッセージも表示することなくシェルに戻ります。

# Micropostsコントローラーの作成

これからMicropostのコントローラーとビューが必要となります。そのため、`rails generate controller`コマンドで自動生成していきます。

```
# rails generate controller Microposts
Running via Spring preloader in process 1783
      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
```

なお、「マイクロポストの表示」という機能に必要となるのはビューのみです。コントローラーのほうは、「Web経由でマイクロポストを操作するためのインターフェースの実装」以降に使っていくことになります。

# 1つのマイクロポストを表示するパーシャル

## 復習 - ユーザーの一覧表示はどのように記述していたか

Usersコントローラーの`index`アクションに対応する埋め込みRubyでは、以下のようにして`render`メソッドを用いることにより、Userモデルに含まれるユーザーを一覧表示していました。

```erb:
<ul class="users">
  <%= render @users %>
</ul>
```

Userモデルに対応するビューにおいて、`@users`内に含まれるユーザーのコレクションを`render`メソッドの引数として取った場合、各ユーザー個別の表示内容に対応するパーシャルは、`app/views/users/_user.html.erb`という名前なのでしたね。

### マイクロポストの一覧表示はどのように記述するか

`@microposts`内に含まれるマイクロポストを一覧表示すための`render`メソッドは、以下のようにして用います。前述「`@users`内に含まれるユーザーの一覧表示」と酷似していますね。

```erb:
<ol class="microposts">
  <%= render @microposts %>
</ol>
```

重要なポイントは以下です。

* 順序なしリストを表す`ul`要素ではなく、順序付きリストを表す`ol`要素を用いる
    * マイクロポストの順序には、「時系列順」という明確な意味があるため
* 対応するパーシャルは`app/views/microposts/_micropost.html.erb`である
    * `app/views/microposts/_micropost.html.erb`の内容を記述していく必要がある

ユーザーの場合との大きな違いは、「順序なしリストである`ul`ではなく、順序付きリストである`ol`を用いている」という点です。これは、「マイクロポストには順序（新しい→古い）がある」という仕様に依拠するものです。

## `app/views/microposts/_micropost.html.erb`の内容

```erb:app/views/microposts/_micropost.html.erb
<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>
```

### `time_ago_in_words`メソッドとは

`time_ago_in_words`というヘルパーメソッドを使っていることがポイントです。[`time_ago_in_words`メソッド](https://api.rubyonrails.org/v5.2.1/classes/ActionView/Helpers/DateHelper.html#method-i-time_ago_in_words)は、[Railsの`ActionView::Helpers::DateHelper`モジュール](https://api.rubyonrails.org/v5.2.1/classes/ActionView/Helpers/DateHelper.html)に存在するメソッドで、「○分前に投稿」といった文字列を出力するものです。

### 各マイクロポストのビューに対し、CSSのidを割り振る

```erb:
<li id="micropost-<%= micropost.id %>">
```

上記`li`要素では、マイクロポストのIDを元として、CSSをのIDを割り振っています。一般に「Webデザインのベストプラクティス」とされる手法であり、例えば、「Javascriptを使って各マイクロポストを操作する」といったユースケースで役に立つことが期待されます。

# Usersコントローラーに、マイクロポストのページネーションに必要な実装を追加する

「ユーザーのshowページで直接マイクロポストを表示する」というユースケースの場合、対応するページネーションの実装は以下のようになります。

```erb:
<%= will_paginate @microposts %>
```

以前に行った「ユーザーのindexページへのページネーションの実装」とは異なり、`will_paginate`メソッドに明示的に引数`@microposts`を渡しています。「引数の有無が異なる」という違いがあるからには、何らかの違いがあるはずです。

## `will_paginate`に引数が必要となる理由

ユーザーのindexページにおけるページネーションの実装と、ユーザーのshowページにおけるページネーションの実装。何が違うのかといいますと、「Usersコントローラー内でUserモデルに対しての処理を行っているのか、あるいはそうでないのか」ということです。この違いが、引数の要不要の違いにつながってきます。

「Usersコントローラー内でUserモデルに対して`will_paginate`メソッドを実行する」という場合、`will_paginate`に引数は必要ありません。暗黙的に`@users`が対象であるとみなされます。一方、「Usersコントローラー内でMicropostモデルに対して`will_paginate`メソッドを実行する」というような場合、`will_paginate`には「どのモデルを対象とするか」を表す引数が必要になります。Micropostモデルが対象であれば`@microposts`ですね。

## `UsersController#show`に、`@microposts`そのものの定義を追加する

`UsersController#show`には、`@microposts`というインスタンス変数そのものの定義も必要になります。

```diff:app/controllers/users_controller.rb
  class UsersController < ApplicationController
    ．．．略

    def index
      @users = User.where(activated: true).paginate(page: params[:page])
    end

    def show
      @user = User.find(params[:id])
+     @microposts = @user.microposts.paginate(page: params[:page])
      redirect_to root_url unless @user.activated?
    end

    ...略
  end
```

`index`アクションにおける`@users`の定義と同様、`panigate`メソッドを使っています。

（復習になりますが）コントローラーにおける`panigate`メソッドは、ビューにおける`will_paginate`メソッドと対応しています。今回用いている`paginate`メソッドは、Userモデルにおける`has_many :microposts`や、Micropostモデルにおける`belongs_to :user`といった定義を通じて、Micropostテーブルに対して必要な処理を実行することができるのです。

# マイクロポストの投稿数を表示する

ユーザーに対するマイクロポストの投稿数のカウントは、以下のメソッドで行うことができます。

```ruby:
user.microposts.count
```

上記コードの動作における重要なポイントは、「マイクロポストの数をカウントする処理は、Ruby環境側ではなく、RDB側で行われている」ということです。「マイクロポストの数をカウントする」などといった処理の場合、RDB側での計算処理は高度に最適化されています。高度に最適化されているということは、それだけ計算が高速、ということであるのです。

# プロフィール画面へのマイクロポストの表示機能を実装する

編集対象は`app/views/users/show.html.erb`となります。編集内容については、以下の別記事にて言及しています。

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

## 実装完了時点でのプロフィール画面の表示

なお、上記実装が完了した時点でプロフィール画面をWebブラウザにて表示すると、結果は以下のようになります。

<img width="1168" alt="スクリーンショット 2019-12-26 15.57.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/24c16fb4-f826-0f50-a569-24bf37c4a354.png">

マイクロポストが1件もないので、今までの画面と表示内容は変わりません。

### RDBの`microposts`テーブルに対して、SQLの`SELECT`文が発行されるようになった

一方で、Railsサーバーには以下のようなログが記録されています。

```
Started GET "/users/1" for ...略
Processing by UsersController#show as HTML
  Parameters: {"id"=>"1"}
  User Load (5.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application
  Micropost Exists (2.7ms)  SELECT  1 AS one FROM "microposts" WHERE "microposts"."user_id" = ? LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
  Rendered users/show.html.erb within layouts/application (5.1ms)
  Rendered layouts/_rails_default.erb (282.3ms)
  Rendered layouts/_shim.html.erb (0.3ms)
  User Load (2.8ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Rendered layouts/_header.html.erb (5.6ms)
  Rendered layouts/_footer.html.erb (0.5ms)
Completed 200 OK in 459ms (Views: 421.6ms | ActiveRecord: 10.6ms)
```

```
Micropost Exists (2.7ms)  SELECT  1 AS one FROM "microposts" WHERE "microposts"."user_id" = ? LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
```

確かに`microposts`テーブルに対してSQLの`SELECT`文が発行されています。