【180日目】【1日20分のRailsチュートリアル】【第12章】ステータスフィードのテストを作成する
今日は「12.3 ステータスフィード」から。
12.3 ステータスフィード
ついに、サンプルアプリケーションの山頂が目の前に現れました。最後の難関、ステータスフィードの実装に取りかかりましょう。
(中略)
これ実現するためには、RailsとRubyの高度な機能の他に、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章】フォロー/フォロー解除をテストする
今日は「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でフォロー/フォロー解除ボタンを更新する
今日は「12.2.5 [フォローする] ボタン (Ajax)」のJavaScriptが無効になっていた場合の対応を実装するところから。
12.2.5 [フォローする] ボタン (Ajax)
リスト12.35でAjaxリクエストに対応したので、今度はブラウザ側で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を使ったフォローボタンを考える
今日は「12.2.5 [フォローする] ボタン (Ajax)」から。
12.2.5 [フォローする] ボタン (Ajax)
ユーザーをフォローした後、本当にそのページから離れて元のページに戻らないといけないのでしょうか。この点を考えなおしてみましょう。
フォローボタンを押した後、そのままだとフォローボタンはフォローボタンのまま。(フォロー解除ボタンにならない)
すぐリダイレクトするようにしているからこそボタンの描画が切り替わる。
これをAjaxを使用して更新しよう、ってことかなー。
へー、一行で済むのか…。
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章】フォローボタン押下時の処理を実装する
今日は「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番目のユーザーのページにアクセス。
フォローしてないのでフォローボタンが表示されてる。
フォローボタン押すとfollowersの数が増えてフォロー解除ボタンに!上手く動いてそう。
followingページ見てみると、ちゃんと追加されてる。
今日の学習時間は【21分】。
次は「12.2.5 [フォローする] ボタン (Ajax)」から。
【176日目】【1日20分のRailsチュートリアル】【第12章】フォローしているユーザー/フォロワーページの統合テストを作成する
今日は「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章】フォローしているユーザー/フォロワーページを作成する
今日は「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
フォローしているユーザーページとフォロワーページにアクセス。
表示されたー。
このとき、上のコードではカレントユーザーを一切使っていない点に注目してください。したがって、他のユーザーのフォロワー一覧ページもうまく動きます (図12.18)。
確かにカレントユーザー使ってないね。他のユーザーのページもちゃんと動く!
今日の学習時間は【29分】。
次は「12.2.3「フォローしているユーザー」ページと「フォロワー」ページ」の統合テストを書くところから。