紙一重の積み重ね

35歳のエンジニアがなれる最高の自分を目指して、学んだことをこつこつ情報発信するブログです。

【13章】Ruby on Railsチュートリアル演習まとめ&解答例【13.3 マイクロポストを操作する】

はじめに

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

image

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
    • ログイン画面にリダイレクトされること
  • should redirect destroy when not logged in

    • Micropostモデルの数をカウントし、数が変化していないこと
      • DELETEリクエストを送信する
        • micropost_path(@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

ブラウザからアクセスできることを確認。

image

演習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>

動作確認

追加したフィードが表示されていることを確認。

image

ただし、マイクロポストの投稿に失敗した場合は、エラーが発生する。 (@feed_itemsを期待しているが取得できていない)

image

対策として、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保存失敗時にエラーが発生しないことを確認。

image

演習1

新しく実装したマイクロポストの投稿フォームを使って、実際にマイクロポストを投稿してみましょう。Railsサーバーのログ内にあるINSERT文では、どういった内容をデータベースに送っているでしょうか? 確認してみてください。

投稿フォームからマイクロポストを投稿する。

image

ログは以下の通り。 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:inblock 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) expected but was .. Expected 0 to be >= 1. test/integration/microposts_interface_test.rb:29:in `block in <class:MicropostsInterfaceTest>'

####「違うユーザーのプロフィールにアクセス」のテスト
ログインユーザーとマイクロポストのユーザーが一致するかの判定をコメントアウト。
<% #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処理と似ていたので、理解しやすかったです。