EagleSearch
EagleSearch is a ruby gem that integrates Rails ActiveRecord to Elasticsearch. It handles the Elasticsearch internals by itself, and in most cases minimal (none) configuration is needed.
Installation
First of all, you should have Elasticsearch installed.
Using Homebrew:
brew install elasticsearchAdd eagle_search to Gemfile:
gem 'eagle_search'Get Started
Add EagleSearch module and call eagle_search class method in your ActiveRecord:
class Article < ActiveRecord::Base
include EagleSearch
eagle_search
def index_data
as_json only: [:title, :body, :active]
end
endNotice that EagleSearch will use the #index_data method to know what will be indexed.
As the model was configured, you should populate the records into Elasticsearch index:
Article.reindexEagleSearch will automatically handle the mapping based on model column types unless you explicit set a custom mapping.
Article.search "programming language"Searching
Basic
The following code will return all articles:
articles = Article.search "*"You can iterate over the records (will hit the database):
articles.each do |article_record|
...
endTo avoid the database, you can access the hits directly:
articles.hits.each do |article_hit|
...
endFiltering
Get only active articles:
Article.search "*", filters: { active: true }If you want to combine conditions, you should add the operator:
Product.search "*", filters: {
and: {
active: true,
in_stock: true
}
}You can go deep, mixing AND and OR operators:
Product.search "*", filters: {
and: {
active: true,
or: [
{
and: {
price: { gte: 543.50 },
available_stock: 300
}
},
{
available_stock: 500
},
{
not: { expired: false }
}
]
}
}Filtering by string (only for not_analyzed strings):
Product.search "*", filters: {
name: "Book: The Hidden Child"
}If you want to map a string field as an exact value, you need to set it, as documented here.
Filtering by ranges:
Product.search "*", filters: {
available_stock: (10..150)
}or equivalent:
Product.search "*", filters: {
available_stock: {
gte: 10,
lte: 150
}
}Available options are gte, gt, lt, lte.
Complex queries
As queries in some cases might to get very complex, you can make your query by yourself, in the next code, EagleSearch will replace ONLY the query part of filtered query, which will let you to use EagleSearch filter:
Product.search "*", custom_query: {
...
}, filters: { price: { gt: 50 } }Similarly, you can make the filter part by yourself:
Product.search "*", custom_filter: {
...
}If you need to make the whole query by yourself, set a custom payload:
Product.search "*", custom_payload: {
...
}Relevance (boost)
If you want to consider some fields more important:
Product.search "*", fields: ["name^5", "description"]The number 5 is the factor that the field will be boosted, see here.
Pagination (tested with Kaminari)
Product.search "*", page: 2, per_page: 20Defaults:
page: 1 and per_page: 10
Typoes and misspellings
Product.search "neighhbour" #matches documents containing whether 'neighbor' or 'neighbour'Highlight
products = Product.search "book", highlight: { fields: [:name], tags: ["<span>"] }
products.hits.each do |hit|
hit["highlight"]["name"] # "<span>Book</span>: The Hidden Child"
endAutocomplete
Product.search "fri" # will match docs with fields like: "fries, friendship..."You can disable this behavior:
Product.search "fri", autocomplete: false # won't match docs with fields like: "fries, friendship..."Aggregations
To aggregate data, set the aggregations option:
products = Product.search "*", aggregations: :category
products.aggregationsResponse:
{
"category"=>{
...
"buckets"=>[
{ "key"=>"Book", "doc_count"=>2 },
{ "key"=>"Vesture", "doc_count"=>1 }
]
...
}
}By default, when an aggregation is a symbol or a string, it will be interpreted as a terms aggregation.
Multiple aggregations:
products = Product.search "*", aggregations: [:category, :country]
products.aggregationsResponse:
{
"category"=>{
...
"buckets"=>[
{ "key"=>"Book", "doc_count"=>2 },
{ "key"=>"Vesture", "doc_count"=>1 }
]
...
},
"country"=>{
...
"buckets"=>[
{ "key"=>"Brazil", "doc_count"=>1 },
{ "key"=>"USA", "doc_count"=>1 },
{ "key"=>"Spain", "doc_count"=>1 }
]
...
}
}Nesting aggregations:
products = Product.search "*", aggregations: { category: :country }
products.aggregationsResponse:
{
"category"=>{
...
"buckets"=>[
{
"key"=>"Book"
"doc_count"=>2,
"country"=>{
"buckets"=>[
{ "key"=>"Brazil", "doc_count"=>1 },
{ "key"=>"USA", "doc_count"=>1 }
]
}
},
{
"key"=>"Vesture"
"doc_count"=>1,
"country"=>{
"buckets"=>[
{ "key"=>"Spain", "doc_count"=>1 }
]
}
}
]
...
}
}Stats aggregations:
products = Product.search "*", aggregations: {
available_stock: { type: "stats" }
}
products.aggregationsResponse:
{
"available_stock"=>{
"count"=>4,
"min"=>20.0,
"max"=>400.0,
"avg"=>185.0,
"sum"=>740.0
}
}Mixing terms and stats aggregations:
products = Product.search "*", aggregations: {
country: {
available_stock: { type: "stats" }
}
}
products.aggregationsResponse:
{
"country"=>{
...
"buckets"=>[
{
"key"=>"Brazil",
"doc_count"=>1,
"available_stock"=>{
"count"=>4,
"min"=>20.0,
"max"=>400.0,
"avg"=>185.0,
"sum"=>740.0
}
},
...
]
}
}Ranges aggregations:
products = Product.search "*", aggregations: {
available_stock: {
ranges: [
(0..30),
(30..60),
{ from: 60, to: 90 },
{ from: 90 }
]
}
}
products.aggregationsResponse:
{
"available_stock"=>{
...
"buckets"=>[
{
...
"key"=>"0-30",
"doc_count"=>4
},
{
...
"key"=>"30-60",
"doc_count"=>2
},
{
...
"key"=>"60-90",
"doc_count"=>13
},
{
...
"key"=>"90-*",
"doc_count"=>37
}
]
}
}Remember that you can go as deep as you want nesting and mixing terms and stats aggregations.
Settings
Exact string fields
By default, EagleSearch (even Elasticsearch) will map string field as analyzed, which won't let you to filter these fields.
If you have an exact value for string, and want to search by its exact value, you explicitly need to declare it, for example:
class Product < ActiveRecord::Base
include EagleSearch
eagle_search exact_match_fields: [:code]
endIt will let you to filter string fields:
Product.search "*", filters: {
code: "JUR123-A"
}Unsearchable fields
You can disable the search and filter on certain fields:
class Product < ActiveRecord::Base
include EagleSearch
eagle_search unsearchable_fields: [:code]
endSynonyms
You can explicitly declare your synonyms in your class:
class Product < ActiveRecord::Base
include EagleSearch
eagle_search synonyms: [
"dog, canine",
"cup, glass, chalice"
]
endSo, whether searching by cup, glass or chalice will match documents containing these words, as well as dog or canine.
Putting synonyms in a separated file:
class Product < ActiveRecord::Base
include EagleSearch
eagle_search synonyms: {
format: "wordnet", #could be solr format
synonyms_path: "synonyms.txt" #relative to elasticsearch config location
}
endUsing WordNet format files:
class Product < ActiveRecord::Base
include EagleSearch
eagle_search synonyms: {
format: "wordnet",
synonyms_path: "[WORDNET_FILE_LOCATION]"
}
endUsing Solr format files:
class Product < ActiveRecord::Base
include EagleSearch
eagle_search synonyms: {
format: "solr",
synonyms_path: "[SOLR_FILE_LOCATION]"
}
endCustom Mapping
You can declare the index mapping by yourself:
class Product < ActiveRecord::Base
include EagleSearch
eagle_search mappings: {
type_name: {
properties: {
name: {
index: "no",
type: "string"
}
}
}
}
endCustom index name
class Product < ActiveRecord::Base
include EagleSearch
eagle_search index_name: "product"
endLanguage
You can set the language of your index (default is english):
class Product < ActiveRecord::Base
include EagleSearch
eagle_search language: "portuguese"
endAvailable languages are here
IMPORTANT all of the settings above require index to be reindexed:
Product.reindexAuto reindex
As a record is created or changed, EagleSearch automatically reindex the record by default.
You can disable the auto reindex:
class Product < ActiveRecord::Base
include EagleSearch
eagle_search reindex: false
endRoadmap
- Suggestions
- Elasticsearch 2.x