【Railsチュートリアル】第13章 ユーザーのマイクロポスト③

# 13.3 マイクロポストを操作する
* データモデリングとマイクロポスト表示テンプレートをWeb経由でそれらを作成するためのインターフェイスに取り掛かる

## 13.3.1 マイクロポストのアクセス制御

関連付けられたユーザーを通してマイクロポストにアクセスするので、createアクションやdestroyアクションを利用するユーザーは、ログイン済みでなければならない。

```ruby:test/controllers/microposts_controller_test.rb
require 'test_helper'

class MicropostsControllerTest < ActionDispatch::IntegrationTest

  def setup
    @micropost = microposts(:orange)
  end

  test "should redirect create when not logged in" do
    # ログインしていないときに作成するとリダイレクトされるか検証するテスト
    assert_no_difference 'Micropost.count' do
      # Micropost.countが変化していなければtrue
      post microposts_path, params: { micropost: { content: "Lorem ipsum" } }
        # マイクロソフトを作成する
    end
    assert_redirected_to login_url
      # login_urlにリダイレクトされているか
  end

  test "should redirect destroy when not logged in" do
    # ログインしていないときに削除しようとするとリダイレクトされるか検証するテスト
    assert_no_difference 'Micropost.count' do
      # Micropost.countが変化していなければtrue
      delete micropost_path(@micropost)
        # マイクロソフトを削除する
    end
    assert_redirected_to login_url
      # login_urlにリダイレクトされているか
  end
end
```

### 演習 1
なぜUsersコントローラ内にあるlogged_in_userフィルターを残したままにするとマズイのでしょうか? 考えてみてください。

コードが重複しているから。
エラーの原因になるかもしれないし、DRYではない。

## 13.3.2 マイクロポストを作成する

```ruby:app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
    # 各アクションに入る前にlogged_in_userを実行
    # logged_in_userはユーザーのログインを確認するメソッド

  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'
        # static_pages/homeを描画
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end
```

### 演習 1
Homeページをリファクタリングして、if-else文の分岐のそれぞれに対してパーシャルを作ってみましょう。

```ruby:views/static_pages/home.html.erb
<% if logged_in? %>
  <%= render 'static_pages/user_logged_in' %>
<% else %>
  <%= render 'static_pages/user_logged_out' %>
<% end %>
```

```ruby:views/static_pages/_user_logged_in.html.erb
<div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
```

```ruby:views/static_pages/_user_not_logged_in.html.erb
<div class="center jumbotron">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="https://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.svg", alt: "Rails logo", width: "200px"), "https://rubyonrails.org/" %>
```

## 13.3.3 フィードの原型

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

```ruby

Micropost Create (11.7ms)
INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at")
VALUES (?, ?, ?, ?)
  [["content", "Hello SAMPLE APP !"],
  ["user_id", 1],
  ["created_at", "2021-03-16 03:08:23.301012"],
  ["updated_at", "2021-03-16 03:08:23.301012"]]
    ↳ app/controllers/microposts_controller.rb:9:in `create'
```


### 演習 2
コンソールを開き、user変数にデータベース上の最初のユーザーを代入してみましょう。その後、Micropost.where("user_id = ?", user.id)とuser.microposts、そしてuser.feedをそれぞれ実行してみて、実行結果がすべて同じであることを確認してみてください。ヒント: ==で比較すると結果が同じかどうか簡単に判別できます。

```sql:user変数にデータベース上の最初のユーザーを代入してみましょう。
>> user = User.first
   (0.1ms)  begin transaction
  User Load (0.7ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2021-03-16 03:01:20", updated_at: "2021-03-16 03:01:20", password_digest: [FILTERED], remember_digest: nil, admin: true, activation_digest: "$2a$12$VFPF70fqU0mWCdYrpnPI5uRjn1yQePv61n5WYjNlvvo...", activated: true, activated_at: "2021-03-16 03:01:19", reset_digest: nil, reset_sent_at: nil>
```


```sql:実行結果がすべて同じであることを確認してみてください
>> Micropost.where("user_id = ?", user.id) == user.microposts
  Micropost Load (0.4ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true

>> user.microposts == user.feed
  Micropost Load (0.8ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true

>> user.microposts == user.feed
  Micropost Load (0.8ms)  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 マイクロポストを削除する
* マイクロポストリソースにポストを削除する機能を追加する
* 自分が投稿したマイクロポストに対してのみ削除リンクが動作するようにする

```ruby:app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy
    # destroyアクションが呼び出される前にcorrect_userメソッドを実行する
  .
  .
  .
  def destroy
    @micropost.destroy
      # マイクロポストを削除する
    flash[:success] = "Micropost deleted"
      # フラッシュメッセージを表示する
    redirect_to request.referrer || root_url
      # DELETEリクエストが発行されたページにリダイレクト
      # DELETEリクエストが発行されたページが取得できない場合はroot_urlにリダイレクト
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end

    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end
```



### 演習 1
マイクロポストを作成し、その後、作成したマイクロポストを削除してみましょう。次に、Railsサーバーのログを見てみて、DELETE文の内容を確認してみてください。

```sql
Micropost Destroy (7.8ms)
DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 301]]
  ↳ app/controllers/microposts_controller.rb:20:in `destroy'
```

### 演習 2
redirect_to request.referrer || root_urlの行をredirect_back(fallback_location: root_url)と置き換えてもうまく動くことを、ブラウザを使って確認してみましょう（このメソッドはRails 5から新たに導入されました）。

確認のみなので省略。

## 13.3.5 フィード画面のマイクロポストをテストする
* Micropostsコントローラの認可をチェックする短いテストを作成する
* それらをまとめる統合テストを作成する

```ruby:test/controllers/microposts_controller_test.rb
require 'test_helper'

class MicropostsControllerTest < ActionDispatch::IntegrationTest

  def setup
    @micropost = microposts(:orange)
  end
  .
  .
  .
  test "should redirect destroy for wrong micropost" do
    # 自分以外のユーザーがマイクロポストを削除できないか検証するテスト
    log_in_as(users(:Michael))
      # Michaelでログイン
    micropost = microposts(:ants)
      # ユーザー「ants」でマイクロソフトを投稿
    assert_no_difference 'Micropost.count' do
      # do-end前後で投稿数に違いがないか
      delete micropost_path(micropost)
        # マイクロポストにDELETEリクエスト
    end
    assert_redirected_to root_url
      # root_urlにリダイレクトする
  end
end
```

```ruby:test/integration/microposts_interface_test.rb
require 'test_helper'

class MicropostsInterfaceTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "micropost interface" do
    # マイクロソフトインターフェイスのテスト
    log_in_as(@user)
      # @userでログイン
    get root_path
      # root_pathにGETリクエスト
    assert_select 'div.pagination'
      # <div>にpaginationがあるかどうか
    # 無効な送信
    assert_no_difference 'Micropost.count' do
      # do-end前後で投稿数に違いが無いか
      post microposts_path, params: { micropost: { content: "" } }
        # 無効な投稿でPOSTリクエスト
    end
    assert_select 'div#error_explanation'
      # <div>内にerror_explanationがあるかどうか
    assert_select 'a[href=?]', '/?page=2'
      # 正しいページネーションリンクがあるかどうか
    # 有効な送信
    content = "This micropost really ties the room together"
      # 有効な投稿内容をcontentに代入
    assert_difference 'Micropost.count', 1 do
      # do-end前後で投稿数に違いが無いか
      post microposts_path, params: { micropost: { content: content } }
        # 有効な投稿でPOSTリクエスト
    end
    assert_redirected_to root_url
      # root_urlにリダイレクトされているか
    follow_redirect!
      # リダイレクト先に移動しているか
    assert_match content, response.body
      # <body>内にcontentがあるかどうか
    # 投稿を削除する
    assert_select 'a', text: 'delete'
      # deleteリンクがあるかどうか
    first_micropost = @user.microposts.paginate(page: 1).first
      # データを変数に代入
    assert_difference 'Micropost.count', -1 do
      # do-end内で投稿数が-1されているかどうか
      delete micropost_path(first_micropost)
        # マイクロソフトにDELETEリクエスト
    end
    # 違うユーザーのプロフィールにアクセス（削除リンクがないことを確認）
    get user_path(users(:archer))
      # archerのユーザーページにGETリクエスト
    assert_select 'a', text: 'delete', count: 0
      # DELETEリンクが表示されていないか
  end
end
```

### 演習 1
リスト 13.56で示した４つのコメント（「無効な送信」など）のそれぞれに対して、テストが正しく動いているか確認してみましょう。具体的には、対応するアプリケーション側のコードをコメントアウトし、テストが red になることを確認し、元に戻すと green になることを確認してみましょう。

確認のみなので省略。

### 演習 2
サイドバーにあるマイクロポストの合計投稿数をテストしてみましょう。このとき、単数形（micropost）と複数形（microposts）が正しく表示されているかどうかもテストしてください。ヒント: リスト 13.58を参考にしてみてください。

```ruby:test/integration/microposts_interface_test.rb

require 'test_helper'

class MicropostInterfaceTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "micropost sidebar count" do
    # 投稿数のテスト
    log_in_as(@user)
      # ログイン
    get root_path
      #  root_pathにGETリクエスト
    assert_match "#{@user.microposts.count} microposts", response.body
      # <body>に#{@user.microposts.count} micropostsが表示されているか
    # まだマイクロポストを投稿していないユーザー
    other_user = users(:malory)
      # maloryを代入
    log_in_as(other_user)
      # 違うユーザーでログイン
    get root_path
      # root_pathにGETリクエスト
    assert_match "0 microposts", response.body
      # <body>に0 micropostsと表示されているか
    other_user.microposts.create!(content: "A micropost")
      # 投稿する
    get root_path
      # root_pathにGETリクエスト
    assert_match "1 micropost", response.body
      # <body>に1 micropostが表示されているか
  end
end
```