searchkick 5.5.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +25 -0
 - data/README.md +249 -209
 - data/lib/searchkick/bulk_reindex_job.rb +3 -5
 - data/lib/searchkick/hash_wrapper.rb +30 -9
 - data/lib/searchkick/index.rb +26 -16
 - data/lib/searchkick/index_options.rb +34 -18
 - data/lib/searchkick/indexer.rb +10 -2
 - data/lib/searchkick/log_subscriber.rb +1 -1
 - data/lib/searchkick/model.rb +18 -8
 - data/lib/searchkick/multi_search.rb +7 -2
 - data/lib/searchkick/process_batch_job.rb +2 -2
 - data/lib/searchkick/process_queue_job.rb +4 -3
 - data/lib/searchkick/query.rb +90 -100
 - data/lib/searchkick/record_data.rb +19 -0
 - data/lib/searchkick/record_indexer.rb +20 -10
 - data/lib/searchkick/reindex_queue.rb +1 -24
 - data/lib/searchkick/reindex_v2_job.rb +3 -3
 - data/lib/searchkick/relation.rb +504 -72
 - data/lib/searchkick/relation_indexer.rb +39 -10
 - data/lib/searchkick/results.rb +14 -9
 - data/lib/searchkick/version.rb +1 -1
 - data/lib/searchkick.rb +12 -31
 - metadata +4 -18
 
    
        data/README.md
    CHANGED
    
    | 
         @@ -43,14 +43,13 @@ Check out [Searchjoy](https://github.com/ankane/searchjoy) for analytics and [Au 
     | 
|
| 
       43 
43 
     | 
    
         
             
            - [Reference](#reference)
         
     | 
| 
       44 
44 
     | 
    
         
             
            - [Contributing](#contributing)
         
     | 
| 
       45 
45 
     | 
    
         | 
| 
      
 46 
     | 
    
         
            +
            Searchkick 6.0 was recently released! See [how to upgrade](#upgrading)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
       46 
48 
     | 
    
         
             
            ## Getting Started
         
     | 
| 
       47 
49 
     | 
    
         | 
| 
       48 
50 
     | 
    
         
             
            Install [Elasticsearch](https://www.elastic.co/downloads/elasticsearch) or [OpenSearch](https://opensearch.org/downloads.html). For Homebrew, use:
         
     | 
| 
       49 
51 
     | 
    
         | 
| 
       50 
52 
     | 
    
         
             
            ```sh
         
     | 
| 
       51 
     | 
    
         
            -
            brew install elastic/tap/elasticsearch-full
         
     | 
| 
       52 
     | 
    
         
            -
            brew services start elasticsearch-full
         
     | 
| 
       53 
     | 
    
         
            -
            # or
         
     | 
| 
       54 
53 
     | 
    
         
             
            brew install opensearch
         
     | 
| 
       55 
54 
     | 
    
         
             
            brew services start opensearch
         
     | 
| 
       56 
55 
     | 
    
         
             
            ```
         
     | 
| 
         @@ -64,9 +63,9 @@ gem "elasticsearch"   # select one 
     | 
|
| 
       64 
63 
     | 
    
         
             
            gem "opensearch-ruby" # select one
         
     | 
| 
       65 
64 
     | 
    
         
             
            ```
         
     | 
| 
       66 
65 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
            The latest version works with Elasticsearch  
     | 
| 
      
 66 
     | 
    
         
            +
            The latest version works with Elasticsearch 8 and 9 and OpenSearch 2 and 3. For Elasticsearch 7 and OpenSearch 1, use version 5.5.2 and [this readme](https://github.com/ankane/searchkick/blob/v5.5.2/README.md).
         
     | 
| 
       68 
67 
     | 
    
         | 
| 
       69 
     | 
    
         
            -
            Add searchkick to models you want to search.
         
     | 
| 
      
 68 
     | 
    
         
            +
            Add `searchkick` to models you want to search.
         
     | 
| 
       70 
69 
     | 
    
         | 
| 
       71 
70 
     | 
    
         
             
            ```ruby
         
     | 
| 
       72 
71 
     | 
    
         
             
            class Product < ApplicationRecord
         
     | 
| 
         @@ -96,19 +95,19 @@ Searchkick supports the complete [Elasticsearch Search API](https://www.elastic. 
     | 
|
| 
       96 
95 
     | 
    
         
             
            Query like SQL
         
     | 
| 
       97 
96 
     | 
    
         | 
| 
       98 
97 
     | 
    
         
             
            ```ruby
         
     | 
| 
       99 
     | 
    
         
            -
            Product.search("apples" 
     | 
| 
      
 98 
     | 
    
         
            +
            Product.search("apples").where(in_stock: true).limit(10).offset(50)
         
     | 
| 
       100 
99 
     | 
    
         
             
            ```
         
     | 
| 
       101 
100 
     | 
    
         | 
| 
       102 
101 
     | 
    
         
             
            Search specific fields
         
     | 
| 
       103 
102 
     | 
    
         | 
| 
       104 
103 
     | 
    
         
             
            ```ruby
         
     | 
| 
       105 
     | 
    
         
            -
            fields: 
     | 
| 
      
 104 
     | 
    
         
            +
            fields(:name, :brand)
         
     | 
| 
       106 
105 
     | 
    
         
             
            ```
         
     | 
| 
       107 
106 
     | 
    
         | 
| 
       108 
107 
     | 
    
         
             
            Where
         
     | 
| 
       109 
108 
     | 
    
         | 
| 
       110 
109 
     | 
    
         
             
            ```ruby
         
     | 
| 
       111 
     | 
    
         
            -
            where 
     | 
| 
      
 110 
     | 
    
         
            +
            where(
         
     | 
| 
       112 
111 
     | 
    
         
             
              expires_at: {gt: Time.now},    # lt, gte, lte also available
         
     | 
| 
       113 
112 
     | 
    
         
             
              orders_count: 1..10,           # equivalent to {gte: 1, lte: 10}
         
     | 
| 
       114 
113 
     | 
    
         
             
              aisle_id: [25, 30],            # in
         
     | 
| 
         @@ -123,13 +122,13 @@ where: { 
     | 
|
| 
       123 
122 
     | 
    
         
             
              _not: {store_id: 1},           # negate a condition
         
     | 
| 
       124 
123 
     | 
    
         
             
              _or: [{in_stock: true}, {backordered: true}],
         
     | 
| 
       125 
124 
     | 
    
         
             
              _and: [{in_stock: true}, {backordered: true}]
         
     | 
| 
       126 
     | 
    
         
            -
             
     | 
| 
      
 125 
     | 
    
         
            +
            )
         
     | 
| 
       127 
126 
     | 
    
         
             
            ```
         
     | 
| 
       128 
127 
     | 
    
         | 
| 
       129 
128 
     | 
    
         
             
            Order
         
     | 
| 
       130 
129 
     | 
    
         | 
| 
       131 
130 
     | 
    
         
             
            ```ruby
         
     | 
| 
       132 
     | 
    
         
            -
            order 
     | 
| 
      
 131 
     | 
    
         
            +
            order(_score: :desc) # most relevant first - default
         
     | 
| 
       133 
132 
     | 
    
         
             
            ```
         
     | 
| 
       134 
133 
     | 
    
         | 
| 
       135 
134 
     | 
    
         
             
            [All of these sort options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html)
         
     | 
| 
         @@ -137,13 +136,13 @@ order: {_score: :desc} # most relevant first - default 
     | 
|
| 
       137 
136 
     | 
    
         
             
            Limit / offset
         
     | 
| 
       138 
137 
     | 
    
         | 
| 
       139 
138 
     | 
    
         
             
            ```ruby
         
     | 
| 
       140 
     | 
    
         
            -
            limit 
     | 
| 
      
 139 
     | 
    
         
            +
            limit(20).offset(40)
         
     | 
| 
       141 
140 
     | 
    
         
             
            ```
         
     | 
| 
       142 
141 
     | 
    
         | 
| 
       143 
142 
     | 
    
         
             
            Select
         
     | 
| 
       144 
143 
     | 
    
         | 
| 
       145 
144 
     | 
    
         
             
            ```ruby
         
     | 
| 
       146 
     | 
    
         
            -
            select: 
     | 
| 
      
 145 
     | 
    
         
            +
            select(:name)
         
     | 
| 
       147 
146 
     | 
    
         
             
            ```
         
     | 
| 
       148 
147 
     | 
    
         | 
| 
       149 
148 
     | 
    
         
             
            [These source filtering options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html#source-filtering)
         
     | 
| 
         @@ -162,7 +161,7 @@ results.each { |result| ... } 
     | 
|
| 
       162 
161 
     | 
    
         
             
            By default, ids are fetched from the search server and records are fetched from your database. To fetch everything from the search server, use:
         
     | 
| 
       163 
162 
     | 
    
         | 
| 
       164 
163 
     | 
    
         
             
            ```ruby
         
     | 
| 
       165 
     | 
    
         
            -
            Product.search("apples" 
     | 
| 
      
 164 
     | 
    
         
            +
            Product.search("apples").load(false)
         
     | 
| 
       166 
165 
     | 
    
         
             
            ```
         
     | 
| 
       167 
166 
     | 
    
         | 
| 
       168 
167 
     | 
    
         
             
            Get total results
         
     | 
| 
         @@ -190,28 +189,28 @@ results.response 
     | 
|
| 
       190 
189 
     | 
    
         
             
            Boost important fields
         
     | 
| 
       191 
190 
     | 
    
         | 
| 
       192 
191 
     | 
    
         
             
            ```ruby
         
     | 
| 
       193 
     | 
    
         
            -
            fields 
     | 
| 
      
 192 
     | 
    
         
            +
            fields("title^10", "description")
         
     | 
| 
       194 
193 
     | 
    
         
             
            ```
         
     | 
| 
       195 
194 
     | 
    
         | 
| 
       196 
195 
     | 
    
         
             
            Boost by the value of a field (field must be numeric)
         
     | 
| 
       197 
196 
     | 
    
         | 
| 
       198 
197 
     | 
    
         
             
            ```ruby
         
     | 
| 
       199 
     | 
    
         
            -
            boost_by: 
     | 
| 
       200 
     | 
    
         
            -
            boost_by 
     | 
| 
      
 198 
     | 
    
         
            +
            boost_by(:orders_count) # give popular documents a little boost
         
     | 
| 
      
 199 
     | 
    
         
            +
            boost_by(orders_count: {factor: 10}) # default factor is 1
         
     | 
| 
       201 
200 
     | 
    
         
             
            ```
         
     | 
| 
       202 
201 
     | 
    
         | 
| 
       203 
202 
     | 
    
         
             
            Boost matching documents
         
     | 
| 
       204 
203 
     | 
    
         | 
| 
       205 
204 
     | 
    
         
             
            ```ruby
         
     | 
| 
       206 
     | 
    
         
            -
            boost_where 
     | 
| 
       207 
     | 
    
         
            -
            boost_where 
     | 
| 
       208 
     | 
    
         
            -
            boost_where 
     | 
| 
      
 205 
     | 
    
         
            +
            boost_where(user_id: 1)
         
     | 
| 
      
 206 
     | 
    
         
            +
            boost_where(user_id: {value: 1, factor: 100}) # default factor is 1000
         
     | 
| 
      
 207 
     | 
    
         
            +
            boost_where(user_id: [{value: 1, factor: 100}, {value: 2, factor: 200}])
         
     | 
| 
       209 
208 
     | 
    
         
             
            ```
         
     | 
| 
       210 
209 
     | 
    
         | 
| 
       211 
210 
     | 
    
         
             
            Boost by recency
         
     | 
| 
       212 
211 
     | 
    
         | 
| 
       213 
212 
     | 
    
         
             
            ```ruby
         
     | 
| 
       214 
     | 
    
         
            -
            boost_by_recency 
     | 
| 
      
 213 
     | 
    
         
            +
            boost_by_recency(created_at: {scale: "7d", decay: 0.5})
         
     | 
| 
       215 
214 
     | 
    
         
             
            ```
         
     | 
| 
       216 
215 
     | 
    
         | 
| 
       217 
216 
     | 
    
         
             
            You can also boost by:
         
     | 
| 
         @@ -233,7 +232,7 @@ Plays nicely with kaminari and will_paginate. 
     | 
|
| 
       233 
232 
     | 
    
         | 
| 
       234 
233 
     | 
    
         
             
            ```ruby
         
     | 
| 
       235 
234 
     | 
    
         
             
            # controller
         
     | 
| 
       236 
     | 
    
         
            -
            @products = Product.search("milk" 
     | 
| 
      
 235 
     | 
    
         
            +
            @products = Product.search("milk").page(params[:page]).per_page(20)
         
     | 
| 
       237 
236 
     | 
    
         
             
            ```
         
     | 
| 
       238 
237 
     | 
    
         | 
| 
       239 
238 
     | 
    
         
             
            View with kaminari
         
     | 
| 
         @@ -259,7 +258,7 @@ Product.search("fresh honey") # fresh AND honey 
     | 
|
| 
       259 
258 
     | 
    
         
             
            To change this, use:
         
     | 
| 
       260 
259 
     | 
    
         | 
| 
       261 
260 
     | 
    
         
             
            ```ruby
         
     | 
| 
       262 
     | 
    
         
            -
            Product.search("fresh honey" 
     | 
| 
      
 261 
     | 
    
         
            +
            Product.search("fresh honey").operator("or") # fresh OR honey
         
     | 
| 
       263 
262 
     | 
    
         
             
            ```
         
     | 
| 
       264 
263 
     | 
    
         | 
| 
       265 
264 
     | 
    
         
             
            By default, results must match the entire word - `back` will not match `backpack`. You can change this behavior with:
         
     | 
| 
         @@ -273,7 +272,7 @@ end 
     | 
|
| 
       273 
272 
     | 
    
         
             
            And to search (after you reindex):
         
     | 
| 
       274 
273 
     | 
    
         | 
| 
       275 
274 
     | 
    
         
             
            ```ruby
         
     | 
| 
       276 
     | 
    
         
            -
            Product.search("back" 
     | 
| 
      
 275 
     | 
    
         
            +
            Product.search("back").fields(:name).match(:word_start)
         
     | 
| 
       277 
276 
     | 
    
         
             
            ```
         
     | 
| 
       278 
277 
     | 
    
         | 
| 
       279 
278 
     | 
    
         
             
            Available options are:
         
     | 
| 
         @@ -293,7 +292,7 @@ The default is `:word`. The most matches will happen with `:word_middle`. 
     | 
|
| 
       293 
292 
     | 
    
         
             
            To specify different matching for different fields, use:
         
     | 
| 
       294 
293 
     | 
    
         | 
| 
       295 
294 
     | 
    
         
             
            ```ruby
         
     | 
| 
       296 
     | 
    
         
            -
            Product.search(query 
     | 
| 
      
 295 
     | 
    
         
            +
            Product.search(query).fields({name: :word_start}, {brand: :word_middle})
         
     | 
| 
       297 
296 
     | 
    
         
             
            ```
         
     | 
| 
       298 
297 
     | 
    
         | 
| 
       299 
298 
     | 
    
         
             
            ### Exact Matches
         
     | 
| 
         @@ -301,7 +300,7 @@ Product.search(query, fields: [{name: :word_start}, {brand: :word_middle}]) 
     | 
|
| 
       301 
300 
     | 
    
         
             
            To match a field exactly (case-sensitive), use:
         
     | 
| 
       302 
301 
     | 
    
         | 
| 
       303 
302 
     | 
    
         
             
            ```ruby
         
     | 
| 
       304 
     | 
    
         
            -
            Product.search(query 
     | 
| 
      
 303 
     | 
    
         
            +
            Product.search(query).fields({name: :exact})
         
     | 
| 
       305 
304 
     | 
    
         
             
            ```
         
     | 
| 
       306 
305 
     | 
    
         | 
| 
       307 
306 
     | 
    
         
             
            ### Phrase Matches
         
     | 
| 
         @@ -309,7 +308,7 @@ Product.search(query, fields: [{name: :exact}]) 
     | 
|
| 
       309 
308 
     | 
    
         
             
            To only match the exact order, use:
         
     | 
| 
       310 
309 
     | 
    
         | 
| 
       311 
310 
     | 
    
         
             
            ```ruby
         
     | 
| 
       312 
     | 
    
         
            -
            Product.search("fresh honey" 
     | 
| 
      
 311 
     | 
    
         
            +
            Product.search("fresh honey").match(:phrase)
         
     | 
| 
       313 
312 
     | 
    
         
             
            ```
         
     | 
| 
       314 
313 
     | 
    
         | 
| 
       315 
314 
     | 
    
         
             
            ### Stemming and Language
         
     | 
| 
         @@ -385,11 +384,7 @@ search_synonyms: ["lightbulb => halogenlamp"] 
     | 
|
| 
       385 
384 
     | 
    
         | 
| 
       386 
385 
     | 
    
         
             
            ### Dynamic Synonyms
         
     | 
| 
       387 
386 
     | 
    
         | 
| 
       388 
     | 
    
         
            -
            The above approach works well when your synonym list is static, but in practice, this is often not the case. When you analyze search conversions, you often want to add new synonyms without a full reindex.
         
     | 
| 
       389 
     | 
    
         
            -
             
     | 
| 
       390 
     | 
    
         
            -
            #### Elasticsearch 7.3+ and OpenSearch
         
     | 
| 
       391 
     | 
    
         
            -
             
     | 
| 
       392 
     | 
    
         
            -
            For Elasticsearch 7.3+ and OpenSearch, we recommend placing synonyms in a file on the search server (in the `config` directory). This allows you to reload synonyms without reindexing.
         
     | 
| 
      
 387 
     | 
    
         
            +
            The above approach works well when your synonym list is static, but in practice, this is often not the case. When you analyze search conversions, you often want to add new synonyms without a full reindex. We recommend placing synonyms in a file on the search server (in the `config` directory). This allows you to reload synonyms without reindexing.
         
     | 
| 
       393 
388 
     | 
    
         | 
| 
       394 
389 
     | 
    
         
             
            ```txt
         
     | 
| 
       395 
390 
     | 
    
         
             
            pop, soda
         
     | 
| 
         @@ -410,29 +405,6 @@ And reload with: 
     | 
|
| 
       410 
405 
     | 
    
         
             
            Product.search_index.reload_synonyms
         
     | 
| 
       411 
406 
     | 
    
         
             
            ```
         
     | 
| 
       412 
407 
     | 
    
         | 
| 
       413 
     | 
    
         
            -
            #### Elasticsearch < 7.3
         
     | 
| 
       414 
     | 
    
         
            -
             
     | 
| 
       415 
     | 
    
         
            -
            You can use a library like [ActsAsTaggableOn](https://github.com/mbleigh/acts-as-taggable-on) and do:
         
     | 
| 
       416 
     | 
    
         
            -
             
     | 
| 
       417 
     | 
    
         
            -
            ```ruby
         
     | 
| 
       418 
     | 
    
         
            -
            class Product < ApplicationRecord
         
     | 
| 
       419 
     | 
    
         
            -
              acts_as_taggable
         
     | 
| 
       420 
     | 
    
         
            -
              scope :search_import, -> { includes(:tags) }
         
     | 
| 
       421 
     | 
    
         
            -
             
     | 
| 
       422 
     | 
    
         
            -
              def search_data
         
     | 
| 
       423 
     | 
    
         
            -
                {
         
     | 
| 
       424 
     | 
    
         
            -
                  name_tagged: "#{name} #{tags.map(&:name).join(" ")}"
         
     | 
| 
       425 
     | 
    
         
            -
                }
         
     | 
| 
       426 
     | 
    
         
            -
              end
         
     | 
| 
       427 
     | 
    
         
            -
            end
         
     | 
| 
       428 
     | 
    
         
            -
            ```
         
     | 
| 
       429 
     | 
    
         
            -
             
     | 
| 
       430 
     | 
    
         
            -
            Search with:
         
     | 
| 
       431 
     | 
    
         
            -
             
     | 
| 
       432 
     | 
    
         
            -
            ```ruby
         
     | 
| 
       433 
     | 
    
         
            -
            Product.search(query, fields: [:name_tagged])
         
     | 
| 
       434 
     | 
    
         
            -
            ```
         
     | 
| 
       435 
     | 
    
         
            -
             
     | 
| 
       436 
408 
     | 
    
         
             
            ### Misspellings
         
     | 
| 
       437 
409 
     | 
    
         | 
| 
       438 
410 
     | 
    
         
             
            By default, Searchkick handles misspelled queries by returning results with an [edit distance](https://en.wikipedia.org/wiki/Levenshtein_distance) of one.
         
     | 
| 
         @@ -440,13 +412,13 @@ By default, Searchkick handles misspelled queries by returning results with an [ 
     | 
|
| 
       440 
412 
     | 
    
         
             
            You can change this with:
         
     | 
| 
       441 
413 
     | 
    
         | 
| 
       442 
414 
     | 
    
         
             
            ```ruby
         
     | 
| 
       443 
     | 
    
         
            -
            Product.search("zucini" 
     | 
| 
      
 415 
     | 
    
         
            +
            Product.search("zucini").misspellings(edit_distance: 2) # zucchini
         
     | 
| 
       444 
416 
     | 
    
         
             
            ```
         
     | 
| 
       445 
417 
     | 
    
         | 
| 
       446 
418 
     | 
    
         
             
            To prevent poor precision and improve performance for correctly spelled queries (which should be a majority for most applications), Searchkick can first perform a search without misspellings, and if there are too few results, perform another with them.
         
     | 
| 
       447 
419 
     | 
    
         | 
| 
       448 
420 
     | 
    
         
             
            ```ruby
         
     | 
| 
       449 
     | 
    
         
            -
            Product.search("zuchini" 
     | 
| 
      
 421 
     | 
    
         
            +
            Product.search("zuchini").misspellings(below: 5)
         
     | 
| 
       450 
422 
     | 
    
         
             
            ```
         
     | 
| 
       451 
423 
     | 
    
         | 
| 
       452 
424 
     | 
    
         
             
            If there are fewer than 5 results, a 2nd search is performed with misspellings enabled. The result of this query is returned.
         
     | 
| 
         @@ -454,13 +426,13 @@ If there are fewer than 5 results, a 2nd search is performed with misspellings e 
     | 
|
| 
       454 
426 
     | 
    
         
             
            Turn off misspellings with:
         
     | 
| 
       455 
427 
     | 
    
         | 
| 
       456 
428 
     | 
    
         
             
            ```ruby
         
     | 
| 
       457 
     | 
    
         
            -
            Product.search("zuchini" 
     | 
| 
      
 429 
     | 
    
         
            +
            Product.search("zuchini").misspellings(false) # no zucchini
         
     | 
| 
       458 
430 
     | 
    
         
             
            ```
         
     | 
| 
       459 
431 
     | 
    
         | 
| 
       460 
432 
     | 
    
         
             
            Specify which fields can include misspellings with:
         
     | 
| 
       461 
433 
     | 
    
         | 
| 
       462 
434 
     | 
    
         
             
            ```ruby
         
     | 
| 
       463 
     | 
    
         
            -
            Product.search("zucini" 
     | 
| 
      
 435 
     | 
    
         
            +
            Product.search("zucini").fields(:name, :color).misspellings(fields: [:name])
         
     | 
| 
       464 
436 
     | 
    
         
             
            ```
         
     | 
| 
       465 
437 
     | 
    
         | 
| 
       466 
438 
     | 
    
         
             
            > When doing this, you must also specify fields to search
         
     | 
| 
         @@ -470,7 +442,7 @@ Product.search("zucini", fields: [:name, :color], misspellings: {fields: [:name] 
     | 
|
| 
       470 
442 
     | 
    
         
             
            If a user searches `butter`, they may also get results for `peanut butter`. To prevent this, use:
         
     | 
| 
       471 
443 
     | 
    
         | 
| 
       472 
444 
     | 
    
         
             
            ```ruby
         
     | 
| 
       473 
     | 
    
         
            -
            Product.search("butter" 
     | 
| 
      
 445 
     | 
    
         
            +
            Product.search("butter").exclude("peanut butter")
         
     | 
| 
       474 
446 
     | 
    
         
             
            ```
         
     | 
| 
       475 
447 
     | 
    
         | 
| 
       476 
448 
     | 
    
         
             
            You can map queries and terms to exclude with:
         
     | 
| 
         @@ -481,13 +453,13 @@ exclude_queries = { 
     | 
|
| 
       481 
453 
     | 
    
         
             
              "cream" => ["ice cream", "whipped cream"]
         
     | 
| 
       482 
454 
     | 
    
         
             
            }
         
     | 
| 
       483 
455 
     | 
    
         | 
| 
       484 
     | 
    
         
            -
            Product.search(query 
     | 
| 
      
 456 
     | 
    
         
            +
            Product.search(query).exclude(exclude_queries[query])
         
     | 
| 
       485 
457 
     | 
    
         
             
            ```
         
     | 
| 
       486 
458 
     | 
    
         | 
| 
       487 
459 
     | 
    
         
             
            You can demote results by boosting by a factor less than one:
         
     | 
| 
       488 
460 
     | 
    
         | 
| 
       489 
461 
     | 
    
         
             
            ```ruby
         
     | 
| 
       490 
     | 
    
         
            -
            Product.search("butter" 
     | 
| 
      
 462 
     | 
    
         
            +
            Product.search("butter").boost_where(category: {value: "pantry", factor: 0.5})
         
     | 
| 
       491 
463 
     | 
    
         
             
            ```
         
     | 
| 
       492 
464 
     | 
    
         | 
| 
       493 
465 
     | 
    
         
             
            ### Emoji
         
     | 
| 
         @@ -503,7 +475,7 @@ gem "gemoji-parser" 
     | 
|
| 
       503 
475 
     | 
    
         
             
            And use:
         
     | 
| 
       504 
476 
     | 
    
         | 
| 
       505 
477 
     | 
    
         
             
            ```ruby
         
     | 
| 
       506 
     | 
    
         
            -
            Product.search("🍨🍰" 
     | 
| 
      
 478 
     | 
    
         
            +
            Product.search("🍨🍰").emoji
         
     | 
| 
       507 
479 
     | 
    
         
             
            ```
         
     | 
| 
       508 
480 
     | 
    
         | 
| 
       509 
481 
     | 
    
         
             
            ## Indexing
         
     | 
| 
         @@ -669,7 +641,7 @@ end 
     | 
|
| 
       669 
641 
     | 
    
         
             
            The best starting point to improve your search **by far** is to track searches and conversions. [Searchjoy](https://github.com/ankane/searchjoy) makes it easy.
         
     | 
| 
       670 
642 
     | 
    
         | 
| 
       671 
643 
     | 
    
         
             
            ```ruby
         
     | 
| 
       672 
     | 
    
         
            -
            Product.search("apple" 
     | 
| 
      
 644 
     | 
    
         
            +
            Product.search("apple").track(user_id: current_user.id)
         
     | 
| 
       673 
645 
     | 
    
         
             
            ```
         
     | 
| 
       674 
646 
     | 
    
         | 
| 
       675 
647 
     | 
    
         
             
            [See the docs](https://github.com/ankane/searchjoy) for how to install and use. Focus on top searches with a low conversion rate.
         
     | 
| 
         @@ -683,7 +655,7 @@ class Product < ApplicationRecord 
     | 
|
| 
       683 
655 
     | 
    
         
             
              has_many :conversions, class_name: "Searchjoy::Conversion", as: :convertable
         
     | 
| 
       684 
656 
     | 
    
         
             
              has_many :searches, class_name: "Searchjoy::Search", through: :conversions
         
     | 
| 
       685 
657 
     | 
    
         | 
| 
       686 
     | 
    
         
            -
              searchkick  
     | 
| 
      
 658 
     | 
    
         
            +
              searchkick conversions_v2: [:conversions] # name of field
         
     | 
| 
       687 
659 
     | 
    
         | 
| 
       688 
660 
     | 
    
         
             
              def search_data
         
     | 
| 
       689 
661 
     | 
    
         
             
                {
         
     | 
| 
         @@ -695,7 +667,7 @@ class Product < ApplicationRecord 
     | 
|
| 
       695 
667 
     | 
    
         
             
            end
         
     | 
| 
       696 
668 
     | 
    
         
             
            ```
         
     | 
| 
       697 
669 
     | 
    
         | 
| 
       698 
     | 
    
         
            -
            Reindex and set up a cron job to add new conversions daily. For zero downtime deployment, temporarily set ` 
     | 
| 
      
 670 
     | 
    
         
            +
            Reindex and set up a cron job to add new conversions daily. For zero downtime deployment, temporarily set `conversions_v2(false)` in your search calls until the data is reindexed.
         
     | 
| 
       699 
671 
     | 
    
         | 
| 
       700 
672 
     | 
    
         
             
            ### Performant Conversions
         
     | 
| 
       701 
673 
     | 
    
         | 
| 
         @@ -711,7 +683,7 @@ Next, update your model. Create a separate method for conversion data so you can 
     | 
|
| 
       711 
683 
     | 
    
         | 
| 
       712 
684 
     | 
    
         
             
            ```ruby
         
     | 
| 
       713 
685 
     | 
    
         
             
            class Product < ApplicationRecord
         
     | 
| 
       714 
     | 
    
         
            -
              searchkick  
     | 
| 
      
 686 
     | 
    
         
            +
              searchkick conversions_v2: [:conversions]
         
     | 
| 
       715 
687 
     | 
    
         | 
| 
       716 
688 
     | 
    
         
             
              def search_data
         
     | 
| 
       717 
689 
     | 
    
         
             
                {
         
     | 
| 
         @@ -728,7 +700,7 @@ class Product < ApplicationRecord 
     | 
|
| 
       728 
700 
     | 
    
         
             
            end
         
     | 
| 
       729 
701 
     | 
    
         
             
            ```
         
     | 
| 
       730 
702 
     | 
    
         | 
| 
       731 
     | 
    
         
            -
            Deploy and reindex your data. For zero downtime deployment, temporarily set ` 
     | 
| 
      
 703 
     | 
    
         
            +
            Deploy and reindex your data. For zero downtime deployment, temporarily set `conversions_v2(false)` in your search calls until the data is reindexed.
         
     | 
| 
       732 
704 
     | 
    
         | 
| 
       733 
705 
     | 
    
         
             
            ```ruby
         
     | 
| 
       734 
706 
     | 
    
         
             
            Product.reindex
         
     | 
| 
         @@ -743,8 +715,8 @@ class UpdateConversionsJob < ApplicationJob 
     | 
|
| 
       743 
715 
     | 
    
         | 
| 
       744 
716 
     | 
    
         
             
                # get records that have a recent conversion
         
     | 
| 
       745 
717 
     | 
    
         
             
                recently_converted_ids =
         
     | 
| 
       746 
     | 
    
         
            -
                  Searchjoy::Conversion.where(convertable_type: class_name 
     | 
| 
       747 
     | 
    
         
            -
             
     | 
| 
      
 718 
     | 
    
         
            +
                  Searchjoy::Conversion.where(convertable_type: class_name, created_at: since..)
         
     | 
| 
      
 719 
     | 
    
         
            +
                    .order(:convertable_id).distinct.pluck(:convertable_id)
         
     | 
| 
       748 
720 
     | 
    
         | 
| 
       749 
721 
     | 
    
         
             
                # split into batches
         
     | 
| 
       750 
722 
     | 
    
         
             
                recently_converted_ids.in_groups_of(1000, false) do |ids|
         
     | 
| 
         @@ -752,8 +724,8 @@ class UpdateConversionsJob < ApplicationJob 
     | 
|
| 
       752 
724 
     | 
    
         
             
                    # fetch conversions
         
     | 
| 
       753 
725 
     | 
    
         
             
                    conversions =
         
     | 
| 
       754 
726 
     | 
    
         
             
                      Searchjoy::Conversion.where(convertable_id: ids, convertable_type: class_name)
         
     | 
| 
       755 
     | 
    
         
            -
             
     | 
| 
       756 
     | 
    
         
            -
             
     | 
| 
      
 727 
     | 
    
         
            +
                        .joins(:search).where.not(searchjoy_searches: {user_id: nil})
         
     | 
| 
      
 728 
     | 
    
         
            +
                        .group(:convertable_id, :query).distinct.count(:user_id)
         
     | 
| 
       757 
729 
     | 
    
         | 
| 
       758 
730 
     | 
    
         
             
                    # group by record
         
     | 
| 
       759 
731 
     | 
    
         
             
                    conversions_by_record = {}
         
     | 
| 
         @@ -771,7 +743,7 @@ class UpdateConversionsJob < ApplicationJob 
     | 
|
| 
       771 
743 
     | 
    
         | 
| 
       772 
744 
     | 
    
         
             
                  if reindex
         
     | 
| 
       773 
745 
     | 
    
         
             
                    # reindex conversions data
         
     | 
| 
       774 
     | 
    
         
            -
                    model.where(id: ids).reindex(:conversions_data)
         
     | 
| 
      
 746 
     | 
    
         
            +
                    model.where(id: ids).reindex(:conversions_data, ignore_missing: true)
         
     | 
| 
       775 
747 
     | 
    
         
             
                  end
         
     | 
| 
       776 
748 
     | 
    
         
             
                end
         
     | 
| 
       777 
749 
     | 
    
         
             
              end
         
     | 
| 
         @@ -808,7 +780,7 @@ end 
     | 
|
| 
       808 
780 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       809 
781 
     | 
    
         | 
| 
       810 
782 
     | 
    
         
             
            ```ruby
         
     | 
| 
       811 
     | 
    
         
            -
            Product.search("milk" 
     | 
| 
      
 783 
     | 
    
         
            +
            Product.search("milk").boost_where(orderer_ids: current_user.id)
         
     | 
| 
       812 
784 
     | 
    
         
             
            ```
         
     | 
| 
       813 
785 
     | 
    
         | 
| 
       814 
786 
     | 
    
         
             
            ## Instant Search / Autocomplete
         
     | 
| 
         @@ -832,7 +804,7 @@ end 
     | 
|
| 
       832 
804 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       833 
805 
     | 
    
         | 
| 
       834 
806 
     | 
    
         
             
            ```ruby
         
     | 
| 
       835 
     | 
    
         
            -
            Movie.search("jurassic pa" 
     | 
| 
      
 807 
     | 
    
         
            +
            Movie.search("jurassic pa").fields(:title).match(:word_start)
         
     | 
| 
       836 
808 
     | 
    
         
             
            ```
         
     | 
| 
       837 
809 
     | 
    
         | 
| 
       838 
810 
     | 
    
         
             
            Use a front-end library like [typeahead.js](https://twitter.github.io/typeahead.js/) to show the results.
         
     | 
| 
         @@ -844,19 +816,13 @@ First, add a route and controller action. 
     | 
|
| 
       844 
816 
     | 
    
         
             
            ```ruby
         
     | 
| 
       845 
817 
     | 
    
         
             
            class MoviesController < ApplicationController
         
     | 
| 
       846 
818 
     | 
    
         
             
              def autocomplete
         
     | 
| 
       847 
     | 
    
         
            -
                render json: Movie.search(
         
     | 
| 
       848 
     | 
    
         
            -
                   
     | 
| 
       849 
     | 
    
         
            -
                  fields: ["title^5", "director"],
         
     | 
| 
       850 
     | 
    
         
            -
                  match: :word_start,
         
     | 
| 
       851 
     | 
    
         
            -
                  limit: 10,
         
     | 
| 
       852 
     | 
    
         
            -
                  load: false,
         
     | 
| 
       853 
     | 
    
         
            -
                  misspellings: {below: 5}
         
     | 
| 
       854 
     | 
    
         
            -
                ).map(&:title)
         
     | 
| 
      
 819 
     | 
    
         
            +
                render json: Movie.search(params[:query]).fields("title^5", "director")
         
     | 
| 
      
 820 
     | 
    
         
            +
                  .match(:word_start).limit(10).load(false).misspellings(below: 5).map(&:title)
         
     | 
| 
       855 
821 
     | 
    
         
             
              end
         
     | 
| 
       856 
822 
     | 
    
         
             
            end
         
     | 
| 
       857 
823 
     | 
    
         
             
            ```
         
     | 
| 
       858 
824 
     | 
    
         | 
| 
       859 
     | 
    
         
            -
            **Note:** Use `load 
     | 
| 
      
 825 
     | 
    
         
            +
            **Note:** Use `load(false)` and `misspellings(below: n)` (or `misspellings(false)`) for best performance.
         
     | 
| 
       860 
826 
     | 
    
         | 
| 
       861 
827 
     | 
    
         
             
            Then add the search box and JavaScript code to a view.
         
     | 
| 
       862 
828 
     | 
    
         | 
| 
         @@ -893,7 +859,7 @@ end 
     | 
|
| 
       893 
859 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       894 
860 
     | 
    
         | 
| 
       895 
861 
     | 
    
         
             
            ```ruby
         
     | 
| 
       896 
     | 
    
         
            -
            products = Product.search("peantu butta" 
     | 
| 
      
 862 
     | 
    
         
            +
            products = Product.search("peantu butta").suggest
         
     | 
| 
       897 
863 
     | 
    
         
             
            products.suggestions # ["peanut butter"]
         
     | 
| 
       898 
864 
     | 
    
         
             
            ```
         
     | 
| 
       899 
865 
     | 
    
         | 
| 
         @@ -904,40 +870,40 @@ products.suggestions # ["peanut butter"] 
     | 
|
| 
       904 
870 
     | 
    
         
             
            
         
     | 
| 
       905 
871 
     | 
    
         | 
| 
       906 
872 
     | 
    
         
             
            ```ruby
         
     | 
| 
       907 
     | 
    
         
            -
            products = Product.search("chuck taylor" 
     | 
| 
      
 873 
     | 
    
         
            +
            products = Product.search("chuck taylor").aggs(:product_type, :gender, :brand)
         
     | 
| 
       908 
874 
     | 
    
         
             
            products.aggs
         
     | 
| 
       909 
875 
     | 
    
         
             
            ```
         
     | 
| 
       910 
876 
     | 
    
         | 
| 
       911 
877 
     | 
    
         
             
            By default, `where` conditions apply to aggregations.
         
     | 
| 
       912 
878 
     | 
    
         | 
| 
       913 
879 
     | 
    
         
             
            ```ruby
         
     | 
| 
       914 
     | 
    
         
            -
            Product.search("wingtips" 
     | 
| 
      
 880 
     | 
    
         
            +
            Product.search("wingtips").where(color: "brandy").aggs(:size)
         
     | 
| 
       915 
881 
     | 
    
         
             
            # aggregations for brandy wingtips are returned
         
     | 
| 
       916 
882 
     | 
    
         
             
            ```
         
     | 
| 
       917 
883 
     | 
    
         | 
| 
       918 
884 
     | 
    
         
             
            Change this with:
         
     | 
| 
       919 
885 
     | 
    
         | 
| 
       920 
886 
     | 
    
         
             
            ```ruby
         
     | 
| 
       921 
     | 
    
         
            -
            Product.search("wingtips" 
     | 
| 
      
 887 
     | 
    
         
            +
            Product.search("wingtips").where(color: "brandy").aggs(:size).smart_aggs(false)
         
     | 
| 
       922 
888 
     | 
    
         
             
            # aggregations for all wingtips are returned
         
     | 
| 
       923 
889 
     | 
    
         
             
            ```
         
     | 
| 
       924 
890 
     | 
    
         | 
| 
       925 
891 
     | 
    
         
             
            Set `where` conditions for each aggregation separately with:
         
     | 
| 
       926 
892 
     | 
    
         | 
| 
       927 
893 
     | 
    
         
             
            ```ruby
         
     | 
| 
       928 
     | 
    
         
            -
            Product.search("wingtips" 
     | 
| 
      
 894 
     | 
    
         
            +
            Product.search("wingtips").aggs(size: {where: {color: "brandy"}})
         
     | 
| 
       929 
895 
     | 
    
         
             
            ```
         
     | 
| 
       930 
896 
     | 
    
         | 
| 
       931 
897 
     | 
    
         
             
            Limit
         
     | 
| 
       932 
898 
     | 
    
         | 
| 
       933 
899 
     | 
    
         
             
            ```ruby
         
     | 
| 
       934 
     | 
    
         
            -
            Product.search("apples" 
     | 
| 
      
 900 
     | 
    
         
            +
            Product.search("apples").aggs(store_id: {limit: 10})
         
     | 
| 
       935 
901 
     | 
    
         
             
            ```
         
     | 
| 
       936 
902 
     | 
    
         | 
| 
       937 
903 
     | 
    
         
             
            Order
         
     | 
| 
       938 
904 
     | 
    
         | 
| 
       939 
905 
     | 
    
         
             
            ```ruby
         
     | 
| 
       940 
     | 
    
         
            -
            Product.search("wingtips" 
     | 
| 
      
 906 
     | 
    
         
            +
            Product.search("wingtips").aggs(color: {order: {"_key" => "asc"}}) # alphabetically
         
     | 
| 
       941 
907 
     | 
    
         
             
            ```
         
     | 
| 
       942 
908 
     | 
    
         | 
| 
       943 
909 
     | 
    
         
             
            [All of these options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-order)
         
     | 
| 
         @@ -946,31 +912,31 @@ Ranges 
     | 
|
| 
       946 
912 
     | 
    
         | 
| 
       947 
913 
     | 
    
         
             
            ```ruby
         
     | 
| 
       948 
914 
     | 
    
         
             
            price_ranges = [{to: 20}, {from: 20, to: 50}, {from: 50}]
         
     | 
| 
       949 
     | 
    
         
            -
            Product.search("*" 
     | 
| 
      
 915 
     | 
    
         
            +
            Product.search("*").aggs(price: {ranges: price_ranges})
         
     | 
| 
       950 
916 
     | 
    
         
             
            ```
         
     | 
| 
       951 
917 
     | 
    
         | 
| 
       952 
918 
     | 
    
         
             
            Minimum document count
         
     | 
| 
       953 
919 
     | 
    
         | 
| 
       954 
920 
     | 
    
         
             
            ```ruby
         
     | 
| 
       955 
     | 
    
         
            -
            Product.search("apples" 
     | 
| 
      
 921 
     | 
    
         
            +
            Product.search("apples").aggs(store_id: {min_doc_count: 2})
         
     | 
| 
       956 
922 
     | 
    
         
             
            ```
         
     | 
| 
       957 
923 
     | 
    
         | 
| 
       958 
924 
     | 
    
         
             
            Script support
         
     | 
| 
       959 
925 
     | 
    
         | 
| 
       960 
926 
     | 
    
         
             
            ```ruby
         
     | 
| 
       961 
     | 
    
         
            -
            Product.search("*" 
     | 
| 
      
 927 
     | 
    
         
            +
            Product.search("*").aggs(color: {script: {source: "'Color: ' + _value"}})
         
     | 
| 
       962 
928 
     | 
    
         
             
            ```
         
     | 
| 
       963 
929 
     | 
    
         | 
| 
       964 
930 
     | 
    
         
             
            Date histogram
         
     | 
| 
       965 
931 
     | 
    
         | 
| 
       966 
932 
     | 
    
         
             
            ```ruby
         
     | 
| 
       967 
     | 
    
         
            -
            Product.search("pear" 
     | 
| 
      
 933 
     | 
    
         
            +
            Product.search("pear").aggs(products_per_year: {date_histogram: {field: :created_at, interval: :year}})
         
     | 
| 
       968 
934 
     | 
    
         
             
            ```
         
     | 
| 
       969 
935 
     | 
    
         | 
| 
       970 
936 
     | 
    
         
             
            For other aggregation types, including sub-aggregations, use `body_options`:
         
     | 
| 
       971 
937 
     | 
    
         | 
| 
       972 
938 
     | 
    
         
             
            ```ruby
         
     | 
| 
       973 
     | 
    
         
            -
            Product.search("orange" 
     | 
| 
      
 939 
     | 
    
         
            +
            Product.search("orange").body_options(aggs: {price: {histogram: {field: :price, interval: 10}}})
         
     | 
| 
       974 
940 
     | 
    
         
             
            ```
         
     | 
| 
       975 
941 
     | 
    
         | 
| 
       976 
942 
     | 
    
         
             
            ## Highlight
         
     | 
| 
         @@ -986,7 +952,7 @@ end 
     | 
|
| 
       986 
952 
     | 
    
         
             
            Highlight the search query in the results.
         
     | 
| 
       987 
953 
     | 
    
         | 
| 
       988 
954 
     | 
    
         
             
            ```ruby
         
     | 
| 
       989 
     | 
    
         
            -
            bands = Band.search("cinema" 
     | 
| 
      
 955 
     | 
    
         
            +
            bands = Band.search("cinema").highlight
         
     | 
| 
       990 
956 
     | 
    
         
             
            ```
         
     | 
| 
       991 
957 
     | 
    
         | 
| 
       992 
958 
     | 
    
         
             
            View the highlighted fields with:
         
     | 
| 
         @@ -1000,19 +966,19 @@ end 
     | 
|
| 
       1000 
966 
     | 
    
         
             
            To change the tag, use:
         
     | 
| 
       1001 
967 
     | 
    
         | 
| 
       1002 
968 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1003 
     | 
    
         
            -
            Band.search("cinema" 
     | 
| 
      
 969 
     | 
    
         
            +
            Band.search("cinema").highlight(tag: "<strong>")
         
     | 
| 
       1004 
970 
     | 
    
         
             
            ```
         
     | 
| 
       1005 
971 
     | 
    
         | 
| 
       1006 
972 
     | 
    
         
             
            To highlight and search different fields, use:
         
     | 
| 
       1007 
973 
     | 
    
         | 
| 
       1008 
974 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1009 
     | 
    
         
            -
            Band.search("cinema" 
     | 
| 
      
 975 
     | 
    
         
            +
            Band.search("cinema").fields(:name).highlight(fields: [:description])
         
     | 
| 
       1010 
976 
     | 
    
         
             
            ```
         
     | 
| 
       1011 
977 
     | 
    
         | 
| 
       1012 
978 
     | 
    
         
             
            By default, the entire field is highlighted. To get small snippets instead, use:
         
     | 
| 
       1013 
979 
     | 
    
         | 
| 
       1014 
980 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1015 
     | 
    
         
            -
            bands = Band.search("cinema" 
     | 
| 
      
 981 
     | 
    
         
            +
            bands = Band.search("cinema").highlight(fragment_size: 20)
         
     | 
| 
       1016 
982 
     | 
    
         
             
            bands.with_highlights(multiple: true).each do |band, highlights|
         
     | 
| 
       1017 
983 
     | 
    
         
             
              highlights[:name].join(" and ")
         
     | 
| 
       1018 
984 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1021,18 +987,18 @@ end 
     | 
|
| 
       1021 
987 
     | 
    
         
             
            Additional options can be specified for each field:
         
     | 
| 
       1022 
988 
     | 
    
         | 
| 
       1023 
989 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1024 
     | 
    
         
            -
            Band.search("cinema" 
     | 
| 
      
 990 
     | 
    
         
            +
            Band.search("cinema").fields(:name).highlight(fields: {name: {fragment_size: 200}})
         
     | 
| 
       1025 
991 
     | 
    
         
             
            ```
         
     | 
| 
       1026 
992 
     | 
    
         | 
| 
       1027 
993 
     | 
    
         
             
            You can find available highlight options in the [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/highlighting.html) or [OpenSearch](https://opensearch.org/docs/latest/search-plugins/searching-data/highlight/) reference.
         
     | 
| 
       1028 
994 
     | 
    
         | 
| 
       1029 
995 
     | 
    
         
             
            ## Similar Items
         
     | 
| 
       1030 
996 
     | 
    
         | 
| 
       1031 
     | 
    
         
            -
            Find similar items 
     | 
| 
      
 997 
     | 
    
         
            +
            Find similar items
         
     | 
| 
       1032 
998 
     | 
    
         | 
| 
       1033 
999 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1034 
1000 
     | 
    
         
             
            product = Product.first
         
     | 
| 
       1035 
     | 
    
         
            -
            product.similar( 
     | 
| 
      
 1001 
     | 
    
         
            +
            product.similar.fields(:name).where(size: "12 oz")
         
     | 
| 
       1036 
1002 
     | 
    
         
             
            ```
         
     | 
| 
       1037 
1003 
     | 
    
         | 
| 
       1038 
1004 
     | 
    
         
             
            ## Geospatial Searches
         
     | 
| 
         @@ -1050,13 +1016,13 @@ end 
     | 
|
| 
       1050 
1016 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       1051 
1017 
     | 
    
         | 
| 
       1052 
1018 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1053 
     | 
    
         
            -
            Restaurant.search("pizza" 
     | 
| 
      
 1019 
     | 
    
         
            +
            Restaurant.search("pizza").where(location: {near: {lat: 37, lon: -114}, within: "100mi"}) # or 160km
         
     | 
| 
       1054 
1020 
     | 
    
         
             
            ```
         
     | 
| 
       1055 
1021 
     | 
    
         | 
| 
       1056 
1022 
     | 
    
         
             
            Bounded by a box
         
     | 
| 
       1057 
1023 
     | 
    
         | 
| 
       1058 
1024 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1059 
     | 
    
         
            -
            Restaurant.search("sushi" 
     | 
| 
      
 1025 
     | 
    
         
            +
            Restaurant.search("sushi").where(location: {top_left: {lat: 38, lon: -123}, bottom_right: {lat: 37, lon: -122}})
         
     | 
| 
       1060 
1026 
     | 
    
         
             
            ```
         
     | 
| 
       1061 
1027 
     | 
    
         | 
| 
       1062 
1028 
     | 
    
         
             
            **Note:** `top_right` and `bottom_left` also work
         
     | 
| 
         @@ -1064,7 +1030,7 @@ Restaurant.search("sushi", where: {location: {top_left: {lat: 38, lon: -123}, bo 
     | 
|
| 
       1064 
1030 
     | 
    
         
             
            Bounded by a polygon
         
     | 
| 
       1065 
1031 
     | 
    
         | 
| 
       1066 
1032 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1067 
     | 
    
         
            -
            Restaurant.search("dessert" 
     | 
| 
      
 1033 
     | 
    
         
            +
            Restaurant.search("dessert").where(location: {geo_polygon: {points: [{lat: 38, lon: -123}, {lat: 39, lon: -123}, {lat: 37, lon: 122}]}})
         
     | 
| 
       1068 
1034 
     | 
    
         
             
            ```
         
     | 
| 
       1069 
1035 
     | 
    
         | 
| 
       1070 
1036 
     | 
    
         
             
            ### Boost By Distance
         
     | 
| 
         @@ -1072,13 +1038,13 @@ Restaurant.search("dessert", where: {location: {geo_polygon: {points: [{lat: 38, 
     | 
|
| 
       1072 
1038 
     | 
    
         
             
            Boost results by distance - closer results are boosted more
         
     | 
| 
       1073 
1039 
     | 
    
         | 
| 
       1074 
1040 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1075 
     | 
    
         
            -
            Restaurant.search("noodles" 
     | 
| 
      
 1041 
     | 
    
         
            +
            Restaurant.search("noodles").boost_by_distance(location: {origin: {lat: 37, lon: -122}})
         
     | 
| 
       1076 
1042 
     | 
    
         
             
            ```
         
     | 
| 
       1077 
1043 
     | 
    
         | 
| 
       1078 
1044 
     | 
    
         
             
            Also supports [additional options](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#function-decay)
         
     | 
| 
       1079 
1045 
     | 
    
         | 
| 
       1080 
1046 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1081 
     | 
    
         
            -
            Restaurant.search("wings" 
     | 
| 
      
 1047 
     | 
    
         
            +
            Restaurant.search("wings").boost_by_distance(location: {origin: {lat: 37, lon: -122}, function: "linear", scale: "30mi", decay: 0.5})
         
     | 
| 
       1082 
1048 
     | 
    
         
             
            ```
         
     | 
| 
       1083 
1049 
     | 
    
         | 
| 
       1084 
1050 
     | 
    
         
             
            ### Geo Shapes
         
     | 
| 
         @@ -1105,19 +1071,19 @@ See the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsea 
     | 
|
| 
       1105 
1071 
     | 
    
         
             
            Find shapes intersecting with the query shape
         
     | 
| 
       1106 
1072 
     | 
    
         | 
| 
       1107 
1073 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1108 
     | 
    
         
            -
            Restaurant.search("soup" 
     | 
| 
      
 1074 
     | 
    
         
            +
            Restaurant.search("soup").where(bounds: {geo_shape: {type: "polygon", coordinates: [[{lat: 38, lon: -123}, ...]]}})
         
     | 
| 
       1109 
1075 
     | 
    
         
             
            ```
         
     | 
| 
       1110 
1076 
     | 
    
         | 
| 
       1111 
1077 
     | 
    
         
             
            Falling entirely within the query shape
         
     | 
| 
       1112 
1078 
     | 
    
         | 
| 
       1113 
1079 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1114 
     | 
    
         
            -
            Restaurant.search("salad" 
     | 
| 
      
 1080 
     | 
    
         
            +
            Restaurant.search("salad").where(bounds: {geo_shape: {type: "circle", relation: "within", coordinates: {lat: 38, lon: -123}, radius: "1km"}})
         
     | 
| 
       1115 
1081 
     | 
    
         
             
            ```
         
     | 
| 
       1116 
1082 
     | 
    
         | 
| 
       1117 
1083 
     | 
    
         
             
            Not touching the query shape
         
     | 
| 
       1118 
1084 
     | 
    
         | 
| 
       1119 
1085 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1120 
     | 
    
         
            -
            Restaurant.search("burger" 
     | 
| 
      
 1086 
     | 
    
         
            +
            Restaurant.search("burger").where(bounds: {geo_shape: {type: "envelope", relation: "disjoint", coordinates: [{lat: 38, lon: -123}, {lat: 37, lon: -122}]}})
         
     | 
| 
       1121 
1087 
     | 
    
         
             
            ```
         
     | 
| 
       1122 
1088 
     | 
    
         | 
| 
       1123 
1089 
     | 
    
         
             
            ## Inheritance
         
     | 
| 
         @@ -1147,9 +1113,9 @@ Dog.reindex # equivalent, all animals reindexed 
     | 
|
| 
       1147 
1113 
     | 
    
         
             
            And to search, use:
         
     | 
| 
       1148 
1114 
     | 
    
         | 
| 
       1149 
1115 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1150 
     | 
    
         
            -
            Animal.search("*") 
     | 
| 
       1151 
     | 
    
         
            -
            Dog.search("*") 
     | 
| 
       1152 
     | 
    
         
            -
            Animal.search("*" 
     | 
| 
      
 1116 
     | 
    
         
            +
            Animal.search("*")                # all animals
         
     | 
| 
      
 1117 
     | 
    
         
            +
            Dog.search("*")                   # just dogs
         
     | 
| 
      
 1118 
     | 
    
         
            +
            Animal.search("*").type(Dog, Cat) # just cats and dogs
         
     | 
| 
       1153 
1119 
     | 
    
         
             
            ```
         
     | 
| 
       1154 
1120 
     | 
    
         | 
| 
       1155 
1121 
     | 
    
         
             
            **Notes:**
         
     | 
| 
         @@ -1157,7 +1123,7 @@ Animal.search("*", type: [Dog, Cat]) # just cats and dogs 
     | 
|
| 
       1157 
1123 
     | 
    
         
             
            1. The `suggest` option retrieves suggestions from the parent at the moment.
         
     | 
| 
       1158 
1124 
     | 
    
         | 
| 
       1159 
1125 
     | 
    
         
             
                ```ruby
         
     | 
| 
       1160 
     | 
    
         
            -
                Dog.search("airbudd" 
     | 
| 
      
 1126 
     | 
    
         
            +
                Dog.search("airbudd").suggest # suggestions for all animals
         
     | 
| 
       1161 
1127 
     | 
    
         
             
                ```
         
     | 
| 
       1162 
1128 
     | 
    
         
             
            2. This relies on a `type` field that is automatically added to the indexed document. Be wary of defining your own `type` field in `search_data`, as it will take precedence.
         
     | 
| 
       1163 
1129 
     | 
    
         | 
| 
         @@ -1166,7 +1132,7 @@ Animal.search("*", type: [Dog, Cat]) # just cats and dogs 
     | 
|
| 
       1166 
1132 
     | 
    
         
             
            To help with debugging queries, you can use:
         
     | 
| 
       1167 
1133 
     | 
    
         | 
| 
       1168 
1134 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1169 
     | 
    
         
            -
            Product.search("soap" 
     | 
| 
      
 1135 
     | 
    
         
            +
            Product.search("soap").debug
         
     | 
| 
       1170 
1136 
     | 
    
         
             
            ```
         
     | 
| 
       1171 
1137 
     | 
    
         | 
| 
       1172 
1138 
     | 
    
         
             
            This prints useful info to `stdout`.
         
     | 
| 
         @@ -1174,7 +1140,7 @@ This prints useful info to `stdout`. 
     | 
|
| 
       1174 
1140 
     | 
    
         
             
            See how the search server scores your queries with:
         
     | 
| 
       1175 
1141 
     | 
    
         | 
| 
       1176 
1142 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1177 
     | 
    
         
            -
            Product.search("soap" 
     | 
| 
      
 1143 
     | 
    
         
            +
            Product.search("soap").explain.response
         
     | 
| 
       1178 
1144 
     | 
    
         
             
            ```
         
     | 
| 
       1179 
1145 
     | 
    
         | 
| 
       1180 
1146 
     | 
    
         
             
            See how the search server tokenizes your queries with:
         
     | 
| 
         @@ -1208,37 +1174,42 @@ As you iterate on your search, it’s a good idea to add tests. 
     | 
|
| 
       1208 
1174 
     | 
    
         | 
| 
       1209 
1175 
     | 
    
         
             
            For performance, only enable Searchkick callbacks for the tests that need it.
         
     | 
| 
       1210 
1176 
     | 
    
         | 
| 
       1211 
     | 
    
         
            -
            ###  
     | 
| 
      
 1177 
     | 
    
         
            +
            ### Rails
         
     | 
| 
       1212 
1178 
     | 
    
         | 
| 
       1213 
     | 
    
         
            -
             
     | 
| 
      
 1179 
     | 
    
         
            +
            Add to your `test/test_helper.rb`:
         
     | 
| 
       1214 
1180 
     | 
    
         | 
| 
       1215 
1181 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1216 
     | 
    
         
            -
             
     | 
| 
       1217 
     | 
    
         
            -
               
     | 
| 
       1218 
     | 
    
         
            -
                 
     | 
| 
      
 1182 
     | 
    
         
            +
            module ActiveSupport
         
     | 
| 
      
 1183 
     | 
    
         
            +
              class TestCase
         
     | 
| 
      
 1184 
     | 
    
         
            +
                parallelize_setup do |worker|
         
     | 
| 
      
 1185 
     | 
    
         
            +
                  Searchkick.index_suffix = worker
         
     | 
| 
       1219 
1186 
     | 
    
         | 
| 
       1220 
     | 
    
         
            -
             
     | 
| 
       1221 
     | 
    
         
            -
             
     | 
| 
       1222 
     | 
    
         
            -
             
     | 
| 
       1223 
     | 
    
         
            -
                # and disable callbacks
         
     | 
| 
       1224 
     | 
    
         
            -
                Searchkick.disable_callbacks
         
     | 
| 
      
 1187 
     | 
    
         
            +
                  # reindex models for parallel tests
         
     | 
| 
      
 1188 
     | 
    
         
            +
                  Product.reindex
         
     | 
| 
      
 1189 
     | 
    
         
            +
                end
         
     | 
| 
       1225 
1190 
     | 
    
         
             
              end
         
     | 
| 
       1226 
1191 
     | 
    
         
             
            end
         
     | 
| 
      
 1192 
     | 
    
         
            +
             
     | 
| 
      
 1193 
     | 
    
         
            +
            # reindex models for non-parallel tests
         
     | 
| 
      
 1194 
     | 
    
         
            +
            Product.reindex
         
     | 
| 
      
 1195 
     | 
    
         
            +
             
     | 
| 
      
 1196 
     | 
    
         
            +
            # and disable callbacks
         
     | 
| 
      
 1197 
     | 
    
         
            +
            Searchkick.disable_callbacks
         
     | 
| 
       1227 
1198 
     | 
    
         
             
            ```
         
     | 
| 
       1228 
1199 
     | 
    
         | 
| 
       1229 
1200 
     | 
    
         
             
            And use:
         
     | 
| 
       1230 
1201 
     | 
    
         | 
| 
       1231 
1202 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1232 
1203 
     | 
    
         
             
            class ProductTest < ActiveSupport::TestCase
         
     | 
| 
       1233 
     | 
    
         
            -
               
     | 
| 
      
 1204 
     | 
    
         
            +
              setup do
         
     | 
| 
       1234 
1205 
     | 
    
         
             
                Searchkick.enable_callbacks
         
     | 
| 
       1235 
1206 
     | 
    
         
             
              end
         
     | 
| 
       1236 
1207 
     | 
    
         | 
| 
       1237 
     | 
    
         
            -
               
     | 
| 
      
 1208 
     | 
    
         
            +
              teardown do
         
     | 
| 
       1238 
1209 
     | 
    
         
             
                Searchkick.disable_callbacks
         
     | 
| 
       1239 
1210 
     | 
    
         
             
              end
         
     | 
| 
       1240 
1211 
     | 
    
         | 
| 
       1241 
     | 
    
         
            -
               
     | 
| 
      
 1212 
     | 
    
         
            +
              test "search" do
         
     | 
| 
       1242 
1213 
     | 
    
         
             
                Product.create!(name: "Apple")
         
     | 
| 
       1243 
1214 
     | 
    
         
             
                Product.search_index.refresh
         
     | 
| 
       1244 
1215 
     | 
    
         
             
                assert_equal ["Apple"], Product.search("apple").map(&:name)
         
     | 
| 
         @@ -1314,25 +1285,24 @@ end 
     | 
|
| 
       1314 
1285 
     | 
    
         | 
| 
       1315 
1286 
     | 
    
         
             
            ### Factory Bot
         
     | 
| 
       1316 
1287 
     | 
    
         | 
| 
       1317 
     | 
    
         
            -
             
     | 
| 
      
 1288 
     | 
    
         
            +
            Define a trait for each model:
         
     | 
| 
       1318 
1289 
     | 
    
         | 
| 
       1319 
1290 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1320 
1291 
     | 
    
         
             
            FactoryBot.define do
         
     | 
| 
       1321 
1292 
     | 
    
         
             
              factory :product do
         
     | 
| 
       1322 
     | 
    
         
            -
                # ...
         
     | 
| 
       1323 
     | 
    
         
            -
             
     | 
| 
       1324 
     | 
    
         
            -
                # Note: This should be the last trait in the list so `reindex` is called
         
     | 
| 
       1325 
     | 
    
         
            -
                # after all the other callbacks complete.
         
     | 
| 
       1326 
1293 
     | 
    
         
             
                trait :reindex do
         
     | 
| 
       1327 
     | 
    
         
            -
                  after(:create) do |product,  
     | 
| 
      
 1294 
     | 
    
         
            +
                  after(:create) do |product, _|
         
     | 
| 
       1328 
1295 
     | 
    
         
             
                    product.reindex(refresh: true)
         
     | 
| 
       1329 
1296 
     | 
    
         
             
                  end
         
     | 
| 
       1330 
1297 
     | 
    
         
             
                end
         
     | 
| 
       1331 
1298 
     | 
    
         
             
              end
         
     | 
| 
       1332 
1299 
     | 
    
         
             
            end
         
     | 
| 
      
 1300 
     | 
    
         
            +
            ```
         
     | 
| 
       1333 
1301 
     | 
    
         | 
| 
       1334 
     | 
    
         
            -
             
     | 
| 
       1335 
     | 
    
         
            -
             
     | 
| 
      
 1302 
     | 
    
         
            +
            And use:
         
     | 
| 
      
 1303 
     | 
    
         
            +
             
     | 
| 
      
 1304 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1305 
     | 
    
         
            +
            FactoryBot.create(:product, :reindex)
         
     | 
| 
       1336 
1306 
     | 
    
         
             
            ```
         
     | 
| 
       1337 
1307 
     | 
    
         | 
| 
       1338 
1308 
     | 
    
         
             
            ### GitHub Actions
         
     | 
| 
         @@ -1354,8 +1324,8 @@ And [setup-opensearch](https://github.com/ankane/setup-opensearch) for an easy w 
     | 
|
| 
       1354 
1324 
     | 
    
         
             
            For the search server, Searchkick uses `ENV["ELASTICSEARCH_URL"]` for Elasticsearch and `ENV["OPENSEARCH_URL"]` for OpenSearch. This defaults to `http://localhost:9200`.
         
     | 
| 
       1355 
1325 
     | 
    
         | 
| 
       1356 
1326 
     | 
    
         
             
            - [Elastic Cloud](#elastic-cloud)
         
     | 
| 
       1357 
     | 
    
         
            -
            - [Heroku](#heroku)
         
     | 
| 
       1358 
1327 
     | 
    
         
             
            - [Amazon OpenSearch Service](#amazon-opensearch-service)
         
     | 
| 
      
 1328 
     | 
    
         
            +
            - [Heroku](#heroku)
         
     | 
| 
       1359 
1329 
     | 
    
         
             
            - [Self-Hosted and Other](#self-hosted-and-other)
         
     | 
| 
       1360 
1330 
     | 
    
         | 
| 
       1361 
1331 
     | 
    
         
             
            ### Elastic Cloud
         
     | 
| 
         @@ -1372,6 +1342,36 @@ Then deploy and reindex: 
     | 
|
| 
       1372 
1342 
     | 
    
         
             
            rake searchkick:reindex:all
         
     | 
| 
       1373 
1343 
     | 
    
         
             
            ```
         
     | 
| 
       1374 
1344 
     | 
    
         | 
| 
      
 1345 
     | 
    
         
            +
            ### Amazon OpenSearch Service
         
     | 
| 
      
 1346 
     | 
    
         
            +
             
     | 
| 
      
 1347 
     | 
    
         
            +
            Create an initializer `config/initializers/opensearch.rb` with:
         
     | 
| 
      
 1348 
     | 
    
         
            +
             
     | 
| 
      
 1349 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1350 
     | 
    
         
            +
            ENV["OPENSEARCH_URL"] = "https://es-domain-1234.us-east-1.es.amazonaws.com:443"
         
     | 
| 
      
 1351 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1352 
     | 
    
         
            +
             
     | 
| 
      
 1353 
     | 
    
         
            +
            To use signed requests, include in your Gemfile:
         
     | 
| 
      
 1354 
     | 
    
         
            +
             
     | 
| 
      
 1355 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1356 
     | 
    
         
            +
            gem "faraday_middleware-aws-sigv4"
         
     | 
| 
      
 1357 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1358 
     | 
    
         
            +
             
     | 
| 
      
 1359 
     | 
    
         
            +
            and add to your initializer:
         
     | 
| 
      
 1360 
     | 
    
         
            +
             
     | 
| 
      
 1361 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1362 
     | 
    
         
            +
            Searchkick.aws_credentials = {
         
     | 
| 
      
 1363 
     | 
    
         
            +
              access_key_id: ENV["AWS_ACCESS_KEY_ID"],
         
     | 
| 
      
 1364 
     | 
    
         
            +
              secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
         
     | 
| 
      
 1365 
     | 
    
         
            +
              region: "us-east-1"
         
     | 
| 
      
 1366 
     | 
    
         
            +
            }
         
     | 
| 
      
 1367 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1368 
     | 
    
         
            +
             
     | 
| 
      
 1369 
     | 
    
         
            +
            Then deploy and reindex:
         
     | 
| 
      
 1370 
     | 
    
         
            +
             
     | 
| 
      
 1371 
     | 
    
         
            +
            ```sh
         
     | 
| 
      
 1372 
     | 
    
         
            +
            rake searchkick:reindex:all
         
     | 
| 
      
 1373 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1374 
     | 
    
         
            +
             
     | 
| 
       1375 
1375 
     | 
    
         
             
            ### Heroku
         
     | 
| 
       1376 
1376 
     | 
    
         | 
| 
       1377 
1377 
     | 
    
         
             
            Choose an add-on: [Bonsai](https://elements.heroku.com/addons/bonsai), [SearchBox](https://elements.heroku.com/addons/searchbox), or [Elastic Cloud](https://elements.heroku.com/addons/foundelasticsearch).
         
     | 
| 
         @@ -1422,36 +1422,6 @@ Then deploy and reindex: 
     | 
|
| 
       1422 
1422 
     | 
    
         
             
            heroku run rake searchkick:reindex:all
         
     | 
| 
       1423 
1423 
     | 
    
         
             
            ```
         
     | 
| 
       1424 
1424 
     | 
    
         | 
| 
       1425 
     | 
    
         
            -
            ### Amazon OpenSearch Service
         
     | 
| 
       1426 
     | 
    
         
            -
             
     | 
| 
       1427 
     | 
    
         
            -
            Create an initializer `config/initializers/opensearch.rb` with:
         
     | 
| 
       1428 
     | 
    
         
            -
             
     | 
| 
       1429 
     | 
    
         
            -
            ```ruby
         
     | 
| 
       1430 
     | 
    
         
            -
            ENV["OPENSEARCH_URL"] = "https://es-domain-1234.us-east-1.es.amazonaws.com:443"
         
     | 
| 
       1431 
     | 
    
         
            -
            ```
         
     | 
| 
       1432 
     | 
    
         
            -
             
     | 
| 
       1433 
     | 
    
         
            -
            To use signed requests, include in your Gemfile:
         
     | 
| 
       1434 
     | 
    
         
            -
             
     | 
| 
       1435 
     | 
    
         
            -
            ```ruby
         
     | 
| 
       1436 
     | 
    
         
            -
            gem "faraday_middleware-aws-sigv4"
         
     | 
| 
       1437 
     | 
    
         
            -
            ```
         
     | 
| 
       1438 
     | 
    
         
            -
             
     | 
| 
       1439 
     | 
    
         
            -
            and add to your initializer:
         
     | 
| 
       1440 
     | 
    
         
            -
             
     | 
| 
       1441 
     | 
    
         
            -
            ```ruby
         
     | 
| 
       1442 
     | 
    
         
            -
            Searchkick.aws_credentials = {
         
     | 
| 
       1443 
     | 
    
         
            -
              access_key_id: ENV["AWS_ACCESS_KEY_ID"],
         
     | 
| 
       1444 
     | 
    
         
            -
              secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
         
     | 
| 
       1445 
     | 
    
         
            -
              region: "us-east-1"
         
     | 
| 
       1446 
     | 
    
         
            -
            }
         
     | 
| 
       1447 
     | 
    
         
            -
            ```
         
     | 
| 
       1448 
     | 
    
         
            -
             
     | 
| 
       1449 
     | 
    
         
            -
            Then deploy and reindex:
         
     | 
| 
       1450 
     | 
    
         
            -
             
     | 
| 
       1451 
     | 
    
         
            -
            ```sh
         
     | 
| 
       1452 
     | 
    
         
            -
            rake searchkick:reindex:all
         
     | 
| 
       1453 
     | 
    
         
            -
            ```
         
     | 
| 
       1454 
     | 
    
         
            -
             
     | 
| 
       1455 
1425 
     | 
    
         
             
            ### Self-Hosted and Other
         
     | 
| 
       1456 
1426 
     | 
    
         | 
| 
       1457 
1427 
     | 
    
         
             
            Create an initializer with:
         
     | 
| 
         @@ -1682,7 +1652,7 @@ end 
     | 
|
| 
       1682 
1652 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       1683 
1653 
     | 
    
         | 
| 
       1684 
1654 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1685 
     | 
    
         
            -
            Business.search("ice cream" 
     | 
| 
      
 1655 
     | 
    
         
            +
            Business.search("ice cream").routing(params[:city_id])
         
     | 
| 
       1686 
1656 
     | 
    
         
             
            ```
         
     | 
| 
       1687 
1657 
     | 
    
         | 
| 
       1688 
1658 
     | 
    
         
             
            ### Partial Reindexing
         
     | 
| 
         @@ -1713,6 +1683,12 @@ And use: 
     | 
|
| 
       1713 
1683 
     | 
    
         
             
            Product.reindex(:prices_data)
         
     | 
| 
       1714 
1684 
     | 
    
         
             
            ```
         
     | 
| 
       1715 
1685 
     | 
    
         | 
| 
      
 1686 
     | 
    
         
            +
            Ignore errors for missing documents with:
         
     | 
| 
      
 1687 
     | 
    
         
            +
             
     | 
| 
      
 1688 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1689 
     | 
    
         
            +
            Product.reindex(:prices_data, ignore_missing: true)
         
     | 
| 
      
 1690 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1691 
     | 
    
         
            +
             
     | 
| 
       1716 
1692 
     | 
    
         
             
            ## Advanced
         
     | 
| 
       1717 
1693 
     | 
    
         | 
| 
       1718 
1694 
     | 
    
         
             
            Searchkick makes it easy to use the Elasticsearch or OpenSearch DSL on its own.
         
     | 
| 
         @@ -1745,7 +1721,7 @@ end 
     | 
|
| 
       1745 
1721 
     | 
    
         
             
            And use the `body` option to search:
         
     | 
| 
       1746 
1722 
     | 
    
         | 
| 
       1747 
1723 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1748 
     | 
    
         
            -
            products = Product.search( 
     | 
| 
      
 1724 
     | 
    
         
            +
            products = Product.search.body(query: {match: {name: "milk"}})
         
     | 
| 
       1749 
1725 
     | 
    
         
             
            ```
         
     | 
| 
       1750 
1726 
     | 
    
         | 
| 
       1751 
1727 
     | 
    
         
             
            View the response with:
         
     | 
| 
         @@ -1757,7 +1733,7 @@ products.response 
     | 
|
| 
       1757 
1733 
     | 
    
         
             
            To modify the query generated by Searchkick, use:
         
     | 
| 
       1758 
1734 
     | 
    
         | 
| 
       1759 
1735 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1760 
     | 
    
         
            -
            products = Product.search("milk" 
     | 
| 
      
 1736 
     | 
    
         
            +
            products = Product.search("milk").body_options(min_score: 1)
         
     | 
| 
       1761 
1737 
     | 
    
         
             
            ```
         
     | 
| 
       1762 
1738 
     | 
    
         | 
| 
       1763 
1739 
     | 
    
         
             
            or
         
     | 
| 
         @@ -1796,13 +1772,13 @@ Then use `products` and `coupons` as typical results. 
     | 
|
| 
       1796 
1772 
     | 
    
         
             
            Search across multiple models with:
         
     | 
| 
       1797 
1773 
     | 
    
         | 
| 
       1798 
1774 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1799 
     | 
    
         
            -
            Searchkick.search("milk" 
     | 
| 
      
 1775 
     | 
    
         
            +
            Searchkick.search("milk").models(Product, Category)
         
     | 
| 
       1800 
1776 
     | 
    
         
             
            ```
         
     | 
| 
       1801 
1777 
     | 
    
         | 
| 
       1802 
1778 
     | 
    
         
             
            Boost specific models with:
         
     | 
| 
       1803 
1779 
     | 
    
         | 
| 
       1804 
1780 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1805 
     | 
    
         
            -
            indices_boost 
     | 
| 
      
 1781 
     | 
    
         
            +
            indices_boost(Category => 2, Product => 1)
         
     | 
| 
       1806 
1782 
     | 
    
         
             
            ```
         
     | 
| 
       1807 
1783 
     | 
    
         | 
| 
       1808 
1784 
     | 
    
         
             
            ## Multi-Tenancy
         
     | 
| 
         @@ -1814,7 +1790,7 @@ Check out [this great post](https://www.tiagoamaro.com.br/2014/12/11/multi-tenan 
     | 
|
| 
       1814 
1790 
     | 
    
         
             
            Searchkick also supports the [scroll API](https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html#scroll-search-results). Scrolling is not intended for real time user requests, but rather for processing large amounts of data.
         
     | 
| 
       1815 
1791 
     | 
    
         | 
| 
       1816 
1792 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1817 
     | 
    
         
            -
            Product.search("*" 
     | 
| 
      
 1793 
     | 
    
         
            +
            Product.search("*").scroll("1m") do |batch|
         
     | 
| 
       1818 
1794 
     | 
    
         
             
              # process batch ...
         
     | 
| 
       1819 
1795 
     | 
    
         
             
            end
         
     | 
| 
       1820 
1796 
     | 
    
         
             
            ```
         
     | 
| 
         @@ -1822,7 +1798,7 @@ end 
     | 
|
| 
       1822 
1798 
     | 
    
         
             
            You can also scroll batches manually.
         
     | 
| 
       1823 
1799 
     | 
    
         | 
| 
       1824 
1800 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1825 
     | 
    
         
            -
            products = Product.search("*" 
     | 
| 
      
 1801 
     | 
    
         
            +
            products = Product.search("*").scroll("1m")
         
     | 
| 
       1826 
1802 
     | 
    
         
             
            while products.any?
         
     | 
| 
       1827 
1803 
     | 
    
         
             
              # process batch ...
         
     | 
| 
       1828 
1804 
     | 
    
         | 
| 
         @@ -1845,7 +1821,7 @@ end 
     | 
|
| 
       1845 
1821 
     | 
    
         
             
            If you just need an accurate total count, you can instead use:
         
     | 
| 
       1846 
1822 
     | 
    
         | 
| 
       1847 
1823 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1848 
     | 
    
         
            -
            Product.search("pears" 
     | 
| 
      
 1824 
     | 
    
         
            +
            Product.search("pears").body_options(track_total_hits: true)
         
     | 
| 
       1849 
1825 
     | 
    
         
             
            ```
         
     | 
| 
       1850 
1826 
     | 
    
         | 
| 
       1851 
1827 
     | 
    
         
             
            ## Nested Data
         
     | 
| 
         @@ -1853,7 +1829,7 @@ Product.search("pears", body_options: {track_total_hits: true}) 
     | 
|
| 
       1853 
1829 
     | 
    
         
             
            To query nested data, use dot notation.
         
     | 
| 
       1854 
1830 
     | 
    
         | 
| 
       1855 
1831 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1856 
     | 
    
         
            -
            Product.search("san" 
     | 
| 
      
 1832 
     | 
    
         
            +
            Product.search("san").fields("store.city").where("store.zip_code" => 12345)
         
     | 
| 
       1857 
1833 
     | 
    
         
             
            ```
         
     | 
| 
       1858 
1834 
     | 
    
         | 
| 
       1859 
1835 
     | 
    
         
             
            ## Nearest Neighbor Search
         
     | 
| 
         @@ -1871,7 +1847,7 @@ Also supports `euclidean` and `inner_product` 
     | 
|
| 
       1871 
1847 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       1872 
1848 
     | 
    
         | 
| 
       1873 
1849 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1874 
     | 
    
         
            -
            Product.search( 
     | 
| 
      
 1850 
     | 
    
         
            +
            Product.search.knn(field: :embedding, vector: [1, 2, 3]).limit(10)
         
     | 
| 
       1875 
1851 
     | 
    
         
             
            ```
         
     | 
| 
       1876 
1852 
     | 
    
         | 
| 
       1877 
1853 
     | 
    
         
             
            ### HNSW Options
         
     | 
| 
         @@ -1889,7 +1865,7 @@ end 
     | 
|
| 
       1889 
1865 
     | 
    
         
             
            Specify `ef_search`
         
     | 
| 
       1890 
1866 
     | 
    
         | 
| 
       1891 
1867 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1892 
     | 
    
         
            -
            Product.search( 
     | 
| 
      
 1868 
     | 
    
         
            +
            Product.search.knn(field: :embedding, vector: [1, 2, 3], ef_search: 40).limit(10)
         
     | 
| 
       1893 
1869 
     | 
    
         
             
            ```
         
     | 
| 
       1894 
1870 
     | 
    
         | 
| 
       1895 
1871 
     | 
    
         
             
            ## Semantic Search
         
     | 
| 
         @@ -1924,7 +1900,7 @@ query_embedding = embed.(query_prefix + query, **embed_options) 
     | 
|
| 
       1924 
1900 
     | 
    
         
             
            And perform nearest neighbor search
         
     | 
| 
       1925 
1901 
     | 
    
         | 
| 
       1926 
1902 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1927 
     | 
    
         
            -
            Product.search( 
     | 
| 
      
 1903 
     | 
    
         
            +
            Product.search.knn(field: :embedding, vector: query_embedding).limit(20)
         
     | 
| 
       1928 
1904 
     | 
    
         
             
            ```
         
     | 
| 
       1929 
1905 
     | 
    
         | 
| 
       1930 
1906 
     | 
    
         
             
            See a [full example](examples/semantic.rb)
         
     | 
| 
         @@ -1934,8 +1910,8 @@ See a [full example](examples/semantic.rb) 
     | 
|
| 
       1934 
1910 
     | 
    
         
             
            Perform keyword search and semantic search in parallel
         
     | 
| 
       1935 
1911 
     | 
    
         | 
| 
       1936 
1912 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1937 
     | 
    
         
            -
            keyword_search = Product.search(query 
     | 
| 
       1938 
     | 
    
         
            -
            semantic_search = Product.search( 
     | 
| 
      
 1913 
     | 
    
         
            +
            keyword_search = Product.search(query).limit(20)
         
     | 
| 
      
 1914 
     | 
    
         
            +
            semantic_search = Product.search.knn(field: :embedding, vector: query_embedding).limit(20)
         
     | 
| 
       1939 
1915 
     | 
    
         
             
            Searchkick.multi_search([keyword_search, semantic_search])
         
     | 
| 
       1940 
1916 
     | 
    
         
             
            ```
         
     | 
| 
       1941 
1917 
     | 
    
         | 
| 
         @@ -2023,36 +1999,33 @@ Searchkick.index_prefix = "datakick" 
     | 
|
| 
       2023 
1999 
     | 
    
         
             
            Use a different term for boosting by conversions
         
     | 
| 
       2024 
2000 
     | 
    
         | 
| 
       2025 
2001 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2026 
     | 
    
         
            -
            Product.search("banana" 
     | 
| 
      
 2002 
     | 
    
         
            +
            Product.search("banana").conversions_v2(term: "organic banana")
         
     | 
| 
       2027 
2003 
     | 
    
         
             
            ```
         
     | 
| 
       2028 
2004 
     | 
    
         | 
| 
       2029 
     | 
    
         
            -
             
     | 
| 
      
 2005 
     | 
    
         
            +
            Define multiple conversion fields
         
     | 
| 
       2030 
2006 
     | 
    
         | 
| 
       2031 
2007 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2032 
2008 
     | 
    
         
             
            class Product < ApplicationRecord
         
     | 
| 
       2033 
2009 
     | 
    
         
             
              has_many :searches, class_name: "Searchjoy::Search"
         
     | 
| 
       2034 
2010 
     | 
    
         | 
| 
       2035 
     | 
    
         
            -
               
     | 
| 
       2036 
     | 
    
         
            -
              searchkick conversions: ["unique_user_conversions", "total_conversions"]
         
     | 
| 
      
 2011 
     | 
    
         
            +
              searchkick conversions_v2: ["unique_conversions", "total_conversions"]
         
     | 
| 
       2037 
2012 
     | 
    
         | 
| 
       2038 
2013 
     | 
    
         
             
              def search_data
         
     | 
| 
       2039 
2014 
     | 
    
         
             
                {
         
     | 
| 
       2040 
2015 
     | 
    
         
             
                  name: name,
         
     | 
| 
       2041 
     | 
    
         
            -
                   
     | 
| 
       2042 
     | 
    
         
            -
                  # {"ice cream" => 234, "chocolate" => 67, "cream" => 2}
         
     | 
| 
      
 2016 
     | 
    
         
            +
                  unique_conversions: searches.group(:query).distinct.count(:user_id),
         
     | 
| 
       2043 
2017 
     | 
    
         
             
                  total_conversions: searches.group(:query).count
         
     | 
| 
       2044 
     | 
    
         
            -
                  # {"ice cream" => 412, "chocolate" => 117, "cream" => 6}
         
     | 
| 
       2045 
2018 
     | 
    
         
             
                }
         
     | 
| 
       2046 
2019 
     | 
    
         
             
              end
         
     | 
| 
       2047 
2020 
     | 
    
         
             
            end
         
     | 
| 
       2048 
2021 
     | 
    
         
             
            ```
         
     | 
| 
       2049 
2022 
     | 
    
         | 
| 
       2050 
     | 
    
         
            -
             
     | 
| 
      
 2023 
     | 
    
         
            +
            And specify which to use
         
     | 
| 
       2051 
2024 
     | 
    
         | 
| 
       2052 
2025 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2053 
2026 
     | 
    
         
             
            Product.search("banana") # boost by both fields (default)
         
     | 
| 
       2054 
     | 
    
         
            -
            Product.search("banana" 
     | 
| 
       2055 
     | 
    
         
            -
            Product.search("banana" 
     | 
| 
      
 2027 
     | 
    
         
            +
            Product.search("banana").conversions_v2("total_conversions") # only boost by total_conversions
         
     | 
| 
      
 2028 
     | 
    
         
            +
            Product.search("banana").conversions_v2(false) # no conversion boosting
         
     | 
| 
       2056 
2029 
     | 
    
         
             
            ```
         
     | 
| 
       2057 
2030 
     | 
    
         | 
| 
       2058 
2031 
     | 
    
         
             
            Change timeout
         
     | 
| 
         @@ -2073,28 +2046,56 @@ Change the search method name 
     | 
|
| 
       2073 
2046 
     | 
    
         
             
            Searchkick.search_method_name = :lookup
         
     | 
| 
       2074 
2047 
     | 
    
         
             
            ```
         
     | 
| 
       2075 
2048 
     | 
    
         | 
| 
       2076 
     | 
    
         
            -
            Change  
     | 
| 
      
 2049 
     | 
    
         
            +
            Change the queue name
         
     | 
| 
      
 2050 
     | 
    
         
            +
             
     | 
| 
      
 2051 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2052 
     | 
    
         
            +
            Searchkick.queue_name = :search_reindex # defaults to :searchkick
         
     | 
| 
      
 2053 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2054 
     | 
    
         
            +
             
     | 
| 
      
 2055 
     | 
    
         
            +
            Change the queue name or priority for a model
         
     | 
| 
      
 2056 
     | 
    
         
            +
             
     | 
| 
      
 2057 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2058 
     | 
    
         
            +
            class Product < ApplicationRecord
         
     | 
| 
      
 2059 
     | 
    
         
            +
              searchkick job_options: {queue: "critical", priority: 10}
         
     | 
| 
      
 2060 
     | 
    
         
            +
            end
         
     | 
| 
      
 2061 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2062 
     | 
    
         
            +
             
     | 
| 
      
 2063 
     | 
    
         
            +
            Change the queue name or priority for a specific call
         
     | 
| 
      
 2064 
     | 
    
         
            +
             
     | 
| 
      
 2065 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2066 
     | 
    
         
            +
            Product.reindex(mode: :async, job_options: {queue: "critical", priority: 10})
         
     | 
| 
      
 2067 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2068 
     | 
    
         
            +
             
     | 
| 
      
 2069 
     | 
    
         
            +
            Change the parent job
         
     | 
| 
       2077 
2070 
     | 
    
         | 
| 
       2078 
2071 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2079 
     | 
    
         
            -
            Searchkick. 
     | 
| 
      
 2072 
     | 
    
         
            +
            Searchkick.parent_job = "ApplicationJob" # defaults to "ActiveJob::Base"
         
     | 
| 
       2080 
2073 
     | 
    
         
             
            ```
         
     | 
| 
       2081 
2074 
     | 
    
         | 
| 
       2082 
2075 
     | 
    
         
             
            Eager load associations
         
     | 
| 
       2083 
2076 
     | 
    
         | 
| 
       2084 
2077 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2085 
     | 
    
         
            -
            Product.search("milk" 
     | 
| 
      
 2078 
     | 
    
         
            +
            Product.search("milk").includes(:brand, :stores)
         
     | 
| 
       2086 
2079 
     | 
    
         
             
            ```
         
     | 
| 
       2087 
2080 
     | 
    
         | 
| 
       2088 
2081 
     | 
    
         
             
            Eager load different associations by model
         
     | 
| 
       2089 
2082 
     | 
    
         | 
| 
       2090 
2083 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2091 
     | 
    
         
            -
            Searchkick.search("*" 
     | 
| 
      
 2084 
     | 
    
         
            +
            Searchkick.search("*").models(Product, Store).model_includes(Product => [:store], Store => [:product])
         
     | 
| 
       2092 
2085 
     | 
    
         
             
            ```
         
     | 
| 
       2093 
2086 
     | 
    
         | 
| 
       2094 
2087 
     | 
    
         
             
            Run additional scopes on results
         
     | 
| 
       2095 
2088 
     | 
    
         | 
| 
       2096 
2089 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2097 
     | 
    
         
            -
            Product.search("milk" 
     | 
| 
      
 2090 
     | 
    
         
            +
            Product.search("milk").scope_results(->(r) { r.with_attached_images })
         
     | 
| 
      
 2091 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2092 
     | 
    
         
            +
             
     | 
| 
      
 2093 
     | 
    
         
            +
            Set opaque id for slow logs
         
     | 
| 
      
 2094 
     | 
    
         
            +
             
     | 
| 
      
 2095 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2096 
     | 
    
         
            +
            Product.search("milk").opaque_id("some-id")
         
     | 
| 
      
 2097 
     | 
    
         
            +
            # or
         
     | 
| 
      
 2098 
     | 
    
         
            +
            Searchkick.multi_search(searches, opaque_id: "some-id")
         
     | 
| 
       2098 
2099 
     | 
    
         
             
            ```
         
     | 
| 
       2099 
2100 
     | 
    
         | 
| 
       2100 
2101 
     | 
    
         
             
            Specify default fields to search
         
     | 
| 
         @@ -2159,7 +2160,7 @@ end 
     | 
|
| 
       2159 
2160 
     | 
    
         
             
            Add [request parameters](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html#search-search-api-query-params) like `search_type`
         
     | 
| 
       2160 
2161 
     | 
    
         | 
| 
       2161 
2162 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2162 
     | 
    
         
            -
            Product.search("carrots" 
     | 
| 
      
 2163 
     | 
    
         
            +
            Product.search("carrots").request_params(search_type: "dfs_query_then_fetch")
         
     | 
| 
       2163 
2164 
     | 
    
         
             
            ```
         
     | 
| 
       2164 
2165 
     | 
    
         | 
| 
       2165 
2166 
     | 
    
         
             
            Set options across all models
         
     | 
| 
         @@ -2174,10 +2175,11 @@ Reindex conditionally 
     | 
|
| 
       2174 
2175 
     | 
    
         | 
| 
       2175 
2176 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2176 
2177 
     | 
    
         
             
            class Product < ApplicationRecord
         
     | 
| 
       2177 
     | 
    
         
            -
              searchkick  
     | 
| 
      
 2178 
     | 
    
         
            +
              searchkick callback_options: {if: :search_data_changed?}
         
     | 
| 
       2178 
2179 
     | 
    
         | 
| 
       2179 
     | 
    
         
            -
               
     | 
| 
       2180 
     | 
    
         
            -
             
     | 
| 
      
 2180 
     | 
    
         
            +
              def search_data_changed?
         
     | 
| 
      
 2181 
     | 
    
         
            +
                previous_changes.include?("name")
         
     | 
| 
      
 2182 
     | 
    
         
            +
              end
         
     | 
| 
       2181 
2183 
     | 
    
         
             
            end
         
     | 
| 
       2182 
2184 
     | 
    
         
             
            ```
         
     | 
| 
       2183 
2185 
     | 
    
         | 
| 
         @@ -2190,13 +2192,7 @@ rake searchkick:reindex:all 
     | 
|
| 
       2190 
2192 
     | 
    
         
             
            Turn on misspellings after a certain number of characters
         
     | 
| 
       2191 
2193 
     | 
    
         | 
| 
       2192 
2194 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2193 
     | 
    
         
            -
            Product.search("api" 
     | 
| 
       2194 
     | 
    
         
            -
            ```
         
     | 
| 
       2195 
     | 
    
         
            -
             
     | 
| 
       2196 
     | 
    
         
            -
            **Note:** With this option, if the query length is the same as `prefix_length`, misspellings are turned off with Elasticsearch 7 and OpenSearch 1
         
     | 
| 
       2197 
     | 
    
         
            -
             
     | 
| 
       2198 
     | 
    
         
            -
            ```ruby
         
     | 
| 
       2199 
     | 
    
         
            -
            Product.search("ah", misspellings: {prefix_length: 2}) # ah, no aha
         
     | 
| 
      
 2195 
     | 
    
         
            +
            Product.search("api").misspellings(prefix_length: 2) # api, apt, no ahi
         
     | 
| 
       2200 
2196 
     | 
    
         
             
            ```
         
     | 
| 
       2201 
2197 
     | 
    
         | 
| 
       2202 
2198 
     | 
    
         
             
            BigDecimal values are indexed as floats by default so they can be used for boosting. Convert them to strings to keep full precision.
         
     | 
| 
         @@ -2234,9 +2230,53 @@ end 
     | 
|
| 
       2234 
2230 
     | 
    
         | 
| 
       2235 
2231 
     | 
    
         
             
            For convenience, this is set by default in the test environment.
         
     | 
| 
       2236 
2232 
     | 
    
         | 
| 
      
 2233 
     | 
    
         
            +
            ## Upgrading
         
     | 
| 
      
 2234 
     | 
    
         
            +
             
     | 
| 
      
 2235 
     | 
    
         
            +
            ### 6.0
         
     | 
| 
      
 2236 
     | 
    
         
            +
             
     | 
| 
      
 2237 
     | 
    
         
            +
            Searchkick 6 brings a new query builder API:
         
     | 
| 
      
 2238 
     | 
    
         
            +
             
     | 
| 
      
 2239 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2240 
     | 
    
         
            +
            Product.search("apples").where(in_stock: true).limit(10).offset(50)
         
     | 
| 
      
 2241 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2242 
     | 
    
         
            +
             
     | 
| 
      
 2243 
     | 
    
         
            +
            All existing options can be used as methods, or you can continue to use the existing API.
         
     | 
| 
      
 2244 
     | 
    
         
            +
             
     | 
| 
      
 2245 
     | 
    
         
            +
            This release also significantly improves the performance of searches when using conversions. To upgrade without downtime, add `conversions_v2` to your model and an additional field to `search_data`:
         
     | 
| 
      
 2246 
     | 
    
         
            +
             
     | 
| 
      
 2247 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2248 
     | 
    
         
            +
            class Product < ApplicationRecord
         
     | 
| 
      
 2249 
     | 
    
         
            +
              searchkick conversions: [:conversions], conversions_v2: [:conversions_v2]
         
     | 
| 
      
 2250 
     | 
    
         
            +
             
     | 
| 
      
 2251 
     | 
    
         
            +
              def search_data
         
     | 
| 
      
 2252 
     | 
    
         
            +
                conversions = searches.group(:query).distinct.count(:user_id)
         
     | 
| 
      
 2253 
     | 
    
         
            +
                {
         
     | 
| 
      
 2254 
     | 
    
         
            +
                  conversions: conversions,
         
     | 
| 
      
 2255 
     | 
    
         
            +
                  conversions_v2: conversions
         
     | 
| 
      
 2256 
     | 
    
         
            +
                }
         
     | 
| 
      
 2257 
     | 
    
         
            +
              end
         
     | 
| 
      
 2258 
     | 
    
         
            +
            end
         
     | 
| 
      
 2259 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2260 
     | 
    
         
            +
             
     | 
| 
      
 2261 
     | 
    
         
            +
            Reindex, then remove `conversions`:
         
     | 
| 
      
 2262 
     | 
    
         
            +
             
     | 
| 
      
 2263 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2264 
     | 
    
         
            +
            class Product < ApplicationRecord
         
     | 
| 
      
 2265 
     | 
    
         
            +
              searchkick conversions_v2: [:conversions_v2]
         
     | 
| 
      
 2266 
     | 
    
         
            +
             
     | 
| 
      
 2267 
     | 
    
         
            +
              def search_data
         
     | 
| 
      
 2268 
     | 
    
         
            +
                {
         
     | 
| 
      
 2269 
     | 
    
         
            +
                  conversions_v2: searches.group(:query).distinct.count(:user_id)
         
     | 
| 
      
 2270 
     | 
    
         
            +
                }
         
     | 
| 
      
 2271 
     | 
    
         
            +
              end
         
     | 
| 
      
 2272 
     | 
    
         
            +
            end
         
     | 
| 
      
 2273 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2274 
     | 
    
         
            +
             
     | 
| 
      
 2275 
     | 
    
         
            +
            Other improvements include the option to ignore errors for missing documents with partial reindexing and more customization for background jobs. Check out the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md) for the full list of changes.
         
     | 
| 
      
 2276 
     | 
    
         
            +
             
     | 
| 
       2237 
2277 
     | 
    
         
             
            ## History
         
     | 
| 
       2238 
2278 
     | 
    
         | 
| 
       2239 
     | 
    
         
            -
            View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md) 
     | 
| 
      
 2279 
     | 
    
         
            +
            View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md)
         
     | 
| 
       2240 
2280 
     | 
    
         | 
| 
       2241 
2281 
     | 
    
         
             
            ## Thanks
         
     | 
| 
       2242 
2282 
     | 
    
         |