ぞえの技術めも

Ruby on Rails勉強中

【176日目】【1日20分のRailsチュートリアル】【第12章】フォローしているユーザー/フォロワーページの統合テストを作成する

Ruby on Railsチュートリアル(第3版)

今日は「12.2.3「フォローしているユーザー」ページと「フォロワー」ページ」の統合テストを書くところから。

12.2.3「フォローしているユーザー」ページと「フォロワー」ページ

フォロー一覧もフォロワー一覧も動くようになったので、この振る舞いを検証するための2つの統合テストを書いていきましょう。
(中略)
したがって今回は、正しい数が表示されているかどうかと、正しいURLが表示されているかどうかの2つのテストを書きます。

網羅的なテストは壊れやすいそうなので最低限を確認する統合テストを作成する。

まずは統合テストを生成。

$ rails generate integration_test following
      invoke  test_unit
      create    test/integration/following_test.rb

リレーションシップ用のfixtureにテストデータを追加すると、リスト12.27のようになります。

ユーザーidでも関連付けできるけど、ユーザー名でも関連付けできる、ってことかな…。
ユーザーidで書くより分かりやすいね。

test/fixtures/relationships.yml

one:
  follower: michael
  followed: lana

two:
  follower: michael
  followed: mallory

three:
  follower: lana
  followed: michael

four:
  follower: archer
  followed: michael

正しい数かどうかを確認するために、assert_matchメソッド (リスト11.27) を使ってプロフィール画面のマイクロポスト数をテストします。さらに、正しいURLかどうかをテストするコードも加えると、リスト12.28のようになります。

フォローしているユーザーのページとフォロワーページそれぞれについて、
・フォローしているユーザー/フォロワーの数が表示されていること
・フォローしているユーザー/フォロワーのユーザーページへのリンクがあること
を確認する感じかな。

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

テストを実行して問題ないことを確認。

$ bundle exec rake test
67 tests, 325 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【18分】

次は「12.2.4 [フォローする] ボタン (標準的な方法)」から。

【175日目】【1日20分のRailsチュートリアル】【第12章】フォローしているユーザー/フォロワーページを作成する

Ruby on Railsチュートリアル(第3版)

今日は「12.2.3「フォローしているユーザー」ページと「フォロワー」ページ」から。

12.2.3「フォローしているユーザー」ページと「フォロワー」ページ

フォローしているユーザーを表示するページと、フォロワーを表示するページは、いずれもユーザープロファイルページとユーザーインデックスページ (9.3.1) を合わせたような作りになるという点で似ています。

モックアップを見ても違いがよく分からないな…。
レイアウトはほぼ一緒だけど、表示するユーザーとの関係性が違ってくるのかな。

前回のアクセス制御と同様に、まずはテストから書いていきます。今回使うテストはリスト12.24のとおりです。

followingアクションとfollowersアクションにアクセスしたときのテストを追加する。

test/controllers/users_controller_test.rb

  test "should redirect following when not logged in" do
    get :following, id: @user
    assert_redirected_to login_url
  end

  test "should redirect followers when not logged in" do
    get :followers, id: @user
    assert_redirected_to login_url
  end

この実装には1つだけトリッキーな部分があります。それはUsersコントローラに2つの新しいアクションを追加する必要があるということです。

followingアクションとfollowersアクション追加しときましょう。

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

renderで呼び出しているビューが同じである理由は、このERbはどちらの場合でもほぼ同じであり、リスト12.26で両方の場合をカバーできるためです。

1つのビューで両方のページをカバーできるんだ。
それぞれのアクションでフォローしているユーザー一覧とフォロワー一覧を設定するから1つのビューでいけるのかな。あとタイトル。

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”をとおって描画したビューを図12.16に、“followers”をとおって描画したビューを図12.17に示します。

サーバーを起動して

$ rails server -b $IP -p $PORT

フォローしているユーザーページとフォロワーページにアクセス。
表示されたー。

f:id:kt_zoe:20170904123817p:plain

f:id:kt_zoe:20170904123829p:plain

このとき、上のコードではカレントユーザーを一切使っていない点に注目してください。したがって、他のユーザーのフォロワー一覧ページもうまく動きます (図12.18)。

確かにカレントユーザー使ってないね。他のユーザーのページもちゃんと動く!

f:id:kt_zoe:20170904123842p:plain

今日の学習時間は【29分】

次は「12.2.3「フォローしているユーザー」ページと「フォロワー」ページ」の統合テストを書くところから。

【174日目】【1日20分のRailsチュートリアル】【第12章】フォロー/フォロー解除ボタンを作成する

Ruby on Railsチュートリアル(第3版)

今日は「12.2.2 統計とフォロー用フォーム」のフォロー/フォロー解除ボタン用のパーシャルを作成するところから。

12.2.2 統計とフォロー用フォーム

プロファイルにも統計情報パーシャルを表示しますが、今のうちにリスト11.23のようにフォロー/フォロー解除ボタン用のパーシャルも作成しましょう。

フォロー/フォロー解除ボタン用のパーシャルを作成する。

まずパーシャル用のファイルを生成して、

$ touch app/views/users/_follow_form.html.erb

下記内容で更新する。

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リソース用の新しいルーティングが必要です。これを、リスト11.29のMicropostsリソースの例に従って作成しましょう (リスト12.20)。

Micropostと同じように、createとdestroyだけ追加。

config/routes.rb

  resources :relationships,       only: [:create, :destroy]

follow/unfollowパーシャル自体は、リスト12.21リスト12.22に示します。

パーシャル用のファイルを生成して、

$ touch app/views/users/_follow.html.erb
$ touch app/views/users/_unfollow.html.erb

下記内容で更新。

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

すなわち、前者はPOSTリクエストを Relationshipsコントローラに送信してリレーションシップをcreate (作成) し、後者はDELETEリクエストを送信してリレーションシップをdestroy (削除) するということです(これらのアクションは12.2.4で実装します)。

ふむふむ。それはなんとなく分かった。

これで、12.23のようにフォロー用のフォームをユーザープロファイルページにインクルードしてパーシャルを出力できるようになりました。

プロファイルページにフォロー/フォロー解除ボタンを表示するのか。

app/views/users/show.html.erb

    <section class="stats">
      <%= render 'shared/stats' %>
    </section>
    :
  <div class="col-md-8">
    <%= render 'follow_form' if logged_in? %>

ボタン見るためにサーバーを起動して

$ rails server -b $IP -p $PORT

他ユーザーのプロファイルページにアクセス。
(ログインユーザーのプロファイルページには何も表示されてなくて最初びびった。そりゃそうだ、表示されんわ。)

f:id:kt_zoe:20170901124539p:plain

f:id:kt_zoe:20170901124551p:plain

フォローボタンもフォロー解除ボタンも表示されてます!

今日の学習時間は【25分】

次は「12.2.3「フォローしているユーザー」ページと「フォロワー」ページ」から。

【173日目】【1日20分のRailsチュートリアル】【第12章】フォローしているユーザーとフォロワーの数を表示する

Ruby on Railsチュートリアル(第3版)

今日は「12.2.2 統計とフォロー用フォーム」から。

12.2.2 統計とフォロー用フォーム

最初に、プロファイルページとHomeページに、フォローしているユーザーとフォロワーの統計情報を表示するためのパーシャルを作成します。次に、フォロー用とフォロー解除用のフォームを作成します。それから、フォローしているユーザーの一覧 (“following”) とフォロワーの一覧 (“followers”) を表示する専用のページを作成します。

やることいっぱい!

実際のページ作成は12.2.3まで行いませんが、ルーティングは今実装します (リスト12.15)。

Userコントローラにfollowingアクションとfollowersアクションを追加するルーティングを実装する。

config/routes.rb

  resources :users do
    member do
      get :following, :followers
    end
  end
#  resources :users

ちなみに、memberメソッドを使うとユーザーidが含まれているURLを扱うようになりますが、

へぇ~、そうなのか…。
URLは/users/1/following/users/1/followersのようになるらしい。

ルーティングを定義したので、統計情報のパーシャルを実装する準備が整いました。このパーシャルでは、divタグの中に2つのリンクを含めるようにします (リスト12.16)。

まずパーシャルのファイルを作って

$ touch app/views/shared/_stats.html.erb

下記内容で更新する。

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>

コラム8.1でも説明したとおり、@userが nilでない場合 (つまりプロファイルページの場合) は何もせず、nilの場合 (つまりHomeページの場合) には@userにカレントユーザーを設定します。

そういえばそんな処理もあったね。便利。

統計情報パーシャルができあがりました。Homeページにこの統計情報を表示するのは、リスト12.17のように簡単にできます。

パーシャル呼び出すだけ。簡単!

app/views/static_pages/home.html.erb

      <section class="stats">
        <%= render 'shared/stats' %>
      </section>

統計情報にスタイルを与えるために、リスト12.18のようにSCSSを追加しましょう (なお、このSCSSにはこの章で使用するスタイルがすべて含まれています)。

スタイル追加も忘れずに。

app/assets/stylesheets/custom.css.scss

.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;
}

変更の結果、Homeページは図12.11のようになります。

久々のサーバー起動!

$ rails server -b $IP -p $PORT

ログインしてHomeページにアクセスすると、、、

f:id:kt_zoe:20170830124512p:plain

表示されました!

節の途中だけど今日はここまで。
今日の学習時間は【24分】

次は「12.2.2 統計とフォロー用フォーム」のフォロー/フォロー解除ボタン用のパーシャルを作成するところから。

【172日目】【1日20分のRailsチュートリアル】【第12章】Relationshipのサンプルデータを生成する

Ruby on Railsチュートリアル(第3版)

今日は「12.2 フォローしているユーザー用のWebインターフェイス」から。

12.2 フォローしているユーザー用のWebインターフェイス

この節では、モックアップで示したようにフォロー/フォロー解除の基本的なインターフェイスを実装します。また、フォローしているユーザーと、フォロワーにそれぞれ表示用のページを作成します。

システム的なところが実装できたので次はビューを実装する感じかな。

12.2.1 フォローしているユーザーのサンプルデータ

1つ前の章のときと同じように、サンプルデータを自動作成するRakeタスクを使用して、データベースに実際のデータを登録するのがやはり便利です。

インターフェース作る前にサンプルデータを生成する。

リスト12.14は、リレーションシップのサンプルデータを生成するためのコードです。ここでは、最初のユーザーにユーザー3からユーザー51までをフォローさせ、それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせます。

ふむふむ、サンプルデータとして最初のユーザーを使うんですね。

db/seeds.rb

# リレーションシップ
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) }

リスト12.14を実行してデータベース上のサンプルデータを作り直すために、いつものコマンドを実行しましょう。

コマンド実行。これでサンプルデータ作り直せたかな。

$ bundle exec rake db:migrate:reset
  :
== 20170726030438 CreateRelationships: migrating ==============================
-- create_table(:relationships)
   -> 0.0007s
-- add_index(:relationships, :follower_id)
   -> 0.0005s
-- add_index(:relationships, :followed_id)
   -> 0.0008s
-- add_index(:relationships, [:follower_id, :followed_id], {:unique=>true})
   -> 0.0013s
== 20170726030438 CreateRelationships: migrated (0.0036s) =====================
$ bundle exec rake db:seed

今日の学習時間は【12分】

次は「12.2.2 統計とフォロー用フォーム」から。

【171日目】【1日20分のRailsチュートリアル】【第12章】フォロワーの関連付けを追加する

Ruby on Railsチュートリアル(第3版)

今日は「12.1.5 フォロワー」から。

12.1.5 フォロワー

リレーションシップというパズルの最後の一片は、user.followersメソッドを追加することです。これは上のuser.followingメソッドと対になります。
(中略)
したがって、データモデルは図12.9のようになります。

対になってるのは分かったけど、いきなり出てきたpassive_relationshipsに困惑した…。
フォローしているユーザーを表すテーブルをactive_relationshipsとしてたので、フォロワーを表すテーブルをpassive_relationshipsとする、ってことだよね。
で、その実態はRelationshipモデル、と。見方を変えただけかな。たぶん

図12.9を参考にしたデータモデルの実装をリスト12.12に示しますが、この実装はリスト12.8とまさに類似しています。

app/models/user.rb

  has_many :passive_relationships, class_name:  "Relationship",
                                   foreign_key: "followed_id",
                                   dependent:   :destroy
  :
  has_many :followers, through: :passive_relationships, source: :follower

一点、リスト12.12で注意すべき箇所は、次のように参照先 (followers) を指定するための:sourceキーを省略してもよかったという点です。

へー、そうなんだ。
followingは明示的に指定してあげないと駄目だけど、followersRailsが探してくれるのかー。

次に、followers.include?メソッドを使って先ほどのデータモデルをテストしていきます。テストコードはリスト12.13のとおりです。

サンプルアプリケーションで実際に使う一行だけ追加。フォロワーはメソッド追加しなくていいのかー。

test/models/user_test.rb

    assert archer.followers.include?(michael)

テストが問題なく通ることを確認。

$ bundle exec rake test
63 tests, 313 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【28分】

次は「12.2 フォローしているユーザー用のWebインターフェイス」から。

【170日目】【1日20分のRailsチュートリアル】【第12章】フォローしているユーザーの関連付けを追加する

Ruby on Railsチュートリアル(第3版)

今日は「12.1.4 フォローしているユーザー」から。

12.1.4 フォローしているユーザー

Railsは“followeds”というシンボル名を見て、これを“followed”という単数形に変え、 relationshipsテーブルのfollowed_idを使って対象のユーザーを取得してきます。しかし、12.1.1で指摘したように、user.followedsという名前は英語として不適切です。

ふと思ったけど英語として不適切な点をスルーすればこの辺の作業要らないのでは…!?
まぁuser.followedsって言語として気持ち悪いんだろうけどね。英語苦手な私には分かるような分からないような。

ここでは:sourceパラメーター (リスト12.10) を使用し、「following配列の元はfollowed idの集合である」ということを明示的にRailsに伝えます。

app/models/user.rb

  has_many :following, through: :active_relationships, source: :followed

リスト12.8で定義した関連付けにより、フォローしているユーザーを配列の様に扱えるようになりました。

user.followingでアクセスできるようになるのか。それは便利だ。

次に、followingで取得した集合をより簡単に取り扱うために、followやunfollowといった便利メソッドを追加しましょう。

メソッドも追加するのか。更に便利。

アプリケーション内でこれらのメソッドを実際に使うのはまだ先だそうだから、まずテストを作成する。

test/models/user_test.rb

  test "should follow and unfollow a user" do
    michael = users(:michael)
    archer  = users(:archer)
    assert_not michael.following?(archer)
    michael.follow(archer)
    assert michael.following?(archer)
    michael.unfollow(archer)
    assert_not michael.following?(archer)
  end

表12.1のメソッドを参考にしながら、followingによる関連付けを使ってfollow、unfollow、following?メソッドを実装していきましょう (リスト12.10)。このとき、可能な限りself (user自身を表すオブジェクト) を省略している点に注目してください。

きっちり書くとself.active_relationships.create()になるのか。
うーん、言われないと省略してるの忘れそう…。

app/models/user.rb

  # ユーザーをフォローする
  def follow(other_user)
    active_relationships.create(followed_id: other_user.id)
  end

  # ユーザーをアンフォローする
  def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
  end

  # 現在のユーザーがフォローしてたらtrueを返す
  def following?(other_user)
    following.include?(other_user)
  end

テスト実行して問題ないことを確認。

$ bundle exec rake test
63 tests, 312 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【24分】

次は「12.1.5 フォロワー」から。