Elasticsearch / Ruby / Rails / RSpec

セッティングをテストする

まずは正しくセッティングされていることをテストします.

最初にインデックスの再作成を行います.

spec/models/recipe_spec.rb

require 'rails_helper'

describe Recipe do
  before :all do
    Recipe.__elasticsearch__.create_index! force: true
    Recipe.__elasticsearch__.refresh_index!
  end
end

続いて取得したセッティングのトークナイザとアナライザの確認を行います.

spec/models/recipe_spec.rb

describe Recipe do
  ...

  context "with settings" do
    let(:settings) { Recipe.__elasticsearch__.client.indices.get_settings[Recipe.index_name] }
    let(:analysis) { settings["settings"]["index"]["analysis"] }

    context "with tokenizer" do
      subject { analysis["tokenizer"]}
      it do
        should eq({"ngram_tokenizer" =>
                     {"max_gram" => "3",
                      "type" => "nGram",
                      "min_gram" => "2",
                      "token_chars" => ["letter", "digit"]}})
      end
    end

    context "with analyzer" do
      subject { analysis["analyzer"]}
      it { should eq({"ngram_analyzer" => {"tokenizer" => "ngram_tokenizer"}}) }
    end
  end
end

ここではN-gramのトークナイザとアナライザが設定されていることを確認しています.

マッピングをテストする

続いてマッピング設定をテストします.

spec/models/recipe_spec.rb

describe Recipe do
  ...

  context "with mappings" do
    subject { Recipe.__elasticsearch__.client.indices.get_mapping index: Recipe.index_name }

    it do
      should eq({
        "recipes" => {
          "mappings" => {
            "recipe" => {
              "properties" => {
                "id" => {"type" => "long"},
                "title" => {"type" => "string",
                            "analyzer" => "ngram_analyzer"},
                "description" => {"type" => "string",
                                  "analyzer" => "ngram_analyzer"},
                "url" => {"type" => "string",
                          "analyzer" => "ngram_analyzer"}
              }
            }
          }
        }
      })
    end
  end
end

ちょっとネストが深いですが, title/description/urlフィールドにそれぞれN-gramアナライザが設定されていることを確認しています.

検索機能をテストする

最後に検索機能のテストをします.

まずは検索用のテストデータをインポートします.

spec/models/recipe_spec.rb

describe Recipe do
  ...

  describe ".search" do
    before :all do
      Recipe.delete_all

      Recipe.create(title: "ミックス赤玉で豚玉を作ってみた", description: "業務スーパーで買ったのは卵(ミックス赤玉)だけですが、豚玉炒めを作りました。", url: "http://gsrecipe.com/2014/08/09/butatama/")
      Recipe.create(title: "パルメザンチーズで濃厚カルボナーラ", description: "業務スーパーのパルメザンチーズ(200g入り!)と卵でカルボナーラを作りました。", url: "http://gsrecipe.com/2014/08/10/carbonara/")
      Recipe.create(title: "ミックス赤玉で卵焼き", description: "またまた卵ですが業務スーパーのミックス赤玉で卵焼きを作りました。", url: "http://gsrecipe.com/2014/08/13/tamagoyaki/")
      Recipe.create(title: "パプリカを使った野菜たっぷりスープ", description: "業務スーパーの冷凍パプリカを使って野菜スープを作りました。我が家の恒例朝食メニューです。", url: "http://gsrecipe.com/2014/08/14/vegesoup/")

      Recipe.__elasticsearch__.import
      Recipe.__elasticsearch__.refresh_index!
    end
  end
end

ここではDBに作成したRecipeデータをElastisearchにインポートしています.

また, 最後のrefresh_index!では強制的にインデックスを更新しています.

Elastisearchでのインデックス更新は非同期なので, 確実にインデックスを更新したい場合はこのようにrefresh_index!メソッドを実行する必要があります.

続いて更新したインデックスから検索できるかテストします.

spec/models/recipe_spec.rb

describe Recipe do
  ...

  describe ".search" do
    ...

    context "when query is 'ミックス赤玉'" do
      let(:query) { "ミックス赤玉" }
      let(:records) { Recipe.search(query).records }
      subject { records }
      it { should have(2).items }

      context "with titles" do
        subject { records.map(&:title) }
        it { should match_array ["ミックス赤玉で豚玉を作ってみた", "ミックス赤玉で卵焼き"] }
      end
    end
  end
end

実行してみると

$ bundle exec rspec spec/models/recipe_spec.rb -f d
Recipe
  with settings
    with tokenizer
      should eq {"ngram_tokenizer"=>{"max_gram"=>"3", "type"=>"nGram", "min_gram"=>"2", "token_chars"=>["letter", "digit"]}}
    with analyzer
      should eq {"ngram_analyzer"=>{"tokenizer"=>"ngram_tokenizer"}}
  with mappings
    should eq {"recipes"=>{"mappings"=>{"recipe"=>{"properties"=>{"id"=>{"type"=>"long"}, "title"=>{"type"=>"string", "analyzer"=>"ngram_analyzer"}, "description"=>{"type"=>"string", "analyzer"=>"ngram_analyzer"}, "url"=>{"type"=>"string", "analyzer"=>"ngram_analyzer"}}}}}}
  .search
    when query is 'ミックス赤玉'
      should have 2 items
      with titles
        should contain exactly "ミックス赤玉で豚玉を作ってみた" and "ミックス赤玉で卵焼き"

Finished in 2.12 seconds (files took 1.14 seconds to load)
5 examples, 0 failures

ということで無事Elasticsearchでの検索機能をテストできました.