Querying

March 25, 2026 ยท View on GitHub

Composing requests

The request DSL have the same chainable nature as AR. The main class is Chewy::Search::Request.

CitiesIndex.query(match: {name: 'London'})

Main methods of the request DSL are: query, filter and post_filter, it is possible to pass pure query hashes or use elasticsearch-dsl.

CitiesIndex
  .filter(term: {name: 'Bangkok'})
  .query(match: {name: 'London'})
  .query.not(range: {population: {gt: 1_000_000}})

You can query a set of indexes at once:

CitiesIndex.indices(CountriesIndex).query(match: {name: 'Some'})

See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html and https://github.com/elastic/elasticsearch-dsl-ruby for more details.

An important part of requests manipulation is merging. There are 4 methods to perform it: merge, and, or, not. See Chewy::Search::QueryProxy for details. Also, only and except methods help to remove unneeded parts of the request.

Every other request part is covered by a bunch of additional methods, see Chewy::Search::Request for details:

CitiesIndex.limit(10).offset(30).order(:name, {population: {order: :desc}})

Request DSL also provides additional scope actions, like delete_all, exists?, count, pluck, etc.

Pagination

The request DSL supports pagination with Kaminari. An extension is enabled on initialization if Kaminari is available. See Chewy::Search and Chewy::Search::Pagination::Kaminari for details.

Named scopes

Chewy supports named scopes functionality. There is no specialized DSL for named scopes definition, it is simply about defining class methods.

See Chewy::Search::Scoping for details.

Scroll API

Elasticsearch scroll API is utilized by a bunch of methods: scroll_batches, scroll_hits, scroll_wrappers and scroll_objects.

See Chewy::Search::Scrolling for details.

Sorting

Use order to sort results. It accepts field names, hashes for direction, and Elasticsearch sort options:

CitiesIndex.order(:name)                                  # ascending by name
CitiesIndex.order(population: :desc)                      # descending by population
CitiesIndex.order(:_score)                                # by relevance (the default)
CitiesIndex.order(population: {order: :desc, missing: '_last'})

You can chain multiple sort clauses โ€” they apply in order:

CitiesIndex.order(country: :asc, name: :asc)

To sort on a text field without tokenization artifacts, define a keyword sub-field (e.g. title.sorted) in your index mapping and sort on that. See indexing.md for an example.

Error handling

When building search UIs where users type free-text queries, malformed input (unbalanced parentheses, invalid syntax) can cause Elasticsearch to return a 400 error. Rescue it to avoid crashing the page:

begin
  results = CitiesIndex.query(query_string: {query: user_input}).to_a
rescue Elastic::Transport::Transport::Errors::BadRequest => e
  results = []
  # Log or display a user-friendly message
end

Loading objects

It is possible to load ORM/ODM source objects with the objects method. To provide additional loading options use load method:

CitiesIndex.load(scope: -> { active }).to_a # to_a returns `Chewy::Index` wrappers.
CitiesIndex.load(scope: -> { active }).objects # An array of AR source objects.

See Chewy::Search::Loader for more details.

In case when it is necessary to iterate through both of the wrappers and objects simultaneously, object_hash method helps a lot:

scope = CitiesIndex.load(scope: -> { active })
scope.each do |wrapper|
  scope.object_hash[wrapper]
end