##webインターフェイス
フォロー機能が構築できたので、実際にアプリケーション上で使えるようにしていく。
###フォローのサンプルデータ
seedファイルを編集して、サンプルユーザー間にフォロー・被フォロー関係を構築する。
# ユーザー
User.create!(name: "Example User",
email: "example@railstutorial.org",
password: "foobar",
password_confirmation: "foobar",
admin: true,
activated: true,
activated_at: Time.zone.now)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password: password,
password_confirmation: password,
activated: true,
activated_at: Time.zone.now)
end
# マイクロポスト
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end
# リレーションシップ
users = User.all
user = users.first
following = users[2..50]
followers = users[3..40]
following.each { |followed| user.follow(followed) }
followers.each { |follower| follower.follow(user) }
最初のユーザーにユーザー3からユーザー51までをフォローさせ、それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせる。
(11、12章をやってない場合はactivated属性を消しておく)
$ rails db:migrate:reset
$ rails db:seed
を実行しておく。
###following/followersページへのルーティング
ユーザーのプロフィールページにフォローユーザーと被フォローユーザーの人数をそれぞれ表示し、その一覧を表示するページへのリンクを付ける。
そこで、usersリソースのルーティングにfollowersページとfollowingアクションへのルーティングを追加する。
:memberメソッドを使って以下のようにする。
resources :users do
member do
get :following, :followers
end
end
###統計情報のパーシャル
フォロー/フォロワー数を表示する統計情報のパーシャルを作成する。
<% @user ||= current_user %>
<div class="stats">
<a href="<%= following_user_path(@user) %>">
<strong id="following" class="stat">
<%= @user.following.count %>
</strong>
following
</a>
<a href="<%= followers_user_path(@user) %>">
<strong id="followers" class="stat">
<%= @user.followers.count %>
</strong>
followers
</a>
</div>
最初の行では、@user ||= current_userとしている。
このパーシャルはhomeページとユーザープロフィールページの両方で使用する。
homeページではcurrent_userを、プロフィールページではshowアクションで定義された@userをそのまま入れる。
strongタグは、文字を強調するためのもので、強調をスタイルシートで指定するなら使わなくてもよい。
homeページでこのパーシャルを呼び出す。
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
<div class="col-md-8">
<h3>Micropost Feed</h3>
<%= render 'shared/feed' %>
</div>
</div>
<% else %>
.
.
.
<% end %>
###フォロー/フォロー解除ボタンのパーシャル
プロフィールページに統計情報のパーシャルを貼る前に、フォロー/フォロー解除ボタン(フォーム)用のパーシャルを作成しておく。
<% unless current_user?(@user) %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
このパーシャルは自身(ログインユーザー)のプロフィールページには表示されない。
つまり、自身とは別のユーザー用である。
そのユーザーをフォローしていればフォロー解除用の、フォローしていなければフォロー用のパーシャルを表示する。
各パーシャルは以下のようになる。
<%= form_for(current_user.active_relationships.build) do |f| %>
<div><%= hidden_field_tag :followed_id, @user.id %></div>
<%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn" %>
<% end %>
これらのフォームは、どちらもform_forを使ってRelationshipオブジェクトを操作している。
前者は新規のオブジェクトをPOSTリクエストでcreateアクションに、後者は既存のオブジェクトをDELETEアクションでdestroyアクションに送信している。
前者ではhidden_field_tagを使って、フォローしたいユーザーのIDをfollowed_idに入れて送信している。
このhidden_field_tagは次のようなhtmlを生成している。
<input id="followed_id" name="followed_id" type="hidden" value="3" />
必要なパーシャルができたので、プロフィールページに統計情報のパーシャルとフォロー用のパーシャルを表示する。
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
</aside>
<div class="col-md-8">
<%= render 'follow_form' if logged_in? %>
<% if @user.microposts.any? %>
<h3>Microposts (<%= @user.microposts.count %>)</h3>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
フォローボタンはログイン中のみ表示するようにしている。
###following/followerページ
フォロー/フォロワーを表示するページを作成する。
各ページへはログインしていないとアクセスできないようにする。
まずはテストを書く。
require 'test_helper'
class UsersControllerTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
@other_user = users(:archer)
end
.
.
.
test "should redirect following when not logged in" do
get following_user_path(@user)
assert_redirected_to login_url
end
test "should redirect followers when not logged in" do
get followers_user_path(@user)
assert_redirected_to login_url
end
end
following/followedアクションへのルーティングはすでに実装しているので、それらの中身を書いて、beforeフィルターをかける。
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
:following, :followers]
.
.
.
def following
@title = "Following"
@user = User.find(params[:id])
@users = @user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
@title = "Followers"
@user = User.find(params[:id])
@users = @user.followers.paginate(page: params[:page])
render 'show_follow'
end
private
.
.
.
end
ここで、following/followersアクションには対応するビューをそれぞれに用意せず、show_followという共通のビューを使用している。
それは、表示するユーザーの内容とタイトル以外は同じだからである。
show_followビューは以下のようになる。
<% provide(:title, @title) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= gravatar_for @user %>
<h1><%= @user.name %></h1>
<span><%= link_to "view my profile", @user %></span>
<span><b>Microposts:</b> <%= @user.microposts.count %></span>
</section>
<section class="stats">
<%= render 'shared/stats' %>
<% if @users.any? %>
<div class="user_avatars">
<% @users.each do |user| %>
<%= link_to gravatar_for(user, size: 30), user %>
<% end %>
</div>
<% end %>
</section>
</aside>
<div class="col-md-8">
<h3><%= @title %></h3>
<% if @users.any? %>
<ul class="users follow">
<%= render @users %>
</ul>
<%= will_paginate %>
<% end %>
</div>
</div>
###following/followerページのテスト
show_followページが正しく描画されているかの統合テストを作成する。
$ rails generate integration_test following
Relationshipのfixtureファイルを書いて、テスト用ユーザーにフォロー/被フォローの関係を設定する。
one:
follower: michael
followed: lana
two:
follower: michael
followed: malory
three:
follower: lana
followed: michael
four:
follower: archer
followed: michael
前半の2つでMichaelがLanaとMaloryをフォローし、後半の2つでLanaとArcherがMichaelをフォローしている。
統合テストは以下のようになる。
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
log_in_as(@user)
end
test "following page" do
get following_user_path(@user)
assert_not @user.following.empty?
assert_match @user.following.count.to_s, response.body
@user.following.each do |user|
assert_select "a[href=?]", user_path(user)
end
end
test "followers page" do
get followers_user_path(@user)
assert_not @user.followers.empty?
assert_match @user.followers.count.to_s, response.body
@user.followers.each do |user|
assert_select "a[href=?]", user_path(user)
end
end
end
assert_not @user.followers.empty?
というテストは、これがtrueだった場合に、後の
@user.followers.each do |user|
assert_select "a[href=?]", user_path(user)
end
でエラーとならないように入れている。