{"id":13427913,"url":"https://github.com/toptal/chewy","last_synced_at":"2025-05-13T19:03:06.286Z","repository":{"id":12389630,"uuid":"15042048","full_name":"toptal/chewy","owner":"toptal","description":"High-level Elasticsearch Ruby framework based on the official elasticsearch-ruby client","archived":false,"fork":false,"pushed_at":"2024-10-08T18:54:39.000Z","size":2537,"stargazers_count":1892,"open_issues_count":66,"forks_count":371,"subscribers_count":154,"default_branch":"master","last_synced_at":"2025-05-11T19:02:03.996Z","etag":null,"topics":["elasticsearch","elasticsearch-client","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/toptal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-12-09T08:33:03.000Z","updated_at":"2025-05-07T12:14:25.000Z","dependencies_parsed_at":"2023-02-14T17:15:23.639Z","dependency_job_id":"10299199-7d2d-415a-8fec-692254636828","html_url":"https://github.com/toptal/chewy","commit_stats":{"total_commits":1034,"total_committers":163,"mean_commits":6.343558282208589,"dds":0.5415860735009671,"last_synced_commit":"6a2176f7d92c4818f1a1f572aed339b39aef087e"},"previous_names":[],"tags_count":53,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toptal%2Fchewy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toptal%2Fchewy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toptal%2Fchewy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toptal%2Fchewy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/toptal","download_url":"https://codeload.github.com/toptal/chewy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253840113,"owners_count":21972425,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elasticsearch","elasticsearch-client","ruby"],"created_at":"2024-07-31T01:00:42.447Z","updated_at":"2025-05-13T19:03:06.266Z","avatar_url":"https://github.com/toptal.png","language":"Ruby","readme":"[![Gem Version](https://badge.fury.io/rb/chewy.svg)](http://badge.fury.io/rb/chewy)\n[![GitHub Actions](https://github.com/toptal/chewy/actions/workflows/ruby.yml/badge.svg)](https://github.com/toptal/chewy/actions/workflows/ruby.yml)\n[![Code Climate](https://codeclimate.com/github/toptal/chewy.svg)](https://codeclimate.com/github/toptal/chewy)\n[![Inline docs](http://inch-ci.org/github/toptal/chewy.svg?branch=master)](http://inch-ci.org/github/toptal/chewy)\n\n# Chewy\n\nChewy is an ODM (Object Document Mapper), built on top of [the official Elasticsearch client](https://github.com/elastic/elasticsearch-ruby).\n\n## Why Chewy?\n\nIn this section we'll cover why you might want to use Chewy instead of the official `elasticsearch-ruby` client gem.\n\n* Every index is observable by all the related models.\n\n  Most of the indexed models are related to other and sometimes it is necessary to denormalize this related data and put at the same object. For example, you need to index an array of tags together with an article. Chewy allows you to specify an updateable index for every model separately - so corresponding articles will be reindexed on any tag update.\n\n* Bulk import everywhere.\n\n  Chewy utilizes the bulk ES API for full reindexing or index updates. It also uses atomic updates. All the changed objects are collected inside the atomic block and the index is updated once at the end with all the collected objects. See `Chewy.strategy(:atomic)` for more details.\n\n* Powerful querying DSL.\n\n  Chewy has an ActiveRecord-style query DSL. It is chainable, mergeable and lazy, so you can produce queries in the most efficient way. It also has object-oriented query and filter builders.\n\n* Support for ActiveRecord.\n\n## Installation\n\nAdd this line to your application's `Gemfile`:\n\n    gem 'chewy'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install chewy\n\n## Compatibility\n\n### Ruby\n\nChewy is compatible with MRI 3.0-3.3¹.\n\n\u003e ¹ Ruby 3 is supported with Rails 6.1, 7.0, 7.1 and 7.2\n\n### Elasticsearch compatibility matrix\n\n| Chewy version | Elasticsearch version              |\n| ------------- | ---------------------------------- |\n| 8.0.0         | 8.x                                |\n| 7.2.x         | 7.x                                |\n| 7.1.x         | 7.x                                |\n| 7.0.x         | 6.8, 7.x                           |\n| 6.0.0         | 5.x, 6.x                           |\n| 5.x           | 5.x, limited support for 1.x \u0026 2.x |\n\n**Important:** Chewy doesn't follow SemVer, so you should always\ncheck the release notes before upgrading. The major version is linked to the\nnewest supported Elasticsearch and the minor version bumps may include breaking changes.\n\nSee our [migration guide](migration_guide.md) for detailed upgrade instructions between\nvarious Chewy versions.\n\n### Active Record\n\n6.1, 7.0, 7.1, 7.2 Active Record versions are supported by Chewy.\n\n## Getting Started\n\nChewy provides functionality for Elasticsearch index handling, documents import mappings, index update strategies and chainable query DSL.\n\n### Minimal client setting\n\nCreate `config/initializers/chewy.rb` with this line:\n\n```ruby\nChewy.settings = {host: 'localhost:9250'}\n```\n\nAnd run `rails g chewy:install` to generate `chewy.yml`:\n\n```yaml\n# config/chewy.yml\n# separate environment configs\ntest:\n  host: 'localhost:9250'\n  prefix: 'test'\ndevelopment:\n  host: 'localhost:9200'\n```\n\n### Elasticsearch\n\nMake sure you have Elasticsearch up and running. You can [install](https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html) it locally, but the easiest way is to use [Docker](https://www.docker.com/get-started):\n\n```shell\n$ docker run --rm --name elasticsearch -p 9200:9200 -p 9300:9300 -e \"discovery.type=single-node\" -e \"xpack.security.enabled=false\" elasticsearch:8.15.0\n```\n\n### Security\n\nPlease note that starting from version 8 ElasticSearch has security features enabled by default.\nDocker command above has it disabled for local testing convenience.  If you want to enable it, omit\n`\"xpack.security.enabled=false\"` part from Docker command, and run these command after starting container (container name `es8` assumed):\n\nReset password for `elastic` user:\n```\ndocker container exec es8 '/usr/share/elasticsearch/bin/elasticsearch-reset-password' -u elastic\n```\n\nExtract CA certificate generated by ElasticSearch on first run:\n```\ndocker container cp es8:/usr/share/elasticsearch/config/certs/http_ca.crt tmp/\n```\n\nAnd then add them to settings:\n\n```yaml\n# config/chewy.yml\ndevelopment:\n  host: 'localhost:9200'\n  user: 'elastic'\n  password: 'SomeLongPassword'\n  transport_options:\n    ssl:\n      ca_file: './tmp/http_ca.crt'\n```\n\n### Index\n\nCreate `app/chewy/users_index.rb` with User Index:\n\n```ruby\nclass UsersIndex \u003c Chewy::Index\n  settings analysis: {\n    analyzer: {\n      email: {\n        tokenizer: 'keyword',\n        filter: ['lowercase']\n      }\n    }\n  }\n\n  index_scope User\n  field :first_name\n  field :last_name\n  field :email, analyzer: 'email'\nend\n```\n\n### Model\n\nAdd User model, table and migrate it:\n\n```shell\n$ bundle exec rails g model User first_name last_name email\n$ bundle exec rails db:migrate\n```\n\nAdd `update_index` to app/models/user.rb:\n\n```ruby\nclass User \u003c ApplicationRecord\n  update_index('users') { self }\nend\n```\n\n### Example of data request\n\n1. Once a record is created (could be done via the Rails console), it creates User index too:\n\n```\nUser.create(\n  first_name: \"test1\",\n  last_name: \"test1\",\n  email: 'test1@example.com',\n  # other fields\n)\n# UsersIndex Import (355.3ms) {:index=\u003e1}\n# =\u003e #\u003cUser id: 1, first_name: \"test1\", last_name: \"test1\", email: \"test1@example.com\", # other fields\u003e\n```\n\n2. A query could be exposed at a given `UsersController`:\n\n```ruby\ndef search\n  @users = UsersIndex.query(query_string: { fields: [:first_name, :last_name, :email, ...], query: search_params[:query], default_operator: 'and' })\n  render json: @users.to_json, status: :ok\nend\n\nprivate\n\ndef search_params\n  params.permit(:query, :page, :per)\nend\n```\n\n3. So a request against `http://localhost:3000/users/search?query=test1@example.com` issuing a response like:\n\n```json\n[\n  {\n    \"attributes\":{\n      \"id\":\"1\",\n      \"first_name\":\"test1\",\n      \"last_name\":\"test1\",\n      \"email\":\"test1@example.com\",\n      ...\n      \"_score\":0.9808291,\n      \"_explanation\":null\n    },\n    \"_data\":{\n      \"_index\":\"users\",\n      \"_type\":\"_doc\",\n      \"_id\":\"1\",\n      \"_score\":0.9808291,\n      \"_source\":{\n        \"first_name\":\"test1\",\n        \"last_name\":\"test1\",\n        \"email\":\"test1@example.com\",\n        ...\n      }\n    }\n  }\n]\n```\n\n## Usage and configuration\n\n### Client settings\n\nTo configure the Chewy client you need to add `chewy.rb` file with `Chewy.settings` hash:\n\n```ruby\n# config/initializers/chewy.rb\nChewy.settings = {host: 'localhost:9250'} # do not use environments\n```\n\nAnd add `chewy.yml` configuration file.\n\nYou can create `chewy.yml` manually or run `rails g chewy:install` to generate it:\n\n```yaml\n# config/chewy.yml\n# separate environment configs\ntest:\n  host: 'localhost:9250'\n  prefix: 'test'\ndevelopment:\n  host: 'localhost:9200'\n```\n\nThe resulting config merges both hashes. Client options are passed as is to `Elasticsearch::Transport::Client` except for the `:prefix`, which is used internally by Chewy to create prefixed index names:\n\n```ruby\n  Chewy.settings = {prefix: 'test'}\n  UsersIndex.index_name # =\u003e 'test_users'\n```\n\nThe logger may be set explicitly:\n\n```ruby\nChewy.logger = Logger.new(STDOUT)\n```\n\nSee [config.rb](lib/chewy/config.rb) for more details.\n\n#### AWS Elasticsearch\n\nIf you would like to use AWS's Elasticsearch using an IAM user policy, you will need to sign your requests for the `es:*` action by injecting the appropriate headers passing a proc to `transport_options`.\nYou'll need an additional gem for Faraday middleware: add `gem 'faraday_middleware-aws-sigv4'` to your Gemfile.\n\n```ruby\nrequire 'faraday_middleware/aws_sigv4'\n\nChewy.settings = {\n  host: 'http://my-es-instance-on-aws.us-east-1.es.amazonaws.com:80',\n  port: 80, # 443 for https host\n  transport_options: {\n    headers: { content_type: 'application/json' },\n    proc: -\u003e (f) do\n        f.request :aws_sigv4,\n                  service: 'es',\n                  region: 'us-east-1',\n                  access_key_id: ENV['AWS_ACCESS_KEY'],\n                  secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']\n    end\n  }\n}\n```\n\n#### Index definition\n\n1. Create `/app/chewy/users_index.rb`\n\n  ```ruby\n  class UsersIndex \u003c Chewy::Index\n\n  end\n  ```\n\n2. Define index scope (you can omit this part if you don't need to specify a scope (i.e. use PORO objects for import) or options)\n\n  ```ruby\n  class UsersIndex \u003c Chewy::Index\n    index_scope User.active # or just model instead_of scope: index_scope User\n  end\n  ```\n\n3. Add some mappings\n\n  ```ruby\n  class UsersIndex \u003c Chewy::Index\n    index_scope User.active.includes(:country, :badges, :projects)\n    field :first_name, :last_name # multiple fields without additional options\n    field :email, analyzer: 'email' # Elasticsearch-related options\n    field :country, value: -\u003e(user) { user.country.name } # custom value proc\n    field :badges, value: -\u003e(user) { user.badges.map(\u0026:name) } # passing array values to index\n    field :projects do # the same block syntax for multi_field, if `:type` is specified\n      field :title\n      field :description # default data type is `text`\n      # additional top-level objects passed to value proc:\n      field :categories, value: -\u003e(project, user) { project.categories.map(\u0026:name) if user.active? }\n    end\n    field :rating, type: 'integer' # custom data type\n    field :created, type: 'date', include_in_all: false,\n      value: -\u003e{ created_at } # value proc for source object context\n  end\n  ```\n\n  [See here for mapping definitions](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html).\n\n4. Add some index-related settings. Analyzer repositories might be used as well. See `Chewy::Index.settings` docs for details:\n\n  ```ruby\n  class UsersIndex \u003c Chewy::Index\n    settings analysis: {\n      analyzer: {\n        email: {\n          tokenizer: 'keyword',\n          filter: ['lowercase']\n        }\n      }\n    }\n\n    index_scope User.active.includes(:country, :badges, :projects)\n    root date_detection: false do\n      template 'about_translations.*', type: 'text', analyzer: 'standard'\n\n      field :first_name, :last_name\n      field :email, analyzer: 'email'\n      field :country, value: -\u003e(user) { user.country.name }\n      field :badges, value: -\u003e(user) { user.badges.map(\u0026:name) }\n      field :projects do\n        field :title\n        field :description\n      end\n      field :about_translations, type: 'object' # pass object type explicitly if necessary\n      field :rating, type: 'integer'\n      field :created, type: 'date', include_in_all: false,\n        value: -\u003e{ created_at }\n    end\n  end\n  ```\n\n  [See index settings here](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html).\n  [See root object settings here](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html).\n\n  See [mapping.rb](lib/chewy/index/mapping.rb) for more details.\n\n5. Add model-observing code\n\n  ```ruby\n  class User \u003c ActiveRecord::Base\n    update_index('users') { self } # specifying index and back-reference\n                                        # for updating after user save or destroy\n  end\n\n  class Country \u003c ActiveRecord::Base\n    has_many :users\n\n    update_index('users') { users } # return single object or collection\n  end\n\n  class Project \u003c ActiveRecord::Base\n    update_index('users') { user if user.active? } # you can return even `nil` from the back-reference\n  end\n\n  class Book \u003c ActiveRecord::Base\n    update_index(-\u003e(book) {\"books_#{book.language}\"}) { self } # dynamic index name with proc.\n                                                               # For book with language == \"en\"\n                                                               # this code will generate `books_en`\n  end\n  ```\n\n  Also, you can use the second argument for method name passing:\n\n  ```ruby\n  update_index('users', :self)\n  update_index('users', :users)\n  ```\n\n  In the case of a belongs_to association you may need to update both associated objects, previous and current:\n\n  ```ruby\n  class City \u003c ActiveRecord::Base\n    belongs_to :country\n\n    update_index('cities') { self }\n    update_index 'countries' do\n      previous_changes['country_id'] || country\n    end\n  end\n  ```\n\n### Default import options\n\nEvery index has `default_import_options` configuration to specify, suddenly, default import options:\n\n```ruby\nclass ProductsIndex \u003c Chewy::Index\n  index_scope Post.includes(:tags)\n  default_import_options batch_size: 100, bulk_size: 10.megabytes, refresh: false\n\n  field :name\n  field :tags, value: -\u003e { tags.map(\u0026:name) }\nend\n```\n\nSee [import.rb](lib/chewy/index/import.rb) for available options.\n\n### Multi (nested) and object field types\n\nTo define an objects field you can simply nest fields in the DSL:\n\n```ruby\nfield :projects do\n  field :title\n  field :description\nend\n```\n\nThis will automatically set the type or root field to `object`. You may also specify `type: 'objects'` explicitly.\n\nTo define a multi field you have to specify any type except for `object` or `nested` in the root field:\n\n```ruby\nfield :full_name, type: 'text', value: -\u003e{ full_name.strip } do\n  field :ordered, analyzer: 'ordered'\n  field :untouched, type: 'keyword'\nend\n```\n\nThe `value:` option for internal fields will no longer be effective.\n\n### Geo Point fields\n\nYou can use [Elasticsearch's geo mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-point.html) with the `geo_point` field type, allowing you to query, filter and order by latitude and longitude. You can use the following hash format:\n\n```ruby\nfield :coordinates, type: 'geo_point', value: -\u003e{ {lat: latitude, lon: longitude} }\n```\n\nor by using nested fields:\n\n```ruby\nfield :coordinates, type: 'geo_point' do\n  field :lat, value: -\u003e{ latitude }\n  field :long, value: -\u003e{ longitude }\nend\n```\n\nSee the section on *Script fields* for details on calculating distance in a search.\n\n### Join fields\n\nYou can use a [join field](https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html)\nto implement parent-child relationships between documents.\nIt [replaces the old `parent_id` based parent-child mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html#parent-child-mapping-types)\n\nTo use it, you need to pass `relations` and `join` (with `type` and `id`) options:\n```ruby\nfield :hierarchy_link, type: :join, relations: {question: %i[answer comment], answer: :vote, vote: :subvote}, join: {type: :comment_type, id: :commented_id}\n```\nassuming you have `comment_type` and `commented_id` fields in your model.\n\nNote that when you reindex a parent, its children and grandchildren will be reindexed as well.\nThis may require additional queries to the primary database and to elastisearch.\n\nAlso note that the join field doesn't support crutches (it should be a field directly defined on the model).\n\n### Crutches™ technology\n\nAssume you are defining your index like this (product has_many categories through product_categories):\n\n```ruby\nclass ProductsIndex \u003c Chewy::Index\n  index_scope Product.includes(:categories)\n  field :name\n  field :category_names, value: -\u003e(product) { product.categories.map(\u0026:name) } # or shorter just -\u003e { categories.map(\u0026:name) }\nend\n```\n\nThen the Chewy reindexing flow will look like the following pseudo-code:\n\n```ruby\nProduct.includes(:categories).find_in_batches(1000) do |batch|\n  bulk_body = batch.map do |object|\n    {name: object.name, category_names: object.categories.map(\u0026:name)}.to_json\n  end\n  # here we are sending every batch of data to ES\n  Chewy.client.bulk bulk_body\nend\n```\n\nIf you meet complicated cases when associations are not applicable you can replace Rails associations with Chewy Crutches™ technology:\n\n```ruby\nclass ProductsIndex \u003c Chewy::Index\n  index_scope Product\n  crutch :categories do |collection| # collection here is a current batch of products\n    # data is fetched with a lightweight query without objects initialization\n    data = ProductCategory.joins(:category).where(product_id: collection.map(\u0026:id)).pluck(:product_id, 'categories.name')\n    # then we have to convert fetched data to appropriate format\n    # this will return our data in structure like:\n    # {123 =\u003e ['sweets', 'juices'], 456 =\u003e ['meat']}\n    data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }\n  end\n\n  field :name\n  # simply use crutch-fetched data as a value:\n  field :category_names, value: -\u003e(product, crutches) { crutches[:categories][product.id] }\nend\n```\n\nAn example flow will look like this:\n\n```ruby\nProduct.includes(:categories).find_in_batches(1000) do |batch|\n  crutches[:categories] = ProductCategory.joins(:category).where(product_id: batch.map(\u0026:id)).pluck(:product_id, 'categories.name')\n    .each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }\n\n  bulk_body = batch.map do |object|\n    {name: object.name, category_names: crutches[:categories][object.id]}.to_json\n  end\n  Chewy.client.bulk bulk_body\nend\n```\n\nSo Chewy Crutches™ technology is able to increase your indexing performance in some cases up to a hundredfold or even more depending on your associations complexity.\n\n### Witchcraft™ technology\n\nOne more experimental technology to increase import performance. As far as you know, chewy defines value proc for every imported field in mapping, so at the import time each of these procs is executed on imported object to extract result document to import. It would be great for performance to use one huge whole-document-returning proc instead. So basically the idea or Witchcraft™ technology is to compile a single document-returning proc from the index definition.\n\n```ruby\nindex_scope Product\nwitchcraft!\n\nfield :title\nfield :tags, value: -\u003e { tags.map(\u0026:name) }\nfield :categories do\n  field :name, value: -\u003e (product, category) { category.name }\n  field :type, value: -\u003e (product, category, crutch) { crutch.types[category.name] }\nend\n```\n\nThe index definition above will be compiled to something close to:\n\n```ruby\n-\u003e (object, crutches) do\n  {\n    title: object.title,\n    tags: object.tags.map(\u0026:name),\n    categories: object.categories.map do |object2|\n      {\n        name: object2.name\n        type: crutches.types[object2.name]\n      }\n    end\n  }\nend\n```\n\nAnd don't even ask how is it possible, it is a witchcraft.\nObviously not every type of definition might be compiled. There are some restrictions:\n\n1. Use reasonable formatting to make `method_source` be able to extract field value proc sources.\n2. Value procs with splat arguments are not supported right now.\n3. If you are generating fields dynamically use value proc with arguments, argumentless value procs are not supported yet:\n\n  ```ruby\n  [:first_name, :last_name].each do |name|\n    field name, value: -\u003e (o) { o.send(name) }\n  end\n  ```\n\nHowever, it is quite possible that your index definition will be supported by Witchcraft™ technology out of the box in most of the cases.\n\n### Raw Import\n\nAnother way to speed up import time is Raw Imports. This technology is only available in ActiveRecord adapter. Very often, ActiveRecord model instantiation is what consumes most of the CPU and RAM resources. Precious time is wasted on converting, say, timestamps from strings and then serializing them back to strings. Chewy can operate on raw hashes of data directly obtained from the database. All you need is to provide a way to convert that hash to a lightweight object that mimics the behaviour of the normal ActiveRecord object.\n\n```ruby\nclass LightweightProduct\n  def initialize(attributes)\n    @attributes = attributes\n  end\n\n  # Depending on the database, `created_at` might\n  # be in different formats. In PostgreSQL, for example,\n  # you might see the following format:\n  #   \"2016-03-22 16:23:22\"\n  #\n  # Taking into account that Elastic expects something different,\n  # one might do something like the following, just to avoid\n  # unnecessary String -\u003e DateTime -\u003e String conversion.\n  #\n  #   \"2016-03-22 16:23:22\" -\u003e \"2016-03-22T16:23:22Z\"\n  def created_at\n    @attributes['created_at'].tr(' ', 'T') \u003c\u003c 'Z'\n  end\nend\n\nindex_scope Product\ndefault_import_options raw_import: -\u003e(hash) {\n  LightweightProduct.new(hash)\n}\n\nfield :created_at, 'datetime'\n```\n\nAlso, you can pass `:raw_import` option to the `import` method explicitly.\n\n### Index creation during import\n\nBy default, when you perform import Chewy checks whether an index exists and creates it if it's absent.\nYou can turn off this feature to decrease Elasticsearch hits count.\nTo do so you need to set `skip_index_creation_on_import` parameter to `false` in your `config/chewy.yml`\n\n### Skip record fields during import\n\nYou can use `ignore_blank: true` to skip fields that return `true` for the `.blank?` method:\n\n```ruby\nindex_scope Country\nfield :id\nfield :cities, ignore_blank: true do\n  field :id\n  field :name\n  field :surname, ignore_blank: true\n  field :description\nend\n```\n\n#### Default values for different types\n\nBy default `ignore_blank` is false on every type except `geo_point`.\n\n### Journaling\n\nYou can record all actions that were made to the separate journal index in ElasticSearch.\nWhen you create/update/destroy your documents, it will be saved in this special index.\nIf you make something with a batch of documents (e.g. during index reset) it will be saved as a one record, including primary keys of each document that was affected.\nCommon journal record looks like this:\n\n```json\n{\n  \"action\": \"index\",\n  \"object_id\": [1, 2, 3],\n  \"index_name\": \"...\",\n  \"created_at\": \"\u003ctimestamp\u003e\"\n}\n```\n\nThis feature is turned off by default.\nBut you can turn it on by setting `journal` setting to `true` in `config/chewy.yml`.\nAlso, you can specify journal index name. For example:\n\n```yaml\n# config/chewy.yml\nproduction:\n  journal: true\n  journal_name: my_super_journal\n```\n\nAlso, you can provide this option while you're importing some index:\n\n```ruby\nCityIndex.import journal: true\n```\n\nOr as a default import option for an index:\n\n```ruby\nclass CityIndex\n  index_scope City\n  default_import_options journal: true\nend\n```\n\nYou may be wondering why do you need it? The answer is simple: not to lose the data.\n\nImagine that you reset your index in a zero-downtime manner (to separate index), and in the meantime somebody keeps updating the data frequently (to old index). So all these actions will be written to the journal index and you'll be able to apply them after index reset using the `Chewy::Journal` interface.\n\nWhen enabled, journal can grow to enormous size, consider setting up cron job that would clean it occasionally using [`chewy:journal:clean` rake task](#chewyjournal).\n\n### Index manipulation\n\n```ruby\nUsersIndex.delete # destroy index if it exists\nUsersIndex.delete!\n\nUsersIndex.create\nUsersIndex.create! # use bang or non-bang methods\n\nUsersIndex.purge\nUsersIndex.purge! # deletes then creates index\n\nUsersIndex.import # import with 0 arguments process all the data specified in index_scope definition\nUsersIndex.import User.where('rating \u003e 100') # or import specified users scope\nUsersIndex.import User.where('rating \u003e 100').to_a # or import specified users array\nUsersIndex.import [1, 2, 42] # pass even ids for import, it will be handled in the most effective way\nUsersIndex.import User.where('rating \u003e 100'), update_fields: [:email] # if update fields are specified - it will update their values only with the `update` bulk action\nUsersIndex.import! # raises an exception in case of any import errors\n\nUsersIndex.reset! # purges index and imports default data for all types\n```\n\nIf the passed user is `#destroyed?`, or satisfies a `delete_if` index_scope option, or the specified id does not exist in the database, import will perform delete from index action for this object.\n\n```ruby\nindex_scope User, delete_if: :deleted_at\nindex_scope User, delete_if: -\u003e { deleted_at }\nindex_scope User, delete_if: -\u003e(user) { user.deleted_at }\n```\n\nSee [actions.rb](lib/chewy/index/actions.rb) for more details.\n\n### Index update strategies\n\nAssume you've got the following code:\n\n```ruby\nclass City \u003c ActiveRecord::Base\n  update_index 'cities', :self\nend\n\nclass CitiesIndex \u003c Chewy::Index\n  index_scope City\n  field :name\nend\n```\n\nIf you do something like `City.first.save!` you'll get an UndefinedUpdateStrategy exception instead of the object saving and index updating. This exception forces you to choose an appropriate update strategy for the current context.\n\nIf you want to return to the pre-0.7.0 behavior - just set `Chewy.root_strategy = :bypass`.\n\n#### `:atomic`\n\nThe main strategy here is `:atomic`. Assume you have to update a lot of records in the db.\n\n```ruby\nChewy.strategy(:atomic) do\n  City.popular.map(\u0026:do_some_update_action!)\nend\n```\n\nUsing this strategy delays the index update request until the end of the block. Updated records are aggregated and the index update happens with the bulk API. So this strategy is highly optimized.\n\n#### `:sidekiq`\n\nThis does the same thing as `:atomic`, but asynchronously using sidekiq. Patch `Chewy::Strategy::Sidekiq::Worker` for index updates improving.\n\n```ruby\nChewy.strategy(:sidekiq) do\n  City.popular.map(\u0026:do_some_update_action!)\nend\n```\n\nThe default queue name is `chewy`, you can customize it in settings: `sidekiq.queue_name`\n```\nChewy.settings[:sidekiq] = {queue: :low}\n```\n\n#### `:lazy_sidekiq`\n\nThis does the same thing as `:sidekiq`, but with lazy evaluation. Beware it does not allow you to use any non-persistent record state for indices and conditions because record will be re-fetched from database asynchronously using sidekiq. However for destroying records strategy will fallback to `:sidekiq` because it's not possible to re-fetch deleted records from database.\n\nThe purpose of this strategy is to improve the response time of the code that should update indexes, as it does not only defer actual ES calls to a background job but `update_index` callbacks evaluation (for created and updated objects) too. Similar to `:sidekiq`, index update is asynchronous so this strategy cannot be used when data and index synchronization is required.\n\n```ruby\nChewy.strategy(:lazy_sidekiq) do\n  City.popular.map(\u0026:do_some_update_action!)\nend\n```\n\nThe default queue name is `chewy`, you can customize it in settings: `sidekiq.queue_name`\n```\nChewy.settings[:sidekiq] = {queue: :low}\n```\n\n#### `:delayed_sidekiq`\n\nIt accumulates IDs of records to be reindexed during the latency window in Redis and then performs the reindexing of all accumulated records at once. \nThis strategy is very useful in the case of frequently mutated records. \nIt supports the `update_fields` option, so it will attempt to select just enough data from the database.\n\nKeep in mind, this strategy does not guarantee reindexing in the event of Sidekiq worker termination or an error during the reindexing phase. \nThis behavior is intentional to prevent continuous growth of Redis db.\n\nThere are three options that can be defined in the index:\n```ruby\nclass CitiesIndex...\n  strategy_config delayed_sidekiq: {\n    latency: 3,\n    margin: 2,\n    ttl: 60 * 60 * 24,\n    reindex_wrapper: -\u003e(\u0026reindex) {\n      ActiveRecord::Base.connected_to(role: :reading) { reindex.call }\n    }\n    # latency - will prevent scheduling identical jobs\n    # margin - main purpose is to cover db replication lag by the margin\n    # ttl - a chunk expiration time (in seconds)\n    # reindex_wrapper - lambda that accepts block to wrap that reindex process AR connection block.\n  }\n\n  ...\nend\n```\n\nAlso you can define defaults in the `initializers/chewy.rb`\n```ruby\nChewy.settings = {\n  strategy_config: {\n    delayed_sidekiq: {\n      latency: 3,\n      margin: 2,\n      ttl: 60 * 60 * 24,\n      reindex_wrapper: -\u003e(\u0026reindex) {\n        ActiveRecord::Base.connected_to(role: :reading) { reindex.call }\n      }\n    }\n  }\n}\n\n```\nor in `config/chewy.yml`\n```ruby\n  strategy_config:\n    delayed_sidekiq:\n      latency: 3\n      margin: 2\n      ttl: \u003c%= 60 * 60 * 24 %\u003e\n      # reindex_wrapper setting is not possible here!!! use the initializer instead\n```\n\nYou can use the strategy identically to other strategies\n```ruby\nChewy.strategy(:delayed_sidekiq) do\n  City.popular.map(\u0026:do_some_update_action!)\nend\n```\n\nThe default queue name is `chewy`, you can customize it in settings: `sidekiq.queue_name`\n```\nChewy.settings[:sidekiq] = {queue: :low}\n```\n\nExplicit call of the reindex using `:delayed_sidekiq strategy`\n```ruby\nCitiesIndex.import([1, 2, 3], strategy: :delayed_sidekiq)\n```\n\nExplicit call of the reindex using `:delayed_sidekiq` strategy with `:update_fields` support\n```ruby\nCitiesIndex.import([1, 2, 3], update_fields: [:name], strategy: :delayed_sidekiq)\n```\n\nWhile running tests with delayed_sidekiq strategy and Sidekiq is using a real redis instance that is NOT cleaned up in between tests (via e.g. `Sidekiq.redis(\u0026:flushdb)`), you'll want to cleanup some redis keys in between tests to avoid state leaking and flaky tests. Chewy provides a convenience method for that:\n```ruby\n# it might be a good idea to also add to your testing setup, e.g.: a rspec `before` hook\nChewy::Strategy::DelayedSidekiq.clear_timechunks!\n```\n\n#### `:active_job`\n\nThis does the same thing as `:atomic`, but using ActiveJob. This will inherit the ActiveJob configuration settings including the `active_job.queue_adapter` setting for the environment. Patch `Chewy::Strategy::ActiveJob::Worker` for index updates improving.\n\n```ruby\nChewy.strategy(:active_job) do\n  City.popular.map(\u0026:do_some_update_action!)\nend\n```\n\nThe default queue name is `chewy`, you can customize it in settings: `active_job.queue_name`\n```\nChewy.settings[:active_job] = {queue: :low}\n```\n\n#### `:urgent`\n\nThe following strategy is convenient if you are going to update documents in your index one by one.\n\n```ruby\nChewy.strategy(:urgent) do\n  City.popular.map(\u0026:do_some_update_action!)\nend\n```\n\nThis code will perform `City.popular.count` requests for ES documents update.\n\nIt is convenient for use in e.g. the Rails console with non-block notation:\n\n```ruby\n\u003e Chewy.strategy(:urgent)\n\u003e City.popular.map(\u0026:do_some_update_action!)\n```\n\n#### `:bypass`\n\nWhen the bypass strategy is active the index will not be automatically updated on object save.\n\nFor example, on `City.first.save!` the cities index would not be updated.\n\n#### Nesting\n\nStrategies are designed to allow nesting, so it is possible to redefine it for nested contexts.\n\n```ruby\nChewy.strategy(:atomic) do\n  city1.do_update!\n  Chewy.strategy(:urgent) do\n    city2.do_update!\n    city3.do_update!\n    # there will be 2 update index requests for city2 and city3\n  end\n  city4..do_update!\n  # city1 and city4 will be grouped in one index update request\nend\n```\n\n#### Non-block notation\n\nIt is possible to nest strategies without blocks:\n\n```ruby\nChewy.strategy(:urgent)\ncity1.do_update! # index updated\nChewy.strategy(:bypass)\ncity2.do_update! # update bypassed\nChewy.strategy.pop\ncity3.do_update! # index updated again\n```\n\n#### Designing your own strategies\n\nSee [strategy/base.rb](lib/chewy/strategy/base.rb) for more details. See [strategy/atomic.rb](lib/chewy/strategy/atomic.rb) for an example.\n\n### Rails application strategies integration\n\nThere are a couple of predefined strategies for your Rails application. Initially, the Rails console uses the `:urgent` strategy by default, except in the sandbox case. When you are running sandbox it switches to the `:bypass` strategy to avoid polluting the index.\n\nMigrations are wrapped with the `:bypass` strategy. Because the main behavior implies that indices are reset after migration, there is no need for extra index updates. Also indexing might be broken during migrations because of the outdated schema.\n\nController actions are wrapped with the configurable value of `Chewy.request_strategy` and defaults to `:atomic`. This is done at the middleware level to reduce the number of index update requests inside actions.\n\nIt is also a good idea to set up the `:bypass` strategy inside your test suite and import objects manually only when needed, and use `Chewy.massacre` when needed to flush test ES indices before every example. This will allow you to minimize unnecessary ES requests and reduce overhead.\n\nDeprecation note: since version 8 wildcard removing of indices is disabled by default. You can enable it for a cluster with setting `action.destructive_requires_name` to false.\n\n```ruby\nRSpec.configure do |config|\n  config.before(:suite) do\n    Chewy.strategy(:bypass)\n  end\nend\n```\n\n### Elasticsearch client options\n\nAll connection options, except the `:prefix`, are passed to the `Elasticseach::Client.new` ([chewy/lib/chewy.rb](https://github.com/toptal/chewy/blob/f5bad9f83c21416ac10590f6f34009c645062e89/lib/chewy.rb#L153-L160)):\n\nHere's the relevant Elasticsearch documentation on the subject: https://rubydoc.info/gems/elasticsearch-transport#setting-hosts\n\n### `ActiveSupport::Notifications` support\n\nChewy has notifying the following events:\n\n#### `search_query.chewy` payload\n\n  * `payload[:index]`: requested index class\n  * `payload[:request]`: request hash\n\n#### `import_objects.chewy` payload\n\n  * `payload[:index]`: currently imported index name\n  * `payload[:import]`: imports stats, total imported and deleted objects count:\n\n    ```ruby\n    {index: 30, delete: 5}\n    ```\n\n  * `payload[:errors]`: might not exist. Contains grouped errors with objects ids list:\n\n    ```ruby\n    {index: {\n      'error 1 text' =\u003e ['1', '2', '3'],\n      'error 2 text' =\u003e ['4']\n    }, delete: {\n      'delete error text' =\u003e ['10', '12']\n    }}\n    ```\n\n### NewRelic integration\n\nTo integrate with NewRelic you may use the following example source (config/initializers/chewy.rb):\n\n```ruby\nrequire 'new_relic/agent/instrumentation/evented_subscriber'\n\nclass ChewySubscriber \u003c NewRelic::Agent::Instrumentation::EventedSubscriber\n  def start(name, id, payload)\n    event = ChewyEvent.new(name, Time.current, nil, id, payload)\n    push_event(event)\n  end\n\n  def finish(_name, id, _payload)\n    pop_event(id).finish\n  end\n\n  class ChewyEvent \u003c NewRelic::Agent::Instrumentation::Event\n    OPERATIONS = {\n      'import_objects.chewy' =\u003e 'import',\n      'search_query.chewy' =\u003e 'search',\n      'delete_query.chewy' =\u003e 'delete'\n    }.freeze\n\n    def initialize(*args)\n      super\n      @segment = start_segment\n    end\n\n    def start_segment\n      segment = NewRelic::Agent::Transaction::DatastoreSegment.new product, operation, collection, host, port\n      if (txn = state.current_transaction)\n        segment.transaction = txn\n      end\n      segment.notice_sql @payload[:request].to_s\n      segment.start\n      segment\n    end\n\n    def finish\n      if (txn = state.current_transaction)\n        txn.add_segment @segment\n      end\n      @segment.finish\n    end\n\n    private\n\n    def state\n      @state ||= NewRelic::Agent::TransactionState.tl_get\n    end\n\n    def product\n      'Elasticsearch'\n    end\n\n    def operation\n      OPERATIONS[name]\n    end\n\n    def collection\n      payload.values_at(:type, :index)\n             .reject { |value| value.try(:empty?) }\n             .first\n             .to_s\n    end\n\n    def host\n      Chewy.client.transport.hosts.first[:host]\n    end\n\n    def port\n      Chewy.client.transport.hosts.first[:port]\n    end\n  end\nend\n\nActiveSupport::Notifications.subscribe(/.chewy$/, ChewySubscriber.new)\n```\n\n### Search requests\n\nQuick introduction.\n\n#### Composing requests\n\nThe request DSL have the same chainable nature as AR. The main class is `Chewy::Search::Request`.\n\n```ruby\nCitiesIndex.query(match: {name: 'London'})\n```\n\nMain methods of the request DSL are: `query`, `filter` and `post_filter`, it is possible to pass pure query hashes or use `elasticsearch-dsl`.\n\n```ruby\nCitiesIndex\n  .filter(term: {name: 'Bangkok'})\n  .query(match: {name: 'London'})\n  .query.not(range: {population: {gt: 1_000_000}})\n```\n\nYou can query a set of indexes at once:\n\n```ruby\nCitiesIndex.indices(CountriesIndex).query(match: {name: 'Some'})\n```\n\nSee https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html and https://github.com/elastic/elasticsearch-dsl-ruby for more details.\n\nAn important part of requests manipulation is merging. There are 4 methods to perform it: `merge`, `and`, `or`, `not`. See [Chewy::Search::QueryProxy](lib/chewy/search/query_proxy.rb) for details. Also, `only` and `except` methods help to remove unneeded parts of the request.\n\nEvery other request part is covered by a bunch of additional methods, see [Chewy::Search::Request](lib/chewy/search/request.rb) for details:\n\n```ruby\nCitiesIndex.limit(10).offset(30).order(:name, {population: {order: :desc}})\n```\n\nRequest DSL also provides additional scope actions, like `delete_all`, `exists?`, `count`, `pluck`, etc.\n\n#### Pagination\n\nThe request DSL supports pagination with `Kaminari`. An extension is enabled on initialization if `Kaminari` is available. See [Chewy::Search](lib/chewy/search.rb) and [Chewy::Search::Pagination::Kaminari](lib/chewy/search/pagination/kaminari.rb) for details.\n\n#### Named scopes\n\nChewy supports named scopes functionality. There is no specialized DSL for named scopes definition, it is simply about defining class methods.\n\nSee [Chewy::Search::Scoping](lib/chewy/search/scoping.rb) for details.\n\n#### Scroll API\n\nElasticSearch scroll API is utilized by a bunch of methods: `scroll_batches`, `scroll_hits`, `scroll_wrappers` and `scroll_objects`.\n\nSee [Chewy::Search::Scrolling](lib/chewy/search/scrolling.rb) for details.\n\n#### Loading objects\n\nIt is possible to load ORM/ODM source objects with the `objects` method. To provide additional loading options use `load` method:\n\n```ruby\nCitiesIndex.load(scope: -\u003e { active }).to_a # to_a returns `Chewy::Index` wrappers.\nCitiesIndex.load(scope: -\u003e { active }).objects # An array of AR source objects.\n```\n\nSee [Chewy::Search::Loader](lib/chewy/search/loader.rb) for more details.\n\nIn case when it is necessary to iterate through both of the wrappers and objects simultaneously, `object_hash` method helps a lot:\n\n```ruby\nscope = CitiesIndex.load(scope: -\u003e { active })\nscope.each do |wrapper|\n  scope.object_hash[wrapper]\nend\n```\n\n### Rake tasks\n\nFor a Rails application, some index-maintaining rake tasks are defined.\n\n#### `chewy:reset`\n\nPerforms zero-downtime reindexing as described [here](https://www.elastic.co/blog/changing-mapping-with-zero-downtime). So the rake task creates a new index with unique suffix and then simply aliases it to the common index name. The previous index is deleted afterwards (see `Chewy::Index.reset!` for more details).\n\n```bash\nrake chewy:reset # resets all the existing indices\nrake chewy:reset[users] # resets UsersIndex only\nrake chewy:reset[users,cities] # resets UsersIndex and CitiesIndex\nrake chewy:reset[-users,cities] # resets every index in the application except specified ones\n```\n\n#### `chewy:upgrade`\n\nPerforms reset exactly the same way as `chewy:reset` does, but only when the index specification (setting or mapping) was changed.\n\nIt works only when index specification is locked in `Chewy::Stash::Specification` index. The first run will reset all indexes and lock their specifications.\n\nSee [Chewy::Stash::Specification](lib/chewy/stash.rb) and [Chewy::Index::Specification](lib/chewy/index/specification.rb) for more details.\n\n\n```bash\nrake chewy:upgrade # upgrades all the existing indices\nrake chewy:upgrade[users] # upgrades UsersIndex only\nrake chewy:upgrade[users,cities] # upgrades UsersIndex and CitiesIndex\nrake chewy:upgrade[-users,cities] # upgrades every index in the application except specified ones\n```\n\n#### `chewy:update`\n\nIt doesn't create indexes, it simply imports everything to the existing ones and fails if the index was not created before.\n\n```bash\nrake chewy:update # updates all the existing indices\nrake chewy:update[users] # updates UsersIndex only\nrake chewy:update[users,cities] # updates UsersIndex and CitiesIndex\nrake chewy:update[-users,cities] # updates every index in the application except UsersIndex and CitiesIndex\n```\n\n#### `chewy:sync`\n\nProvides a way to synchronize outdated indexes with the source quickly and without doing a full reset. By default field `updated_at` is used to find outdated records, but this could be customized by `outdated_sync_field` as described at [Chewy::Index::Syncer](lib/chewy/index/syncer.rb).\n\nArguments are similar to the ones taken by `chewy:update` task.\n\nSee [Chewy::Index::Syncer](lib/chewy/index/syncer.rb) for more details.\n\n```bash\nrake chewy:sync # synchronizes all the existing indices\nrake chewy:sync[users] # synchronizes UsersIndex only\nrake chewy:sync[users,cities] # synchronizes UsersIndex and CitiesIndex\nrake chewy:sync[-users,cities] # synchronizes every index in the application except except UsersIndex and CitiesIndex\n```\n\n#### `chewy:deploy`\n\nThis rake task is especially useful during the production deploy. It is a combination of `chewy:upgrade` and `chewy:sync` and the latter is called only for the indexes that were not reset during the first stage.\n\nIt is not possible to specify any particular indexes for this task as it doesn't make much sense.\n\nRight now the approach is that if some data had been updated, but index definition was not changed (no changes satisfying the synchronization algorithm were done), it would be much faster to perform manual partial index update inside data migrations or even manually after the deploy.\n\nAlso, there is always full reset alternative with `rake chewy:reset`.\n\n#### `chewy:create_missing_indexes`\n\nThis rake task creates newly defined indexes in ElasticSearch and skips existing ones. Useful for production-like environments.\n\n#### Parallelizing rake tasks\n\nEvery task described above has its own parallel version. Every parallel rake task takes the number for processes for execution as the first argument and the rest of the arguments are exactly the same as for the non-parallel task version.\n\n[https://github.com/grosser/parallel](https://github.com/grosser/parallel) gem is required to use these tasks.\n\nIf the number of processes is not specified explicitly - `parallel` gem tries to automatically derive the number of processes to use.\n\n```bash\nrake chewy:parallel:reset\nrake chewy:parallel:upgrade[4]\nrake chewy:parallel:update[4,cities]\nrake chewy:parallel:sync[4,-users]\nrake chewy:parallel:deploy[4] # performs parallel upgrade and parallel sync afterwards\n```\n\n#### `chewy:journal`\n\nThis namespace contains two tasks for the journal manipulations: `chewy:journal:apply` and `chewy:journal:clean`. Both are taking time as the first argument (optional for clean) and a list of indexes exactly as the tasks above. Time can be in any format parsable by ActiveSupport.\n\n```bash\nrake chewy:journal:apply[\"$(date -v-1H -u +%FT%TZ)\"] # apply journaled changes for the past hour\nrake chewy:journal:apply[\"$(date -v-1H -u +%FT%TZ)\",users] # apply journaled changes for the past hour on UsersIndex only\n```\n\nWhen the size of the journal becomes very large, the classical way of deletion would be obstructive and resource consuming. Fortunately, Chewy internally uses [delete-by-query](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/docs-delete-by-query.html#docs-delete-by-query-task-api) ES function which supports async execution with batching and [throttling](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html#docs-delete-by-query-throttle).\n\nThe available options, which can be set by ENV variables, are listed below:\n* `WAIT_FOR_COMPLETION` - a boolean flag. It controls async execution. It waits by default. When set to `false` (`0`, `f`, `false` or `off` in any case spelling is accepted as `false`), Elasticsearch performs some preflight checks, launches the request, and returns a task reference you can use to cancel the task or get its status.\n* `REQUESTS_PER_SECOND` - float. The throttle for this request in sub-requests per second. No throttling is enforced by default.\n* `SCROLL_SIZE` - integer. The number of documents to be deleted in single sub-request. The default batch size is 1000.\n\n```bash\nrake chewy:journal:clean WAIT_FOR_COMPLETION=false REQUESTS_PER_SECOND=10 SCROLL_SIZE=5000\n```\n\n### RSpec integration\n\nJust add `require 'chewy/rspec'` to your spec_helper.rb and you will get additional features:\n\n[update_index](lib/chewy/rspec/update_index.rb) helper\n`mock_elasticsearch_response` helper to mock elasticsearch response\n`mock_elasticsearch_response_sources` helper to mock elasticsearch response sources\n`build_query` matcher to compare request and expected query (returns `true`/`false`)\n\nTo use `mock_elasticsearch_response` and `mock_elasticsearch_response_sources` helpers add `include Chewy::Rspec::Helpers` to your tests.\n\nSee [chewy/rspec/](lib/chewy/rspec/) for more details.\n\n### Minitest integration\n\nAdd `require 'chewy/minitest'` to your test_helper.rb, and then for tests which you'd like indexing test hooks, `include Chewy::Minitest::Helpers`.\n\nSince you can set `:bypass` strategy for test suites and manually handle import for the index and manually flush test indices using `Chewy.massacre`. This will help reduce unnecessary ES requests\n\nBut if you require chewy to index/update model regularly in your test suite then you can specify `:urgent` strategy for documents indexing. Add `Chewy.strategy(:urgent)` to test_helper.rb.\n\nAlso, you can use additional helpers:\n\n`mock_elasticsearch_response` to mock elasticsearch response\n`mock_elasticsearch_response_sources` to mock elasticsearch response sources\n`assert_elasticsearch_query` to compare request and expected query (returns `true`/`false`)\n\nSee [chewy/minitest/](lib/chewy/minitest/) for more details.\n\n### DatabaseCleaner\n\nIf you use `DatabaseCleaner` in your tests with [the `transaction` strategy](https://github.com/DatabaseCleaner/database_cleaner#how-to-use), you may run into the problem that `ActiveRecord`'s models are not indexed automatically on save despite the fact that you set the callbacks to do this with the `update_index` method. The issue arises because `chewy` indices data on `after_commit` run as default, but all `after_commit` callbacks are not run with the `DatabaseCleaner`'s' `transaction` strategy. You can solve this issue by changing the `Chewy.use_after_commit_callbacks` option. Just add the following initializer in your Rails application:\n\n```ruby\n#config/initializers/chewy.rb\nChewy.use_after_commit_callbacks = !Rails.env.test?\n```\n\n### Pre-request Filter\n\nShould you need to inspect the query prior to it being dispatched to ElasticSearch during any queries, you can use the `before_es_request_filter`. `before_es_request_filter` is a callable object, as demonstrated below:\n\n```ruby\nChewy.before_es_request_filter = -\u003e (method_name, args, kw_args) { ... }\n```\n\nWhile using the `before_es_request_filter`, please consider the following:\n\n* `before_es_request_filter` acts as a simple proxy before any request made via the    `ElasticSearch::Client`. The arguments passed to this filter include:\n  * `method_name` -  The name of the method being called. Examples are search, count, bulk and etc.\n  * `args` and `kw_args` - These are the positional arguments provided in the method call.\n* The operation is synchronous, so avoid executing any heavy or time-consuming operations within the filter to prevent performance degradation.\n* The return value of the proc is disregarded. This filter is intended for inspection or modification of the query rather than generating a response.\n* Any exception raised inside the callback will propagate upward and halt the execution of the query. It is essential to handle potential errors adequately to ensure the stability of your search functionality.\n\n### Import scope clean-up behavior\n\nWhenever you set the `import_scope` for the index, in the case of ActiveRecord,\noptions for order, offset and limit will be removed. You can set the behavior of\nchewy, before the clean-up itself.\n\nThe default behavior is a warning sent to the Chewy logger (`:warn`). Another more\nrestrictive option is raising an exception (`:raise`). Both options have a\nnegative impact on performance since verifying whether the code uses any of\nthese options requires building AREL query.\n\nTo avoid the loading time impact, you can ignore the check (`:ignore`) before\nthe clean-up.\n\n```\nChewy.import_scope_cleanup_behavior = :ignore\n```\n\n## Contributing\n\n1. Fork it (http://github.com/toptal/chewy/fork)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Implement your changes, cover it with specs and make sure old specs are passing\n4. Commit your changes (`git commit -am 'Add some feature'`)\n5. Push to the branch (`git push origin my-new-feature`)\n6. Create new Pull Request\n\nUse the following Rake tasks to control the Elasticsearch cluster while developing, if you prefer native Elasticsearch installation over the dockerized one:\n\n```bash\nrake elasticsearch:start # start Elasticsearch cluster on 9250 port for tests\nrake elasticsearch:stop # stop Elasticsearch\n```\n\n## Copyright\n\nCopyright (c) 2013-2021 Toptal, LLC. See [LICENSE.txt](LICENSE.txt) for\nfurther details.\n","funding_links":[],"categories":["Searching","Ruby","搜索","Search","Uncategorized"],"sub_categories":["Omniauth","Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoptal%2Fchewy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoptal%2Fchewy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoptal%2Fchewy/lists"}