検索機能の実装時に全文検索を実装する為、Like検索ではなくMySQLのN-gramを用いて実装を行いました。
実装はスムーズにできたのですが、minitest実行時にハマったのでよければ参考にしてください。
N-gram自体が何なのかについては、下記の記事が非常に分かりやすく記載して下さっていたので、参考にしてみてください。
MySQLでの全文検索(N-gram編)
##実装方法
MySQLで全文検索機能ができるように設定
N-gram自体の実装については、検索時に取得したいカラム名に対して、
FULLTEXT INDEXを貼り、パーサーにN-gramを指定するといった具合です。
※パーサーとはデータベースが持つ機能の1つで、SQL文の解析に用いられる際に使用されるそうです。
ALTER TABLE content ADD FULLTEXT INDEX ngram_idx (content_name) WITH PARSER ngram;
Rails側での全文検索の記述
INDEXを貼り終えたら、Railsのコントローラー側で実装していきます。
下記のように、Railsで記法が用意されていないので、生SQLで書く必要があります。
MATCH()には検索したいカラム名・AGAINST(?)には引数として後ろに記載されているword(検索フォームで入力した値)が入ります。
Content.where('MATCH(content_name) AGAINST(❔)',word)
##問題点
上記のように実装自体は簡単にできるのですが、Railsのminitestを実行した際に問題が発生しました。
いくらテストを流しても想定している値が取得できないのです...
用意しているymlファイルにデータ上おかしな点はないか・このSQLの構文自体がそもそも間違っていないか等色々検証を重ねましたが解決には至らず。
自力では解決できなかったので、周りの先輩社員に相談してみたところ
インデックスはcommitのタイミングで生成される為、現行の書き方ではインデックスにヒットしないのではないかと。
現行の書き方ではsetup doの中に使用するymlファイルとモデルを記載していました。
setup do
ActiveRecord::FixtureSet.reset_cache
ActiveRecord::FixtureSet.create_fixtures('test/fixtures/hoge', [:content],
{:content => Content}, ContentDbInfo)
end
このsetupはrunという関数から呼び出され、テストを回したと同時に用意したデータをテーブルの中にInsertします。
この際トランザクションが貼られますが、commitはされずテストを回し終えると自動でロールバックされる仕組みになっています。
その為インデックスが生成されていない状態で検索を掛けにいってしまっている状態にある訳です。
##解決策
この仕組みを回避する為、呼び出し元であるrunをオーバーライドして先にデータを入れておくように実装してみます。
def run
ActiveRecord::FixtureSet.reset_cache
ActiveRecord::FixtureSet.create_fixtures('test/fixtures/hoge', [:content],
{:content => Content}, ContentDbInfo)
super
end
このように実装してやることで、トランザクションが貼られるより前にテーブルに値を入れておくことができます。
言い換えれば既にコミットされている状態にあるので、きちんとインデックスが生成されている状態にできる訳です。
結果として無事に想定していた値を取得することができました。
※上記の方法で実装した場合、テストデータはDBに残ることになりますのでその点は注意する必要があります。
##終わりに
そもそも現行の作りではIndexは生成されていない状態で、テストが実行されている点に驚きましたね...
あくまでテストデータを入れているだけなので、基本的にデータが大量には必要ないですし、
インデックス自体不要というのも言われてみれば納得かなと。
ハマりはしましたが、データベース側の知識も含めて色々と勉強になりました。