estella

Gem Version Build Status License Status Coverage Status

Builds on elasticsearch-model to make your Ruby objects searchable with Elasticsearch. Provides fine-grained control of fields, analysis, filters, weightings and boosts.

Compatibility

This library is compatible with Elasticsearch 1.5.x, 2.x when using versions 2.0.0 and earlier. It is compatible with Elasticsearch 5.x via version 5.0.0 or installing directly from the master branch of this repository. It works with many ORM/ODMs, including ActiveRecord and Mongoid.

Dependencies

Installation

gem 'estella'

Estella will try to use Elasticsearch on localhost:9200 by default.

You can configure your global ElasticSearch client like so:

Elasticsearch::Model.client = Elasticsearch::Client.new host: 'foo.com', log: true

It's also configurable on a per-model basis. Refer to the ElasticSearch documentation for details.

Indexing

Include the Estella::Searchable module and add a searchable block in your model declaring the fields to be indexed.

class Artist < ActiveRecord::Base
  include Estella::Searchable

  searchable do
    field :name, type: :string, analysis: Estella::Analysis::FULLTEXT_ANALYSIS, factor: 1.0
    field :keywords, type: :string, analysis: ['snowball', 'shingle'], factor: 0.5
    field :bio, using: :biography, type: :string, index: :not_analyzed
    field :birth_date, type: :date
    field :follows, type: :integer
    field :published, type: :boolean, filter: true
    boost :follows, modifier: 'log1p', factor: 1E-3
  end
end

For a full list of the options available for field mappings, see the ElasticSearch mapping documentation.

The filter option allows the field to be used as a filter at search time.

You can optionally provide field weightings to be applied at search time using the factor option. These are multipliers.

Document-level boosts can be applied with the boost declaration, see the field_value_factor documentation for boost options.

While filter, boost and factor are query options, Estella allows for their static declaration in the searchable block for simplicity - they will be applied at query time by default when using #estella_search.

You can now create your index mappings with this migration:

Artist.reload_index!

This uses a default index naming scheme based on your model name, which you can override simply by declaring the following in your model:

index_name 'my_index_name'

Start indexing documents simply by creating or saving them:

Artist.create(name: 'Frank Estella', keywords: ['art', 'minimalism'])

Estella adds after_save and after_destroy callbacks for inline indexing, override these callbacks if you'd like to do your indexing in a background process. For example:

class Artist < ActiveRecord::Base
  include Estella::Searchable

  # disable estella inline callbacks
  skip_callback(:save, :after, :es_index)
  skip_callback(:destroy, :after, :es_delete)

  # declare your own
  after_save :delay_es_index
  after_destroy :delay_es_delete

  ...
end

A number of class methods are available for indexing.

# return true if the index exists
Artist.index_exists?

# create the index
Artist.create_index!

# delete and re-create the index without reindexing data
Artist.reload_index!

# recreate the index and reindex all data
Artist.recreate_index!

# delete the index
Artist.delete_index!

# commit any outstanding writes
Artist.refresh_index!

Custom Analysis

Estella defines standard, snowball, ngram and shingle analyzers by default. These cover most search contexts, including auto-suggest. In order to enable full-text search for a field, use:

analysis: Estella::Analysis::FULLTEXT_ANALYSIS

Or alternatively select your analysis by listing the analyzers you want enabled for a given field:

field :keywords, type: :string, analysis: ['snowball', 'shingle']

Estella default analyzer and sharding options are defined here and can be customized by passing a settings hash to the searchable block.

my_analysis = {
  tokenizer: {
    ...
  },
  filter: {
    ...
  }
}

my_settings = {
  analysis: my_analysis,
  index: {
    number_of_shards: 1,
    number_of_replicas: 1
  }
}

searchable my_settings do
  ...
end

See configuring analyzers for more information.

Searching

Perform full-text search with estella_search:

Artist.estella_search(term: 'frank')
Artist.estella_search(term: 'minimalism')

Estella searches all analyzed text fields by default, using a multi_match search. The search will return an array of database records, ordered by score. If you'd like access to the raw Elasticsearch response data use the raw option:

Artist.estella_search(term: 'frank', raw: true)

Estella supports filtering on filter fields and pagination:

Artist.estella_search(term: 'frank', published: true)
Artist.estella_search(term: 'frank', size: 10, from: 5)

You can exclude records:

Artist.estella_search(term: 'frank', exclude: { keywords: 'sinatra' })

If you'd like to customize your term query further, you can extend Estella::Query and override query_definition and field_factors:

class MyQuery < Estella::Query
  def query_definition
    {
      multi_match: {
        ...
      }
    }
  end

  def field_factors
    {
      default: 5,
      ngram: 5,
      snowball: 2,
      shingle: 1,
      search: 1
    }
  end
end

Or manipulate the query for all cases (with or without term) in the initializer directly via query or by using built-in helpers must and exclude.

class MyQuery < Estella::Query
  def initialize(params)
    super
    # same as query[:filter][:bool][:must] = { keywords: 'frank' }
    must(term: { keywords: 'frank' })
    # same as query[:filter][:bool][:must_not] = { keywords: 'sinatra' }
    exclude(term: { keywords: 'sinatra' })
  end
end

And then override class method estella_search_query to direct Estella to use your query object:

class Artist < ActiveRecord::Base
  include Estella::Searchable

  searchable do
    ...
  end

  def self.estella_search_query
    MyQuery
  end
end

Artist.estella_search(term: 'frank')

For further search customization, see the ElasticSearch DSL.

Contributing

See CONTRIBUTING.

License

MIT License. See LICENSE.