Ruby
Rails
Railsチュートリアル

Rails 5 Auto completeを実装してみた

More than 1 year has passed since last update.

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

前提

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

準備

jQuery UIで実装することにしました。

まず、アセットパイプラインに簡単にjQuery UIを入れられるようになるjquery-ui-railsをGemfileへ。

Gemfile.rb
gem 'jquery-ui-rails'

を追加し、

bundle install

を実行します。

app/assets/javascripts/application.js
//= require jquery-ui
app/assets/stylesheets/application.css
 *= require jquery-ui

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

オートコンプリートを実装する

ビューを変更する

マイクロポスト投稿のフォームに変更を加えます。

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_contentf.text_areaに追加しました。

JavaScriptを追加する

ビューに画像の大きさを判定する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を追加します。

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

コントローラーで受け取る

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

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に追加する

routes.rb
resources :users do
    member do
      get :following, :followers
    end
    collection do
      get :auto_complete
    end
  end

新しく追加したauto_completeroutes.rbに追加します。

テストについて

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

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でテストを書いているので、以下のようになりました。

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の返信機能を拡張し、オートコンプリート機能を実装できました。

結果

スクリーンショット 2017-06-23 16.17.54.png

こちらでソースコードを公開しています。

参考リンク