Rspec on Railsで、controllerで使うライブラリのテストをする方法

環境

ライブラリ


例えば、includeしたら、before_filterで、params[:price]の末尾に"円"を追加するライブラリのテストを書く場合

# -*- encoding: utf-8 -*-
module TestLib
  # includeされたら、before_filterを追加する
  def self.included(klass)
    klass.class_eval do
      before_filter :add_unit
    end
  end
  # "円"を追加する
  def add_unit
    params['price'] = "#{params['price']}"
  end
end

テスト


テストはこんな感じになる

# -*- encoding: utf-8 -*-
require File.dirname(__FILE__) + '/../spec_helper'

# テスト用のコントローラ
class TestLibController < ApplicationController
  include TestLib
  def index
    response.content_type = 'text/html'
    render :text => params['price']
  end
end

# :type => :controller でcontrollerのspec_helperを使えるようにする
# * controllerディレクトリのテストの場合は勝手にやってくれる
describe TestLibController, :type => :controller do
  before :all do
    # テスト用のroutesを追加
    AppLibTest::Application.routes.draw do
      match 'index', :to => 'test_lib#index'
    end
  end

  # routesの初期化
  after(:all){ Rails.application.reload_routes! }

  subject do
    get :index, :price => '100'
    response
  end
  it{ subject.should be_success }
  its(:body){ '100円' }
end

ポイント

  • テストを実行するためのコントローラを作る
  • describeで明示的に、コントローラのテストであることを、rspec側に教える
  • before :all で、Application.routesでテスト用コントローラのルーティングを切る
  • after :all で、ルーティングを初期化する


gistにコード上げてみた

HomebrewでMemcachedのインストール

インストール

$ brew install memcached
==> Installing memcached dependency: libevent
==> Downloading https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
######################################################################## 100.0%
==> ./configure --disable-debug-mode --prefix=/usr/local/Cellar/libevent/2.0.21
==> make
==> make install
/usr/local/Cellar/libevent/2.0.21: 48 files, 1.9M, built in 77 seconds
==> Installing memcached
==> Downloading http://memcached.googlecode.com/files/memcached-1.4.15.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/memcached/1.4.15 --disable-coverage
==> make install
==> Caveats
To have launchd start memcached at login:
    ln -sfv /usr/local/opt/memcached/*.plist ~/Library/LaunchAgents
Then to load memcached now:
    launchctl load ~/Library/LaunchAgents/homebrew.mxcl.memcached.plist
Or, if you don't want/need launchctl, you can just run:
    /usr/local/opt/memcached/bin/memcached
/usr/local/Cellar/memcached/1.4.15: 10 files, 180K, built in 17 seconds

起動テスト

$ /usr/local/opt/memcached/bin/memcached

接続確認


接続して、ステータスをチェック
その後、値の出し入れをしてみる

$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
stats 
STAT pid 20725
STAT uptime 25
STAT time 1357612193
STAT version 1.4.15
STAT libevent 2.0.21-stable
STAT pointer_size 64
STAT rusage_user 0.001140
STAT rusage_system 0.003183
STAT curr_connections 10
STAT total_connections 11
STAT connection_structures 11
STAT reserved_fds 20
STAT cmd_get 0
STAT cmd_set 0
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 0
STAT get_misses 0
STAT delete_misses 0
STAT delete_hits 0
STAT incr_misses 0
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 7
STAT bytes_written 0
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT bytes 0
STAT curr_items 0
STAT total_items 0
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 0
END
set foo 0 0 3
123
STORED
get foo
VALUE foo 0 3
123
END
quit
Connection closed by foreign host.

launchctlの設定


この辺はHomebrewでインストールした時に出てきたメッセージに沿っていくだけ。簡単。

$ ln -sfv /usr/local/opt/memcached/*.plist ~/Library/LaunchAgents
/Users/kobayashi/Library/LaunchAgents/homebrew.mxcl.memcached.plist -> /usr/local/opt/memcached/homebrew.mxcl.memcached.plist
$  ll ~/Library/LaunchAgents
lrwxr-xr-x 1 kobayashi staff 54 1 8 11:31 homebrew.mxcl.memcached.plist@ -> /usr/local/opt/memcached/homebrew.mxcl.memcached.plist

起動していることを確認

$ ps aux | grep memcached
kobayashi 21392 0.0 0.0 2438616 940 ?? S 11:31AM 0:00.00 /usr/local/opt/memcached/bin/memcached -l localhost

SnowLeopardにhomebrewで、MySQLにmysqlftppc mecab pluginをICU対応で入れようとして頓挫した話


MySQLmecabのインストールまでは、macportsからHomebrewに乗り換えてMySQL5.1をインストールするまでの流れ 参照

mysqlftppc mecab pluginって何?


MySQLでFULLTEXTインデックスをパースする時に、mecabを使うようにするプラグイン

FULLTEXTインデックスって何?


MySQLで使える全文検索機能用のインデックス
like検索より早い
内部的にはパーサを使って文章を分割して、インデックス用のデータを作っている
標準のパーサだと、カンマや空白区切りでパースするので、日本語の検索に向かない

FULLTEXT パーサは特定の区切り文字を見て、語の頭と最後を定義します。
その例には、‘ ’ ( スペース ) , ‘,’ ( カンマ ) , そして ‘.’ ( ピリオド ) があります。
単語が非区切り文字 ( 例えば中国語 ) で区切られている場合は、FULLTEXT パーサは単語の最初と最後を定義することができません。
単語や、インデックスのついた他の表現をそのような言語で FULLTEXT インデックスに加えるには、事前に処理して ‘"’ などの任意の区切り文字で区切る必要があります。 


なので、FULLTEXTインデックスを作るときのパーサーをプラグインで拡張する必要がある
上記mysqlftppc mecab pluginは、mecabを使って文章を解析してFULLTEXTインデックスを作ってくれるプラグイン

mecabのインストール

$ brew install mecab

mecab用の辞書 naist-jdic をインストール


mecabは標準では、IPAdicという辞書を使うような設定になっているのだけど、
IPAdicはライセンス上の問題があったりするので、IPAdicの改良版のnaist-jdic を入れるのが良いらしい

$ brew create "http://sourceforge.jp/frs/redir.php?m=iij&f=%2Fnaist-jdic%2F48487%2Fmecab-naist-jdic-0.6.3-20100801.tar.gz"
$ cd /usr/local/Library/Formula
# すごい名前になるのでmv
$ mv redir.php\?m=iij\&f=%2fnaist-jdic%2f48487%2fmecab-naist-jdic.rb mecab-naist-jdic.rb
# コンパイル時に、/etc/mecab が必要なので作っておく
$ sudo mkdir /etc/mecab
$ sudo chown kobayashi /etc/mecab
$ vim mecab-naist-jdic.rb

一部設定を編集

require 'formula'

# Documentation: https://github.com/mxcl/homebrew/wiki/Formula-Cookbook
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!

# class名も修正
class MecabNaistJdic < Formula
  homepage ''
  url 'http://sourceforge.jp/frs/redir.php?m=iij&f=%2Fnaist-jdic%2F48487%2Fmecab-naist-jdic-0.6.3-20100801.tar.gz'
  version '0.6.3-20100801'
  homepage 'http://sourceforge.jp/projects/naist-jdic/' #追加した
  sha1 '86ccbb15d208a99d81ebcef9f31aee384cc7b31e'

  # depends_on 'cmake' => :build
  depends_on :x11 # if your formula requires any X11/XQuartz components

  def install
    # ENV.j1  # if your formula's build system can't parallelize
    system "./configure", "--disable-debug", "--disable-dependency-tracking",
                          "--prefix=#{prefix}", "--with-charset=utf8" # utf8を追加
    # system "cmake", ".", *std_cmake_args
    system "make install" # if this fails, try separate make/make install steps
  end

  def test
    # This test will fail and we won't accept that! It's enough to just replace
    # "false" with the main program this formula installs, but it'd be nice if you
    # were more thorough. Run the test with `brew test redir.php?m=iij&f=%2Fnaist-jdic%2F48487%2Fmecab-naist-jdic`.
    system "false"
  end
end
$ brew install mecab-naist-jdic
辞書の設定を更新


mecab-naist-jdicを使うように設定ファイルを更新

vim /usr/local/Cellar/mecab/0.994/etc/mecabrc
;dicdir =  /usr/local/Cellar/mecab/0.994/lib/mecab/dic/ipadic
dicdir =  /usr/local/Cellar/mecab/0.994/lib/mecab/dic/naist-jdic
動作確認


ここまではOK

$ echo "東急東横線" | mecab
東急東横        名詞,固有名詞,一般,*,*,*,東急東横,トウキュウトウヨコ,トーキュートーヨコ,,
線      名詞,接尾,一般,*,*,*,線,セン,セン,,
EOS

icu4cをインストール

$ brew install icu4c
icuって?


ICUUnicode国際化コンポーネント
4cは、c++,c 向けのICUのライブラリらしい
これをmysqlftppc-mecabに組み込むことでUnicode正規化に対応できるらしい
Unicode正規化とは? → http://sourceforge.net/apps/mediawiki/mysqlftppc/index.php?title=Home-j#Unicode_.E6.AD.A3.E8.A6.8F.E5.8C.96
全角と半角文字の比較とかができるようになるらしい → 検索精度が上がる

mysqlftppc-mecabをインストール

$ brew create http://sourceforge.net/projects/mysqlftppc/files/mysqlftppc/1.6.1/mysqlftppc-mecab-1.6.1.tar.gz/download?use_mirror=jaist
$ cd /usr/local/Library/Formula
$ mv download\?use.rb mysqlftppc-mecab.rb
$ vim mysqlftppc-mecab.rb
require 'formula'

# Documentation: https://github.com/mxcl/homebrew/wiki/Formula-Cookbook
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!

# class名も修正
class MysqlftppcMecab < Formula
  homepage ''
  url 'http://sourceforge.net/projects/mysqlftppc/files/mysqlftppc/1.6.1/mysqlftppc-mecab-1.6.1.tar.gz/download?use_mirror=jaist'
  version '1.6.1'
  sha1 'dbccb787f4f6f588481e0b0623993cec5ba0f106'

  # depends_on 'cmake' => :build
  depends_on :x11 # if your formula requires any X11/XQuartz components

  def install
    # ENV.j1  # if your formula's build system can't parallelize

    system "./configure", "--disable-debug", "--disable-dependency-tracking",
                          "--prefix=#{prefix}"
                          "--enable-utf8-only",
                          # icuを使うように設定
                          "--with-icu-config=/usr/local/Cellar/icu4c/50.1/bin/icu-config"
    # system "cmake", ".", *std_cmake_args
    system "make install" # if this fails, try separate make/make install steps
  end

  def test
    # This test will fail and we won't accept that! It's enough to just replace
    # "false" with the main program this formula installs, but it'd be nice if you
    # were more thorough. Run the test with `brew test download?use`.
    system "false"
  end
end
インストール
$ brew install mysqlftppc-mecab
==> Downloading http://sourceforge.net/projects/mysqlftppc/files/mysqlftppc/1.6.1/mysqlftppc-mecab-1.6.1.tar.gz/download?use_mirror=jaist
Already downloaded: /Library/Caches/Homebrew/mysqlftppc-mecab-1.6.1
==> ./configure --prefix=/usr/local/Cellar/mysqlftppc-mecab/1.6.1 --enable-utf8-only --with-icu-dir=/usr/local/Cellar/icu4c/50.1
==> make install
i686-apple-darwin10-gcc-4.2.1: c: No such file or directory
i686-apple-darwin10-gcc-4.2.1: c: No such file or directory
make: *** [libftmecab_la-ftnorm.lo] Error 1
make: *** Waiting for unfinished jobs....
make: *** [libftmecab_la-ftbool.lo] Error 1

READ THIS: https://github.com/mxcl/homebrew/wiki/troubleshooting


エラーが発生

64bitを明示的に指定して手動でコンパイル
tar-ball をダウンロードした場合は展開した後、configureスクリプトを実行してください。
mysqld_config が見つからない場合は、--mysqld-config=/path/to/mysqld_config 引数をつけてください。
mysqld 自体が 64bit でビルドされていたり、debug=full でコンパイルされている場合は、適宜適切な CFLAGS を渡してください。


と、あるので明示的に64bitでコンパイルしてみる

$ make clean
$ CFLAGS="-m64" ./configure --disable-debug --disable-dependency-tracking --enable-utf8-only --with-icu-config=/usr/local/Cellar/icu4c/50.1/bin/icu-config
$ make
$ make install

make/bin/sh ./libtool --tag=CC --mode=compile gcc -DPACKAGE_NAME=\"plugin_mecab\" -DPACKAGE_TARNAME=\"plugin_mecab\" -DPACKAGE_VERSION=\"1.6.1\" -DPACKAGE_STRING=\"plugin_mecab\ 1.6.1\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"plugin_mecab\" -DVERSION=\"1.6.1\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1  -I. -I. -I./include -I./include -I/usr/local/Cellar/mysql51/5.1.67/include/mysql  -I/usr/local/Cellar/mecab/0.994/include -F/usr/local/Frameworks     -I/usr/local/Cellar/icu4c/50.1/include \c -DMYSQL_DYNAMIC_PLUGIN -m64 -DHAVE_ICU -c -o libftmecab_la-ftnorm.lo `test -f 'ftnorm.c' || echo './'`ftnorm.c
mkdir .libs
 gcc -DPACKAGE_NAME=\"plugin_mecab\" -DPACKAGE_TARNAME=\"plugin_mecab\" -DPACKAGE_VERSION=\"1.6.1\" "-DPACKAGE_STRING=\"plugin_mecab 1.6.1\"" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"plugin_mecab\" -DVERSION=\"1.6.1\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 -I. -I. -I./include -I./include -I/usr/local/Cellar/mysql51/5.1.67/include/mysql -I/usr/local/Cellar/mecab/0.994/include -F/usr/local/Frameworks -I/usr/local/Cellar/icu4c/50.1/include c -DMYSQL_DYNAMIC_PLUGIN -m64 -DHAVE_ICU -c ftnorm.c  -fno-common -DPIC -o .libs/libftmecab_la-ftnorm.o
i686-apple-darwin10-gcc-4.2.1: c: No such file or directory
In file included from /usr/local/Cellar/mysql51/5.1.67/include/mysql/my_global.h:86,
                 from ftnorm.c:2:
/usr/local/Cellar/mysql51/5.1.67/include/mysql/my_config.h:1167:1: warning: "PACKAGE" redefined
<command-line>: warning: this is the location of the previous definition
/usr/local/Cellar/mysql51/5.1.67/include/mysql/my_config.h:1173:1: warning: "PACKAGE_NAME" redefined
<command-line>: warning: this is the location of the previous definition
/usr/local/Cellar/mysql51/5.1.67/include/mysql/my_config.h:1176:1: warning: "PACKAGE_STRING" redefined
<command-line>: warning: this is the location of the previous definition
/usr/local/Cellar/mysql51/5.1.67/include/mysql/my_config.h:1179:1: warning: "PACKAGE_TARNAME" redefined
<command-line>: warning: this is the location of the previous definition
/usr/local/Cellar/mysql51/5.1.67/include/mysql/my_config.h:1182:1: warning: "PACKAGE_VERSION" redefined
<command-line>: warning: this is the location of the previous definition
/usr/local/Cellar/mysql51/5.1.67/include/mysql/my_config.h:1298:1: warning: "VERSION" redefined
<command-line>: warning: this is the location of the previous definition
make: *** [libftmecab_la-ftnorm.lo] Error 1


やっぱりエラー

諦めて、icuを外してみる
$ make clean
$ CFLAGS="-m64" ./configure --disable-debug --disable-dependency-tracking --enable-utf8-only
$ make
$ make install
make[1]: Nothing to be done for `install-exec-am'.
test -z "/usr/local/Cellar/mysql51/5.1.67/lib/mysql/plugin" || /Users/kobayashi/tmp/mysqlftppc-mecab/mysqlftppc-mecab-1.6.1/install-sh -d "/usr/local/Cellar/mysql51/5.1.67/lib/mysql/plugin"
 /bin/sh ./libtool --mode=install /usr/bin/install -c  'libftmecab.la' '/usr/local/Cellar/mysql51/5.1.67/lib/mysql/plugin/libftmecab.la'
/usr/bin/install -c .libs/libftmecab.0.0.0.so /usr/local/Cellar/mysql51/5.1.67/lib/mysql/plugin/libftmecab.0.0.0.so
(cd /usr/local/Cellar/mysql51/5.1.67/lib/mysql/plugin && { ln -s -f libftmecab.0.0.0.so libftmecab.0.so || { rm -f libftmecab.0.so && ln -s libftmecab.0.0.0.so libftmecab.0.so; }; })
(cd /usr/local/Cellar/mysql51/5.1.67/lib/mysql/plugin && { ln -s -f libftmecab.0.0.0.so libftmecab.so || { rm -f libftmecab.so && ln -s libftmecab.0.0.0.so libftmecab.so; }; })
/usr/bin/install -c .libs/libftmecab.lai /usr/local/Cellar/mysql51/5.1.67/lib/mysql/plugin/libftmecab.la
----------------------------------------------------------------------
Libraries have been installed in:
   /usr/local/Cellar/mysql51/5.1.67/lib/mysql/plugin

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the `DYLD_LIBRARY_PATH' environment variable
     during execution

See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.
----------------------------------------------------------------------


あっさり成功


必要なファイルが作られていることを確認

$ ll /usr/local/Cellar/mysql51/5.1.67/lib/mysql/plugin | grep ftm
-rwxr-xr-x  1 kobayashi  admin    20392  1  7 18:58 libftmecab.0.0.0.so*
lrwxr-xr-x  1 kobayashi  admin       19  1  7 18:58 libftmecab.0.so@ -> libftmecab.0.0.0.so
-rwxr-xr-x  1 kobayashi  admin      890  1  7 18:58 libftmecab.la*
lrwxr-xr-x  1 kobayashi  admin       19  1  7 18:58 libftmecab.so@ -> libftmecab.0.0.0.so


MySQLプラグインをインストールする

mysql> INSTALL PLUGIN mecab SONAME 'libftmecab.so';
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW STATUS LIKE "Mecab_info";
+---------------+-------------------------------+
| Variable_name | Value                         |
+---------------+-------------------------------+
| Mecab_info    | with mecab 0.994, without ICU |
+---------------+-------------------------------+
1 row in set (0.00 sec)


ICUが入っていないmecabプラグインが追加されたことを確認

参考


まめ畑

  • ここだと明示的にicu-configの場所を指定してない。自前の環境だとwithoutICUになる...


MySQL full-text parser plugin collectionのmecab pluginを入れる - kouheiの日記

  • エラーの内容が似ている。後日調査


PHP で MySQL FULLTEXT + MeCab で簡単に全文検索を実装する | ウェブル

  • FULLTEXTインデックスの説明や、MeCebを選択する理由などがわかりやすくまとめられている

チラシの裏


以下うまくいかない原因の考察

SnowLeopardでは、32bitアプリと64bitアプリが混在しているけどこの辺が悪さをしている?


例えば、icuを32bitでしか作ってなくて、mysqlftppcを64bitでコンパイルしようとしたためにエラーが出たとか
Homebrewには、ユニバーサルバイナリでmakeするか否かを決める環境変数を設定するメソッドがある


Library/Homebrew/extend/ENV.rb

  # i386 and x86_64 (no PPC)
  def universal_binary
    append_to_cflags '-arch i386 -arch x86_64'
    replace_in_cflags '-O4', '-O3' # O4 seems to cause the build to fail
    append 'LDFLAGS', '-arch i386 -arch x86_64'

    unless compiler == :clang
      # Can't mix "-march" for a 32-bit CPU  with "-arch x86_64"
      replace_in_cflags(/-march=\S*/, '-Xarch_i386 \0') if Hardware.is_32_bit?
    end
  end


で、MySQLとか、icu4cのFormulaでは、ユニバーサルバイナリコンパイルする設定が書かれている

  def install
    # Make universal for bindings to universal applications
    ENV.universal_binary if build.universal?


だけど、mysqlftppc-mecabは自作の為そのような設定が無いのでそれが原因かなぁ... と思ったけど
そもそも、build.universal? がfalseを返していて64bit版を作っていたので違った


Lionだと特に躓くことも無くあっさりICU版がインストールできた


なので、SnowLeopard環境に何かしらの原因があるはずだが...

mysql> SHOW STATUS LIKE "Mecab_info";
+---------------+-----------------------------------------+
| Variable_name | Value                                   |
+---------------+-----------------------------------------+
| Mecab_info    | with mecab 0.994, ICU 50.1(Unicode 6.2) |
+---------------+-----------------------------------------+
1 row in set (0.01 sec)


そんな煮え切らない感じで



mecabにユーザ辞書を追加して、MySQL5.1のフルテキストインデックスを更新するまでの話

環境

  • MySQL5.1
  • Homebrewでmecabをインストール済
  • DBに以下のようなテーブルを追加してある
CREATE TABLE stations (name CHAR(255), info TEXT, FULLTEXT(info) WITH PARSER mecab);
mysql> CREATE TABLE stations (name CHAR(255), info TEXT, FULLTEXT(info) WITH PARSER mecab);
Query OK, 0 rows affected (0.01 sec)

mysql> desc stations;
+-------+-----------+------+-----+---------+-------+
| Field | Type      | Null | Key | Default | Extra |
+-------+-----------+------+-----+---------+-------+
| name  | char(255) | YES  |     | NULL    |       |
| info  | text      | YES  | MUL | NULL    |       |
+-------+-----------+------+-----+---------+-------+
2 rows in set (0.00 sec)
    • 中身は、駅名とwikipediaから取ってきた適当なテキスト

駅名を正しく認識してくれない問題


標準の辞書では駅名を正しく形態素解析してくれない

$ echo "愛甲石田" | mecab
愛甲    名詞,固有名詞,人名,姓,*,*,愛甲,アイコウ,アイコー,,
石田    名詞,固有名詞,地域,一般,*,*,石田,イシダ,イシダ,,
EOS


なので、MySQL全文検索結果もおかしい

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("愛甲石田駅")\G
*************************** 1. row ***************************
name: 愛甲石田駅
info:  愛甲石田駅 北口(2008713日) あいこういしだ - Aik&#333;-Ishida &#9668;本厚木 (3.1km) (3.7km) 伊勢原&#9658; 所在地 神奈川県厚木市愛甲1丁目11号 (一部は同県伊勢原市石田) 北緯3525分3.5秒 東経1392038秒座標: 北緯3525分3.5秒 東経1392038秒 所属事業者 ■小田急電鉄 所属路線 小田原線 キロ程 48.5km(新宿起点) 駅構造 地上駅(橋上駅) ホーム 22線 乗降人員 -統計年度- 47,052人/日 -2011年- 開業年月日 1927年(昭和2年)41日 この表について この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 
*************************** 2. row ***************************
name: 芦沢駅
info:  芦沢駅 駅舎(20055月) あしさわ - Ashisawa &#9668;北大石田 (2.9km) (6.6km) 舟形&#9658; 所在地 山形県尾花沢市大字芦沢1012 北緯3839分19.76秒 東経14021分39.83秒 所属事業者 東日本旅客鉄道(JR東日本) 所属路線 ■奥羽本線(山形線) キロ程 133.7km(福島起点) 電報略号 アハ 駅構造 地上駅 ホーム 22線 乗車人員 -統計年度- 66人/日(降車客含まず) -2011年- 開業年月日 1916年(大正5年)121日 備考 簡易委託駅 この表について この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 
2 rows in set (0.00 sec)


特に隣接駅でもない芦沢駅が出てくる


なので、ユーザ辞書に駅名を追加して、全文検索の結果を正しくする

  • ちなみにブーリアンモードならば正しい結果が出るのだけど、速度的に問題があるので今回は見送り

ユーザ辞書の作成


以下のスクリプトでユーザ辞書生成に必要な形式のcsvファイルを作成する

  • 01.csvは、駅名と本文が入ったcsvファイル


このコードには不具合があるので、下の「追記」も参照してください

# -*- encoding: utf-8 -*-
require 'rubygems'
require 'csv'


CSV.open('./02.csv', 'w') do |csv_w|
  CSV.open('./01.csv', 'r:UTF-8').each do |csv|
    name = csv.first
    csv_w << [name.gsub(/$/, ''), nil, nil, 10, '名詞', '一般', '*', '*', '*', '*', 'ユーザ設定', 'ユーザセッテイ', 'ユーザセッテイ', '追加エントリ']
  end
end

こんなのが生成される

あいの里公園,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あいの里教育大,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あおば通,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あかおか,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あかぢ,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あきた白神,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あさぎり,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あざみ野,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あつみ温泉,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あぶくま,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あまや,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あやめ公園,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あわくら温泉,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あわら湯のまち,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
...
2012/01/11 追記


上記のようにコストを0に固定していると辞書の単語を含む長文の単語が形態素解析されない問題がある
例えば辞書に「循環器」「内科」「循環器内科」がある時に「循環器内科」を形態素解析すると、

$ echo "循環器内科" | mecab
循環器 名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
内科 名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
EOS

のように、「循環器内科」で形態素解析されない


なので、以下のように、長文の方のコストを低くしてあげる必要がある

  result.each do |name|
     cost = -400 * name.split(//u).size ** 1.5
     cost = -36000 if cost < -36000
     csv<< [name, nil, nil, cost, '名詞', '一般', '*', '*', '*', '*', 'ユーザ設定', 'ユーザセッテイ', 'ユーザセッテイ', '追加エントリ']
  end

ユーザ辞書のコンパイル


csvで生成した辞書ファイルをコンパイルする

$ /usr/local/Cellar/mecab/0.994/libexec/mecab/mecab-dict-index -d /usr/local/Cellar/mecab/0.994/lib/mecab/dic/naist-jdic/ -u usr.dic -f utf8 -t utf8 02.csv
reading 02.csv ... 479
emitting double-array: 100% |###########################################|

done!
  • mecab-dict-indexが辞書
  • -d オプションで使用する辞書を設定。今回の環境ではNAIST-jdicを使っているのでそっちを指定
  • その他のオプションは参考リンクを参照

コンパイルした辞書を使うように設定


生成した辞書を適当な場所に移動

$ cp usr.dic /usr/local/lib/mecab/dic/.


設定ファイルを更新

$ vim /usr/local/Cellar/mecab/0.994/etc/mecabrc


ユーザ辞書のpathを設定する

userdic = /usr/local/lib/mecab/dic/usr.dic

動作確認

$ echo "愛甲石田" | mecab              
愛甲石田        名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
EOS


ちなみにmecabのユーザ辞書設定後、MySQLを更新するまでの間は正しい検索は期待できない様子

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("愛甲石田駅")\G
Empty set (0.01 sec)

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST('+"愛甲石田駅"' IN BOOLEAN MODE)\G
Empty set (0.00 sec)

フルテキストインデックスの更新

mysql> REPAIR TABLE stations QUICK;
+---------------+--------+----------+----------+
| Table         | Op     | Msg_type | Msg_text |
+---------------+--------+----------+----------+
| test.stations | repair | status   | OK       |
+---------------+--------+----------+----------+
1 row in set (0.16 sec)

MySQLの動作確認


芦沢駅がヒットしなくなることを確認

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("愛甲石田駅")\G
*************************** 1. row ***************************
name: 愛甲石田駅
info:  愛甲石田駅 北口(2008713日) あいこういしだ - Aik&#333;-Ishida &#9668;本厚木 (3.1km) (3.7km) 伊勢原&#9658; 所在地 神奈川県厚木市愛甲1丁目11号 (一部は同県伊勢原市石田) 北緯35253.5秒 東経1392038秒座標: 北緯35253.5秒 東経1392038秒 所属事業者 ■小田急電鉄 所属路線 小田原線 キロ程 48.5km(新宿起点) 駅構造 地上駅(橋上駅) ホーム 22線 乗降人員 -統計年度- 47,052人/日 -2011年- 開業年月日 1927年(昭和2年)41日 この表について この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 
1 row in set (0.00 sec)

ユーザ辞書追加後、フルテキストインデックスが更新されるまでの挙動の考察


以下チラシの裏


愛甲石田は検索できなくなった

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("愛甲石田駅")\G
Empty set (0.01 sec)

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST('+"愛甲石田駅"' IN BOOLEAN MODE)\G
Empty set (0.00 sec)


ただし、元々固有名詞として存在している名詞の検索は大丈夫だった

echo "あざみ野" | mecab
あざみ野        名詞,固有名詞,一般,*,*,*,あざみ野,アザミノ,アザミノ,,
EOS
mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("あざみ野")\G
*************************** 1. row ***************************
name: あざみ野駅
info:  あざみ野駅 田園都市線あざみ野駅西口(201111月) あざみの - Azamino 所在地 横浜市青葉区あざみ野二丁目 所属事業者 東京急行電鉄(駅詳細) 横浜市交通局(駅詳細) この表につ いて この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 東急 あざみ野駅 あざみの - Azamino &#9668;たまプラーザ (1.1km) (1.1km) 江田&#9658; 所在地 横浜 市青葉区あざみ野二丁目1番地1 北緯35347.4秒 東経1393312.8秒座標: 北緯35347.4秒 東経1393312.8秒 駅番号 &#160;DT 16&#160; 所属事業者 東京急行電鉄(東急) 所属路線 ■田園都市線 キロ程 18.2km(渋谷起点) 駅構造 高架駅 ホーム 相対式 22線 乗降人員 -統計年度- 128,986人/日 -2011年- 開業年月日 1977年(昭和52年)525日 この表について この表はテンプレートを 用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 横浜市交通局 あざみ野駅 あざみの - Azamino &#9668;B31 中川 (1.5km) 所在地 北緯35345.1秒 東経1393312.5秒 駅番号 ○B32 所属事業者 横浜市交通局(横浜市営地下鉄) 所属路線 ■ブルーライン(3号線) キロ程 20.7km(関内*起点) 湘南台から40.4km 駅構造 地下駅 ホーム 12線 乗車人員 -統計年度- 38,897人/日(降車客含まず) -2010年- 開業年月日 1993年(平成5年)318日 備考 *3号線の起点として この表について この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情 報をごらんください。
1 row in set (0.00 sec)
全文検索にヒットしなくなるワードの条件


多分、以下のようなワードだと思われる

  • ユーザ辞書で追加された単語を含むワードの中で
  • 今までと違う形で形態素解析が行われるようになったワード
愛甲石田」で検索した場合


以下のような処理になっていると思う

  • 愛甲石田」を「愛甲」「石田」に形態素解析
  • フルテキストインデックスの中に「愛甲」や「石田」を持つものを探して関連度が高い順に表示


なので、ユーザ辞書に「愛甲石田」が追加されると、
形態素解析、「愛甲石田」でフルテキストインデックスを探しに行く為、1件もヒットしないのではないか?

「あざみ野」で検索した場合


ユーザ辞書追加前から「あざみ野」で形態素解析されているので、きちんとヒットするのだと思う

ブーリアンモードでも検索出来ない理由


よくわからないけど、内部的に形態素解析されたフルテキストインデックスを使ってるはずだから、
あるはずの「愛甲石田」を探している為に0件になるのかな?

rubyでRailsなしでActiveRecordを使う

DBの環境


Homebrewで構築した、ローカルのMySQL5.1


テーブルはこんな感じのを用意

CREATE TABLE stations (name CHAR(255), info TEXT, FULLTEXT(info) WITH PARSER mecab);
mysql> CREATE TABLE stations (name CHAR(255), info TEXT, FULLTEXT(info) WITH PARSER mecab);
Query OK, 0 rows affected (0.01 sec)

mysql> desc stations;
+-------+-----------+------+-----+---------+-------+
| Field | Type      | Null | Key | Default | Extra |
+-------+-----------+------+-----+---------+-------+
| name  | char(255) | YES  |     | NULL    |       |
| info  | text      | YES  | MUL | NULL    |       |
+-------+-----------+------+-----+---------+-------+
2 rows in set (0.00 sec)

使い方

# -*- encoding: utf-8 -*-
require 'rubygems'
require 'active_record'

#DBに接続
ActiveRecord::Base.establish_connection(
            :adapter  => 'mysql2',
            :username => 'root',
            :password => '********',
            :host     => 'localhost',
            :database => 'test',
            :encoding => 'utf8'
        )

# テーブル用のクラスを用意
class Station < ActiveRecord::Base
end

# 1個データを作ってsaveしてみる
station = Station.new
station.name = '入間市駅'
station.info = '解説文'
station.save

# 検索
Station.find(:all).each{|s|
  puts s.name # "入間市駅" が出力される
}

ハマったこと

adapterをmysqlにしていたらエラーになった
`rescue in mysql_connection': !!! Missing the mysql2 gem. Add it to your Gemfile: gem 'mysql2' (RuntimeError)


Rails3では、mysql2をアダブターにしているので修正する

mysql2をインストールする時に警告が発生する
$ gem install mysql2

WARNING: This version of mysql2 (0.3.11) doesn't ship with the ActiveRecord adapter bundled anymore as it's now part of Rails 3.1
WARNING: Please use the 0.2.x releases if you plan on using it in Rails <= 3.0.x

ActiveRecordでは、0.2.x を入れる必要があるらしい


バージョンを指定して、mysql2 を入れる

$ gem install mysql2 -v 0.2.6

Homebrewで入れたMySQL5.1でutf-8を使えるようにする設定


クライアント、サーバ側それぞれをutf8にする

方法


~/.my.cnf にクライアント側の設定を追加

[client]
default-character-set = utf8


/usr/local/var/mysql/my.cnf にサーバ側の設定を追加

[mysqld]
character-set-server=utf8

サーバの再起動

mysql.server restart

MySQLでのキャラクタ設定の確認方法

mysql> show variables like 'char%';
+--------------------------+--------------------------------------------------------+
| Variable_name            | Value                                                  |
+--------------------------+--------------------------------------------------------+
| character_set_client     | utf8                                                   |
| character_set_connection | utf8                                                   |
| character_set_database   | utf8                                                   |
| character_set_filesystem | binary                                                 |
| character_set_results    | utf8                                                   |
| character_set_server     | utf8                                                   |
| character_set_system     | utf8                                                   |
| character_sets_dir       | /usr/local/Cellar/mysql51/5.1.67/share/mysql/charsets/ |
+--------------------------+--------------------------------------------------------+
8 rows in set (0.00 sec)


変数の意味はリファレンス参照

テーブルの文字コードの確認方法

mysql> CREATE TABLE stations (name CHAR(255), info TEXT, FULLTEXT(info) WITH PARSER mecab);
mysql> mysql> SHOW CREATE TABLE stations\G
*************************** 1. row ***************************
       Table: stations
Create Table: CREATE TABLE `stations` (
  `name` char(255) DEFAULT NULL,
  `info` text,
  FULLTEXT KEY `info` (`info`) /*!50100 WITH PARSER `mecab` */ 
) ENGINE=MyISAM DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

メモ


サーバ側に skip-character-set-client-handshake を設定して、クライアント側の文字コードの設定を自動的にサーバに合わせる方法もあるけど、セキュリティ的に問題があるらしい

Homebrewで入れたMySQL5.1を停止できない件の対策

現象


MySQLを停止しようとするとエラーがでる

$ mysql.server stop
Shutting down MySQL
....... ERROR! Manager of pid-file quit without updating file.
 ERROR! Failed to stop running server, so refusing to try to start.

原因


Homebrewで作られたmysqlのplistの設定で、死なないようになってる

  • その為、停止処理がまだ生きているプロセスを発見してエラーを出す

対策


plistのKeepAliveの値をtrueから、falseに修正する

$ vim ~/Library/LaunchAgents/com.mysql.mysqld.plist
  <key>KeepAlive</key>
  <false/>


KeepAliveは、そのプロセスを常に起動した状態にするか否かを設定するオプション

KeepAlive <boolean or dictionary of stuff>
     This optional key is used to control whether your job is to be kept continuously running or to let
     demand and conditions control the invocation.


変更後、plistを再読込させる

$ launchctl unload -w ~/Library/LaunchAgents/com.mysql.mysqld.plist
$ launchctl load -w ~/Library/LaunchAgents/com.mysql.mysqld.plist


無事終了/起動できるようになった

$ mysql.server stop                                             
Shutting down MySQL
... SUCCESS!
$ mysql.server start
Starting MySQL
. SUCCESS!
$ mysql.server restart
Shutting down MySQL
.. SUCCESS!
Starting MySQL
. SUCCESS!