Search code examples
ruby-on-railsrubyruby-on-rails-4elasticsearchtire

Asciifolding with Elasticsearch and Tire in Rails 4 not Working


I created a search engine in my rails 4 app that was working great, but I am having a very hard time getting the asciifolding filter to work. My model has a lot of terms with accents which will not come up unless they're spelled exactly right, ie: I want a search for "Rodriguez" to display results with "Rodríguez." I have tried to follow many different examples but for some reason when I reset my database with the following code, the search won't work at all (I don't get an error but nothing comes up regardless of the query).

Here is my model:

class Product < ActiveRecord::Base
    include Tire::Model::Search
    include Tire::Model::Callbacks

    settings :analysis => {
        :analyzer => {
          :default => {
            :tokenizer  => "standard",
            :filter  => ["standard", "asciifolding"]
          }
        }
    } do

        mapping do
          indexes :id, type: 'integer', index: :not_analyzed
          indexes :name, boost: 5, analyzer: 'default'
          indexes :website, index: :not_analyzed
          indexes :price, type: 'integer', index: :not_analyzed
          indexes :artist, boost: 3, analyzer: 'default'
          indexes :company, boost: 4, analyzer: 'default'
          indexes :date, type: 'date', index: :not_analyzed
        end
    end

    def self.search(params)
      tire.search(page: params[:page], per_page: 12) do
        query { string params[:query], default_operator: "AND" } if params[:query].present?
      end
    end

Before testing, I clear the database with:

rake db:setup

Then I run:

rake environment tire:import CLASS=Product FORCE=true

I have looked at many different resources including the elasticsearch and tire documentation, but for whatever reason (i am anticipating a stupid mistake) it simply won't work. Small note, to populate my database I have been importing a csv file, but I don't see why this would effect anything, especially considering nothing is coming up in the search in this form (it works fine sans the accent problem when I delete the settings part and just have the mapping block and search method). Do I need to call some sort of Tire.index and import? Any help is appreciated!

UPDATE

So I made an edit to the search query which fixed the problem but raised a new one:

    def self.search(params)
          tire.search(page: params[:page], per_page: 12) do
            query { string params[:query], analyzer: :search_analyzer, :default_field => 'name' } if params[:query].present?
          end
        end

By identifying a default field, I can now search accent agnostic, but now my search is limited to just the name and I cannot receive results for other indexed attributes that were working previously. Does anyone know how to set multiple default fields?


Solution

  • This is example that I use in production. It's not solution to your problem but it will get you off the ground. My code is using ICU plugin for icu_folding, shingle_filter, multi_field and Tire. Also note that Tire is retired since september 2013 and that you should be using elasticsearch-ruby or elasticsearch-rails if its a new project. With this code I can have autocomplete with cases like Rod finding Rodríguez.

    class Profile
      # ...
      include Tire::Model::Persistence
      # I'm also using ES as persistance storage.
      include Tire::Model::DynamicPersistence
    
      property :title, analyzer: 'snowball', type: :multi_field,
        fields: {
          title: {
            type: 'string',
            boost: 20.0,
            search_analyzer: "autocomplete_search_analyzer",
            index_analyzer: "autocomplete_indexer_analyzer"
          },
          completed: {
            type: 'string',
            boost: 15.0,
            search_analyzer: "autocomplete_search_analyzer",
            index_analyzer: "autocomplete_indexer_analyzer"
        }
      }
    
      CONFIGURATION = {
        settings: {
          analysis: {
            analyzer: {
              shingle_analyzer: {
                tokenizer: "keyword",
                filter: ["icu_folding", "lowercase", "shingle_filter"]
              },
              sx_index_analyzer: {
                tokenizer: "standard",
                filter: ["icu_folding", "lowercase"]
              },
              sx_search_analyzer: {
                tokenizer: "standard",
                filter: ["icu_folding", "lowercase"]
              }
            },
            filter: {
              shingle_filter: {
                type: "shingle",
                min_shingle_size: 2,
                max_shingle_size: 5
              }
            }
    
          }
        },
    
        mappings: {
          profile: {
            "_all" => {
              enabled: true,
              index_analyzer: "sx_index_analyzer",
              search_analyzer: "sx_search_analyzer"
            },
    
           properties: {
            title: {
             type: "multi_field",
             fields: {
              title: {
                type: "string",
                store: "yes",
                boost: 20.0
                #index_analyzer: "sx_index_analyzer",
                #search_analyzer: "sx_search_analyzer"
              },
              sortable: {
                type: "string",
                index: "analyzed"
              },
              autocomplete: {
                type: "string",
                index_analyzer: "shingle_analyzer",
                boost: 15.0
              },
             }
            }
           }
          }
        }
      }
    
      def self.rebuild_index
        Tire.index Profile.index_name do
          delete if Profile.index.exists?
          create Profile::CONFIGURATION
        end
      end
    end
    

    Hope it helps!