ActiveGroongaを使ってみた

Rails で Groonga をいい感じに扱える [ActiveGroonga](http://ranguba.org/ja/#about-active-groonga) というものがあるようなので，2014-12-19 に出た [Rails4.2](http://weblog.rubyonrails.org/2014/12/19/Rails-4-2-final/) で試してみた．

***2014-12-23 18:18:58 +0900 [コメント](http://qiita.com/niku/items/508705c0dbc5357b126c#comment-764d53b994716a213651)を受けて内容を修正．ActiveGroonga4.2では全て意図通りに動作する***

# 参考にしたサイト

- [Try ActiveGroonga!](https://web.archive.org/web/20111008122752/http://typo.okkez.net/2010/12/05/try-activegroonga)
- [ActiveGroongaを使ってみる (1)](http://shoutakenaka.blogspot.jp/2014/08/activegroonga-1.html)
- [ActiveGroongaを使ってみる (2)](http://shoutakenaka.blogspot.jp/2014/09/activegroonga-2.html)

# Rails プロジェクトを作る

名前は active_record-playground とした．
ActiveRecord は使わないので --skip-active-record する．

```bash
$ rails new active_groonga_playground --skip-active-record
```

# ActiveGroonga をインストールする

Gemfile に

```rb
# Gemfile
gem "activegroonga", require: "active_groonga"
```

を追加する．

次に

```bash
$ bin/bundle insall
```

する．

# ActiveGroonga のライブラリを Rails 起動時に読み込む

```rb
# config/application.rb

# require "active_record/railtie"
require "active_groonga/railtie"
```

# Groonga データベースを作成する

```bash
$ bin/rake groonga:create
```

# User モデルを作成する

```bash
$ bin/rails generate model user name:short_text
$ bin/rake groonga:migrate
```

指定可能なカラム名は [Schema::TableDefinition](http://ranguba.org/rroonga/ja/Groonga/Schema/TableDefinition.html) に書いてあるようだ．


# Tweet モデルを作成する

```bash
$ bin/rails generate model tweet content:short_text user:reference
$ bin/rake groonga:migrate
```

# 検索用テーブルを作成する

```bash
$ bin/rails generate migration CreateFullTextSearchIndex
```

生成された migration ファイルを編集する

```rb
# db/groonga/migrate/20141221113954_create_full_text_search_index.rb
class CreateFullTextSearchIndex < ActiveGroonga::Migration
  def up
    create_table("bigram",
                 type: :patricia_trie,
                 key_type: "ShortText",
                 default_tokenizer: "TokenBigram",
                 key_normalize: true) do |table|
      table.index("tweets.content")
    end
  end

  def down
    remove_table("bigram")
  end
end
```

create_table に渡すオプションは [Schema.create_table](http://ranguba.org/rroonga/ja/Groonga/Schema.html#create_table-class_method) を参考のこと．

schema を定義したら migrate する．

```bash
$ bin/rake groonga:migrate
```

# 使ってみる

```irb
$ bin/rails console
Loading development environment (Rails 4.2.0)
irb(main):001:0> User.create(name: "北 太郎")
=> #<User id: 1, created_at: 2014-12-23 18:08:07 +0900, name: "北 太郎", updated_at: 2014-12-23 18:08:07 +0900>
irb(main):002:0> User.create(name: "南 花子")
=> #<User id: 2, created_at: 2014-12-23 18:08:22 +0900, name: "南 花子", updated_at: 2014-12-23 18:08:22 +0900>
irb(main):003:0> Tweet.create(user: 1, content: "北風を吹かせます")
=> #<Tweet id: 1, content: "北風を吹かせます", created_at: 2014-12-23 18:08:32 +0900, updated_at: 2014-12-23 18:08:32 +0900, user: #<User id: 1, created_at: 2014-12-23 18:08:07 +0900, name: "北 太郎", updated_at: 2014-12-23 18:08:07 +0900>>
irb(main):004:0> Tweet.create(user: 1, content: "冷たい空気を送ります")
=> #<Tweet id: 2, content: "冷たい空気を送ります", created_at: 2014-12-23 18:08:40 +0900, updated_at: 2014-12-23 18:08:40 +0900, user: #<User id: 1, created_at: 2014-12-23 18:08:07 +0900, name: "北 太郎", updated_at: 2014-12-23 18:08:07 +0900>>
irb(main):005:0> Tweet.create(user: 2, content: "南風を吹かせます")
=> #<Tweet id: 3, content: "南風を吹かせます", created_at: 2014-12-23 18:08:47 +0900, updated_at: 2014-12-23 18:08:47 +0900, user: #<User id: 2, created_at: 2014-12-23 18:08:22 +0900, name: "南 花子", updated_at: 2014-12-23 18:08:22 +0900>>
irb(main):006:0> Tweet.create(user: 1, content: "暖かい空気を送ります")
=> #<Tweet id: 4, content: "暖かい空気を送ります", created_at: 2014-12-23 18:08:54 +0900, updated_at: 2014-12-23 18:08:54 +0900, user: #<User id: 1, created_at: 2014-12-23 18:08:07 +0900, name: "北 太郎", updated_at: 2014-12-23 18:08:07 +0900>>
```

意図通りに動いていそうだ．

検索してみよう．検索は条件をブロックで指定する．

```irb
irb(main):007:0> set = Tweet.select { |record| record.content =~ "空気" }
=> #<ActiveGroonga::ResultSet:0x007ffec6ac7958 @records=#<Groonga::Hash id: <2147483649>, name: (anonymous), path: (temporary), domain: <tweets>, range: (nil), flags: <WITH_SUBREC>, size: <2>, encoding: <:utf8>, default_tokenizer: (nil), token_filters: [], normalizer: (nil)>, @klass=Tweet(content: ShortText, created_at: Time, updated_at: Time, user: users), @groups={}, @expression=#<Groonga::Expression #<expr
  vars:{
    $1:#<record:no_key:tweets id:0(nonexistent)>
  },
  codes:{
    0:<push(), modify:1, value:#<record:no_key:tweets id:0(nonexistent)>>,
    1:<get_value(), modify:2, value:#<column:var_size tweets.content range:ShortText type:scalar compress:none>>,
    2:<push(), modify:0, value:"空気">,
    3:<match(), modify:0, value:(NULL)>
  }
>>, @n_records=2, @default_sort_keys=nil, @default_limit=nil, @n_key_nested=1>
irb(main):008:0> set.each { |e| p e }
#<Tweet id: 2, content: "冷たい空気を送ります", created_at: 2014-12-23 18:08:40 +0900, updated_at: 2014-12-23 18:08:40 +0900, user: #<User id: 1, created_at: 2014-12-23 18:08:07 +0900, name: "北 太郎", updated_at: 2014-12-23 18:08:07 +0900>>
#<Tweet id: 4, content: "暖かい空気を送ります", created_at: 2014-12-23 18:08:54 +0900, updated_at: 2014-12-23 18:08:54 +0900, user: #<User id: 1, created_at: 2014-12-23 18:08:07 +0900, name: "北 太郎", updated_at: 2014-12-23 18:08:07 +0900>>
=> nil
```

うむ．うまくできている．