Amazon CloudSearch用Gem、Nazrinをつくった

# はじめに
2015年10月25日の[ニコニコ超パーティー2015](http://choparty.jp/)で発表されたプレゼン共有サービス「[ニコナレ](http://niconare.nicovideo.jp)」（詳しくは[こちら](http://niconare.nicovideo.jp/watch/kn29)）では、AWSのサービスを主に使用しており、本AdventCalendar1日目の記事で紹介されている[メディアストレージ基盤](http://qiita.com/namusyaka/items/e805d1098827a4bd6835)を使用しています。
その他にも、検索システムとして[Amazon CloudSearch](http://aws.amazon.com/jp/cloudsearch)を使用しています。
ニコナレはRailsで開発を行っており、今回つくったCloudSearchを使用するための*探し物を探し当てる程度の機能*を持つGem、[tsuwatch/nazrin](https://github.com/tsuwatch/nazrin)の元となるライブラリが使用されています。
**【追記】現在はニコナレ本番環境に導入されています**

# 既存のGem
CloudSearchのGemを探したところ、すでにいくつかのGemが存在しています。

- [willian/cloud_search](https://github.com/willian/cloud_search)
- [spokesoftware/aws_cloud_search](https://github.com/spokesoftware/aws_cloud_search)

などです。
またRails（ActiveRecord）と連携できるGemの候補として[asari](https://github.com/wellbredgrapefruit/asari)というGemがありましたが、以下の理由により、使用することを控えさせていただきました。

- [aws-sdk](https://github.com/aws/aws-sdk-ruby)を使用せず、エンドポイントへリクエストを直接書いている
- CloudSearchのインデックスに登録できる値が、必ずレコードの値でなければならず、自分で値を操作できない
- CloudSearchのインデックスに登録、更新、削除するタイミングが固定されている
- ActiveRecordへの`find`時にeager_loadingなどができない

などの点により、無理に使うくらいならということで自作することになりました。

# Nazrin
[tsuwatch/nazrin](https://github.com/tsuwatch/nazrin)
## 使い方
Railsで

```
$ bundle exec rails g nazrin:config
```
とするとinitializers以下に下記のファイルが生成されます。

```rb
Nazrin.configure do |config|
  config.debug_mode = false          # trueにするとindex登録しなくなる
  config.search_endpoint = ''
  config.document_endpoint = ''
  config.region = ''
  config.access_key_id = ''
  config.secret_access_key = ''
  # currently support kaminari or nazrin
  config.pagination = 'kaminari'     # Nazrinから返却される配列がkaminariの配列になる
end
```

モデル内では以下のように使用します。

```rb
class Post < ActiveRecord::Base
  include Nazrin::ActiveRecord::Searchable

  searchable do
    fields [:content]
    field(:created_at) { created_at.utc.iso8601 }
  end

  after_create :add_to_index
  after_update :update_in_index
  after_destroy :delete_from_index
end
```

`searchable`メソッドが生えるので、その中でCloudSearchのインデックスに登録するフィールドについての定義を書くことができます。
`fields`メソッドで指定された配列（カラム名）は、CloudSearchのインデックスに作成されている同名のフィールドに、対象のレコードの値が保存されます。
また`field`メソッドを使用することで、`Post`モデルのインスタンスをレシーバとして、任意の値をインデックスに登録することができます。（ちなみに上記の例のようにcreated_at（date field）は[IETF RFC3339](http://tools.ietf.org/html/rfc3339)でなければならないので注意）

`add_to_index`、`update_in_index`、`delete_from_index`はそれぞれCloudSearchで追加、更新、削除するメソッドとなっています。インスタンスメソッドなのでいつでも任意のタイミングで実行することができます。

また、検索するには`search`メソッドを使用し以下のように

```rb
Post.search.size(1).start(0).query("(and 'content')").query_parser('structured').execute
=> [#<Post id: 1, content: "content">]
```
することでCloudSearchから検索し、取得したものをActiveRecordで引き直します。
結果の配列は、設定により、[kaminari](https://github.com/amatsuda/kaminari)もしくは独自実装の配列が返却されます。
また、

```rb
Post.search(includes: :hoge).size(1).start(0).query("(and 'content')").query_parser('structured').execute
```
とすることで、ActiveRecordで引き直すタイミングでeager_loadingしたり、`Post.search(where: { status: :opened })`としたり、実行時に追加で実行したいActiveRecordのメソッドを指定することができます。

検索に関しては、aws-sdkを軽くラップしたものになっているので、`query_parser`の設定など適切なクエリを投げる必要があります。

#さいごに
拙作[tsuwatch/nazrin](https://github.com/tsuwatch/nazrin)の開発を行いました。
AWSのGemは開発するのにお金を払う必要があり、厳しいものがありますね…。
これだ！というデファクトスタンダード的なGemもそれほどなくて結構困りものです。
RailsでCloudSearchを使うのであれば、オススメできるGemになっていると思います。ご検討いただければ幸いです。

ドワンゴは、[RubyKaigi 2015](http://rubykaigi.org/2015)でPlatinum Sponsorをさせていただいています。
[ニコナレ](http://niconare.nicovideo.jp)に少しでも興味を持っていただければなお幸いです :)