0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】ユーザーのフォロー機能その2 webインターフェイス【Rails Tutorial 14章まとめ】

Posted at

##webインターフェイス
フォロー機能が構築できたので、実際にアプリケーション上で使えるようにしていく。

###フォローのサンプルデータ
seedファイルを編集して、サンプルユーザー間にフォロー・被フォロー関係を構築する。

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) }

最初のユーザーにユーザー3からユーザー51までをフォローさせ、それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせる。
(11、12章をやってない場合はactivated属性を消しておく)

$ rails db:migrate:reset
$ rails db:seed

を実行しておく。

###following/followersページへのルーティング
ユーザーのプロフィールページにフォローユーザーと被フォローユーザーの人数をそれぞれ表示し、その一覧を表示するページへのリンクを付ける。
そこで、usersリソースのルーティングにfollowersページとfollowingアクションへのルーティングを追加する。
:memberメソッドを使って以下のようにする。

config/routes.rb
  resources :users do
    member do
      get :following, :followers
    end
  end

これによって次のルーティングが実装される。
スクリーンショット 2019-12-04 23.29.00.jpg

###統計情報のパーシャル
フォロー/フォロワー数を表示する統計情報のパーシャルを作成する。

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としている。
このパーシャルはhomeページとユーザープロフィールページの両方で使用する。
homeページではcurrent_userを、プロフィールページではshowアクションで定義された@userをそのまま入れる。

strongタグは、文字を強調するためのもので、強調をスタイルシートで指定するなら使わなくてもよい。

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 %>

###フォロー/フォロー解除ボタンのパーシャル
プロフィールページに統計情報のパーシャルを貼る前に、フォロー/フォロー解除ボタン(フォーム)用のパーシャルを作成しておく。

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 %>

このパーシャルは自身(ログインユーザー)のプロフィールページには表示されない。
つまり、自身とは別のユーザー用である。
そのユーザーをフォローしていればフォロー解除用の、フォローしていなければフォロー用のパーシャルを表示する。

各パーシャルは以下のようになる。

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 %>

これらのフォームは、どちらも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" />

必要なパーシャルができたので、プロフィールページに統計情報のパーシャルとフォロー用のパーシャルを表示する。

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>

フォローボタンはログイン中のみ表示するようにしている。

###following/followerページ
フォロー/フォロワーを表示するページを作成する。
各ページへはログインしていないとアクセスできないようにする。
まずはテストを書く。

test/controllers/users_controller_test.rb
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フィルターをかける。

app/controllers/users_controller.rb
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ビューは以下のようになる。

app/views/users/show_follow.html.erb
<% 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ファイルを書いて、テスト用ユーザーにフォロー/被フォローの関係を設定する。

test/fixtures/relationships.yml
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をフォローしている。

統合テストは以下のようになる。

test/integration/following_test.rb
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

でエラーとならないように入れている。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?