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

new(*indexes) click to toggle source

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

==(other) click to toggle source

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

Calls superclass method
# File lib/chewy/search/request.rb, line 95
def ==(other)
  super || other.is_a?(Chewy::Search::Request) ? compare_internals(other) : to_a == other
end
aggregations(value = UNDEFINED)
Alias for: aggs
aggs(value = UNDEFINED) click to toggle source

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
Also aliased as: aggregations
count() click to toggle source

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
delete_all(refresh: true) click to toggle source

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
except(*values) click to toggle source

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
exist?()
Alias for: exists?
exists?() click to toggle source

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
Also aliased as: exist?
find(*ids) click to toggle source

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
Calls superclass method
# 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
first(limit = UNDEFINED) click to toggle source

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
indices(value, *values) click to toggle source

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
inspect() click to toggle source

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
load(options = nil) click to toggle source

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
merge(other) click to toggle source

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
only(*values) click to toggle source

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
parameters() click to toggle source

Underlying parameter storage collection.

@return [Chewy::Search::Parameters]

# File lib/chewy/search/request.rb, line 76
def parameters
  @parameters ||= Parameters.new
end
performed?() click to toggle source

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
pluck(*fields) click to toggle source

Returns and array of values for specified fields. Uses `source` to restrict the list of returned fields. Fields `_id`, `_type` 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
render() click to toggle source

ES request body

@return [Hash] request body

# File lib/chewy/search/request.rb, line 120
def render
  @render ||= parameters.render
end
reorder(value, *values) click to toggle source

@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
response() click to toggle source

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
response=(from_elasticsearch) click to toggle source

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
search_after(value, *values) click to toggle source

@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
suggest(value = UNDEFINED) click to toggle source

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

initialize_clone(origin) click to toggle source
# File lib/chewy/search/request.rb, line 973
def initialize_clone(origin)
  @parameters = origin.parameters.clone
  reset
end

Private Instance Methods

_indices() click to toggle source
# File lib/chewy/search/request.rb, line 1016
def _indices
  parameters[:indices].indices
end
build_response(raw_response) click to toggle source
# File lib/chewy/search/request.rb, line 980
def build_response(raw_response)
  Response.new(raw_response, loader, collection_paginator)
end
chain(&block) click to toggle source
# File lib/chewy/search/request.rb, line 992
def chain(&block)
  clone.tap { |r| r.instance_exec(&block) }
end
collection_paginator() click to toggle source
# File lib/chewy/search/request.rb, line 1043
def collection_paginator
  method(:paginated_collection).to_proc if respond_to?(:paginated_collection, true)
end
compare_internals(other) click to toggle source
# File lib/chewy/search/request.rb, line 984
def compare_internals(other)
  parameters == other.parameters
end
fetch_field(hit, field) click to toggle source
# 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
loader() click to toggle source
# File lib/chewy/search/request.rb, line 1028
def loader
  @loader ||= Loader.new(
    indexes: parameters[:indices].indices,
    **parameters[:load].value
  )
end
modify(name, &block) click to toggle source
# File lib/chewy/search/request.rb, line 988
def modify(name, &block)
  chain { parameters.modify!(name, &block) }
end
notification_payload(additional) click to toggle source
# File lib/chewy/search/request.rb, line 1009
def notification_payload(additional)
  {
    indexes: _indices,
    index: _indices.one? ? _indices.first : _indices
  }.merge(additional)
end
perform(additional = {}) click to toggle source
# 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
raw_limit_value() click to toggle source
# File lib/chewy/search/request.rb, line 1020
def raw_limit_value
  parameters[:limit].value
end
raw_offset_value() click to toggle source
# File lib/chewy/search/request.rb, line 1024
def raw_offset_value
  parameters[:offset].value
end
reset() click to toggle source
# File lib/chewy/search/request.rb, line 996
def reset
  @response, @render, @loader = nil
end