はじめに
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版の 13章 13.3 マイクロポストを操作するの演習まとめ&解答例です。
個人の解答例なので、誤りがあればご指摘ください。
動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1
13.3 マイクロポストを操作する
本章での学び
本章では、マイクロポストの作成と削除を行えるようにする。 Micropostsコントローラのアクションは、createとdestroyのみ必要となる。
resources :microposts, only: [:create, :destroy]
routesコマンドで設定値を確認する。
yokoyan:~/workspace/sample_app (user-microposts) $ rails routes Prefix Verb URI Pattern ・・・略・・・ microposts POST /microposts(.:format) microposts#create micropost DELETE /microposts/:id(.:format) microposts#destroy
13.3.1 マイクロポストのアクセス制御
本章での学び
Micropostsコントローラのcreateアクションや、destroyアクションは、ログイン済みである必要がある。
【test】Micropostsコントローラの認可テスト
setup
- micropostsのfixtureからorangeを取得する
- インスタンス変数@micropostに代入する
should redirect create when not logged in
- Micropostモデルの数をカウントし、数が変化していないこと
- POSTリクエストを送信する
- microposts_path
- params
- micropost
- content
- micropost
- POSTリクエストを送信する
- ログイン画面にリダイレクトされること
- Micropostモデルの数をカウントし、数が変化していないこと
should redirect destroy when not logged in
- Micropostモデルの数をカウントし、数が変化していないこと
- DELETEリクエストを送信する
- micropost_path(@micropost)
- DELETEリクエストを送信する
- ログイン画面にリダイレクトされること
- Micropostモデルの数をカウントし、数が変化していないこと
上記の仕様で実装する。
def setup @micropost = microposts(:orange) end test "should redirect create when not logged in" do assert_no_difference 'Micropost.count' do post microposts_path, params: { micropost: { content: "Lorem ipsum" } } end assert_redirected_to login_url end test "should redirect destroy when not logged in" do assert_no_difference 'Micropost.count' do delete micropost_path(@micropost) end assert_redirected_to login_url end
【controller】beforeフィルタのリファクタリング
ユーザーがログインしているかどうかを判定するlogged_in_user
メソッドは、Usersコントローラだけでなく、Micropostsコントローラからも使用する。
private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end # beforeアクション # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end end
そのため、Usersコントローラ内のlogged_in_user
メソッドを、共通の親クラスであるApplicationコントローラに処理を移す。(Javaとは違って、Rubyは継承した親クラス内のprivateメソッドを呼び出せるようだ)
application_controllerにhello, world!のメソッドが残ってる・・・。ここまで長い道のりでした。
class ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper def hello render html:"hello, world!" end private # ユーザーのログインを確認する def logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end end end
リファクタリングが終わったので、Usersコントローラ内のlogged_in_user
メソッドは削除する。
【controller】Micropostsコントローラの実装
create、destroyアクションを追加し、前項でリファクタリングしたbeforeフィルタを実装する。
class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] def create end def destroy end end
【test】テストの実行
この時点でテストがgreenになることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test Running via Spring preloader in process 2188 Started with run options --seed 11090 56/56: [===============================================================================================================================================================================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.56572s 56 tests, 243 assertions, 0 failures, 0 errors, 0 skips
演習1
なぜUsersコントローラ内にあるlogged_in_userフィルターを残したままにするとマズイのでしょうか? 考えてみてください。
Railsの基本理念であるDRY(Don’t repeat yourself)に反するため。 Micropostsコントローラ側でもユーザーのログイン判定を行う必要があり、Usersコントローラ内にlogged_in_userフィルターを残したままにすると、Micropostsコントローラ側にも同じ処理を実装する必要が出てくるため無駄なコードが増えてしまう。
13.3.2 マイクロポストを作成する
本章での学び
【controller】createアクションの実装
Strong parametersで、paramsハッシュでは:micropost属性を必須として、:content属性を許可する。
private def micropost_params params.require(:micropost).permit(:content) end
ログインしているユーザーのマイクロポストを生成し、DBに保存する。 成功すればroot_urlへリダイレクトする。 失敗すればstatic_pages配下のhomeページを呼び出す。
def create @micropost = current_user.microposts.build(micropost_params) if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else render 'static_pages/home' end end
【View】Homeページのリファクタリング
ユーザーがログインしているかどうかで表示を変える。
- ログインしている
- user_infoパーシャル(未実装)を表示する
- micropost_formパーシャル(未実装)を表示する
- ログインしていない
- Sign upを促す
<% if logged_in? %> <div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section> <%= render 'shared/micropost_form' %> </section> </aside> </div> <% else %> <div class="center jumbotron"> <h1>Welcome to the Sample App</h1> <h2> This is the home page for the <a href="http://railstutorial.jp">Ruby on Rails Tutorial</a> sample application. </h2> <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary"%> </div> <%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/'%> <% end %>
【View】パーシャルの作成
未実装の2つのパーシャルを作成する。
yokoyan:~/workspace/sample_app (user-microposts) $ touch app/views/shared/_user_info.html.erb yokoyan:~/workspace/sample_app (user-microposts) $ touch app/views/shared/_micropost_form.html.erb
ユーザー情報パーシャル
Homeページのサイドバーにユーザー情報を表示する。
- ユーザーのGravatar画像
- ユーザー名
- ユーザープロフィール画面へのリンク
- ユーザーのマイクロポスト数の合計
- pluralizeヘルパーで英語の複数形に変換
<%= link_to gravatar_for(current_user, size: 50), current_user %> <h1><%= current_user.name %></h1> <span><%= link_to "view my profile", current_user %></span> <span><%= pluralize(current_user.microposts.count, "micropost")%></span>
マイクロポスト投稿フォームパーシャル
マイクロポストを投稿するためのフォーム。
- form_forで、Micropostモデルに紐づいたフォームを作成する
- エラーメッセージパーシャルを表示
- テキストエリアのcontent属性を表示
- submitボタン
<%= form_for(@micropost) do |f| %> <%= render 'shared/error_messages', object: f.object %> <div class="field"> <%= f.text_area :content, placeholder: "Compose new micropost..." %> </div> <%= f.submit "Post", class: "btn btn-primary" %> <% end %>
【controller】StaticPagesコントローラのリファクタリング
homeアクションにマイクロポストのインスタンス変数を定義する。 ログインしているユーザのマイクロポストを取得する。
class StaticPagesController < ApplicationController def home @micropost = current_user.microposts.build if logged_in? end
【view】エラーメッセージパーシャルのリファクタリング
form_forで定義する変数が、画面によって変化する。
- ユーザー登録画面:@user
- マイクロポスト投稿画面:@micropost
そのため、エラーメッセージパーシャルの呼び出し方法は、
複数のインスタンス変数に対応するためにobject: f.object
となる。
<% form_for(@micropost) do |f| %> <%= render 'shared/error_messages', object: f.object %>
そのため、エラーメッセージパーシャル内で
もともと@user
固定で呼び出していた箇所を、object変数を使うように
リファクタリングする。
■修正前
<% if @user.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(@user.errors.count, "error") %> </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>
■修正後
<% if object.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(object.errors.count, "error") %> </div> <ul> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>
【test】テスト実行
現時点ではredになってしまう。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test Running via Spring preloader in process 3577 Started with run options --seed 41588 ERROR["test_password_resets", PasswordResetsTest, 2.4003681868780404] test_password_resets#PasswordResetsTest (2.40s) ActionView::Template::Error: ActionView::Template::Error: undefined local variable or method `object' for #<#<Class:0x007efdd02d7e20>:0x00000005ff8f20> Did you mean? object_id app/views/shared/_error_messages.html.erb:1:in `_app_views_shared__error_messages_html_erb__4293521809018927093_50498580' app/views/password_resets/edit.html.erb:7:in `block in _app_views_password_resets_edit_html_erb__1534623624385800471_50317940' app/views/password_resets/edit.html.erb:6:in `_app_views_password_resets_edit_html_erb__1534623624385800471_50317940' test/integration/password_resets_test.rb:40:in `block in <class:PasswordResetsTest>' 56/56: [=======================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02 Finished in 2.83377s 56 tests, 234 assertions, 0 failures, 1 errors, 0 skips
【view】エラーメッセージパーシャルの呼び出し元画面のリファクタリング
1.ユーザー登録画面、ユーザー編集画面
この2画面は共通のフォームを使用しているため、object:@user
の箇所を、
object: f.object
にリファクタリングする。
<%= form_for(@user, url: yield(:url)) do |f| %> <%= render 'shared/error_messages', object: f.object %>
2.パスワード再登録画面
エラーメッセージパーシャルの呼び出し部分に、object: f.object
を追加する。
・・・略・・・ <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %> <%= render 'shared/error_messages', object: f.object %>
【test】再度テストを実行
テスト結果がgreenになることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test Running via Spring preloader in process 4197 Started with run options --seed 46999 56/56: [===============================================================================================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.34028s 56 tests, 243 assertions, 0 failures, 0 errors, 0 skips
ブラウザからアクセスできることを確認。
演習1
Homeページをリファクタリングして、if-else文の分岐のそれぞれに対してパーシャルを作ってみましょう。
新規に2つパーシャルを作成する。
yokoyan:~/workspace/sample_app (user-microposts) $ touch app/views/static_pages/_home_logged_in.html.erb yokoyan:~/workspace/sample_app (user-microposts) $ touch app/views/static_pages/_home_not_logged_in.html.erb
ログイン時に表示するパーシャル。
<div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section> <%= render 'shared/micropost_form' %> </section> </aside> </div>
未ログイン時に表示するパーシャル。
<div class="center jumbotron"> <h1>Welcome to the Sample App</h1> <h2> This is the home page for the <a href="http://railstutorial.jp">Ruby on Rails Tutorial</a> sample application. </h2> <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary"%> </div> <%= link_to image_tag("rails.png", alt: "Rails logo"), 'http://rubyonrails.org/'%>
リファクタリング後のhomeページ。 かなりスッキリしました。
<% if logged_in? %> <%= render 'static_pages/home_logged_in' %> <% else %> <%= render 'static_pages/home_not_logged_in' %> <% end %>
リファクタリング後もtestがgreenになることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test Running via Spring preloader in process 5658 Started with run options --seed 10843 56/56: [===============================================================================================================] 100% Time: 00:00:03, Time: 00:00:03 Finished in 3.49162s 56 tests, 243 assertions, 0 failures, 0 errors, 0 skips
13.3.3 フィードの原型
本章での学び
ユーザープロフィール画面に、ユーザーのマイクロポストのフィードを追加する。
【model】Userモデルにfeedメソッドを実装する
すべてのユーザーがフィードを持つため、Userモデルにfeedメソッドを追加する。 SQLインジェクション対策のために、プリペアドステートメントを使う。 (SQLのwhere句にuser.idが入る際にエスケープされる)
# 試作feedの定義 # 完全な実装は次章の「ユーザーをフォローする」を参照 def feed Micropost.where("user_id = ?", id) end
【controller】static_pagesコントローラのリファクタリング
homeアクションのリファクタリングを行う。 1行だった後置if文を、前置if文に変更する。 また、前項で実装したfeedメソッドの取得結果をインスタンス変数@feed_itemsに格納する。
def home if logged_in? @micropost = current_user.microposts.build if logged_in? @feed_items = current_user.feed.paginate(page: params[:page]) end end
【view】フィード表示パーシャルを作成する
インスタンス変数@feed_itemsを表示するためのパーシャルを作成する。 @feed_items内の要素は、Micropostクラスを保持しているため、Micropostのパーシャルを呼び出すことができる。 (app/view/microposts/_micropost.html.erb)
yokoyan:~/workspace/sample_app (user-microposts) $ touch app/views/shared/_feed.html.erb
<% if @feed_items.any? %> <ol class="microposts"> <%= render @feed_items %> </ol> <%= will_paginate @feed_items %> <% end %>
【view】homeページヘの組み込み
13.3.2の演習で作成したパーシャルから、フィード表示パーシャルの呼び出しを行う。
<div class="row"> <aside class="col-md-4"> <section class="user_info"> <%= render 'shared/user_info' %> </section> <section> <%= render 'shared/micropost_form' %> </section> </aside> <div class="col-md-8"> <h3>Micropost Feed</h3> <%= render 'shared/feed' %> </div> </div>
動作確認
追加したフィードが表示されていることを確認。
ただし、マイクロポストの投稿に失敗した場合は、エラーが発生する。 (@feed_itemsを期待しているが取得できていない)
対策として、DB保存失敗時には空の配列で@feed_itemsを定義する。
def create @micropost = current_user.microposts.build(micropost_params) if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else @feed_items = [] render 'static_pages/home' end end
DB保存失敗時にエラーが発生しないことを確認。
演習1
新しく実装したマイクロポストの投稿フォームを使って、実際にマイクロポストを投稿してみましょう。Railsサーバーのログ内にあるINSERT文では、どういった内容をデータベースに送っているでしょうか? 確認してみてください。
投稿フォームからマイクロポストを投稿する。
ログは以下の通り。 INSERT文もプリペアドステートメントになっている。
Started POST "/microposts" for 121.119.136.219 at 2017-08-19 05:37:49 +0000 Cannot render console from 121.119.136.219! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255 Processing by MicropostsController#create as HTML Parameters: {"utf8"=>"✓", "authenticity_token"=>"qHHNxLqH2NJGPTtIdVD5Q4qaNFZXQVt9qkT0D3caNiy5wZ5QD4JaK/VGomjss64pq7nSXcneQXcPCehIT5W3rQ==", "micropost"=>{"content"=>"投稿テスト1行目\r\n投稿テスト2行目"}, "commit"=>"Post"} User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] (0.1ms) begin transaction SQL (0.6ms) INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["content", "投稿テスト1行目\r\n投稿テスト2行目"], ["user_id", 1], ["created_at", 2017-08-19 05:37:49 UTC], ["updated_at", 2017-08-19 05:37:49 UTC]] (10.9ms) commit transaction Redirected to https://rails-tutorial-yokoyan.c9users.io/ Completed 302 Found in 23ms (ActiveRecord: 11.9ms)
演習2
コンソールを開き、user変数にデータベース上の最初のユーザーを代入してみましょう。その後、Micropost.where(“user_id = ?”, user.id)とuser.microposts、そしてuser.feedをそれぞれ実行してみて、実行結果がすべて同じであることを確認してみてください。ヒント: ==で比較すると結果が同じかどうか簡単に判別できます。
実行結果は以下の通り。 すべて同じであることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails console --sandbox Running via Spring preloader in process 6116 Loading development environment in sandbox (Rails 5.0.0.1) Any modifications you make will be rolled back on exit >> ?> user = User.first User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-07-26 21:31:59", updated_at: "2017-07-26 21:31:59", password_digest: "$2a$10$hr3puKUHMOdKoV.IkakM0uxzu3mOW./qM63oJ6Xq3q9...", remember_digest: nil, admin: true, activation_digest: "$2a$10$g6M8JTC1B48HIxzpi6iy8.qBKbQsxyaRos1ldkd2q9n...", activated: true, activated_at: "2017-07-26 21:31:59", reset_digest: nil, reset_sent_at: nil> >> ?> Micropost.where("user_id = ?", user.id) == user.microposts Micropost Load (0.6ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC [["user_id", 1]] Micropost Load (0.5ms) SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC => true >> ?> Micropost.where("user_id = ?", user.id) == user.feed => true
13.3.4 マイクロポストを削除する
本章での学び
本章では、マイクロポストの削除機能を実装する。
【view】マイクロポストのパーシャルのリファクタリング
削除リンクを追加する。 自分が投稿したマイクロポストのみ削除することができる。
<li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"><%= micropost.content %></span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %> <% end %> </span> </li>
【controller】Micropostsコントローラのdestroyアクションを実装する
request.referrer
メソッドを使って、1つ前のページヘリダイレクトする。
リファラーが取得できない場合は、root_urlへリダイレクトする。
def destroy @micropost.destroy flash[:success] = "Micropost deleted" redirect_to request.referrer || root_url end``` ####【controller】beforeフィルタを追加する destroyアクション実施前実行するbeforeフィルタを追加する。 ログインしているユーザのマイクロポストが存在しない場合は、root_urlへリダイレクトする。
class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] before_action :correct_user, only: [:destroy] ・・・略・・・ private
def micropost_params
params.require(:micropost).permit(:content)
end
def correct_user
@micropost = current_user.micropost.find_by(id: params[:id])
redirect_to root_url if @micropost.nil?
end
end
###演習1 *マイクロポストを作成し、その後、作成したマイクロポストを削除してみましょう。次に、Railsサーバーのログを見てみて、DELETE文の内容を確認してみてください。* ブラウザからアクセスし、任意のマイクロポストが削除できることを確認。 ![image](https://qiita-image-store.s3.amazonaws.com/0/98130/8d412edb-66fc-6a56-2e93-c0c90528f74d.png) ログは以下の通り。
Started DELETE “/microposts/302” for 121.119.136.219 at 2017-08-19 07:14:05 +0000 Cannot render console from 121.119.136.219! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255 Processing by MicropostsController#destroy as HTML Parameters: {“authenticity_token”=>“IdXOzp0PdnF6givkKNnhrUQrfoTz5WNa3X9ZQHAbZU/nu0rzvvRjbr0Iix4fPzhqzmx435NnWbNhAk0Oz6TcOw==”, “id”=>“302”} User Load (0.2ms) SELECT “users”. FROM “users” WHERE “users”.“id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]] Micropost Load (0.2ms) SELECT “microposts”. FROM “microposts” WHERE “microposts”.“user_id” = ? AND “microposts”.“id” = ? ORDER BY “microposts”.“created_a ” DESC LIMIT ? [[“user_id”, 1], [“id”, 302], [“LIMIT”, 1]] (0.1ms) begin transaction SQL (108.7ms) DELETE FROM “microposts” WHERE “microposts”.“id” = ? “id”, 302 (17.0ms) commit transaction Redirected to https://rails-tutorial-yokoyan.c9users.io/ Completed 302 Found in 168ms (ActiveRecord: 128.5ms)
###演習2 *redirect_to request.referrer || root_urlの行をredirect_back(fallback_location: root_url)と置き換えてもうまく動くことを、ブラウザを使って確認してみましょう (このメソッドはRails 5から新たに導入されました)。* 確かにGithubでも、redirect_backメソッドを追加したって言ってます。 `redirect_to :back`だと、完全に安全ではなく、 リクエストで利用できるリファラーがない場合、`ActionController::RedirectBackError`が発生するとのこと。 [Add `redirect_back` and deprecate `redirect_to :back` #22506](https://github.com/rails/rails/pull/22506) 以下の記事もわかりやすかったです。 [Rails 5.1系からは redirect_to :backがきえまっせ](http://qiita.com/100010/items/3ae5dea25e85b0cbf1ee) ということで、rediret_backメソッドを使うようにリファクタリング。
def destroy @micropost.destroy flash[:success] = “Micropost deleted” # redirect_to request.referrer || root_url redirect_back(fallback_location: root_url) end
問題なく動くことを確認。 新しいAPI使うほうがいいですね! ![image](https://qiita-image-store.s3.amazonaws.com/0/98130/9657403b-7b26-98f9-04fa-ab6b6cd5bdbc.png) ##13.3.5 フィード画面のマイクロポストをテストする ###本章での学び 作成したマイクロポストの単体テストと統合テストを作成する。 ####【事前準備】テストデータの用意 マイクロポストのfixtureにデータを追加する。
・・・略・・・ ants: content: “Oh, is that what you want? Because that’s how you get ants!” created_at: <%= 2.years.ago %> user: archer
zone: content: “Danger zone!” created_at: <%= 3.days.ago %> user: archer
tone: content: “I’m sorry. Your words made sense, but your sarcastic tone did not.” created_at: <%= 10.minutes.ago %> user: lana
van: content: “Dude, this van’s, like, rolling probable cause.” created_at: <%= 4.hours.ago %> user: lana
####【test】テストコードの作成 マイクロポストのテストコードを作成する。 自分のマイクロポストではないものを削除して - should redirect destroy for wrong micropost - michaelユーザでログインする - マイクロポストのfixtureのantsを取得する(archerユーザのマイクロポスト) - マイクロポストの件数が変わらないことを確認 - マイクロポストを削除する - root_urlにリダイレクトすることを確認
test “should redirect destroy for wrong micropost” do log_in_as(users(:michael)) micropost = microposts(:ants) assert_no_difference ‘Micropost.count’ do delete micropost_path(micropost) end assert_redirected_to root_url end
実行結果がgreenになることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test Running via Spring preloader in process 7833 Started with run options –seed 65115
57/57: [===================================================================================================================================] 100% Time: 00:00:03, Time: 00:00:03
Finished in 3.68433s 57 tests, 245 assertions, 0 failures, 0 errors, 0 skips
####【test】マイクロポストのUIに関する統合テストを作成する rails generateで自動生成する。
yokoyan:~/workspace/sample_app (user-microposts) $ rails generate integration_test microposts_interface Running via Spring preloader in process 7975 Expected string default value for ‘–jbuilder’; got true (boolean) invoke test_unit create test/integration/microposts_interface_test.rb
- setup - michaelユーザの情報を取得する - @userに代入する - micropost interface - ログインする - root_pathへ、getリクエストを送信する - HTMLに div class=pagination要素があることを確認 - マイクロポストの数が変わらないことを確認 - microposts_pathへpostリクエストを送信 - params - micropost - content属性は空 - HTMLにdiv id=error_explanation要素があることを確認 - 変数contentに文字列を代入 - マイクロポストの数が変わることを確認 - microposts_pathへpostリクエストを送信 - params - micropost - content属性は代入した変数content - root_urlへリダイレクトされること - リダイレクトを追う - 代入したcontentの文字列が、HTML内に一致すること - HTML内にdeleteテキストとaタグがあることを確認 - ユーザの1番目のマイクロポストを取得する - マイクロポストの数が-1されることを確認 - micropost_pathへdeleteリクエストを送信 - archerユーザのプロフィールページヘgetリクエストを送信 - HTML内にdeleteテキストとaタグが0件であることを確認 上記を踏まえて実装する。
test “micropost interface” do log_in_as(@user) get root_path assert_select ‘div.pagination’ # 無効な送信 assert_no_difference ‘Micropost.count’ do post microposts_path params: { micropost: { content: “” } } end assert_select ‘div#error_explanation’ # 有効な送信 content = “This micropost really ties the room together” assert_difference ‘Micropost.count’, 1 do post microposts_path params: { micropost: { content: content } } end assert_redirected_to root_url follow_redirect! assert_match content, response.body # 投稿を削除する assert_select ‘a’, text: ‘delete’ micropost = @user.microposts.paginate(page: 1).first assert_difference ‘Micropost.count’, -1 do delete micropost_path(micropost) end # 違うユーザーのプロフィールにアクセス(削除リンクがないことを確認) get user_path(users(:archer)) assert_select ‘a’, text: ‘delete’, count: 0 end
実行結果がgreenになることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test Running via Spring preloader in process 2271 Started with run options –seed 12967
58/58: [===================================================================================================================================] 100% Time: 00:00:03, Time: 00:00:03
Finished in 3.92997s 58 tests, 255 assertions, 0 failures, 0 errors, 0 skips
###演習1 *リスト 13.55で示した4つのコメント (「無効な送信」など) のそれぞれに対して、テストが正しく動いているか確認してみましょう。具体的には、対応するアプリケーション側のコードをコメントアウトし、テストが redになることを確認し、元に戻すと greenになることを確認してみましょう。* ####「無効な送信」のテスト createメソッドをコメントアウトする。
# def create # @micropost = current_user.microposts.build(micropost_params) # if @micropost.save # flash[:success] = “Micropost created!” # redirect_to root_url # else # @feed_items = [] # render ‘static_pages/home’ # end # end
16行目でエラーになることを確認。
ERROR[“test_micropost_interface”, MicropostsInterfaceTest, 3.938271726015955]
test_micropost_interface#MicropostsInterfaceTest (3.94s)
AbstractController::ActionNotFound: AbstractController::ActionNotFound: The action ‘create’ could not be found for MicropostsController
test/integration/microposts_interface_test.rb:17:in block (2 levels) in <class:MicropostsInterfaceTest>'
test/integration/microposts_interface_test.rb:16:in
block in <class:MicropostsInterfaceTest>'
####「有効な送信」のテスト DB保存に成功した際の処理をコメントアウトする。
@micropost = current_user.microposts.build(micropost_params)
if @micropost.save
# flash[:success] = "Micropost created!"
# redirect_to root_url
else
25行目でエラーになることを確認。
FAIL[“test_micropost_interface”, MicropostsInterfaceTest, 2.1839206719305366] test_micropost_interface#MicropostsInterfaceTest (2.18s) Expected response to be a <3XX: redirect>, but was a <204: No Content> test/integration/microposts_interface_test.rb:25:in `block in <class:MicropostsInterfaceTest>'
####「投稿を削除する」のテスト deleteリンクを表示させないために、@feed_itemsの描画をコメントアウトする。
<% if @feed_items.any? %>
-
–>
29行目でエラーになることを確認。
FAIL[“test_micropost_interface”, MicropostsInterfaceTest, 1.6857899979222566]
test_micropost_interface#MicropostsInterfaceTest (1.69s)
####「違うユーザーのプロフィールにアクセス」のテスト ログインユーザーとマイクロポストのユーザーが一致するかの判定をコメントアウト。
<% #if current_user?(micropost.user) %>
<%= link_to "delete", micropost, method: :delete,
data: { confirm: "You sure?" } %>
<% #end %>
36行目でエラーになることを確認。
FAIL[“test_micropost_interface”, MicropostsInterfaceTest, 2.53247292409651] test_micropost_interface#MicropostsInterfaceTest (2.53s) Expected exactly 0 elements matching “a”, found 2.. Expected: 0 Actual: 2 test/integration/microposts_interface_test.rb:36:in `block in <class:MicropostsInterfaceTest>'
###演習2 *サイドバーにあるマイクロポストの合計投稿数をテストしてみましょう。このとき、単数形 (micropost) と複数形 (microposts) が正しく表示されているかどうかもテストしてください。ヒント: リスト 13.57を参考にしてみてください。* - micropost sidebar count - michaelユーザでログイン - root_pathへgetリクエスト送信 - michaelユーザのマイクロポスト数をカウントし、複数形で表示されることを確認 - 別ユーザ(maloryユーザ)でログイン - root_pathへgetリクエスト送信 - HTMLに"0 micropost"と単数形で表示されることを確認 - maloryユーザでマイクロポストを投稿する - root_pathへgetリクエスト送信 - HTMLに"1 micropost"と単数形で表示されることを確認 上記を踏まえて実装する。
test “micropost sidebar count” do log_in_as(@user) get root_path assert_match “#{ @user.microposts.count } microposts”, response.body # まだマイクロポストを投稿していないユーザー other_user = users(:malory) log_in_as(other_user) get root_path assert_match “0 microposts”, response.body other_user.microposts.create!(content: “A micropost”) get root_path assert_match “1 micropost”, response.body end
#おわりに マイクロポストの投稿から、フィード表示、削除までマイクロポストの主要機能が実装できました。 本章はボリュームがあってしんどかったですが、 ユーザーのCRUD処理と似ていたので、理解しやすかったです。