ぞえの技術めも

Ruby on Rails勉強中

【180日目】【1日20分のRailsチュートリアル】【第12章】ステータスフィードのテストを作成する

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

今日は「12.3 ステータスフィード」から。

12.3 ステータスフィード

ついに、サンプルアプリケーションの山頂が目の前に現れました。最後の難関、ステータスフィードの実装に取りかかりましょう。
(中略)
これ実現するためには、RailsRubyの高度な機能の他に、SQLプログラミングの技術も必要です。

今まで作ったものを組み合わせるだけじゃないのか…!
そうか、色んなユーザーのマイクロポストが混ざったフィードを作らないといけないのか。それは今までなかったね。

12.3.1 動機と計画

この目的は、現在のユーザーによってフォローされているユーザーに対応するユーザーidを持つマイクロポストを取り出し、同時に現在のユーザー自身のマイクロポストも一緒に取り出すことです。

フィードにはあるユーザーがフォローしているユーザーとそのユーザー自身のマイクロポストを表示する。Twitterのタイムラインか。

このテストで重要なことは、フィードに必要な3つの条件を満たすことです。
1) フォローしているユーザーのマイクロポストがフィードに含まれていること。2) 自分自身のマイクロポストもフィードに含まれていること。3) フォローしていないユーザーのマイクロポストがフィードに含まれていないこと。

どう実装するかは決まってないけど、テストすることは明確なので先にテストを作成する。

テストユーザーのフォロー関係を意識して、michaelをメインにテストを書いていく感じかな。
post_followingとかってすでに定義したんだっけ…。忘れた…。

test/models/user_test.rb

  test "feed should have the right posts" do
    michael = users(:michael)
    archer  = users(:archer)
    lana    = users(:lana)
    # フォローしているユーザーの投稿を確認
    lana.microposts.each do |post_following|
      assert michael.feed.include?(post_following)
    end
    # 自分自身の投稿を確認
    michael.microposts.each do |post_self|
      assert michael.feed.include?(post_self)
    end
    # フォローしていないユーザーの投稿を確認
    archer.microposts.each do |post_unfollowed|
      assert_not michael.feed.include?(post_unfollowed)
    end
  end

フィードはまだ実装してないのでテストは失敗しますね。

$ bundle exec rake test
  :
 FAIL["test_feed_should_have_the_right_posts", UserTest, 2017-08-23 21:13:41 +0000]
 test_feed_should_have_the_right_posts#UserTest (1503522821.24s)
        Expected false to be truthy.
        test/models/user_test.rb:105:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:104:in `block in <class:UserTest>'
  :

Finished in 2.97709s
74 tests, 334 assertions, 1 failures, 0 errors, 0 skips

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

次は「12.3.2 フィードを初めて実装する」から。

【179日目】【1日20分のRailsチュートリアル】【第12章】フォロー/フォロー解除をテストする

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

今日は「12.2.6 フォローをテストする」から。

12.2.6 フォローをテストする

ユーザーのフォローに対するテストでは、 /relationshipsに対してPOSTリクエストを送り、フォローされたユーザーが1人増えたことをチェックします。

Ajaxでのリクエスト発行もテストで書けるのか…!xhr (XmlHttpRequest) っていうメソッド使うらしい。

これらのテストをまとめた結果を、リスト12.39に示します。

普通のHTTPリクエストとAjaxでのPOST/DELETEリクエストをテストする。
リクエスト発行後、フォローされたユーザーが1人増えているか、フォローしているユーザーが1人減っているかを見る。

ふむ、結構シンプルなテスト。

test/integration/following_test.rb

  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, followed_id: @other.id
    end
  end

  test "should follow a user with Ajax" do
    assert_difference '@user.following.count', 1 do
      xhr :post, relationships_path, 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
      xhr :delete, relationship_path(relationship)
    end
  end

特に問題なくテスト通りました。

$ bundle exec rake test
73 tests, 333 assertions, 0 failures, 0 errors, 0 skips

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

次は「12.3 ステータスフィード」から。

【178日目】【1日20分のRailsチュートリアル】【第12章】JavaScriptでフォロー/フォロー解除ボタンを更新する

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

今日は「12.2.5 [フォローする] ボタン (Ajax)」のJavaScriptが無効になっていた場合の対応を実装するところから。

12.2.5 [フォローする] ボタン (Ajax)

リスト12.35Ajaxリクエストに対応したので、今度はブラウザ側でJavaScriptが無効になっていた場合 (Ajaxリクエストが送れない場合) でもうまく動くようにします (リスト12.36)。

この一行でJavaScriptが無効になっていても上手く動作するのか…。

config/application.rb

    # 認証トークンをremoteフォームに埋め込む
    config.action_view.embed_authenticity_token_in_remote_forms = true

一方で、JavaScriptが有効になっていても、まだ十分に対応できていない部分があります。
(中略)
ユーザーをフォローしたときやフォロー解除したときにプロフィールページを更新するために、私たちがこれから作成および編集しなければならないのは、まさにこれらのファイルです。

Ajaxリクエストを受信した場合は、Railsが自動的にアクションと同じ名前を持つJavaScript用の埋め込みRuby (.js.erb) ファイルを呼び出すらしいので呼び出されるファイルをまず生成しておく。

$ touch app/views/relationships/create.js.erb
$ touch app/views/relationships/destroy.js.erb

create.js.erbファイルでは、フォロー用のフォームをunfollowパーシャルで更新し、フォロワーのカウントを更新するのにERbを使用しています (もちろんこれは、フォローに成功した場合の動作です)。

フォローボタンを押下されたときはアンフォロー用のHTMLで置き換えて、フォロワー数を更新する。
フォロー解除ボタンを押下されたときはフォロー用のHTMLで置き換えて、フォロワー数を更新する。
って感じなのかー。

app/views/relationships/create.js.erb

$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");
$("#followers").html('<%= @user.followers.count %>');

app/views/relationships/destroy.js.erb

$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");
$("#followers").html('<%= @user.followers.count %>');

これらのコードにより、ユーザープロファイルを表示して、ページを更新せずにフォローまたはフォロー解除ができるようになったはずです。

サーバーを起動して動作見てみた。
スクショでは表現できないので貼らないけど、多分ページ更新してない!でもフォロー/フォロー解除できてる!
こっちの方が自然な感じでいいね。

$ rails server -b $IP -p $PORT

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

次は「12.2.6 フォローをテストする」から。

【178日目】【1日20分のRailsチュートリアル】【第12章】Ajaxを使ったフォローボタンを考える

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

今日は「12.2.5 [フォローする] ボタン (Ajax)」から。

12.2.5 [フォローする] ボタン (Ajax)

ユーザーをフォローした後、本当にそのページから離れて元のページに戻らないといけないのでしょうか。この点を考えなおしてみましょう。

フォローボタンを押した後、そのままだとフォローボタンはフォローボタンのまま。(フォロー解除ボタンにならない)
すぐリダイレクトするようにしているからこそボタンの描画が切り替わる。 これをAjaxを使用して更新しよう、ってことかなー。

たったこれだけで、Rails自動的にAjaxを使用します。更新の結果をリスト12.33リスト12.34に示します。

へー、一行で済むのか…。

app/views/users/_follow.html.erb

<%= form_for(current_user.active_relationships.build, remote: true) do |f| %>

app/views/users/_unfollow.html.erb

  :
             remote: true) do |f| %>

上の (ブロック内の) コードのうち、いずれかの1行が実行されるという点が重要です (このためrespond_toメソッドは、上から順に実行する逐次処理というより、if文を使った分岐処理に近いイメージです)。

ややこしいね。忘れそう。

ユーザーのローカル変数 (user) をインスタンス変数 (@user) に変更した点に注目してください。これは、リスト12.32のときはインスタンス変数は必要なかったのですが、リスト12.33リスト12.34を実装したことにより、インスタンス変数が必要になったためです。

へー。インスタンス変数じゃないと動かないコードがあるのか…。
format.html { redirect_to @user }インスタンス変数が必要なのかな?

HTMLリクエストなら上のコード、Ajaxリクエストなら下のコードが動く、ってことなんだよね…難しい…。

app/controllers/relationships_controller.rb

  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

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

次は「12.2.5 [フォローする] ボタン (Ajax)」のJavaScriptが無効になっていた場合の対応を実装するところから。

【177日目】【1日20分のRailsチュートリアル】【第12章】フォローボタン押下時の処理を実装する

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

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

12.2.4 [フォローする] ボタン (標準的な方法)

フォローとフォロー解除はそれぞれリレーションシップの作成と削除に対応しているため、まずはRelationshipsコントローラが必要です。

むしろ今までコントローラ作ってなかったんだ…。そういえば作ってない…。

$ rails generate controller Relationships
      create  app/controllers/relationships_controller.rb
      invoke  erb
      create    app/views/relationships
      invoke  test_unit
      create    test/controllers/relationships_controller_test.rb
      invoke  helper
      create    app/helpers/relationships_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/relationships.coffee
      invoke    scss
      create      app/assets/stylesheets/relationships.scss

今回はまず、コントローラのアクションにアクセスするとき、ログイン済みのユーザーであるかどうかをチェックします。
もしログインしていなければ、ログインページにリダイレクトさせ、Relationshipのカウントが変わっていないことを確認します (リスト12.30)。

コントローラ実装していく前にテストから作成する。

ログインしていない場合はRelationshipコントローラのアクションにアクセスしようとしてもログインページにリダイレクトされるよ、ってテストなのか。

test/controllers/relationships_controller_test.rb

require 'test_helper'

class RelationshipsControllerTest < ActionController::TestCase

  test "create should require logged-in user" do
    assert_no_difference 'Relationship.count' do
      post :create
    end
    assert_redirected_to login_url
  end

  test "destroy should require logged-in user" do
    assert_no_difference 'Relationship.count' do
      delete :destroy, id: relationships(:one)
    end
    assert_redirected_to login_url
  end
end

次に、リスト12.30のテストをパスさせるために、logged_in_userフィルターをRelationshipsコントローラのアクションに対して追加します (リスト12.31)。

今はログイン状態を見てるだけなのでこの対応だけでもテストは通る、と。

app/controllers/relationships_controller.rb

class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
  end

  def destroy
  end
end

フォロー/フォロー解除ボタンを動かすためには、フォーム (リスト12.21/リスト12.22) から送信されたパラメータを使って、followed_idに対応するユーザーを見つけてくる必要があります 。その後、見つけてきたユーザーに対して適切にfollow/unfollowメソッド (リスト12.10) を使います。

実装されたもの見るとシンプルな作りだなぁ。

app/controllers/relationships_controller.rb

  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

これで、フォロー/フォロー解除の機能が完成しました。

テスト通してみる。

$ bundle exec rake test
69 tests, 329 assertions, 0 failures, 0 errors, 0 skips

問題ないね。

次はボタン動くかどうか見てみよう。

サーバーを起動して

$ rails server -b $IP -p $PORT

2番目のユーザーのページにアクセス。
フォローしてないのでフォローボタンが表示されてる。

f:id:kt_zoe:20170906123958p:plain

フォローボタン押すとfollowersの数が増えてフォロー解除ボタンに!上手く動いてそう。

f:id:kt_zoe:20170906124012p:plain

followingページ見てみると、ちゃんと追加されてる。

f:id:kt_zoe:20170906124111p:plain

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

次は「12.2.5 [フォローする] ボタン (Ajax)」から。

【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「フォローしているユーザー」ページと「フォロワー」ページ」の統合テストを書くところから。