わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

acts_as_ludiaをRailsなしで使う

締切のある重要な仕事を終えまして,これから,別の仕事に力を入れられそうです.
そこではLudiaを使う予定でして,まずはRuby + ActiveRecordでやれるかなと,外を調べてみたところ,Ludia 用の Rails プラグイン acts_as_ludia を作りました - のほほん徒然というのを発見しました.
スクリプトをチェックアウトして,ざっと中身を見たのですが,これならRailsなしでできそうです.あとは試行錯誤というやつです.
なお,当初は「PostgreSQL 8.3ではLudia検索の演算子が%%になる」ことも配慮しようとしたのですが,テスト環境がDebian (etch)で,8.1ですので,@@のままです.

準備

  • Rubyをインストール
  • RubyGemsをインストール
  • gem installで,activerecordとpostgres*1をインストール
  • postgresqlをインストールして起動しておき,データベース作成などをできるようにしておく

SennaとLudiaのインストール

Ludia プロジェクト日本語トップページ - OSDNから,「ludia-withdeps」の最新バージョンをダウンロードします.「ludia」そのものよりサイズがうんと大きいですが,Ludiaで動作確認済のSennaが入っているので,これのほうがいいでしょう.
ビルドのためのコマンドを並べておきます.

$ wget http://globalbase.dl.sourceforge.jp/ludia/30152/ludia-withdeps-1.5.0.tar.gz
$ tar xzf ludia-withdeps-1.5.0.tar.gz
$ cd ludia-1.5.0
$ cd deps
$ tar xzf senna-1.1.2.tar.gz
$ cd senna-1.1.2
$ ./configure
$ make
# make install
$ cd ../..
$ ./configure
$ make
# make install

最後の出力に,「/usr/share/postgresql/8.1/pgsenna2.sql」というのが出てきます.データベースをLudia対応にするためのSQLファイルです.PostgreSQLのバージョンが違えば,パスも違ってきますので,メモしておきます.

MeCabに手入れ

すでにDebianのapt-get installで,mecabmecab-ipadicをインストールしていましたので,ludia-withdepsに入っているmecabは使いません.
なのですが,Debian版のコードはEUC-JPのようなので,UTF-8に変換しておきます.

(# apt-get install mecab mecab-ipadic)
# mkdir /usr/share/mecab/dic/ipadic-utf-8
# /usr/lib/mecab/mecab-dict-index -c utf8 -d /usr/share/mecab/dic/ipadic -o /usr/share/mecab/dic/ipadic-utf-8
# cp /usr/share/mecab/dic/ipadic/dicrc /usr/share/mecab/dic/ipadic-utf-8
# vim /etc/mecabrc
5ji;[Esc]odicdir = /usr/share/mecab/dic/ipadic-utf-8[Esc]ZZ
# grep dicdir /etc/mecabrc
;dicdir = /var/lib/mecab/dic/debian
dicdir = /usr/share/mecab/dic/ipadic-utf-8

上の「5ji;」から始まるところは,Vimのタイプ内容です.「[Esc]」はEscキーを,それ以外はそのまま打てば,もともとの辞書指定はコメントし,新たなパスを設定できます.
動作確認しておきます.要nkfです.

$ echo 'ももから生まれた桃太郎' | nkf --utf8 | mecab
もも    名詞,一般,*,*,*,*,もも,モモ,モモ
から    助詞,格助詞,一般,*,*,*,から,カラ,カラ
生まれ  動詞,自立,*,*,一段,連用形,生まれる,ウマレ,ウマレ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
桃太郎  名詞,固有名詞,一般,*,*,*,桃太郎,モモタロウ,モモタロー
EOS

データベースを作る

データベースを作ります.

$ createdb -U ユーザ名 -O ユーザ名 -E UTF-8 aalwor
$ psql -f /usr/share/postgresql/8.1/pgsenna2.sql aalwor ユーザ名
$ psql aalwor ユーザ名
aalwor=# \q

「/usr/share/postgresql/8.1/pgsenna2.sql」については,Ludiaインストールの最後にメモしたファイル名を書きます.
今回作ったデータベース名「aalwor」は,「asts_as_ludia without rails」の略です.
ここで,RubyなしのLudiaの動作確認をしたいのですが,日記を書く時間の都合で,省略します.

acts_as_ludiaをチェックアウトして,必要なファイルを取り出す

subversion

$ svn co svn://rubyforge.org/var/svn/actsasludia

とすると,actsasludiaというディレクトリができ,そこにいくつかファイルが置かれます.必要なものだけコピーします.

$ cp actsasludia/lib/acts_as_ludia.rb
$ mkdir ludia
$ cp actsasludia/ludia/* ludia

データベースにデータを格納

テーブル作成・破棄,値の挿入,検索のためのRubyスクリプトを書きました(momo.rb).

#!/usr/bin/env ruby

# momo.rb

$KCODE = 'u'

require 'pp'
require 'rubygems'
require 'activerecord'
require 'acts_as_ludia'

def establish_connection
  return  if ActiveRecord::Base.connected?
  ActiveRecord::Base.establish_connection(
                                          :adapter => 'postgresql',
                                          :host => 'localhost',
                                          :username => ユーザ名,
                                          :port => ポート番号(デフォルトは5432), 
                                          :password => '',
                                          :database => 'aalwor'
                                          )
  ActiveRecord::Base.send :include, Ludia::ActsAsLudia
end
establish_connection

class CreateMomo < ActiveRecord::Migration
  def self.up
    create_table :momos do |t|
      t.column :col1, :text
      t.column :col2, :string
    end
    execute(%|CREATE INDEX momos_col1_index ON momos USING fulltext(col1);|)
    execute(%|CREATE INDEX momos_col2_index ON momos USING fulltextb((col2::text));|)
  end

  def self.down
    execute(%|DROP INDEX momos_col1_index;|)
    execute(%|DROP INDEX momos_col2_index;|)
    drop_table :momos
    execute(%|SELECT pgs2destroy();|)
  end
end

class Momo < ActiveRecord::Base
  acts_as_ludia
end

def insert_momo
  Momo.create(:col1 => "すもももももももものうち", :col2 => "あの壺はよいものだ")
  Momo.create(:col1 => "ももから生まれた桃太郎", :col2 => "あの壷はよいものだ")
  Momo.create(:col1 => "ももんが飛んだら木が揺れた", :col2 => "あの壺は悪いものだ")
  Momo.create(:col1 => "あなたももうけ話が聞きたいの", :col2 => "あの壷は悪いものだ")
  Momo.create(:col1 => "昨夜もももの缶詰を開けた", :col2 => "この壷はよいものだ")
  Momo.create(:col1 => "にわにはにわにわとりがいる", :col2 => "この壺はよいものだ")
end

def query_momo
  puts '(test 1)'
  pp Momo.find_fulltext(:col1 => "もも")
  puts '(test 1\')'
  pp Momo.find_by_sql ["select * from momos where col1 @@ ?" , 'もも']
  puts '(test 2)'
  pp Momo.find_fulltext(:col1 => "もも 桃太郎")
  puts '(test 3)'
  pp Momo.find_fulltext(:col2 => ["あの 壺", "悪い"])
  puts '(test 4)'
  pp Momo.find_fulltext(:col1 => "もも", :col2 => "")
  puts '(test 5)'
  pp Momo.find_fulltext({:col1 => "もも", :col2 => ""}, :all => true)
end

if $0 == __FILE__
  require 'optparse'
  OptionParser.new {|opt|
    opt.on('-u', '--up') {CreateMomo.up; exit}
    opt.on('-d', '--down') {CreateMomo.down; exit}
    opt.on('-i', '--insert') {insert_momo; exit}
    opt.on('-r', '--reset') {CreateMomo.down; CreateMomo.up; insert_momo; exit}

    opt.parse!(ARGV)
  }
  query_momo
end

コンテンツやクエリは,Ludia 用の Rails プラグイン acts_as_ludia を作りました - のほほん徒然のを使わせてもらいました.Migrationも大部分が流用ですが,インデックス名が「index1」「index2」だと,Rubyなしの動作確認で作ったインデックス名と衝突してしまったので,「momos_col1_index」「momos_col1_index」に変えています.
要注意なのは「ActiveRecord::Base.send :include, Ludia::ActsAsLudia」です.これがないと,あとでエラーになります.この行は,チェックアウトした中のactsasludia/init.rbに書かれています.

動作確認

$ ruby momo.rb --up
$ ruby momo.rb --insert

までは順調で

$ ruby momo.rb

だと,検索結果が全部カラです.
ludia/searchable.rbの中の

             ssql =~ /^.*=\s*'(.*)'$/
             string = $1

のところで,2つの「'」で挟まれた部分をstringに格納しようとしていますが,ここのパターンマッチに失敗して,stringにnilが代入されているようです.以下のように書き換え,先の「'」の前と,後ろの「'」の後を除去することにしました.

             string = ssql.sub(/^.*?\'/u, '').sub(/\'.*$/u, '')

改めて動作確認.うまくいきました.

$ ruby momo.rb
(test 1)
[#<Momo id: 1, col1: "すもももももももものうち", col2: "あの壺はよいものだ">,
 #<Momo id: 5, col1: "昨夜もももの缶詰を開けた", col2: "この壷はよいものだ">,
 #<Momo id: 2, col1: "ももから生まれた桃太郎", col2: "あの壷はよいものだ">]
(test 1')
[#<Momo id: 1, col1: "すもももももももものうち", col2: "あの壺はよいものだ">,
 #<Momo id: 5, col1: "昨夜もももの缶詰を開けた", col2: "この壷はよいものだ">,
 #<Momo id: 2, col1: "ももから生まれた桃太郎", col2: "あの壷はよいものだ">]
(test 2)
[#<Momo id: 2, col1: "ももから生まれた桃太郎", col2: "あの壷はよいものだ">]
(test 3)
[#<Momo id: 3, col1: "ももんが飛んだら木が揺れた", col2: "あの壺は悪いものだ">,
 #<Momo id: 1, col1: "すもももももももものうち", col2: "あの壺はよいものだ">,
 #<Momo id: 4, col1: "あなたももうけ話が聞きたいの", col2: "あの壷は悪いものだ">]
(test 4)
[#<Momo id: 1, col1: "すもももももももものうち", col2: "あの壺はよいものだ">,
 #<Momo id: 3, col1: "ももんが飛んだら木が揺れた", col2: "あの壺は悪いものだ">]
(test 5)
[#<Momo id: 1, col1: "すもももももももものうち", col2: "あの壺はよいものだ">,
 #<Momo id: 2, col1: "ももから生まれた桃太郎", col2: "あの壷はよいものだ">,
 #<Momo id: 3, col1: "ももんが飛んだら木が揺れた", col2: "あの壺は悪いものだ">,
 #<Momo id: 5, col1: "昨夜もももの缶詰を開けた", col2: "この壷はよいものだ">,
 #<Momo id: 6, col1: "にわにはにわにわとりがいる", col2: "この壺はよいものだ">]

*1:またはpostgres-pr.