Rails Tutorial 拡張機能の検索を作ってみた：ユーザー検索

Rails Tutorialの第14章にある、拡張機能を作る件の続きです。
前回までで返信機能、メッセージ機能ができました。
今回は検索機能を作ります。検索機能には２つあり、ユーザーの検索と、マイクロポストの検索です。
そのうちのユーザーの検索機能を作ります。

チュートリアルにある参考例を読みます。
>@budougumi0617 さんの簡単な検索フォームの実装例を参考にしてください)。

https://qiita.com/budougumi0617/items/d98fc15adea4dab438e7

この例では、projectというモデルがあり、列nameで検索しています。
検察の条件は、検索に入力された文字が、部分一致することです。


###ユーザー検索の設計
モデルは作る必要ないです。
ビューは、既存のindexビューに、検索の入力項目を追加します。
コントローラーは、indexにpostを追加するか考えます。

ネットで「rails post GET 使い分け」で検索してみます。
https://qiita.com/kanataxa/items/522efb74421255f0e0a1

getにパラメーターを追加すればできそうです。
POSTを使う用途としては、
>    GETはリソースを取得するときに使うものです。
>   POSTはリソースに対して特有の処理をするときに使うものです。

とありました。検索はリソースの取得と考え、getを選びます。

既にgetのページはあるので、そのコントローラーに追記します。


トピックブランチを作ります。

```
ubuntu:~/environment/sample_app (master) $ git checkout -b user-search
```

###ビューに検索の入力項目を追加

チュートリアルの参考にあった実装例では、form_tagを使っています。
form_forとの使い分けが分からなかったので、ネットで検索します。

https://qiita.com/shunsuke227ono/items/7accec12eef6d89b0aa9
>form_for: 任意のmodelに基づいたformを作るときに使う
>form_tag: modelに基づかないformを作るときに使う

とあり、form_tagでよいことが分かりました。

```html:app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= form_tag users_path, :method => 'get' do %>

  <p>
    <%= text_field_tag :search, params[:search] %>
    <%= submit_tag "Search", :name => nil %>
  </p>

<% end %>

<%= will_paginate %>

<ul class="users">
  <%# @users.each do |user| %>
    <%= render @users %>
  <%# end %>
</ul>

<%= will_paginate %> 
```

###コントローラーに検索の条件を追加
現在のusersコントローラーを見ます。

```ruby:app/controllers/users_controller.rb
  def index
    @users = User.where(activated: true).paginate(page: params[:page])
  end

```

このindexメソッドに追加すればよいと考えます。参照例のwhereと同様に追加します。

```ruby:
 Project.where(['name LIKE ?', "%#{search}%"])
```

whereにAND条件を追加する方法を、チュートリアルで探します。
「リスト 14.44: とりあえず動くフィードの実装」にあったので、参考にします。
コンソールで試してみます。

```ruby:
>> User.where('activated: true and name like ?', "example")                                                                      
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE (activated: true and name like 'example') LIMIT ?  [["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (SQLite3::SQLException: unrecognized token: ":": SELECT  "users".* FROM "users" WHERE (activated: true and name like 'example') LIMIT ?)

>> User.where('activated = true and name like ?', "example")                                                                     
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE (activated = true and name like 'example') LIMIT ?  [["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: true: SELECT  "users".* FROM "users" WHERE (activated = true and name like 'example') LIMIT ?)

>> User.where('activated = "true" and name like ?', "example")                                                                   
  User Load (2.0ms)  SELECT  "users".* FROM "users" WHERE (activated = "true" and name like 'example') LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
>> 

>> User.where('activated = "true" and name like ?', "%example%")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE (activated = "true" and name like '%example%') LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
>> 

>> User.where('activated = "true" and name like ?', "Example%")
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE (activated = "true" and name like 'Example%') LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
>> 
```

うまくヒットしません。ANDをいったんやめて、条件を一つずつ試します。

```ruby:
>> User.where('name like ?', "%Example%")
```
でヒットしました。

もう一方の条件は、trueがヒットしないようです。

```ruby:
>> User.where('activated = ?', true)       ヒットする
>> User.where('activated = ?', "true" )　ヒットしない
```

文字列の"true"だとダメだと分かりました。それぞれをふまえ、ANDを加えて

```ruby
>> User.where('activated = ? and name like ?', true,"Example%")     
```

でヒットしました。

コントローラーに条件を追記します。

```ruby:app/controllers/users_controller.rb
  def index
    @search = params[:search]
    if @search
        @users = User.where('activated = ? and name like ?', true, "%#{@search}%").paginate(page: params[:page])
    else
        @users = User.where(activated: true).paginate(page: params[:page])
    end
  end
```  

rails serverでテストしてみます。うまく検索できました。

![search1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/d5a5d224-ba56-96b4-2070-b5d14dce6ac0.png)

![search2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/60f4f170-382d-bf03-da55-4b3f1457c525.png)


###Not Foundのテスト

検索ワードでヒットしないNot foundのケースをテストしてみます。

![search3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/663cc766-264d-84e9-169b-5e777c0cc18c.png)

何もメッセージが出ないので、Not Foundのメッセージを出したいです。
参考として、Twitterの検索画面でNot Foundを試してみたところ、
「「feagaeaaa」の検索結果はありません」
のメッセージが出ました。

「リスト 8.11: ログイン失敗時の正しい処理 green」を参考にします。

```ruby
   if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end

   if @search
        @users = User.where('activated = ? and name like ?', true, "%#{@search}%").paginate(page: params[:page])
        if @users == nil
          flash_now[:danger] = 'search not found'
        end
    else
```

試してみたところ、Not Foundのメッセージが出ません。
コンソールで確認します。
nilの判定がおかしいのか、ネットで検索します。
Not Foundで変数が空かどうかの判定は、empty?メソッドを使うのが正しいと分かりました。
https://techacademy.jp/magazine/22199

```ruby

>> users = User.where('name like ?', "example%")
>> users == nil
=> nil

>> users.empty?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE (name like 'aaaa%') LIMIT ?  [["LIMIT", 1]]
=> true
```

判定のif文を修正します。

```ruby:app/controllers/users_controller.rb
 def index
    @search = params[:search]
    if @search
        @users = User.where('activated = ? and name like ?', true, "%#{@search}%").paginate(page: params[:page])
        if @users.empty?
          flash_now[:danger] = 'search not found'
        end
    else
        @users = User.where(activated: true).paginate(page: params[:page])
    end
  end
```  

エラーが起きました。

``` 
NameError in UsersController#index
undefined local variable or method `flash_now' for #<UsersController:0x00007f3e4d161bc8> Did you mean? flash
``` 

flash.nowをflash_nowに間違えていました。直したところエラーが直りました。

```ruby:app/controllers/users_controller.rb
  def index
    @search = params[:search]
    if @search
        @users = User.where('activated = ? and name like ?', true, "%#{@search}%").paginate(page: params[:page])
        if @users.empty?
          flash.now[:danger] = 'search not found'
        end
    else
        @users = User.where(activated: true).paginate(page: params[:page])
    end
  end
```
![search4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/6fadd0ea-f2d8-3efd-c797-789b34293fde.png)

###テストの作成
テストを作ります。Greenになりました。

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

 test "index as non-admin" do
    log_in_as(@non_admin)
    get users_path
    assert_select 'a', text: 'delete', count: 0
  end

  test "search found and not_found" do
    log_in_as(@non_admin) 
    get users_path
    # found
    get users_path, params: { search: "Example" }
    assert_select  'a', text: @admin.name 
    # not found
    get users_path, params: { search: "aaaaaa" }
    assert_not flash.empty?
  end
```

ユーザーの検索は完成です。

トピックブランチをマージします。

```
ubuntu:~/environment/sample_app (user-search) $ git add -A
ubuntu:~/environment/sample_app (user-search) $ git commit -m "Add User Search"
ubuntu:~/environment/sample_app (user-search) $ git checkout master
ubuntu:~/environment/sample_app (master) $ git merge user-search
ubuntu:~/environment/sample_app (master) $ git push
ubuntu:~/environment/sample_app (master) $ git push heroku
```

### ローカルとherokuで大文字・小文字の扱いに差あり
herokuで試したところ、キーワードが"example"では、ユーザー"Example user"がnot foundでした。
ローカルでは"example"でヒットしていたので、大文字・小文字の両方を検索する仕様なのだろうと思っていました。
herokuはデータベースがpostgresで、ローカルのcloud9と違うとチュートリアルに書いてあったと思うので、その差なのかと思います。深堀りしたいところですが、後回しにします。開発環境と本番環境で動作が変わるのはトラブルでよく聞く話です。


##所要時間

12/6から12/10までの3.5時間です