速攻で検索機能を実装できるsearchkickを調べた（Ruby）


##説明
searchkickとは検索エンジンの一つであるelasticsearchを簡単に扱えるようにしたGemです。これを使うことにより

- 高速検索
- 誤字訂正してくれる検索
- 部分合致検索
-自動補完

etc…

など様々な検索に関する機能を実装することが出来ます。
今回はこのsearchkickを使うために[公式ドキュメント](https://github.com/ankane/searchkick)を和訳したのでそれを記しときます。

以下ドキュメントの和訳です


##[Searchkick](https://github.com/ankane/searchkick)とは
searchkickとはユーザーが検索をするときの方法です。これを使うことによりより多くの人が簡単により良く検索結果を得ることが出来るようになります。

Searchkickを利用する機会

- 制御 -tomatoesをtomatoで合致するようにする
- 固有のもの ―jalapenoがjalapeñoと合致するようにする
- 余分な余白 -　dishwasher　が　dish washer　に合致するようにする
- 誤字 - 　zuchini　が　zucchini　に合致するようにする
- 習慣的異名 -　qtip　が　cotton swab　に合致する

利点

- SQLの様にqueryを書けるので新しくqueryの文法を学習する必要がありません
-  システムを休止することなくindexをつくることが出来ます
-　簡単に互いのUserを専用のものとして扱うことができます
-  自動補完してくれます
- 意味の提案をしてくれます
- ActiveRecord や Mongoid　と一緒に使うことができます

##使用方法
homebrewを使いelasticsearchをインストールします

```bash:
$ brew install elasticsearch
```
Gemfileに以下を追記します

```ruby:gem
gem "searchkick"
```

検索を使いたいmodelにelasticsearchを追加します

```ruby:model
class Product < ActiveRecord::Base
  searchkick #追記
end
```
indexを追加する場合は以下のようにします

```
Product.reindex
```
queryは以下のように書きます

```
products = Product.search "2% Milk"
products.each do |product|
  puts product.name
end
```

##Queryについて
queryはSQLの様に書きます

```
Product.search "2% Milk", where: {in_stock: true}, limit: 10, offset: 50
```
具体的なfieldを検索する

```
fields: [:name, :brand]
```
whereの使い方

```ruby:
where: {
  expires_at: {gt: Time.now}, # lt, gte, lte also available
  orders_count: 1..10,        # equivalent to {gte: 1, lte: 10}
  aisle_id: [25, 30],         # in
  store_id: {not: 2},         # not
  aisle_id: {not: [25, 30]},  # not in
  or: [
    [{in_stock: true}, {backordered: true}]
  ]
}
```
Limit / offset　の使い方

```
limit: 20, offset: 40
```
Boost by a field　(フィールドによって押し上げる？)

```ruby:
boost: "orders_count" # give popular documents a little boost
```

##ページ付け
ページ付けを正しく適用させる

```ruby:
# controller
@products = Product.search "milk", page: params[:page], per_page: 20

# view
<%= paginate @products %>
```

##部分合致検索
defaultの設定では完全一致となっています

```ruby:
Product.search "fresh honey" # fresh AND honey
```
これを変えて使うには

```ruby;
Product.search "fresh honey", partial: true # fresh OR honey
```
##*Synonyms
(*一つの実体に複数の名前や値などが割り当てられる機能や状態を指すこと)

```ruby:
class Product < ActiveRecord::Base
  searchkick synonyms: [["scallion", "green onion"], ["qtip", "cotton swab"]]
end
```
(注)必ずsynonymsを変更した後Product.reindexを呼んでください

##誤字
Searchkickではdefaultで誤字の修正を取り扱っています。この機能をOFFにしたい場合は以下のようにします

```ruby:
Product.search "zuchini", misspellings: false # no zucchini
```
また、edit distanceの範囲を選択することが出来ます

```ruby:
Product.search "zucini", misspellings: {distance: 2} # zucchini
```

##indexを作る
どのデータをindex化するかは search_data というメソッドを使って決めることができます。このメソッドを定義したあとに　Product.reindex　を呼んでください。

```ruby:model
class Product < ActiveRecord::Base
  def search_data
    as_json only: [:name, :active]
    # or equivalently
    {
      name: name,
      active: active
    }
  end
end
```
Searchkickは　find_in_batches　を使って情報を付与することができます。associationを求める場合は　search_import　というスコープを使ってください。

```
class Product < ActiveRecord::Base
  scope :search_import, includes(:searches)
end
```
##Reindexをする　又は　Reindexしない　
Reindexが必要なとき

- searchkickをインストールするとき、アップグレードするとき
- search_data　メソッド変更したとき
- searchkick　メソッドを変更したとき

Reindexが必要ないとき

- アプリを開始するとき
- データが追加されたり、変更されたり、削除された場合（自動的に同期されます）

##より良くデータを保ち、受け取る
Searchkickは具体的なデータからuserを探すことが出来ます。もしuserがBemとjerryの大きな猿がカートを引くアイスクリームを探そうとしたら、それは簡単に見つかるでしょう。

この手順では会話の軌跡と距離から定義されます。このデータベースは少ない容量ですが他のデータベースよりも自由に感じられるでしょう

```ruby:
class Search < ActiveRecord::Base
  belongs_to :product
  # fields: id, query, searched_at, converted_at, product_id
end
```
search queryを整理する必要はありません。searchkickは自動的にappleとAPPLESを同じものとして扱ってくれます

次はindexを改変しましょう。使用しているバージョンを　0.2.0　と明示しなくてはなりません((･ ･?)よくわかんない)

```ruby:
class Product < ActiveRecord::Base
  has_many :searches

  searchkick conversions: "conversions" # name of field

  def search_data
    {
      name: name,
      conversions: searches.group("query").count
      # {"ice cream" => 234, "chocolate" => 67, "cream" => 2}
    }
  end
end
```
毎日最新のものに変更するためにReindexとcron jobをset upしなくてはなりません

```
rake searchkick:reindex CLASS=Product
```

##専用の結果
userの結果ごとに並び替えることができます。例えば他の結果を出す前にユーザーが以前購入した商品を表示することができます。

```ruby:
class Product < ActiveRecord::Base
  searchkick personalize: "user_ids"

  def search_data
    {
      name: name,
      user_ids: orders.pluck(:user_id) # boost this product for these users
      # [4, 8, 15, 16, 23, 42]
    }
  end
end
```
Reindexとsearchはこのようにします

```
Product.search "milk", user_id: 8
```

##自動補完
autocompleteはユーザーが何を打つのか予測して、より簡単で早い検索経験を作ります。

最初にどのfieldを主要にするか明示します。これはindexの数が増えるに連れて著しい意味を持ちます。しかしこれは酷く簡単なので心配はいりません。

```ruby:
class City < ActiveRecord::Base
  searchkick autocomplete: ["name"]
end
```
Reindexとsearchはこのようにします

```
City.search "san fr", autocomplete: true
```

典型的な [typeahead.js](http://twitter.github.io/typeahead.js/)や jQuery UIといったjqueryライブラリを使うことが出来ます。

##どのようにRailsで動かすのか
最初にcontrollerにアクションを追加します

```ruby:
# app/controllers/cities_controller.rb
class CitiesController < ApplicationController

  def autocomplete
    render json: City.search(params[:query], autocomplete: true, limit: 10).map(&:name)
  end

end
```
このときにViewにsearch boxとjavascriptのコードを追記します

```
<input type="text" id="query" name="query" />

<script src="jquery.js"></script>
<script src="typeahead.js"></script>
<script>
  $("#query").typeahead({
    name: "city",
    remote: "/cities/autocomplete?query=%QUERY"
  });
</script>
```

##Facets(小枠)
[Facets](http://www.elasticsearch.org/guide/reference/api/search/facets/)は検索結果の集計を表示します
![facets](https://github-camo.global.ssl.fastly.net/16d4cf1db76cda4dac1cca3242f86fdd907631d5/687474703a2f2f616e6b616e652e6769746875622e696f2f7365617263686b69636b2f6661636574732e706e67)

```
products = Product.search "chuck taylor", facets: [:product_type, :gender, :brand]
p products.facets
```
さらに高度な集計結果を表示したい場合は

```
Product.search "2% Milk", facets: {store_id: {where: {in_stock: true}, limit: 10}}
```

##強調
queryによる検索結果の強調をすることができます

```
bands = Band.search "cinema", fields: [:name], highlight: true
```
(注意)fieldsオプションが必須です

fieldsを用いて強調をみるには

```
bands.with_details.each do |band, details|
  puts details[:highlight][:name] # "Two Door <em>Cinema</em> Club"
end
```
使うタグを変える場合は

```
Band.search "cinema", fields: [:name], highlight: {tag: "<strong>"}
```

##類似検索
似た要素を探すことができます

```
product = Product.first
product.similar(fields: ["name"])
```

##土地検索
(注意) 0.3.0以前のバージョンではlocationが不正確です。アップグレードしてReindexしてください

```ruby:
class City < ActiveRecord::Base
  searchkick locations: ["location"]

  def search_data
    attributes.merge location: [latitude, longitude]
  end
end
```
Reindexとsearchのやりかた

```ruby:
City.search "san", where: {location: {near: [37, -114], within: "100mi"}} # or 160km
```

Boxで仕分ける

```
City.search "san", where: {location: {top_left: [38, -123], bottom_right: [37, -122]}}
```

##Deployment
SearchkickはENV["ELASTICSEARCH_URL"]をサーバーとして使います。ただしコレが無い場合は
http://localhost:9200
がdefaultとして使用されます

###Heroku
[Searchbox](https://addons.heroku.com/searchbox)か[Bonsai](https://addons.heroku.com/bonsai)か[Found](https://addons.heroku.com/foundelasticsearch)を使用してください

```ruby:
# SearchBox
heroku addons:add searchbox:starter
heroku config:add ELASTICSEARCH_URL=`heroku config:get SEARCHBOX_URL`

# Bonsai
heroku addons:add bonsai
heroku config:add ELASTICSEARCH_URL=`heroku config:get BONSAI_URL`

# Found
heroku addons:add foundelasticsearch
heroku config:add ELASTICSEARCH_URL=`heroku config:get FOUNDELASTICSEARCH_URL`
```
このときにdeployとReindexをしてください

```
heroku run rake searchkick:reindex CLASS=Product
```

##その他
作成時には```config/initializers/elasticsearch.rb```を初期化してください

```ruby:
ENV["ELASTICSEARCH_URL"] = "http://username:password@api.searchbox.io"
```
このときにdeployとreindexをして下さい

```
rake searchkick:reindex CLASS=Product
```
##継承
searchkickは一つのテーブルを受け続ことが出来ます

```
class Dog < Animal
end
```
このとき親と子の要素は両方Reindexしてください

```
Animal.reindex
Dog.reindex # equivalent
```
検索するためには

```
Animal.search "*" # all animals
Dog.search "*"    # just dogs
```

##参照
Searchkickは0.90.0より高いelasticsearchを必要とします
ReindexはRecordの一つです

```
product = Product.find 10
product.reindex
```
古いindexの削除は

```
Product.clean_indices
```
異なったindex名を使うには

```
class Product < ActiveRecord::Base
  searchkick index_name: "products_v2"
end
```
Prefixはindexの名前です

```
class Product < ActiveRecord::Base
  searchkick index_prefix: "datakick"
end
```
callback機能をOFFにするには

```
class Product < ActiveRecord::Base
  searchkick callbacks: false
end
```
associationの利用を望むなら

```
Product.search "milk", include: [:brand, :stores]
```
modelのロードをOFFにするには

```
Product.search "milk", load: false
```
特徴を消すには

```
class Product < ActiveRecord::Base
  # A will not match Ä
  searchkick special_characters: false
end
```
すべてのモデルをReindexする(Railsだけしか使えません)

```
rake searchkick:reindex:all
```

##Tireから移動するには
1,searchメソッドをtire.searchに変えて、既存のindexを呼べるようにします

```
Product.search "fruit"
```
置き換えるには

```
Product.tire.search "fruit", index: "products"
```
2.tire mapping wをsearchkickメソッドに置き換えます

```
class Product < ActiveRecord::Base
  searchkick
end
```
deployとreindexをします

```
rake searchkick:reindex CLASS=Product # or Product.reindex in the console
```
4,これでsearch calls wはsearchkick callsに置き換わりました