elastics-client - Result Extenders
Each result that comes from a Elastics request is the same data structure that comes from the elasticsearch response. It is the untouched structure (hash) that you would expect from the request you did, so you can always inspect and interact with it directly.
However Elastics implements also a mechanism that allows to extend the structure in place, with the useful methods that makes sense with the particular result retrieved. Elastics do so with a few Extender modules, that extend the original Elastics::Result
hash object. Writing your own extender is extremely easy and recommended for more specialized methods.
For example the structure returned from a search query, is the regular structure that elasticsearch serves, but you can call more methods on that hash or on part of that structure, because Elastics extends them in place. For example:
# search the index
result = MyClass.my_search :my_tag => 'the tag value'
#=> { ... tipical response from elasticsearch ... }
# get the document directly from the elasticsearch structure
collection = result['hits']['hits']
#=> [ ... the hits array ... ]
# or use a method added by elastics
collection = result.collection
#=> [ ... same object as above ... ]
# also the collection array is extended with the tipical methods for pagination as well
collection.is_a?(Array) #=> true
collection.total_entries
collection.per_page
collection.total_pages
collection.current_page
# the documents gets also extended
document = collection.first
document.id
document.type
document.any_attribute
The important concept to know is that the Elastics extension mechanism leaves the elasticsearch structure received always intact, it just extends the structure with utility methods and shortcuts where it makes sense.
Elastics Result Extenders
The elastics-client
gem comes with a few extenders (documented here) and more are added by other gems (see Result Extenders). They are applied to the particular structure they make sense for. So a single structure could be extended by more than one extender or by none, depending on what applies to that structure.
-
Elastics::Result::Search
It extends the results coming from a search query. It adds the following methods:-
result.collection
It is the shortcut toresult['hits']['hits']
from search results. The collection is extended by the typical pagination methods (see Elastics::Result::Collection), Besides, each hit in the array gets extended also by theElastics::Result::Document
extender (see below). -
result.facets
Just a shortcut forresult['facets']
-
-
Elastics::Result::MultiGet
It extends the results coming from a multi-get query. It adds the following methods:result.docs
orresult.collection
It is the shortcut toresult['docs']
from multi-get results. The collection is extended by the typical pagination methods (see Elastics::Result::Collection), Besides, each hit in the array gets extended also by theElastics::Result::Document
extender (see below).
-
Elastics::Result::Document
Applies to documents that contain (at least, but not limited to)_index
,_type
,_id
. It adds the following methods:-
document.index
,document.type
,document.id
Shortcuts methods pointing todocument['_index']
,document['_type']
,document['_id']
-
document.index_basename
Returns the unprefixed index name:my_index
for the20130608103457_my_index
index (see Index Renaming). -
method_missing
This module extends the_source
by supplying object-like readers methods. It also exposes the meta fields like _id, _source, etc. For example:
-
-
Elastics::Result::Bulk
Applies to the responses returned after sending a bulk post. Used internally by theelastics::import
rake task (see Rake Tasks). Adds the following methods:-
successful
Array of the items that elasticsearch marked as ‘ok’ -
failed
Array of the items that elasticsearch marked as not ‘ok’
-
Elastics::Result::Collection
Provides the pagination-like methods and aliases in order to support both will_paginate
and kaminari
total_entries
per_page
total_pages
current_page
previous_page
next_page
offset
out_of_bounds?
limit_value
total_count
num_pages
offset_value
Custom Result Extenders
An extender is simply a module that is included in the Elastics::Configuration.result_extenders
array. You can manipulate that array if you wish, by adding or removing modules, so adding or removing methods to the original result structure returned by elasticsearch. For example, let’s say that you would like to be able to do:
result = MySearch.some_search
custom_structure = result.type_counts
and the type_counts
should do some arbitrary aggregation on the returned facet counts, returning a custom structure that you need to pass around.
First you write a module with the methods that will be used to extend the original result. A simplified real world example:
module MyExtender
# returns a structure like:
# {:total=>294, :blogs=>5, :projects=>1, :products=>1, :forums=>287}
def type_counts
counts = Hash.new(0)
self['facets']['type_counts']['terms'].each do |t|
term = t['term'] + 's'
counts[term.to_sym] = t['count']
end
counts[:forums] = counts[:threads] + counts[:posts]
counts[:total] = self['facets']['type_counts']['total']
counts
end
# tells Elastics whether to extend the result with this module or not
def self.should_extend?(result)
result.response.url =~ /\b_search\b/ && result['facets'] && result['facets']['type_counts']
end
end
Notice that in that module self
is the returned elasticsearch structure. As you see the method type_counts
elaborates the facets ‘type_counts’ returning the custom structure that we need.
Also notice that unless we want to extend ALL the results returned from elasticsearch, we define a should_extend?
class method that will check if the result contains what we expect, so extending only when it makes sense.
Now you want to add it to the list of extenders (usually in the elastics.rb
initializer if you are doing it in Rails)
Elastics::Configuration.result_extenders << MyExtender
You could have done the same thing with an external helper, passing it the facet object, something like:
result = MySearch.some_search
custom_structure = type_count(result.facets)
It all depends whether you want to put the logic of what you are doing. Personally I prefer the first approach, which looks more OO and self-contained, specially if you have a lot of result related methods, but in this particular case even the functional approach might be OK.
Model.elastics_result(result)
If your context class defines it, it is internally called by elastics just before returning the result, so you can change it as you prefer, maybe creating objects, extending, checking, whatever. You have also access to the final variables through the passed result. For example:
def self.elastics_result(result)
vars = result.variables
vars[:my_class_wrapper] ? vars[:my_class_wrapper].new(result) : result
end