14.2 FollowのWebインターフェイス
フォロー/フォロー解除の基本的なインターフェイスを実装します。また、
フォローしているユーザーと、フォロワーにそれぞれ表示用のページを作成します。
ユーザーのステータスフィードを追加して、サンプルアプリケーションを完成させます。
14.2.1 フォローのサンプルデータ
サンプルデータを自動作成するrails db:seedを使って、
データベースにサンプルデータを登録できるとやはり便利です。
先にサンプルデータを自動作成できるようにしておけば、
Webページの見た目のデザインから先にとりかかることができ、
バックエンド機能の実装を後に回すことができます。
最初のユーザーにユーザー3からユーザー51までをフォローさせ、
それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせます。
ソースを見るとわかるように、このような設定を自由に行うことができます。こうしてリレーションシップを作成しておけば、
アプリケーションのインターフェイスを開発するには十分です。
サンプルデータにfollowing/followerの関係性を追加する
db/seeds.rb
# ユーザー 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) }
rails db:migrate:reset rails db:seed
演習
1:コンソールを開き、User.first.followers.countの結果が
期待している結果と合致していることを確認してみましょう。
User.first.followers.count User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] (0.4ms) SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ? [["followed_id", 1]] => 38
2:先ほどの演習と同様に、User.first.following.countの結果も合致していることを確認してみましょう。
User.first.following.count User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] (0.2ms) SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]] => 49
14.2.2 統計とFollowフォーム
これでサンプルユーザーに、フォローしているユーザーとフォロワーができました。プロフィールページとHomeページを更新して、
これを反映しましょう。
最初に、プロフィールページとHomeページに、
フォローしているユーザーとフォロワーの統計情報を表示するための
パーシャルを作成します。次に、フォロー用とフォロー解除用のフォームを作成します。
それから、フォローしているユーザーの一覧 (“following”)と
フォロワーの一覧 (“followers”) を表示する専用のページを作成します。
resourcesブロックの内側で:memberメソッドを使っています。
これは初登場のメソッドですが、まずはどんな動作をするのか推測してみてください
Usersコントローラにfollowingアクションとfollowersアクションを追加する
config/routes.rb
Rails.application.routes.draw do root 'static_pages#home' get '/help', to: 'static_pages#help' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' get '/signup', to: 'users#new' get '/login', to: 'sessions#new' post '/login', to: 'sessions#create' delete '/logout', to: 'sessions#destroy' resources :users do member do get :following, :followers end end resources :account_activations, only: [:edit] resources :password_resets, only: [:new, :create, :edit, :update] resources :microposts, only: [:create, :destroy] end
どちらもデータを表示するページなので、
適切なHTTPメソッドはGETリクエストになります。
したがって、getメソッドを使って適切なレスポンスを返すようにします。
ちなみに、memberメソッドを使うとユーザーidが含まれているURLを扱うように
なりますが、 idを指定せずにすべてのメンバーを表示するには、
次のようにcollectionメソッドを使います。
resources :users do collection do get :tigers end end
このコードは /users/tigers というURLに応答します
(アプリケーションにあるすべてのtigerのリストを表示します)。
HTTPリクエスト URL アクション 名前付きルート GET /users/1/following following following_user_path(1) GET /users/1/followers followers followers_user_path(1)
この表で示したフォロー用とフォロワー用の名前付きルートを、今後の実装で使っていきます。
ルーティングを定義したので、統計情報のパーシャルを実装する準備が整いました。このパーシャルでは、divタグの中に2つのリンクを含めるようにします
フォロワーの統計情報を表示するパーシャル
app/views/shared/_stats.html.erb
<% @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 %>
これは前説明したとおり、@userがnilでない場合
(つまりプロフィールページの場合)は何もせず、nilの場合
(つまりHomeページの場合)には@userにcurrent_userを代入するコードです。
その後、フォローしているユーザーの人数を、関連付けを使って計算します。
@user.following.count
これはフォロワーについても同様です。
@user.followers.count
次のようにCSS idを指定していることにもぜひ注目してください。
<strong id="following" class="stat"> ... </strong>
こうしておくと、Ajaxを実装するときに便利です。
そこでは、一意のidを指定してページ要素にアクセスしています。
Homeページにこの統計情報を表示するには、リスト 14.17のようにすると簡単です。
Homeページにフォロワーの統計情報を追加する
app/views/static_pages/home.html.erb
<% 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 %>
統計情報にスタイルを与えるために、SCSSを追加しましょう
(なお、このSCSSにはこの章で使うすべてのスタイルが含まれています)。変更の結果、Homeページは↓のようになります。
Homeページのサイドバー用のSCSS
app/assets/stylesheets/custom.scss
.
.
.
/* sidebar */
.
.
.
.gravatar {
float: left;
margin-right: 10px;
}
.gravatar_edit {
margin-top: 15px;
}
.stats {
overflow: auto;
margin-top: 0;
padding: 0;
a {
float: left;
padding: 0 10px;
border-left: 1px solid $gray-lighter;
color: gray;
&:first-child {
padding-left: 0;
border: 0;
}
&:hover {
text-decoration: none;
color: blue;
}
}
strong {
display: block;
}
}
.user_avatars {
overflow: auto;
margin-top: 10px;
.gravatar {
margin: 1px 1px;
}
a {
padding: 0;
}
}
.users.follow {
padding: 0;
}
/* forms */
.
.
.
この後すぐ、プロフィールにも統計情報パーシャルを表示しますが、
今のうちにリスト [Follow] / [Unfollow] ボタン用のパーシャルも
作成しましょう。
フォロー/フォロー解除フォームのパーシャル
app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %> <div id="follow_form"> <% if current_user.following?(@user) %> <%= render 'unfollow' %> <% else %> <%= render 'follow' %> <% end %> </div> <% end %>
followとunfollowのパーシャルに作業を振っているだけです。
パーシャルでは、Relationshipsリソース用の新しいルーティングが必要です。
これを、Micropostsリソースの例に従って作成しましょう
Relationshipリソース用のルーティングを追加する
config/routes.rb
Rails.application.routes.draw do root 'static_pages#home' get 'help' => 'static_pages#help' get 'about' => 'static_pages#about' get 'contact' => 'static_pages#contact' get 'signup' => 'users#new' get 'login' => 'sessions#new' post 'login' => 'sessions#create' delete 'logout' => 'sessions#destroy' resources :users do member do get :following, :followers end end resources :account_activations, only: [:edit] resources :password_resets, only: [:new, :create, :edit, :update] resources :microposts, only: [:create, :destroy] resources :relationships, only: [:create, :destroy] end
フォロー/フォロー解除用のパーシャル自体は、↓に示します。
ユーザーをフォローするフォーム
app/views/users/_follow.html.erb
<%= 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 %>
ユーザーをフォロー解除するフォーム
app/views/users/_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id), html: { method: :delete }) do |f| %> <%= f.submit "Unfollow", class: "btn" %> <% end %>
これらの2つのフォームでは、いずれもform_forを使って
Relationshipモデルオブジェクトを操作しています。
2つのフォームの主な違いは、前者は新しいリレーションシップを作成するのに対し、後者は既存のリレーションシップを見つけ出すという点です。
つまり、前者はPOSTリクエストをRelationshipsコントローラに送信してリレーションシップをcreate (作成) し、
後者はDELETEリクエストを送信してリレーションシップをdestroy(削除)するということです。
最終的に、このフォロー/フォロー解除フォームにはボタンしかないことを理解していただけたと思います。
しかし、それでもこのフォームはfollowed_idをコントローラに送信する必要があります。
これを行うために、hidden_field_tagメソッドを使います。
このメソッドは、次のフォーム用HTMLを生成します
<input id="followed_id" name="followed_id" type="hidden" value="3" />
隠しフィールドのinputタグを使うことで、
ブラウザ上に表示させずに適切な情報を含めることができます。
このテクニックを使ってフォロー用フォームをパーシャルとしてプロフィール画面に表示した結果が↓になります。
プロフィール画面に [Follow] ボタンと[Unfollow] ボタンがそれぞれ表示されることを確認してみましょう。
プロフィールページにフォロー用フォームとフォロワーの統計情報を追加する
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> <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>
実はこのボタンの実装には2通りの方法があります。1つは標準的な方法、もう1つはAjaxを使う方法です。
でもその前に、フォローしているユーザーとフォロワーを表示するページをそれぞれ作成してHTMLインターフェイスを完成させてしまいましょう。
演習
1:ブラウザから /users/2 にアクセスし、フォローボタンが表示されていることを確認してみましょう。
同様に、/users/5 では [Unfollow] ボタンが表示されているはずです。
さて、/users/1 にアクセスすると、どのような結果が表示されるでしょうか?
followもUnfollowも表示されない
2:ブラウザからHomeページとプロフィールページを表示してみて、
統計情報が正しく表示されているか確認してみましょう。
正しく表示されている
3:Homeページに表示されている統計情報に対してテストを書いてみましょう。同様にして、プロフィールページにもテストを追加してみましょう。
test "count relationships" do
log_in_as(@user)
get root_path
assert_match @user.active_relationships.count.to_s, response.body
assert_match @user.passive_relationships.count.to_s, response.body
end
end
assert_select @user.microposts.count
assert_match @user.active_relationships.to_s, response.body
assert_match @user.passive_relationships.to_s, response.body
end
テストを行う