Rails テキストボックスの入力補完(サジェスト)機能 text_field_with_auto_complete

Railsで、テキストボックスに入力された値をもとにDBを検索し、入力補完をするような機能をつけてみる。
Google Suggest のようなオートコンプリート機能のイメージだ。

環境

Ubuntu 8.0.4
jruby 1.1.4
Rails 2.1.1
MySQL 5.0

検索してみるといくつかの方法があったが、一番メジャーっぽい text_field_with_auto_complete というのを使ってみることにした。

参考にしたサイトはこちら

http://wiki.rubyonrails.org/rails/pages/How+to+use+text_field_with_auto_complete
http://d.hatena.ne.jp/zariganitosh/20071007
http://blog.livedoor.jp/maru_tak/archives/50606971.html
http://www.thinkit.co.jp/cert/article/0605/2/5/2.htm

今回は、テキストフィールドに入力された値をもとに、会社マスタ(companies)を検索し、会社名(name)と部分一致するリストを出して入力補完を行えるという機能を作ってみよう。

まずは適当なRailsプロジェクト(仮に autocomplete というプロジェクト名にする)を作って、Company クラスのScaffoldを作る。

jruby -S script/generate scaffold Company

auto_complete のプラグインをインストール。

jruby -S script/plugin install http://svn.rubyonrails.org/rails/plugins/auto_complete

CreateCompanies のマイグレーションファイルに会社名(name)のカラムを追記。

class CreateCompanies < ActiveRecord::Migration
  def self.up
    create_table :companies do |t|
      <span style="color:#FF0000;">t.string :name</span>
      t.timestamps
    end
  end

  def self.down
    drop_table :companies
  end
end

config/database.yml にhostを追記
(原因はよくわからないが、hostの情報を書いておかないとDB操作ができなかった)

development:
  adapter: mysql
  encoding: utf8
  database: autocomplete_development
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock
  host: 127.0.0.1
  
test:
  adapter: mysql
  encoding: utf8
  database: autocomplete_test
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock
  host: 127.0.0.1
  
production:
  adapter: mysql
  encoding: utf8
  database: autocomplete_production
  username: root
  password: 
  socket: /var/run/mysqld/mysqld.sock
  host: 127.0.0.1

ちなみに database.yml は、共通の記述箇所は以下のようにまとめることができる(内容は、上記database.ymlと同じ。参考までに。)

common: &common
  adapter: mysql
  encoding: utf8
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock
  host: 127.0.0.1

development:
  database: autocomplete_development
  <<: *common

test:
  database: autocomplete_test
  <<: *common

production:
  database: autocomplete_production
  <<: *common

DBをCreateして、マイグレーションファイルを実行

jruby -S rake db:create
jruby -S rake db:migrate

DBとcompaniesテーブルができたので、companiesテーブルにダミーデータを入れておこう。
以下のような簡単なCSVファイルを作って、companies.csv という名前で test/fixtures 配下に保存する。
この時、もともと companies.yml というファイルが存在しているので、org_companies.yml というような適当な名前にリネームしておく。

"id","name","created_at","updated_at"
1,"まるまる建設",,
2,"居酒屋ぺけぺけ",,
3,"いたくない歯科医院",,
4,"きれい美容室",,
5,"ろっくんろーる電器",,
6,"123病院",,
7,"1256総合病院",,
8,"1237777整形外科",,
9,"しかく商事",,

このCSVファイルをrakeコマンドを使ってDBにLoadする。

jruby -S rake db:fixtures:load FIXTURES=companies

必要なJavascriptライブラリをincludeする。
1.prototype.js
2.effects.js
3.controls.js
(app/views/layouts/companies.html.erb のhead部分に以下3ファイルを追加)

<%= javascript_include_tag "prototype" %>
<%= javascript_include_tag "effects" %>
<%= javascript_include_tag "controls" %>

今回はサンプルなので、index.html.erb にサジェスト機能のついたテキストボックスを配置してみる。
下記1行をerbファイルに追加する。

<%= text_field_with_auto_complete :company, :name %>


companies_controller.rb にメソッドを追加

auto_complete_for :company, :name

def auto_complete_for_company_name
  find_options = { 
    :conditions => [ "name LIKE ?", '%' + params[:company][:name] + '%' ], 
    :limit => 10 }
  @items = Company.find(:all, find_options)
  render :partial => 'auto_complete_for_company_name'
end

app/views/companies 配下に、_auto_complete_for_company_name.html.erb を追加

<ul>
  <% @items.each do |item| -%>
    <li id=<%= item.id %>>
      <%= item.name -%>
    </li>
  <% end -%>
</ul>

http://localhost:3000/companies にアクセスしてみる。

動かない・・・。。
なぜだ。調べてみると、Rails 2 以降では、CSRF対策処理にひっかかるので、それをスキップさせる必要があるようだ。
(ちょっとセキュリティ的には問題があると思うが、まぁ今はとりあえずこれで回避しておこう。)
companies_controller.rb に下記1行を追加

skip_before_filter :verify_authenticity_token

すると、、

ちゃんとテキストボックスに補完機能がついた!

ただ、なぜか文字列の両端に半角スペースが入ってしまっている。


いろいろやってみると、以下のようにView上のliタグのところを1行で書いてやれば解決した。

<ul>
  <% @items.each do |item| -%>
    <li id=<%= item.id %>><%= item.name -%></li>
  <% end -%>
</ul>

あとは、選択された会社のidをコントローラー側に渡してやりたいので、index.html.erb を以下のように変更した。

変更前

<%= text_field_with_auto_complete :company, :name %>

変更後

<%= text_field_with_auto_complete :company, :name,{},
    :after_update_element=>"function(element, selectedItem){$('company_id').value = selectedItem.id;}" -%>
<%= hidden_field :company, :id %>

これでコントローラー側で params[:company][:id] で会社のidを取得することができる。