ActiveGroongaはActiveRecordのようなインターフェイスでGroongaにアクセスするgemです。最近、ちょっと触ってみた1ので、他記事との重複も多いですが、憶えた使い方など書きたいと思います。
Groongaとは
ご存知の人も多いと思いますが、Groongaは、(公式サイトによると)カラムストア機能付き全文検索エンジン
です。全文検索エンジンとしてはもう何も言うことはありませんが2、「カラムストア機能付き」というのはぴんと来ない人もいるかも知れません。
カラムストアについての技術的な解説やメリット・デメリットはここでは割愛しますが、と言うか、解説できるほど知らないのですが、非常に大雑把に言うと
- データを行と列(カラム)の組み合わせで管理する
- 一つの行は複数の列を持つことができる
- データの保存は、一つの列が同じ場所に集まるようにしているので、列単位の統計処理などが得意
というデータベースです。上二つ、「データを行と列(カラム)の組み合わせで管理する」「一つの行は複数の列を持つことができる」というのを見て、何か思い浮かぶことはありませんか、そう、PostgreSQLなどのRDBMSですね。
非常に大雑把に言うと、テーブル型のデータベースなのです。Groongaは「カラムストア機能付き全文検索エンジン」ですが、ActiveGroongaはむしろ、「全文検索機能付きテーブル型データベース」としての側面を使いやすくするgemなのだというのが私見です。
ActiveGroongaとは
既に説明してしまってこの項目立てる意味あったのか疑問ですが、ActiveGroongaは、Groongaデータベースをバックエンドに持つActiveRecordのような物です。
[ActiveModel][]の各種モジュールをinclude
したりextend
しているので、比較的自然にRailsと統合できます。
実はGroongaには三つの使い方があって、
- デーモンとして起動してHTTPや[GQTP][]3経由でAPIを叩く
-
groonga
コマンド経由で各種コマンドを実行する(内部ではローカルのデータベースファイルに直接アクセスする) - Cライブラリーである
libgroonga
を使って自分でアプリケーションに組み込む
です。
この内、ActiveGroongaでは三つ目のlibgroonga
を使う方法を採用しています。と言っても直接libgroonga
にアクセスしているわけではなく、内部で使っている[Rroonga][] gem経由でアクセスしています。
ちょうど、ActiveRecordが[pg][] gem経由でlibpq
にアクセスしているようなものですね。
ただ、libgroonga
は飽くまでローカルの4データベースファイルにアクセスするためのライブラリーなので、HTTP等のアクセスとは違ってデータベースだけを別マシンに分離することはできません5。この点はpgや[mysql2][]よりは[sqlite3][]のように考えたほうがよさそうです。
Elasticsearch公式gemとの違い
リアルタイムでのドキュメント追加・インデックス更新が可能な全文検索エンジンとして、[Elasticsearch][]が競合の位置にあります。Elasticsearchでも、Rails用にgemが用意されています([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を使ってテーブル定義や実際の全文検索ができるようになります。全文検索を使わないふつうの(?)データ管理もできます。興味が出て来たら調べてみてください6。
PGroongaは同様にPostgreSQLとGroongaを連携させた物です。
インストール
閑話休題、インストール方法です。
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など)でインストールします7。
Groongaをインストールした後にRroongaをインストールする場合は、Groonga側でMeCabプラグインをインストールして登録しておけば、Ruby側で特に準備なく使うことができます。
ところが、Groongaのインストール前にRroongaをインストールする場合、つまりRuby側でソースコードからGroongaをコンパイルした場合には、Rubyのソースコード中で明示的にMeCabプラグインを登録する必要があります:
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
を読み込むようにします。
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
に戻せます。
# :
# :
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
テーブルをマイグレートしましょう。
マイグレーションファイルはこうなっています。
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のことだけ書きます。
このテーブルはインデックスとしての役割しかなく、コントローラーなどから触ることはないので、マイグレーションだけを作成します8。
モデルと同じように、マイグレーションのデフォルト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を模倣しているので、テーブルの細かな定義を書く場所がなかったのでしょう)。
:
で句切られたそれぞれの項目がどういう意味を持つかは、出来上がったマイグレーションファイルを見たほうが分かりやすい気がしますね:
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)
モデル定義
モデルの定義はこんな感じです。
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制約やユニークキー制約など)をサポートしているわけではないので、必要ならアプリケーション側できっちり定義しておきましょう。
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
:
:
ちゃんとバリデーションできていますね。
コールバックも試しましょう。
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
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はそちらを参照するといいと思います。
中の人ではないので、ウェブ上の記事や自分で触った体験、中の人に伺ったお話を元に書いていますが、間違いがあるかも知れません。優しいご指摘お待ちしています。
-
http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150924 ↩
-
ググったら色々出て来ます。Qiitaにもたくさんの投稿があります:http://qiita.com/tags/groonga
[ActiveModel]: http://railsguides.jp/active_model_basics.html
[GQTP]: http://groonga.org/ja/docs/spec/gqtp.html ↩ -
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 ↩ -
これもググったら色々出て来ます。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 ↩ -
$ 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 ↩