締切のある重要な仕事を終えまして,これから,別の仕事に力を入れられそうです.
そこでは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で,mecabとmecab-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をチェックアウトして,必要なファイルを取り出す
$ 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.