Ruby on Rails チュートリアル 機能拡張2(マイクロポスト検索)

#Ruby on Rails チュートリアルについて
Ruby on Railsを勉強したいというと、まず紹介される有名なRailsのチュートリアル。
内容はハードですが、無料でRailsによるWebアプリケーション開発を楽しく学べます。<br>
**Ruby on Rails チュートリアル**
https://railstutorial.jp/

#Sample Appの拡張
チュートリアルの最後には、作成したSampleAppの拡張機能についていくつかのヒントが記載されています。
その中の以下の機能を順に実装していきます(途中で挫折するかも。。。)。
1. ユーザー検索
2. マイクロポスト検索
3. フォロワーの通知
4. 返信機能
5. メッセージ機能

#マイクロポスト検索
今回は、2つ目のマイクロポスト検索の実装を行います。
マイクロポストは、ログイン時のホーム画面およびプロフィール画面の二つの画面で表示されるので、検索フォームもホーム画面とプロフィール画面の両方に実装します。

前回(ユーザー検索)は[こちら](https://qiita.com/YN6127yn/items/fca9efa22536b8b2a9ef)

##環境と準備
マイクロポスト検索(および前回のユーザー検索)では、Rails用の検索機能を簡単に実装できる[Ransack Gem](https://github.com/activerecord-hackery/ransack)を利用することにします。
Ransackは他のGemと同様に、Gemfileに追加し、bundle installします。

|モジュール   |  バージョン |  
|:----------|-----------:|
| Rails     |      5.1.2 |
| Ruby      |      2.3.1 |
| Ransack   |      1.8.4 |

##実装
まず、マイクロポスト検索用のフォームを作成します。

```html+erb:app/views/shared/_microposts_search_form.html.erb
<%= search_form_for @q, url: @url do |f| %>
  <%= f.label :content_cont, 'Micoropost Search' %>
  <div class="input-group">
    <%= f.text_field :content_cont, placeholder: "Enter keyword...",
                      class: 'form-control' %>
    <span class="input-group-btn">
     <%= f.submit 'Go', class: "btn btn-primary" %>
    </span>
  </div>
<% end %>
```

今回の実装ではここがいきなり肝になりました。というか、ハマりました。。。
検索条件の`content_cont`はMicropostのcontentからの検索ということで、難しくはないと思います。
前回のユーザー検索フォームとの大きな違いは、`url: @url`の部分になります。
ユーザー検索時は、urlをわざわざ指定せずとも上手く動きましたが、
マイクロポスト検索ではここでurlを以下のように指定(上の実装では各controllerから与えています)しなければ、検索実行時に「get /microposts(= index)」のrouteが見つからない！とエラーになります。
![routeエラー.png](https://qiita-image-store.s3.amazonaws.com/0/229468/ac5da966-c7c7-fdaa-a848-209cd966da6c.png)

理由は、`search_form_for`のデフォルトの送信先が検索モデルのindexアクションだからです。(当たり前ですね。。。)
そのため、今回は明示的にフォームの送信先を指定してやる必要があります。
インスタンス変数で渡しているのは、このフォームをホーム画面/プロフィール画面の両方で使うためです。

検索フォームができたので、次にcontrollerの実装を行います。
まずは、ホーム画面のフィードの検索のため、StaticPagesContorollerのhomeアクションに処理を加えます。

```ruby:app/controllers/static_pages_controller
def home
    if logged_in?
      @micropost = current_user.microposts.build
      if params[:q] && params[:q].reject { |key, value| value.blank? }.present?
        @q = current_user.feed.ransack(microposts_search_params)
        @feed_items = @q.result.paginate(page: params[:page])
      else
        @q = Micropost.none.ransack
        @feed_items = current_user.feed.paginate(page: params[:page])
      end
      @url = root_path
    end
  end
~
省略
~
```

ここの処理も前回のユーザー検索とほぼ同じですね。
個人的に驚いたのは、`current_user.feed.ransack`と書けるところです。
Micropost.ransack(〜)とfeedメソッドでやっていることを、ransackの条件としてだらだら実装しないといけないと思っていましたが、そんな必要ありませんでした。(でもDBアクセスとかぜんぜん考えてない。。。)

あとは、`@q`が必要なのは当然として、検索しない場合はnilとかじゃなくて`Model名.none.ransack`と書かないといけないんですね。
検索フォーム作成の部分でも記載しましたが、ここでは`@url`に`root_path`を指定しています。

続けて、UsersControllerのshowアクションに処理を追加します。

```ruby:app/controllers/users_controller
~
省略
~
def show
    redirect_to root_url and return unless @user.activated?
    if params[:q] && params[:q].reject { |key, value| value.blank? }.present?
      @q = @user.microposts.ransack(microposts_search_params)
      @microposts = @q.result.paginate(page: params[:page])
    else
      @q = Micropost.none.ransack
      @microposts = @user.microposts.paginate(page: params[:page])
    end
    @url = user_path(@user)
  end
~
省略
~
```

特に気になるところはないと思います。
`@url`には`user_path(@user)`を代入しています。

そういえば、`microposts_search_params`は、UsersControllerとStaticPagesControllerの両方から利用されるため、ApplicationControllerに設置しました。

```ruby:app/controllers/application_controller
private
    def microposts_search_params
      params.require(:q).permit(:content_cont)
    end
```

忘れかけていましたが、MicropostControllerのcreateアクションに`@q`と`ransack`の処理を追加しないといけません。

```ruby:ruby:app/controllers/micropost_controller
def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      @q = Micropost.none.ransack
      @feed_items = current_user.feed.paginate(page: params[:page])
      render 'static_pages/home'
    end
  end
```
追加するのは、マイクロポストの投稿が失敗した際の処理のみです。
失敗した場合のみ、MicropostContorollerからホームのviewを`render`(`redirect_to`ではなく)しているため、`@q`が必要になります。
renderとredirectの違いについては、[こちら](https://qiita.com/january108/items/54143581ab1f03deefa1)にとてもわかりやすくまとめられています。


最後にviewに戻って、最初に作った_microposts_search_form.html.erbをそれぞれのviewに挿入します。

```html+erb:app/views/static_pages/_logged_in_home.html.erb
<% provide(:title, @user.name) %>
~
省略
~
  <div class="col-md-8">
    <div class="row">
      <div class="col-md-4">
        <h3>Micropost Feed</h3>
      </div>
      <div class="search_form">
        <%= render 'shared/microposts_search_form' %>
      </div>
    </div>
    <%= render 'shared/feed' %>
  </div>
</div>
```

 ```html+erb:app/views/users/show.html.erb
<% provide(:title, @user.name) %>
~
省略
~
  <div class="col-md-8">
    <%= render 'follow_form' if logged_in? %>
    <% if @user.microposts.any? %>
    <div class="row">
      <div class="col-md-4">
        <h3>Microposts (<%= @user.microposts.count %>)</h3>
      </div>
      <div class="search_form">
        <%= render 'shared/microposts_search_form' %>
      </div>
    </div>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>
```

scssですが、前回のユーザー検索時に設定した値で十分のため、今回は何も加えません。

見た目はこんな感じです。
ホーム画面
![ホーム画面.png](https://qiita-image-store.s3.amazonaws.com/0/229468/b1a049e4-59b0-de2a-0a20-03a155806722.png)


プロフィール画面
![プロフィール画面.png](https://qiita-image-store.s3.amazonaws.com/0/229468/a4c366d7-a135-0fa4-fdf2-6bd118bc82d6.png)

##テスト
最後にテストについて記載します。
今回は簡単に、検索後に期待したマイクロポストが画面上に表示されているかを、ホーム画面とプロフィール画面の両方に対してテストします。
テストはチュートリアルで作成していた、users_profile_test.rbに追記しました。

```ruby:test/integration/user_profile_test.rb
~
省略
~
test "profile display" do
    ~
    省略
    ~
    # Micropost Search
    get user_path(@user), params: {q: {content_cont: "a"}}
    q = @user.microposts.ransack(content_cont: "a")
    q.result.paginate(page:1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end

  test "home profile display" do
    ~
    省略
    ~
    # Micropost Search
    get root_path, params: {q: {content_cont: "a"}}
    q = @user.feed.ransack(content_cont: "a")
    q.result.paginate(page:1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end
```

テストが通ったことを確認して終了です。

#最後に
Rails歴1週間もなく、以上の実装/テストも私の環境で動いたということに過ぎません。
修正点や指摘等ございましたら、ぜひコメントお願いいたします。

#参考記事
マイクロポスト検索では、下記のページを参考にさせていただきました。

**GitHub**
https://github.com/activerecord-hackery/ransack/issues/21
https://github.com/activerecord-hackery/ransack/issues/170