Rails 5 Auto completeを実装してみた

[Rails Tutorial](https://railstutorial.jp/)が一通り終わったので、返信機能を拡張するオートコンプリート機能を実装してみました。
返信機能に、@ + ユーザー名を入力する際、入力済みの文字列を元にユーザー名の候補のメニューを表示して入力の補完ができる機能を目指しました。

#前提
Rails Tutorial 第4版を一通り実装済みで、14章最後の拡張機能で挙げられている６つの機能を実装済みです。
返信機能は、ユーザーテーブルに`nickname`を追加し、一意に特定できるようにしています。
Rails 5.1.1にアップデート済みです。
`form_for`を`form_with`に置き換えています。
ERBはHamlに置き換えています。
テストはminitestの代わりにRSpecを使っています。
E2EテストはTurnipを導入しています。

#準備
[jQuery UI](https://jqueryui.com/
)で実装することにしました。

まず、アセットパイプラインに簡単にjQuery UIを入れられるようになる[jquery-ui-rails](https://github.com/jquery-ui-rails/jquery-ui-rails)をGemfileへ。

```ruby:Gemfile.rb
gem 'jquery-ui-rails'
```
を追加し、

```bash
bundle install
```
を実行します。


``` js:app/assets/javascripts/application.js
//= require jquery-ui
```

```css:app/assets/stylesheets/application.css
 *= require jquery-ui
```

を追加します。
これでjQuery UIの準備ができました。

#オートコンプリートを実装する
##ビューを変更する
マイクロポスト投稿のフォームに変更を加えます。

```haml:app/views/shared/_micropost_form.html.haml
= form_with model: @micropost do |f|
  = render 'shared/error_messages', object: f.object
  .field
    = f.text_area :content, placeholder: "Compose new micropost...", id: :micropost_content
  = f.submit "Post", class: "btn btn-primary"
  %span.picture
    = f.file_field :picture, accept: 'image/jpeg,image/gif,image/png'

```

`id: :micropost_content`を`f.text_area`に追加しました。


##JavaScriptを追加する
ビューに画像の大きさを判定するJSのコードがあるので、その後に差し込みます。


```js:app/views/shared/_micropost_form.html.haml
:javascript
  $(function() {
    $('#micropost_content').autocomplete({
      source: "/users/auto_complete.json",
      delay: 500,
      minLength: 2,
      focus: function(event, ui) {
        $("#micropost_content").val(ui.item.nickname);
        return false;
      },
      select: function(event, ui) {
        $('#micropost_content').val(ui.item.nickname);
        return false;
      }
    }).data("ui-autocomplete")._renderItem = function(ul, item) {
      return $("<li>").attr("data-value", item.nickname).data("ui-autocomplete-item", item).append("<a>" + item.nickname + "</a>").appendTo(ul);
    };
  });

```
`source:`にリクエスト先を指定します。

autocompleteのオプションについて詳しくは以下を参照してください。
https://api.jqueryui.com/autocomplete/

オートコンプリートした際に何件検索で見つかったか、デフォルトで表示する設定のため、非表示にするCSSを追加します。

```css:app/assets/stylesheets/custom.scss
/* auto-complete */
.ui-helper-hidden-accessible { display:none; }
```


##コントローラーで受け取る
リクエストをコントローラーで受け取り、検索結果をJSONで出力します。

```rb:app/controllers/users_controller.rb
class UsersController < ApplicationController
  #...

  def auto_complete
    @users = if params[:term] =~ /(@[^\s]+)\s.*/
             elsif user_nickname = params[:term].match(/(@[^\s]+)/)
               users = User.select('nickname').where('nickname LIKE ? AND activated = ?', "%#{user_nickname[1].to_s[1..-1]}%", true)

               users.map {|user| {nickname: "#@{user.nickname}"} }
             end
    render json: @users.to_json
  end

  #...
  
end
```
データベースに@を保存しないようにしているので、検索ではそれを考慮したコードになっています。
`params[:term]`でフォームに入力された値を取得することができます。

###routes.rbに追加する

```rb:routes.rb
resources :users do
    member do
      get :following, :followers
    end
    collection do
      get :auto_complete
    end
  end
```
新しく追加した`auto_complete`を`routes.rb`に追加します。

##テストについて

以下のリンクを参考にして、メソッドを追加しました。
http://ruby-journal.com/how-to-do-jqueryui-autocomplete-with-capybara-2/

```rb:spec/support/utilities.rb

def fill_autocomplete(field, options = {})
  fill_in field, with:options[:with]

  page.execute_script %{ $('##{field}').trigger('focus') }
  page.execute_script %{ $('##{field}').trigger('keydown') }

  selector = %{ul.ui-autocomplete li.ui-menu-item a:contains("#{options[:select]}")}

  expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item a')
  page.execute_script %{ $('#{selector}').trigger('mouseenter').click() }
end
```
Turnipでテストを書いているので、以下のようになりました。

```rb:app/spec/steps/home_steps.rb
step '検索フォームmicropost_contentに@arと入力するとオートコンプリートで表示され、選択する' do 
  fill_autocomplete 'micropost_content', with: '@ar'
end

step 'Postボタンをクリックする' do
  click_button 'Post'
end
```

以上で、Rails Tutorialの返信機能を拡張し、オートコンプリート機能を実装できました。

##結果
<img width="922" alt="スクリーンショット 2017-06-23 16.17.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/183497/c68e1bdc-b95d-fd60-0ff2-16f7171043e0.png">



[こちら](https://github.com/swamp09/sample_app)でソースコードを公開しています。


#参考リンク



- http://easyramble.com/jquery-ui-autocomplete-on-rails.html
- https://stackoverflow.com/questions/3188157/how-to-set-up-jquery-ui-autocomplete-in-rails
- https://stackoverflow.com/questions/13011127/how-to-remove-change-jquery-ui-autocomplete-helper-text