class Chewy::Search::Request
The main request DSL class. Supports multiple index requests. Supports ES5 search API and query DSL.
@note The class tries to be as immutable as possible,
so most of the methods return a new instance of the class.
@see Chewy::Search
@example
scope = Chewy::Search::Request.new(PlacesIndex) # => <Chewy::Search::Request {:index=>["places"], :body=>{}}> scope.limit(20) # => <Chewy::Search::Request {:index=>["places"], :body=>{:size=>20}}> scope.order(:name).offset(10) # => <Chewy::Search::Request {:index=>["places"], :body=>{:sort=>["name"], :from=>10}}>
Constants
- DEFAULT_BATCH_SIZE
- DEFAULT_PLUCK_BATCH_SIZE
- DEFAULT_SCROLL
- DELEGATED_METHODS
- EVERFIELDS
- EXTRA_STORAGES
An array of storage names that are not related to hits at all.
- FIELD_STORAGES
An array of storage names that are modifying returned fields in hits
- UNDEFINED
- WHERE_STORAGES
An array of storage names that are changing the returned hist collection in any way.
Public Class Methods
The class is initialized with the list of chewy indexes, which are later used to compose requests. Any symbol/string passed is treated as an index identifier.
@example
Chewy::Search::Request.new(:places) # => <Chewy::Search::Request {:index=>["places"], :body=>{}}> Chewy::Search::Request.new(PlacesIndex) # => <Chewy::Search::Request {:index=>["places"], :body=>{}}> Chewy::Search::Request.new(UsersIndex, PlacesIndex) # => <Chewy::Search::Request {:index=>["users", "places"], :body=>{}}>
@param indexes [Array<Chewy::Index, String, Symbol>] indexes
# File lib/chewy/search/request.rb, line 67 def initialize(*indexes) parameters.modify!(:indices) do replace!(indices: indexes) end end
Public Instance Methods
Compare two scopes or scope with a collection of wrappers. If other is a collection it performs the request to fetch data from ES.
@example
PlacesIndex.limit(10) == PlacesIndex.limit(10) # => true PlacesIndex.limit(10) == PlacesIndex.limit(10).to_a # => true PlacesIndex.limit(10) == PlacesIndex.limit(10).objects # => true PlacesIndex.limit(10) == UsersIndex.limit(10) # => false PlacesIndex.limit(10) == UsersIndex.limit(10).to_a # => false PlacesIndex.limit(10) == Object.new # => false
@param other [Object] any object @return [true, false] the result of comparison
# File lib/chewy/search/request.rb, line 95 def ==(other) super || other.is_a?(Chewy::Search::Request) ? compare_internals(other) : to_a == other end
A dual-purpose method.
@overload aggs(value)
With the value provided it adds a new aggregation to the aggregation hash. @example PlacesIndex .aggs(avg_population: {avg: {field: :population}}) .aggs(avg_age: {avg: {field: :age}}) # => <PlacesIndex::Query {..., :body=>{:aggs=>{ # "avg_population"=>{:avg=>{:field=>:population}}, # "avg_age"=>{:avg=>{:field=>:age}}}}}> @see Chewy::Search::Parameters::Aggs @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html @param value [Hash] @return [Chewy::Search::Request]
@overload aggs
Without value provided, it performs the request and returns {Chewy::Search::Response#aggs} contents. @example PlacesIndex.aggs(avg_population: {avg: {field: :population}}).aggs @see Chewy::Search::Response#aggs @return [Hash]
# File lib/chewy/search/request.rb, line 703 def aggs(value = UNDEFINED) if value == UNDEFINED response.aggs else modify(:aggs) { update!(value) } end end
Returns total count of hits for the request. If the request was already performed - it uses the `total` value, otherwise it executes a fast count request.
@return [Integer] total hits count
# File lib/chewy/search/request.rb, line 829 def count if performed? total else Chewy.client.count(only(WHERE_STORAGES).render)['count'] end rescue Elasticsearch::Transport::Transport::Errors::NotFound 0 end
Deletes all the documents from the specified scope it uses `delete_by_query`
@see www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html @note The result hash is different for different API used. @param refresh [true, false] field names @return [Hash] the result of query execution
# File lib/chewy/search/request.rb, line 956 def delete_all(refresh: true) request_body = only(WHERE_STORAGES).render.merge(refresh: refresh) ActiveSupport::Notifications.instrument 'delete_query.chewy', notification_payload(request: request_body) do request_body[:body] = {query: {match_all: {}}} if request_body[:body].empty? Chewy.client.delete_by_query(request_body) end end
Returns a new scope containing all the storages except specified.
@example
PlacesIndex.limit(10).offset(10).order(:name).only(:offset, :order) # => <PlacesIndex::Query {..., :body=>{:from=>10, :sort=>["name"]}}>
@param values [Array<String, Symbol>] @return [Chewy::Search::Request] new scope
# File lib/chewy/search/request.rb, line 818 def except(*values) chain { parameters.except!(values.flatten(1)) } end
Checks if any of the document exist for this request. If the request was already performed - it uses the `total`, otherwise it executes a fast request to check existence.
@return [true, false] wether hits exist or not
# File lib/chewy/search/request.rb, line 844 def exists? if performed? total != 0 else limit(0).terminate_after(1).total != 0 end end
Finds documents with specified ids for the current request scope.
@raise [Chewy::DocumentNotFound] in case of any document is missing @overload find(id)
If single id is passed - it returns a single object. @param id [Integer, String] id of the desired document @return [Chewy::Index] result document
@overload find(*ids)
If several field are passed - it returns an array of wrappers. Respect the amount of passed ids and if it is more than the default batch size - uses scroll API to retrieve everything. @param ids [Array<Integer, String>] ids of the desired documents @return [Array<Chewy::Index>] result documents
# File lib/chewy/search/request.rb, line 896 def find(*ids) return super if block_given? ids = ids.flatten(1).map(&:to_s) scope = except(EXTRA_STORAGES).filter(ids: {values: ids}) results = if ids.size > DEFAULT_BATCH_SIZE scope.scroll_wrappers else scope.limit(ids.size) end.to_a if ids.size != results.size missing_ids = ids - results.map(&:id).map(&:to_s) raise Chewy::DocumentNotFound, "Could not find documents for ids: #{missing_ids.to_sentence}" end results.one? ? results.first : results end
Return first wrapper object or a collection of first N wrapper objects if the argument is provided. Tries to use cached results of possible. If the amount of cached results is insufficient - performs a new request.
@overload first
If nothing is passed - it returns a single object. @return [Chewy::Index] result document
@overload first(limit)
If limit is provided - it returns the limit amount or less of wrapper objects. @param limit [Integer] amount of requested results @return [Array<Chewy::Index>] result document collection
# File lib/chewy/search/request.rb, line 869 def first(limit = UNDEFINED) request_limit = limit == UNDEFINED ? 1 : limit if performed? && (request_limit <= size || size == total) limit == UNDEFINED ? wrappers.first : wrappers.first(limit) else result = except(EXTRA_STORAGES).limit(request_limit).to_a limit == UNDEFINED ? result.first : result end end
Modifies `index` request parameter. Updates the storage on every call. Added passed indexes to the parameter list.
@example
UsersIndex.indices(CitiesIndex).indices(:another) # => <UsersIndex::Query {:index=>["another", "cities", "users"]}>
@see Chewy::Search::Parameters::Indices
@see www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html @param values [Array<Chewy::Index, String, Symbol>] index names @return [Chewy::Search::Request]
# File lib/chewy/search/request.rb, line 308 def indices(value, *values) modify(:indices) { update!(indices: [value, *values]) } end
Includes the class name and the result of rendering.
@return [String]
# File lib/chewy/search/request.rb, line 127 def inspect "<#{self.class} #{render}>" end
Stores ORM/ODM objects loading options. Options might be define per-index or be global, depends on the adapter loading implementation. Also, there are 2 loading options to select or exclude indexes from loading: `only` and `except` respectively. Options are updated on further method calls.
@example
PlaceIndex.load(only: 'city').load(scope: -> { active })
@see Chewy::Search::Loader
@see Chewy::Search::Response#objects
@see Chewy::Search::Scrolling#scroll_objects
@param options [Hash] adapter-specific loading options
# File lib/chewy/search/request.rb, line 578 def load(options = nil) modify(:load) { update!(options) } end
Merges 2 scopes by merging their parameters.
@example
scope1 = PlacesIndex.limit(10).offset(10) scope2 = PlacesIndex.limit(20) scope1.merge(scope2) # => <PlacesIndex::Query {..., :body=>{:size=>20, :from=>10}}> scope2.merge(scope1) # => <PlacesIndex::Query {..., :body=>{:size=>10, :from=>10}}>
@see Chewy::Search::Parameters#merge @param other [Chewy::Search::Request] scope to merge @return [Chewy::Search::Request] new scope
# File lib/chewy/search/request.rb, line 726 def merge(other) chain { parameters.merge!(other.parameters) } end
Returns a new scope containing only specified storages.
@example
PlacesIndex.limit(10).offset(10).order(:name).except(:offset, :order) # => <PlacesIndex::Query {..., :body=>{:size=>10}}>
@param values [Array<String, Symbol>] @return [Chewy::Search::Request] new scope
# File lib/chewy/search/request.rb, line 807 def only(*values) chain { parameters.only!(values.flatten(1) + [:indices]) } end
Underlying parameter storage collection.
@return [Chewy::Search::Parameters]
# File lib/chewy/search/request.rb, line 76 def parameters @parameters ||= Parameters.new end
Returns whether or not the query has been performed.
@return [true, false]
# File lib/chewy/search/request.rb, line 967 def performed? !@response.nil? end
Returns and array of values for specified fields. Uses `source` to restrict the list of returned fields. Fields
`_id`, `_type`, `_routing` and `_index` are also supported.
@overload pluck(field)
If single field is passed - it returns and array of values. @param field [String, Symbol] field name @return [Array<Object>] specified field values
@overload pluck(*fields)
If several field are passed - it returns an array of arrays of values. @param fields [Array<String, Symbol>] field names @return [Array<Array<Object>>] specified field values
# File lib/chewy/search/request.rb, line 930 def pluck(*fields) fields = fields.flatten(1).reject(&:blank?).map(&:to_s) source_fields = fields - EVERFIELDS scope = except(FIELD_STORAGES, EXTRA_STORAGES) .source(source_fields.presence || false) hits = raw_limit_value ? scope.hits : scope.scroll_hits(batch_size: DEFAULT_PLUCK_BATCH_SIZE) hits.map do |hit| if fields.one? fetch_field(hit, fields.first) else fields.map do |field| fetch_field(hit, field) end end end end
ES request body
@return [Hash] request body
# File lib/chewy/search/request.rb, line 120 def render @render ||= parameters.render end
@overload reorder(*values)
Replaces the value of the `sort` parameter with the provided value. @example PlacesIndex.order(:name, population: {order: :asc}).reorder(:coordinates) # => <PlacesIndex::Query {..., :body=>{:sort=>["coordinates"]}}> @see Chewy::Search::Parameters::Order @see https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html @param values [Array<Hash, String, Symbol>] sort fields and options @return [Chewy::Search::Request]
# File lib/chewy/search/request.rb, line 322 def reorder(value, *values) modify(:order) { replace!([value, *values]) } end
Access to ES response wrappers providing useful methods such as {Chewy::Search::Response#total} or {Chewy::Search::Response#max_score}.
@see Chewy::Search::Response
@return [Chewy::Search::Response] a response object instance
# File lib/chewy/search/request.rb, line 104 def response @response ||= build_response(perform) end
Wraps and sets the raw Elasticsearch response to provide access to convenience methods.
@see Chewy::Search::Response
@param from_elasticsearch [Hash] An Elasticsearch response
# File lib/chewy/search/request.rb, line 113 def response=(from_elasticsearch) @response = build_response(from_elasticsearch) end
@overload search_after
(*values) Replaces the storage value for `search_after` request part.
@example
PlacesIndex.search_after(42, 'Moscow').search_after('London') # => <PlacesIndex::Query {..., :body=>{:search_after=>["London"]}}>
@see Chewy::Search::Parameters::SearchAfter
@see www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html#search-after @param value [Array, Object] @return [Chewy::Search::Request]
# File lib/chewy/search/request.rb, line 562 def search_after(value, *values) modify(:search_after) { replace!(values.empty? ? value : [value, *values]) } end
A dual-purpose method.
@overload suggest(value)
With the value provided it adds a new suggester to the suggestion hash. @example PlacesIndex .suggest(names: {text: 'tring out Elasticsearch'}) .suggest(descriptions: {text: 'some other text'}) # => <PlacesIndex::Query {..., :body=>{:suggest=>{ # "names"=>{:text=>"tring out Elasticsearch"}, # "descriptions"=>{:text=>"some other text"}}}}> @see Chewy::Search::Parameters::Suggest @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html @param value [Hash] @return [Chewy::Search::Request]
@overload suggest
Without value provided, it performs the request and returns {Chewy::Search::Response#suggest} contents. @example PlacesIndex.suggest(names: {text: 'tring out Elasticsearch'}).suggest @see Chewy::Search::Response#suggest @return [Hash]
# File lib/chewy/search/request.rb, line 669 def suggest(value = UNDEFINED) if value == UNDEFINED response.suggest else modify(:suggest) { update!(value) } end end
Protected Instance Methods
# File lib/chewy/search/request.rb, line 973 def initialize_clone(origin) @parameters = origin.parameters.clone reset end
Private Instance Methods
# File lib/chewy/search/request.rb, line 1016 def _indices parameters[:indices].indices end
# File lib/chewy/search/request.rb, line 980 def build_response(raw_response) Response.new(raw_response, loader, collection_paginator) end
# File lib/chewy/search/request.rb, line 992 def chain(&block) clone.tap { |r| r.instance_exec(&block) } end
# File lib/chewy/search/request.rb, line 1043 def collection_paginator method(:paginated_collection).to_proc if respond_to?(:paginated_collection, true) end
# File lib/chewy/search/request.rb, line 984 def compare_internals(other) parameters == other.parameters end
# File lib/chewy/search/request.rb, line 1035 def fetch_field(hit, field) if EVERFIELDS.include?(field) hit[field] else hit.fetch('_source', {})[field] end end
# File lib/chewy/search/request.rb, line 1028 def loader @loader ||= Loader.new( indexes: parameters[:indices].indices, **parameters[:load].value ) end
# File lib/chewy/search/request.rb, line 988 def modify(name, &block) chain { parameters.modify!(name, &block) } end
# File lib/chewy/search/request.rb, line 1009 def notification_payload(additional) { indexes: _indices, index: _indices.one? ? _indices.first : _indices }.merge(additional) end
# File lib/chewy/search/request.rb, line 1000 def perform(additional = {}) request_body = render.merge(additional) ActiveSupport::Notifications.instrument 'search_query.chewy', notification_payload(request: request_body) do Chewy.client.search(request_body) rescue Elasticsearch::Transport::Transport::Errors::NotFound {} end end
# File lib/chewy/search/request.rb, line 1020 def raw_limit_value parameters[:limit].value end
# File lib/chewy/search/request.rb, line 1024 def raw_offset_value parameters[:offset].value end
# File lib/chewy/search/request.rb, line 996 def reset @response, @render, @loader = nil end