開発環境のデータベースのリセット
演習等で生成したマイクロポストの影響を排除するため、一旦開発環境のデータベースをリセットします。
# 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モデルに含まれるユーザーを一覧表示していました。
<ul class="users">
<%= render @users %>
</ul>
Userモデルに対応するビューにおいて、@users
内に含まれるユーザーのコレクションをrender
メソッドの引数として取った場合、各ユーザー個別の表示内容に対応するパーシャルは、app/views/users/_user.html.erb
という名前なのでしたね。
マイクロポストの一覧表示はどのように記述するか
@microposts
内に含まれるマイクロポストを一覧表示すためのrender
メソッドは、以下のようにして用います。前述「@users
内に含まれるユーザーの一覧表示」と酷似していますね。
<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
の内容
<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
メソッドは、RailsのActionView::Helpers::DateHelper
モジュールに存在するメソッドで、「○分前に投稿」といった文字列を出力するものです。
各マイクロポストのビューに対し、CSSのidを割り振る
<li id="micropost-<%= micropost.id %>">
上記li
要素では、マイクロポストのIDを元として、CSSをのIDを割り振っています。一般に「Webデザインのベストプラクティス」とされる手法であり、例えば、「Javascriptを使って各マイクロポストを操作する」といったユースケースで役に立つことが期待されます。
Usersコントローラーに、マイクロポストのページネーションに必要な実装を追加する
「ユーザーのshowページで直接マイクロポストを表示する」というユースケースの場合、対応するページネーションの実装は以下のようになります。
<%= 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
というインスタンス変数そのものの定義も必要になります。
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テーブルに対して必要な処理を実行することができるのです。
マイクロポストの投稿数を表示する
ユーザーに対するマイクロポストの投稿数のカウントは、以下のメソッドで行うことができます。
user.microposts.count
上記コードの動作における重要なポイントは、「マイクロポストの数をカウントする処理は、Ruby環境側ではなく、RDB側で行われている」ということです。「マイクロポストの数をカウントする」などといった処理の場合、RDB側での計算処理は高度に最適化されています。高度に最適化されているということは、それだけ計算が高速、ということであるのです。
プロフィール画面へのマイクロポストの表示機能を実装する
編集対象はapp/views/users/show.html.erb
となります。編集内容については、以下の別記事にて言及しています。
実装完了時点でのプロフィール画面の表示
なお、上記実装が完了した時点でプロフィール画面をWebブラウザにて表示すると、結果は以下のようになります。
マイクロポストが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
文が発行されています。