##フォローボタンの実装
###Relationshipsコントローラ
フォロー/フォロー解除ボタンが機能するように、Relationshipsコントローラを作成する。
$ rails generate controller Relationships
Relationshipsコントローラに必要なのは、フォロー用のcreateアクションと、フォロー解除用のdestroyアクションだけである。
よって、ルーティングは以下のようになる。
Rails.application.routes.draw do
.
.
.
resources :users do
member do
get :following, :followers
end
end
resources :microposts, only: [:create, :destroy]
resources :relationships, only: [:create, :destroy]
end
###create/destroyアクション
createアクションとdestroyアクションはやはりログインしていなければアクセスできないので、テストを書いてフィルターをつける。
require 'test_helper'
class RelationshipsControllerTest < ActionDispatch::IntegrationTest
test "create should require logged-in user" do
assert_no_difference 'Relationship.count' do
post relationships_path
end
assert_redirected_to login_url
end
test "destroy should require logged-in user" do
assert_no_difference 'Relationship.count' do
delete relationship_path(relationships(:one))
end
assert_redirected_to login_url
end
end
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
user = User.find(params[:followed_id])
current_user.follow(user)
redirect_to user
end
def destroy
user = Relationship.find(params[:id]).followed
current_user.unfollow(user)
redirect_to user
end
end
create/destroyアクションでは、フォロー/フォロー解除フォームから送信された値から、followed_idに対応するユーザーを見つけ、それにfollow/unfollowメソッドを使っている。
##Ajax
###Ajaxを使った非同期リクエスト
現在の仕様では、フォローボタンを押した後はプロフィールページにリダイレクトされるようになっている。
ここで、ページを維持したままリクエストを送るために、Ajaxを使用する。
form_forにremote: trueを与える。
<%= form_for(current_user.active_relationships.build, remote: true) 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 },
remote: true) do |f| %>
<%= f.submit "Unfollow", class: "btn" %>
<% end %>
次に、create/destroyアクションにrespond_toメソッドを使う。
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
@user = User.find(params[:followed_id])
current_user.follow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
def destroy
@user = Relationship.find(params[:id]).followed
current_user.unfollow(@user)
respond_to do |format|
format.html { redirect_to @user }
format.js
end
end
end
ここではインスタンス変数を使うことに注意する。
また、ブラウザ側でJavaScriptが無効になっていた場合 (Ajaxリクエストが送れない場合) でもうまく動くようにする。
require File.expand_path('../boot', __FILE__)
.
.
.
module SampleApp
class Application < Rails::Application
.
.
.
# 認証トークンをremoteフォームに埋め込む
config.action_view.embed_authenticity_token_in_remote_forms = true
end
end
次に、JavaScriptと埋め込みRubyを使って、プロフィールページを更新する。
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");
$("#followers").html('<%= @user.followers.count %>');
$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");
$("#followers").html('<%= @user.followers.count %>');
このあたりの詳細がよく分からないので、とりあえず今はスキップすることにする。
##フォロー機能のテスト
###フォロー/フォロー解除
Relationshipsコントローラのcreate/destroyアクションにそれぞれPOST/DELETEリクエストを送り、フォローされたユーザーの数が増減することを確認する。
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
@other = users(:archer)
log_in_as(@user)
end
.
.
.
test "should follow a user the standard way" do
assert_difference '@user.following.count', 1 do
post relationships_path, params: { followed_id: @other.id }
end
end
test "should follow a user with Ajax" do
assert_difference '@user.following.count', 1 do
post relationships_path, xhr: true, params: { followed_id: @other.id }
end
end
test "should unfollow a user the standard way" do
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship)
end
end
test "should unfollow a user with Ajax" do
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship), xhr: true
end
end
end
Ajax使用する場合はリクエストの後にxhr: trueを追加する。