Rails-tutorialのまとめ14.2(FollowのWebインターフェイス)

その14から続く

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ページに表示されている統計情報に対してテストを書いてみましょう。同様にして、プロフィールページにもテストを追加してみましょう。

site_layout_test.rb
  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
users_profile_test.rb
    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

テストを行う

14.25に続く

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です