estella
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 6.x via version 6.0.0. It is compatible with Elasticsearch 7.x via version 7.0.0 or installing directly from the main 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.