Ruby on Rails チュートリアル 機能拡張1(ユーザー検索)

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

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

#ユーザー検索
今回は、1つ目のユーザー検索の実装を行います。
具体的には、全ユーザーを表示するページ(/users)上での、ユーザー名の検索、検索結果の表示を実現します。

##環境と準備
ユーザー検索(および次回のマイクロポスト検索)では、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/users/_search_form.html.erb
<%= search_form_for @q do |f| %>
  <%= f.label :name_cont, 'User Search' %>
  <div class="input-group">
    <%= f.text_field :name_cont, placeholder: "Enter username...",
                      class: 'form-control' %>
    <span class="input-group-btn">
     <%= f.submit 'Go', class: "btn btn-primary" %>
    </span>
  </div>
<% end %>
```

上記のコードでは、「:name_cont」を利用しています。
今回の検索では、ユーザー名(name)に検索ワードが含まれているか(cont、containのこと)を調べるため、「:name_cont」を指定しています。
このフォームは後ほどindex.html.erbに組み込みます。

次に、UsersContorollerのindexアクションに処理を加えます。

```ruby:app/controllers/users_controller
def index
    if params[:q] && params[:q].reject { |key, value| value.blank? }.present?
      @q = User.ransack(search_params, activated_true: true)
      @title = "Search Result"
    else
      @q = User.ransack(activated_true: true)
      @title = "All users"
    end
    @users = @q.result.paginate(page: params[:page])
  end
~
省略
~
private
    def search_params
      params.require(:q).permit(:name_cont)
    end
```

params[:q][:name_cont]にユーザーが指定した検索ワードが含まれており、検索ワードの指定がない場合は全アクティブユーザーを取得します。
「モデル名.Ransack(検索条件)」の結果を「result」で取得します。
また、検索なしの時と検索ありの時でタイトルを変更しています。

Viewに戻って、index.html.erbに最初に作った_search_form.html.erbを挿入します。

 ```html+erb:app/views/users/index.html.erb
<% provide(:title, @title) %>
<h1><%= @title %></h1>

<div class="row">
  <div class="search_form">
    <%= render 'users/search_form' %>
  </div>
</div>

<%= will_paginate %>

<% unless @users.empty? %>
  <ul class="users">
    <%= render @users %>
  </ul>
<% else %>
  <p>
    Couldn't find any user.<br>
    Please check username you are looking for.
  </p>
<% end %>

<%= will_paginate %>
```

検索結果が0の時にはメッセージを表示するようにしましたが、それ以外に特に気になる点はないと思います。

見た目を整えるために少しscssをいじって、実装は完了です。

```scss:app/assets/stylesheets/custom.scss
.search_form {
  overflow: auto;
  margin: 10px;
  float: right;
  .input-group {
    width: 300px;
    .form-control {
      width: 250px;
    }
    .btn {
      width: 50px;
    }
  }
}
```

見た目はこんな感じです。
全ユーザー
![alluser.png](https://qiita-image-store.s3.amazonaws.com/0/229468/a7ed4223-e051-2fc5-811f-b91ea5553c88.png)

検索結果
![searchresult.png](https://qiita-image-store.s3.amazonaws.com/0/229468/9398dbbe-df77-f05a-b280-bf60258f469d.png)

検索結果なし
![noresult.png](https://qiita-image-store.s3.amazonaws.com/0/229468/c5428a8f-875a-856d-f89c-d17119ec6d95.png)

##テスト
最後にテストについて記載します。
まず、1.全ユーザー表示、2.検索(結果あり)、3.検索(結果なし)のパターンに分け、
1と2についてはタイトルとユーザー名のチェック、3についてはメッセージの確認を行うことにします。
テストはチュートリアルで作成していた、users_index_test.rbに追記しました。

```ruby:test/integration/user_index_test.rb
~
省略
~
test "users search" do
  log_in_as(@admin)
  # All users
  get users_path, params: {q: {name_cont: ""}}
  User.paginate(page:1).each do |user|
  assert_select 'a[href=?]', user_path(user), text:user.name
  end
  assert_select 'title', "All users | Ruby on Rails Tutorial Sample App"

  # User search
  get users_path, params: {q: {name_cont: "a"}}
  q = User.ransack(name_cont: "a", activated_true: true)
  q.result.paginate(page:1).each do |user|
    assert_select 'a[href=?]', user_path(user), text:user.name
  end
  assert_select 'title', "Search Result | Ruby on Rails Tutorial Sample App"

  # User search (no result)
  get users_path, params: {q: {name_cont: "abcdefghijk"}}
  assert_match "Couldn't find any user.", response.body

  # Make sure the title is back to 'All users'
  get users_path, params: {q: {name_cont: ""}}
  assert_select 'title', "All users | Ruby on Rails Tutorial Sample App"
end
```

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

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

#参考記事
ユーザー検索では、下記の記事を参考にさせていただきました。
**Ransackのススメ**
https://qiita.com/nysalor/items/9a95d91f2b97a08b96b0
**検索用のgem「ransack」を使ってみる**
https://qiita.com/LuckOfWise/items/e020e896e71d47d0c6a4
**Ransackで簡単に検索フォームを作る73のレシピ**
http://nekorails.hatenablog.com/entry/2017/05/31/173925