RailsでActiveGroongaを使う

[ActiveGroonga][]は[ActiveRecord][]のようなインターフェイスで[Groonga][]にアクセスするgemです。最近、ちょっと触ってみた[^ブログの似た記事リスト作ってみた]ので、他記事との重複も多いですが、憶えた使い方など書きたいと思います。

Groongaとは
===========

ご存知の人も多いと思いますが、[Groonga][]は、（公式サイトによると）<q cite="http://groonga.org/ja/">カラムストア機能付き全文検索エンジン</q>です。全文検索エンジンとしてはもう何も言うことはありませんが[^全文検索エンジンとしてのGroonga]、「カラムストア機能付き」というのはぴんと来ない人もいるかも知れません。

カラムストアについての技術的な解説やメリット・デメリットはここでは割愛しますが、と言うか、解説できるほど知らないのですが、非常に大雑把に言うと

* データを行と列（カラム）の組み合わせで管理する
* 一つの行は複数の列を持つことができる
* データの保存は、一つの列が同じ場所に集まるようにしているので、列単位の統計処理などが得意

というデータベースです。上二つ、「データを行と列（カラム）の組み合わせで管理する」「一つの行は複数の列を持つことができる」というのを見て、何か思い浮かぶことはありませんか、そう、PostgreSQLなどのRDBMSですね。

非常に大雑把に言うと、テーブル型のデータベースなのです。Groongaは「カラムストア機能付き全文検索エンジン」ですが、ActiveGroongaはむしろ、「全文検索機能付きテーブル型データベース」としての側面を使いやすくするgemなのだというのが私見です。

ActiveGroongaとは
=================

既に説明してしまってこの項目立てる意味あったのか疑問ですが、ActiveGroongaは、Groongaデータベースをバックエンドに持つActiveRecordのような物です。

[ActiveModel][]の各種モジュールを`include`したり`extend`しているので、比較的自然にRailsと統合できます。

実はGroongaには三つの使い方があって、

* デーモンとして起動してHTTPや[GQTP][][^GQTP]経由でAPIを叩く
* `groonga`コマンド経由で各種コマンドを実行する（内部ではローカルのデータベースファイルに直接アクセスする）
* Cライブラリーである`libgroonga`を使って自分でアプリケーションに組み込む

です。

この内、ActiveGroongaでは三つ目の`libgroonga`を使う方法を採用しています。と言っても直接`libgroonga`にアクセスしているわけではなく、内部で使っている[Rroonga][] gem経由でアクセスしています。
ちょうど、ActiveRecordが[pg][] gem経由で`libpq`にアクセスしているようなものですね。

ただ、`libgroonga`は飽くまでローカルの[^ファイルシステム経由]データベースファイルにアクセスするためのライブラリーなので、HTTP等のアクセスとは違ってデータベースだけを別マシンに分離することはできません[^リモートアクセス]。この点はpgや[mysql2][]よりは[sqlite3][]のように考えたほうがよさそうです。

Elasticsearch公式gemとの違い
---------------------------

リアルタイムでのドキュメント追加・インデックス更新が可能な全文検索エンジンとして、[Elasticsearch][]が競合の位置にあります。Elasticsearchでも、Rails用にgeｍが用意されています（[elasticsearch-model][]、[elasticsearch-rails][]）。しかし、全文検索エンジンとしての関係性とは違って、ActiveGroonga gemはこれらと比較するようなものではありません。

elasticsearch-modelなどは**既存のモデルに検索機能を追加する**物ですが、ActiveGroongaは**それ自体でモデルを作る**ためのgemです。elasticsearch-modelのように検索機能だけを追加するには

* 既存のモデルのヘルパーメソッドとしてActiveGroongaのモデルを呼び出すような物を定義する
* より低レイヤーの[Rroonga][] gemを使って独自に検索機能を追加する（ようなgemを作って公開してください！！）
* Groongaを[Groonga HTTPサーバー]として起動して、ウェブAPIを叩いて独自に検索機能を追加する（ようなgemを作って公開してください！！！）
* [Mroonga][]や[PGroonga][]を使う

といった選択肢があります。

余談ですが中でもMroongaはおすすめで、MySQLやMariaDBを使っているならぜひ検討してほしい物です。
MySQLでよく使われるMyISAMやInnoDBといったのと並ぶストレージエンジンで、Groongaのテーブル型データベース、全文検索エンジンとしての両側面を使うことができます。「あるテーブルはMyISAM、あるテーブルはInnoDB」という使い方ができるように、特定のテーブルだけをMroongaエンジンにすることができます。Mroongaにすれば**SQLを使って**テーブル定義や実際の全文検索ができるようになります。全文検索を使わないふつうの（？）データ管理もできます。興味が出て来たら調べてみてください[^Mroonga]。

PGroongaは同様にPostgreSQLとGroongaを連携させた物です。

インストール
===========

閑話休題、インストール方法です。

Gemfileに

```ruby:Gemfile
gem 'activegroonga', require: 'active_groonga'
```

と書いて

    $ bundle install

してください。

Cのライブラリーがどうこう、とか言って脅かしていたのに、拍子抜けしましたか？
ActiveGroonga（と言うより内部で使っているRroonga）は、インストール時にシステムの状態を調べて、必要なライブラリー（`libgroonga`）がなければ自分でソースコードからコンパイルするようになっているので、使う側は特に気にせずgemとしてインストールするだけでいいのです。

事前にGroongaをインストールする
-----------------------------

※ ややアドバンストな内容です。取り敢えず試したい、という時には読み飛ばしてください。

特に気にせずインストールするだけでいいのですが、個人的には、gemをインストールする前に`libgroonga`をインストールしておくことをおすすめします。
[公式ドキュメント][Rroongaインストール]の「Groongaのバイナリパッケージを事前にインストールする方法」でインストールします。

理由は二つあって、

* Groongaの最新の機能（やバグ修正）をRubyからでも使える
* プラグインの登録が不要になる（物がある）

からです。

### Groongaの最新の機能をRubyからでも使える ###

Rroongaは前述の通り、インストール時に、システムにGroongaがなければ自分で必要なライブラリーをコンパイルしてくれます。しかしこれは、Rroonga出荷時点のGroongaのソースコードを使用しており、

* Groongaはアップデート版が出ている
* しかしRroongaはまだそれに追従していない

という時に、最新のGroongaが使えないことになってしまいます。

予めGroongaの一式をインストールしておけば、Rubyからはその中のライブラリーにリンクするだけです。Groongaのアップデートが出た時にそれを適用しておけば、Rroonga側は特に何もしなくても最新の機能が使えるようになります。

### プラグインの登録が不要 ###

日本語を扱う際は、[MeCabトークナイザー][]を使いたいことが殆どだと思います。
これはGroongaではプラグインとして提供されていて、各プラットフォーム用のパッケージ管理システム（APTやyumなど）でインストールします[^MeCabトークナイザーのインストール]。

Groongaをインストールした**後に**Rroongaをインストールする場合は、Groonga側でMeCabプラグインをインストールして登録しておけば、Ruby側で特に準備なく使うことができます。
ところが、Groongaのインストール**前に**Rroongaをインストールする場合、つまりRuby側でソースコードからGroongaをコンパイルした場合には、Rubyのソースコード中で明示的にMeCabプラグインを登録する必要があります：

```ruby
Groonga::Context.default.register_plugin 'path/to/mecab'
```

システムにインストールされている物に暗黙に依存するよりも、ソースコード中に残っている方がいい、という考え方もあると思いますが、結局

    $ dpkg -L groonga-tokenizer-mecab

などでパスを調べてから登録する、つまり`groonga-tokenizer-mecab`というRubyやアプリケーション外部の物に依存しているので、どちらかと言うと管理が独立している方がいいかな、と個人的には考えています、今のところ。

追記。コメントで教えてもらいました。**`Groonga::Context.default.register_plugin`でプラグインを登録すると、プラグインパッケージとRroongaとの間でABI互換性が壊れる可能性がある**そうです。やっぱり、先にGroongaをインストールしておくほうをおすすめしたいです。

設定
====

さて、前置きがものすごく長くなりましたが、ようやくRubyのコードです。

まず、`config/application.rb`で`ActiveGroonga::Railtie`を読み込むようにします。

```ruby:config/application.rb
require File.expand_path('../boot', __FILE__)

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
# :
# :
require "active_groonga/railtie"
# :
# :
```

これで、Groonga用の各種Rakeタスクがインストールされます。

    $ ./bin/rake -T groonga
    rake groonga:create          # Create the database
    rake groonga:drop            # Drops the database
    rake groonga:migrate         # Migrate the database (options: VERSION=x)
    rake groonga:migrate:down    # Migrate the schema down to the version (options: VERSION=x)
    rake groonga:migrate:redo    # Rolls the schema back and migrate the schema again
    rake groonga:migrate:status  # Display status of migration
    rake groonga:migrate:up      # Migrate the schema up to the version (options: VERSION=x)
    rake groonga:rollback        # Rolls the schema back to the previous version (specify steps w/ STEP=n)
    rake groonga:schema:dump     # Dump the schema
    rake groonga:schema:load     # Load the schema
    rake groonga:seed            # Load the seed data from db/groonga/seeds/#{RAILS_ENV}.grn, db/groonga/seeds/#{RAILS_ENV}.rb, db/groonga/seeds.grn or db/groonga/seeds.rb
    rake groonga:setup           # Create the database and load the schema
    rake groonga:test:env        # Set Rails.env = 'test'
    rake groonga:test:prepare    # Prepare groonga database for testing

ActiveRecordで見覚えのある感じですね。

ジェネレーター
=============

（ちょいちょいやり直しながら書いてるので、マイグレーションの日時がずれてるのはご愛嬌ってことでヒトツ）

ActiveGroongaではActiveRecordのようなジェネレーターが提供されているので、マイグレーションやモデル定義用のファイルをコマンドラインから作成することができます。

    $ ./bin/rails g -h
    (snip)
    ActiveGroonga:
      active_groonga:migration
      active_groonga:model
    (snip)

それぞれ使い方を見てみましょう。

`active_groonga:migration`：

    $ ./bin/rails g active_groonga:migration -h
    (snip)
    Usage:
      rails generate active_groonga:migration NAME [name:type[:option:option] name:type[:option:option]] [options]
    
    Options:
      [--skip-namespace], [--no-skip-namespace]  # Skip namespace (affects only isolated applications)
    
    Runtime options:
      -f, [--force]                    # Overwrite files that already exist
      -p, [--pretend], [--no-pretend]  # Run but do not make any changes
      -q, [--quiet], [--no-quiet]      # Suppress status output
      -s, [--skip], [--no-skip]        # Skip files that already exist
    
    Description:
        Create active groonga files for migration generator.
    (snip)

`active_groonga:model`：

    $ ./bin/rails g active_groonga:model -h
    (snip)
    Usage:
      rails generate active_groonga:model NAME [name:type[:option:option] name:type[:option:option]] [options]
    
    Options:
          [--skip-namespace], [--no-skip-namespace]  # Skip namespace (affects only isolated applications)
          [--migration], [--no-migration]            # Indicates when to generate migration
                                                     # Default: true
          [--timestamps], [--no-timestamps]          # Indicates when to generate timestamps
                                                     # Default: true
          [--parent=PARENT]                          # The parent class for the generated model
      -t, [--test-framework=NAME]                    # Test framework to be invoked
                                                     # Default: test_unit
    
    TestUnit options:
          [--fixture], [--no-fixture]   # Indicates when to generate fixture
                                        # Default: true
      -r, [--fixture-replacement=NAME]  # Fixture replacement to be invoked
    
    Runtime options:
      -f, [--force]                    # Overwrite files that already exist
      -p, [--pretend], [--no-pretend]  # Run but do not make any changes
      -q, [--quiet], [--no-quiet]      # Suppress status output
      -s, [--skip], [--no-skip]        # Skip files that already exist
    
    Description:
        Create active groonga files for model generator.
    (snip)

Rails触るの久々なんで、ついでに、モデル用のジェネレーターがどうなっているかも見てみましょう：

    $ ./bin/rails g model -h
    (snip)
    Usage:
      rails generate model NAME [field[:type][:index] field[:type][:index]] [options]
    
    Options:
          [--skip-namespace], [--no-skip-namespace]  # Skip namespace (affects only isolated applications)
          [--force-plural], [--no-force-plural]      # Forces the use of the given model name
      -o, --orm=NAME                                 # Orm to be invoked
                                                     # Default: active_groonga
    
    ActiveGroonga options:
          [--migration], [--no-migration]    # Indicates when to generate migration
                                             # Default: true
          [--timestamps], [--no-timestamps]  # Indicates when to generate timestamps
                                             # Default: true
          [--parent=PARENT]                  # The parent class for the generated model
      -t, [--test-framework=NAME]            # Test framework to be invoked
                                             # Default: test_unit
    (snip)

ん？

      -o, --orm=NAME                                 # Orm to be invoked
                                                     # Default: active_groonga

デフォルトが`active_groonga`になってる！？　デフォルトO/Rマッパーの上書きなんてできたんですね、知りませんでした。

なお、`railtie`の読み込み順を変えることでデフォルトを`active_record`に戻せます。

```ruby:config/application.rb
# :
# :
require "active_groonga/railtie"
require "active_model/railtie"
# :
# :
```

    $ ./bin/rails g model -h
    (snip)
    Usage:
      rails generate model NAME [field[:type][:index] field[:type][:index]] [options]
    
    Options:
          [--skip-namespace], [--no-skip-namespace]  # Skip namespace (affects only isolated applications)
          [--force-plural], [--no-force-plural]      # Forces the use of the given model name
      -o, --orm=NAME                                 # Orm to be invoked
                                                     # Default: active_record
    
    ActiveRecord options:
          [--migration], [--no-migration]    # Indicates when to generate migration
                                             # Default: true
          [--timestamps], [--no-timestamps]  # Indicates when to generate timestamps
                                             # Default: true
          [--parent=PARENT]                  # The parent class for the generated model
          [--indexes], [--no-indexes]        # Add indexes for references and belongs_to columns
                                             # Default: true
      -t, [--test-framework=NAME]            # Test framework to be invoked
                                             # Default: test_unit
    (snip)

以後はせっかくなんで（？）`active_groonga`のほうをデフォルトにして進めます。

デフォルトO/Rマッパーが`active_groonga`になったので、単に`model`用のジェネレーターを使うだけでよさそうです。

    $ ./bin/rails g model article key:int:hash title:short_text body:long_text
    (snip)
          invoke  active_groonga
          create    db/groonga/migrate/20151003192809_create_articles.rb
          create    app/models/article.rb
          invoke    test_unit
          create      test/models/article_test.rb
          create      test/fixtures/articles.yml
    (snip)

データ型が、ActiveRecordにはない`short_text`や`long_text`になっています。ここで指定できる型は[Groongaのデータ型][]を参照してください。使いたい物を選んで、Rubyらしく（？）スネークケースにします。

フィールドの所、ヘルプでは

    field[:type][:index]

となっていますが、`:index`の所に（ActiveRecordのように）`index`や`unique`を指定しても無視されます。詳しくは後述します。

マイグレーション
===============

何はともあれデータベースがないと始まらないので、さっき作った`articles`テーブルをマイグレートしましょう。

マイグレーションファイルはこうなっています。

```ruby:db/groonga/migrate20151003192809_create_articles.rb
class CreateArticles < ActiveGroonga::Migration
  def up
    create_table(:articles, :type => :hash, :key_type => "Int32") do |table|
      table.short_text(:title)
      table.long_text(:body)
      table.timestamps
    end
  end

  def down
    remove_table(:articles)
  end
end
```

マイグレートします。

    $ ./bin/rake groonga:migrate
    == 20151003192809 CreateArticles (db/groonga/migrate/20151003192809_create_articles.rb): migrating
    == 20151003192809 CreateArticles (db/groonga/migrate/20151003192809_create_articles.rb): migrated (0.0001s)

いつも通りの手順ですね。

データベースファイルは`db/groonga/development/db`に出来ています。`groonga`コマンド（Groongaパッケージについてきます）をインストール済みであれば、以下のようにしてデータベースの中身を確認できます。`groonga`コマンドは結果をJSONで表示するので、`jq`で見やすくしています。

    $ groonga db/groonga/development/db table_list | jq .
    (snip)
        [
          258,
          "articles",
          "db/groonga/development/db.0000102",
          "TABLE_NO_KEY|PERSISTENT",
          "Int32",
          null,
          null,
          null
        ],
        [
          256,
          "schema_migrations",
          "db/groonga/development/db.0000100",
          "TABLE_HASH_KEY|PERSISTENT",
          "UInt64",
          null,
          null,
          null
        ]
    (snip)

何となくですが、`articles`テーブルの他、マイグレーション管理用のテーブルが出来ていることも伺えますね。

さて、Groongaで全文検索をしようと思ったら、ここで作った`articles`だけでなく、「`articles`から全文検索をするためのインデックスを格納するテーブル」を作る必要があります。RDBMSだったらインデックスを張るようなものですが、Groongaではテーブルを作ります。
ただ、覚えることが多いので、Groongaに親しんでいない場合は取り敢えず書いてある通りに実行して、慣れてから調べてみたほうがいいかも知れません。

この辺の話もできるとよかったのですが、ここまでで既に、当初の「つもり」を大幅に超えて長くなってしまっているので、割愛します。[Groongaのチュートリアル][]などを見てください。ここではActiveGroongaのことだけ書きます。

このテーブルはインデックスとしての役割しかなく、コントローラーなどから触ることはないので、マイグレーションだけを作成します[^モデルとしての語彙表]。
モデルと同じように、マイグレーションのデフォルトO/Rマッパーも`active_groonga`になっているので、いつものRailsの手順でマイグレーションを作ります。

……と思ったのですが、マイグレーションジェネレーターではテーブルの細かな設定ができないようなので、モデルジェネレーターを使うことにします（プルリクエスト事案かな……）。

    $ ./bin/rails g model term key:short_text:patricia_trie:normalize:tokenizer:token_mecab articles.body:index:with_section:with_weight:with_position
    (snip)
          invoke  active_groonga
          create    db/groonga/migrate/20151003211724_create_terms.rb
          create    app/models/term.rb
          invoke    test_unit
          create      test/models/term_test.rb
          create      test/fixtures/terms.yml
    (snip)

はい、いきなり色々出て来ました。順番に説明します。

まず、一般に、カラムの定義は`カラム名:型:オプション:オプション:……`となっています。

`key`というカラムを定義していますが、これはGroongaで特別な意味を持つカラムです。強いてActiveRecordに対応付けると、`id`カラムに当たります。ただ、`key`はGroongaの知識なので、ここでは割愛します。
ActiveGroongaに特有なのは、このジェネレーターを使って、`key`カラムだけでなく、テーブル自体の定義も同時にしているところです（ActiveRecordを模倣しているので、テーブルの細かな定義を書く場所がなかったのでしょう）。
`:`で句切られたそれぞれの項目がどういう意味を持つかは、出来上がったマイグレーションファイルを見たほうが分かりやすい気がしますね：

```ruby:db/groonga/migrate/20151003211545_create_terms.rb
class CreateTerms < ActiveGroonga::Migration
  def up
    create_table(:terms, :type => :patricia_trie, :key_type => "ShortText", :key_normalize => true, :default_tokenizer => "TokenMecab") do |table|
      table.index("articles.body", :with_section => true, :with_weight => true, :with_position => true)
      table.timestamps
    end
  end

  def down
    remove_table(:terms)
  end
end
```

`key`カラムへの指定が全部`create_table`のオプションになりました。
`patricia_trie`とか`normalize`とかの用語を見付けると、自動で「これはテーブルの`type`の定義だな」「これはノーマライザーの使用の有無だな」といった感じでうまい具合に解釈してくれます。トークナイザーの指定が特殊で、`tokenizer`というフィールドがあったらトークナイザーの定義が始まり、直後のフィールドがその値（`token_mecab`）として扱われています。

続くカラム定義も変わっています。
`articles.body`という、`.`を含んだカラム名になっています。これは「`articles`テーブルの`body`カラム用のインデックスを作る」というGroonga独自の記法です。
その他のオプションは、インデックス型のカラム専用のオプションです。それぞれの意味についてはGroongaの知識なので、やはり割愛します。

これでマイグレートすれば、取り敢えず`Article`モデルを作ったり、全文検索をしたりできるようになります。

    $ ./bin/rake groonga:migrate
    == 20151004143850 CreateTerms (db/groonga/migrate/20151004143850_create_terms.rb): migrating 
    == 20151004143850 CreateTerms (db/groonga/migrate/20151004143850_create_terms.rb): migrated (0.0002s) 

モデル定義
=========

モデルの定義はこんな感じです。

```ruby:app/models/book.rb
class Article < ActiveGroonga::Base
end
```

なんの変哲もないモデルですね。

コンソールで少し遊んでみましょう。

    $ ./bin/rails c
    >> Article.all.records.size
    => 0
    >> a1 = Article.new(key: 1, title: 'article1', body: 'body')
    => #<Article key: 1, author: nil, body: "body", created_at: nil, title: "article1", updated_at: nil>
    >> Article.all.records.size
    => 0
    >> a1.save
    => true
    >> Article.all.records.size
    => 1
    >> a2 = Article.create(key: 2, title: 'article2', body: 'body')
    => #<Article key: 2, author: nil, body: "body", created_at: 2015-10-08 06:08:27 +0900, title: "article2", updated_at: 2015-10-08 06:08:27 +0900>
    >> Article.all.records.size
    => 2
    >> a1.body = 'first article'
    => "first article"
    >> a1.save
    => true
    >> Article.all.records.first
    => #<Groonga::Record:0x007f043c0824f0 @table=#<Groonga::Hash id: <258>, name: <articles>, path: <db/groonga/development/db.0000102>, domain: <Int32>, range: (nil), flags: <>, size: <2>, encoding: <:utf8>, default_tokenizer: (nil), token_filters: [], normalizer: (nil)>, @id=1, @added=false, attributes: {"_id"=>1, "_key"=>2, "author"=>nil, "body"=>"body", "created_at"=>2015-10-08 06:08:27 +0900, "title"=>"article2", "updated_at"=>2015-10-08 06:08:27 +0900}>

全件の取得方法がActiveRecordと違いますが、概ね期待通りに動きますね。

バリデーション、コールバック
==========================

バリデーションや`before_save`なんかのコールバックも、ActiveRecord同様に使えます。特に、GroongaはRDBMSほどの様々な制約（NOT NULL制約やユニークキー制約など）をサポートしているわけではないので、必要ならアプリケーション側できっちり定義しておきましょう。

```ruby:app/modeuls/article.rb
class Article < ActiveGroonga::Base
  validates :title, presence: true
end
```

試しに`title`を持たないインスタンスを作ってみます。

    >> a3 = Article.create(key: 3, body: 'body')
    => #<Article key: 3, author: nil, body: "body", created_at: 2015-10-08 06:09:36 +0900, title: "article 2015-10-08 06:09:36 +0900", updated_at: 2015-10-08 06:09:36 +0900>
    >> a3.valid?
    => false
    >> a3.save!
    ActiveGroonga::RecordInvalid: translation missing: en.activegroonga.errors.messages.record_invalid
        :
        :

ちゃんとバリデーションできていますね。

コールバックも試しましょう。

```ruby:app/models/article.rb
class Article < ActiveGroonga::Base
  validates :title, presence: true

  before_validation :ensure_title

  private

  def ensure_title
    return if title
    self.title = 'article ' + Time.now.to_s
  end
end
```

    >> a4 = Article.create(key: 4, body: 'body')
    => #<Article key: 4, author: nil, body: "body", created_at: 2015-10-08 06:10:18 +0900, title: "article 2015-10-08 06:10:18 +0900", updated_at: 2015-10-08 06:10:18 +0900>
    >> a4.title
    => "article 2015-10-08 06:10:18 +0900"

うまくいっているようです。

アソシエーション
===============

ActiveGroongaにはActiveRecordのようなアソシエーション用マクロ（`belongs_to`など）はありません。
`has_many`と`has_one`についてはそういう機能がないので利用できません（と、思う……）。
`belogs_to`は、マクロはありませんがデフォルトで使えます。

まず、`Article`の持ち主である`Author`用のテーブルとモデルを作りましょう。

    $ ./bin/rails g model author name:short_text
          invoke  active_groonga
          create    db/groonga/migrate/20151006234946_create_authors.rb
          create    app/models/author.rb
          invoke    test_unit
          create      test/models/author_test.rb
          create      test/fixtures/authors.yml

著者に関する検索機能を使うには、そのためのマイグレーションも作る必要がありますが、取り敢えずここでは置いておきます。

次に、`articles`テーブルに、`authors`テーブルを参照するカラムを追加します。

    $ ./bin/rails g migration AddAuthorsToArticles
          invoke  active_groonga
          create    db/groonga/migrate/20151007003328_add_authors_to_articles.rb

```ruby:db/groonga/migrate/20151007003328_add_authors_to_articles.rb
class AddAuthorsToArticles < ActiveGroonga::Migration
  def up
    change_table(:articles) do |table|
      table.reference :author
    end
  end

  def down
    change_table(:articles) do |table|
      table.remove_column :author
    end
  end
end
```

`table.reference`が、他のテーブルを参照するカラムの型です。

マイグレートします。

    $ ./bin/rake groonga:migrate
    == 20151006234946 CreateAuthors (db/groonga/migrate/20151006234946_create_authors.rb): migrating
    == 20151006234946 CreateAuthors (db/groonga/migrate/20151006234946_create_authors.rb): migrated (0.0001s)
    == 20151007003328 AddAuthorsToArticles (db/groonga/migrate/20151007003328_add_authors_to_articles.rb): migrating
    == 20151007003328 AddAuthorsToArticles (db/groonga/migrate/20151007003328_add_authors_to_articles.rb): migrated (0.0001s)

試してみましょう。

    >> author1 = Author.create(name: 'author1')
    => #<Author id: 2, created_at: 2015-10-08 04:44:52 +0900, name: "author1", updated_at: 2015-10-08 04:44:52 +0900>
    >> a4.author = author1
    => #<Author id: 2, created_at: 2015-10-08 04:44:52 +0900, name: "author1", updated_at: 2015-10-08 04:44:52 +0900>
    >> a4.save!
    => true
    >> Article.all.to_a.last.author
    => #<Author id: 2, created_at: 2015-10-08 04:44:52 +0900, name: "author1", updated_at: 2015-10-08 04:44:52 +0900>

ちゃんと参照できています。

検索
====

ActiveGroongaにも`.find`メソッドはあって、ActiveRecord同様に使えます。

    >> Article.find(1)
    => #<Article key: 1, author: nil, body: "first article", created_at: 2015-10-08 06:09:00 +0900, title: "article1", updated_at: 2015-10-08 06:09:00 +0900>

が、Groongaの`id`（`_id`）はActiveRecordのデフォルトで設定されるサロゲートキーとは違う物です。[Groongaドキュメントの_idの説明][]にあるように、値が使い回される物なので、一時的に使う分にはいいと思いますが、URIなど恒久的な所には使わないほうがいいと思います。
ActiveRecordの`id`のような用途では、`key`がいいでしょう。ただ、`key`もActiveRecordの`id`とはだいぶ使い勝手が違います。正確には、違うことがあります。作成するテーブルの定義によって`key`の性質が変わるからです。存在しないこともあります。[公式ドキュメントのテーブルの説明][]などが参考になると思います。
面倒くさいようですが、そもそもバックエンドのデータベースの性質がActiveRecordとは違うので、仕方のないところです。

ActiveRecordの`find_by`や`where`に相当するのは、`select`メソッドです。

    >> articles = Article.select {|article| article.title == 'article1'}
    => #<ActiveGroonga::ResultSet:0x007f043c003e70 @records=#<Groonga::Hash id: <2147483649>, name: (anonymous), path: (temporary), domain: <articles>, range: (nil), flags: <WITH_SUBREC>, size: <1>, encoding: <:utf8>, default_tokenizer: (nil), token_filters: [], normalizer: (nil)>, @klass=Article(author: authors, body: LongText, created_at: Time, title: ShortText, updated_at: Time), @groups={}, @expression=#<Groonga::Expression
       :
       :
    >> articles.first.title
    => "article1"

ActiveGroongaの`Base.select`は内部でRroongaの[`Groonga::Table#select`][Groonga::Table#select]を呼んでいるので、ブロックの使い方詳細はそちらのドキュメントを見てください。以下で二つだけ紹介します。

全文検索
--------

上の例では、ブロック中で`==`を使っており、これは完全一致検索を実行するのですが、全文検索には`=~`を使います。

    >> ['こんにちはGroonga', 'こんにちはElasticsearch', 'こんにちはよいお日柄で'].each_with_index do |body, index|
    ?>   Article.create(key: index + 4, body: body)
    >> end
    => ["こんにちはGroonga", "こんにちはElasticsearch", "こんにちはよいお日柄で"]
    >> Article.select {|article| article.body =~ 'こんにちは'}.to_a
    => [#<Article key: 4, author: nil, body: "こんにちはGroonga", created_at: 2015-10-08 06:21:32 +0900, title: "article 2015-10-08 06:21:32 +0900", updated_at: 2015-10-08 06:21:32 +0900>, #<Article key: 5, author: nil, body: "こんにちはElasticsearch", created_at: 2015-10-08 06:21:32 +0900, title: "article 2015-10-08 06:21:32 +0900", updated_at: 2015-10-08 06:21:32 +0900>, #<Article key: 6, author: nil, body: "こんにちはよいお日柄で", created_at: 2015-10-08 06:21:32 +0900, title: "article 2015-10-08 06:21:32 +0900", updated_at: 2015-10-08 06:21:32 +0900>]
    >> Article.select {|article| article.body =~ '日柄'}.to_a
    => [#<Article key: 6, author: nil, body: "こんにちはよいお日柄で", created_at: 2015-10-08 06:21:32 +0900, title: "article 2015-10-08 06:21:32 +0900", updated_at: 2015-10-08 06:21:32 +0900>]

よい感じの気がします！

類似文書検索
------------

Groongaには類似文書検索の機能があって、ActiveGroongaでも使えます。

    >> Article.select {|article| article.body.similar_search 'こんにちはActiveGroonga'}.to_a
    => [#<Article key: 4, author: nil, body: "こんにちはGroonga", created_at: 2015-10-08 06:21:32 +0900, title: "article 2015-10-08 06:21:32 +0900", updated_at: 2015-10-08 06:21:32 +0900>, #<Article key: 5, author: nil, body: "こんにちはElasticsearch", created_at: 2015-10-08 06:21:32 +0900, title: "article 2015-10-08 06:21:32 +0900", updated_at: 2015-10-08 06:21:32 +0900>, #<Article key: 6, author: nil, body: "こんにちはよいお日柄で", created_at: 2015-10-08 06:21:32 +0900, title: "article 2015-10-08 06:21:32 +0900", updated_at: 2015-10-08 06:21:32 +0900>]

「こんにちはActiveGroonga」で検索しました。これと一致するレコードはありませんが、似た感じの「こんにちは〜」というのがヒットしています。

終わりに
========

なんか当初考えていた十倍くらいの分量になってしまいましたが、お役に立てれば幸いです。

GroongaはやはりRDBMSとは違うので、ちゃんと使おうと思うとGroonga
自体を知る必要がありますが、ActiveGroongaはあまり知らなくてもまあまあ使えるので、取り敢えず入れて、あとから段々と勉強していくんでもいいんじゃないでしょうか。

あと、処理は大部分をRroongaに投げているので、RubyのAPIはそちらを参照するといいと思います。

中の人ではないので、ウェブ上の記事や自分で触った体験、中の人に伺ったお話を元に書いていますが、間違いがあるかも知れません。優しいご指摘お待ちしています。

[ActiveGroonga]: https://github.com/ranguba/activegroonga
[ActiveRecord]: http://railsguides.jp/active_record_basics.html
[Groonga]: http://groonga.org/ja/
[^ブログの似た記事リスト作ってみた]: http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150924
[^全文検索エンジンとしてのGroonga]: ググったら色々出て来ます。Qiitaにもたくさんの投稿があります：http://qiita.com/tags/groonga
[ActiveModel]: http://railsguides.jp/active_model_basics.html
[GQTP]: http://groonga.org/ja/docs/spec/gqtp.html
[^GQTP]: Groonga Query Transfer Protocol。Groonga独自プロトコルで、HTTPよりオーバーヘッドが小さい。
[pg]: https://bitbucket.org/ged/ruby-pg/wiki/Home
[^ファイルシステム経由]: ファイルシステム経由でアクセスできればよさそうなので、NFSやAFP経由でリモートサーバーも使えるとは思いますが。
[^リモートアクセス]: リモートサーバーへのアクセスにはHTTPやGQTPを使ったアクセスとモデルとの間のマッパーを自分で書く必要があります。
[mysql2]: https://github.com/brianmario/mysql2
[sqlite3]: https://github.com/sparklemotion/sqlite3-ruby
[Elasticsearch]: https://www.elastic.co/jp/
[elasticsearch-model]: https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model
[elasticsearch-rails]: https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-rails
[Rroonga]: http://ranguba.org/ja/#about-rroonga
[Groonga HTTPサーバー]: http://groonga.org/ja/docs/reference/executables/groonga-server-http.html
[Mroonga]: http://mroonga.org/ja/
[PGroonga]: https://github.com/pgroonga/pgroonga
[^Mroonga]: これもググったら色々出て来ます。Qiitaにも投稿があります：http://qiita.com/tags/mroonga
[Rroongaインストール]: http://ranguba.org/rroonga/ja/file.install.html
[Mecabトークナイザー]: http://groonga.org/ja/docs/reference/tokenizers.html#tokenmecab
[^Mecabトークナイザーのインストール]: `$ sudo apt-get install groonga-tokenizer-mecab` 詳細は公式ドキュメントのインストールページに、Groongaその物のインストール方法と一緒に載っています： http://groonga.org/ja/docs/install.html
[Groongaのデータ型]: http://groonga.org/ja/docs/reference/types.html
[Groongaのチュートリアル]: http://groonga.org/ja/docs/tutorial/introduction.html#create-a-lexicon-table-for-full-text-search
[^モデルとしての語彙表]: ストップワードの管理など、インデックス用テーブル（語彙表と呼ばれます）をモデルとして使うのが有用な場面もありますが、割愛します。モデルを定義するのは難しくないはずです。ストップワードとか、どのように使うのか、というのはGroongaの知識が必要なので少し調べてから考えてみてください。
[コールバックのサンプル]: http://railsguides.jp/active_record_callbacks.html
[Groongaドキュメントの_idの説明]: http://groonga.org/ja/docs/reference/columns/pseudo.html?highlight=_id
[公式ドキュメントのテーブルの説明]: http://groonga.org/ja/docs/reference/tables.html
[Groonga::Table#select]: http://ranguba.org/rroonga/ja/Groonga/Table.html#select-instance_method