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.