16
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RailsでActiveGroongaを使う

Last updated at Posted at 2015-10-07

ActiveGroongaActiveRecordのようなインターフェイスで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に

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.rbActiveGroonga::Railtieを読み込むようにします。

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に戻せます。

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_textlong_textになっています。ここで指定できる型は[Groongaのデータ型][]を参照してください。使いたい物を選んで、Rubyらしく(?)スネークケースにします。

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

field[:type][:index]

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

マイグレーション

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

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

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のことだけ書きます。

このテーブルはインデックスとしての役割しかなく、コントローラーなどから触ることはないので、マイグレーションだけを作成します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を模倣しているので、テーブルの細かな定義を書く場所がなかったのでしょう)。
:で句切られたそれぞれの項目がどういう意味を持つかは、出来上がったマイグレーションファイルを見たほうが分かりやすい気がしますね:

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) 

モデル定義

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

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制約やユニークキー制約など)をサポートしているわけではないので、必要ならアプリケーション側できっちり定義しておきましょう。

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
    :
    :

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

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

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_manyhas_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
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_bywhereに相当するのは、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はそちらを参照するといいと思います。

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

  1. http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150924

  2. ググったら色々出て来ます。Qiitaにもたくさんの投稿があります:http://qiita.com/tags/groonga
    [ActiveModel]: http://railsguides.jp/active_model_basics.html
    [GQTP]: http://groonga.org/ja/docs/spec/gqtp.html

  3. Groonga Query Transfer Protocol。Groonga独自プロトコルで、HTTPよりオーバーヘッドが小さい。
    [pg]: https://bitbucket.org/ged/ruby-pg/wiki/Home

  4. ファイルシステム経由でアクセスできればよさそうなので、NFSやAFP経由でリモートサーバーも使えるとは思いますが。

  5. リモートサーバーへのアクセスには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

  6. これもググったら色々出て来ます。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

  7. $ 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

  8. ストップワードの管理など、インデックス用テーブル(語彙表と呼ばれます)をモデルとして使うのが有用な場面もありますが、割愛します。モデルを定義するのは難しくないはずです。ストップワードとか、どのように使うのか、というのは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

16
18
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?