searchkick 5.5.2 → 6.0.1
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 +26 -0
 - data/README.md +333 -224
 - data/lib/searchkick/bulk_reindex_job.rb +3 -5
 - data/lib/searchkick/hash_wrapper.rb +34 -9
 - data/lib/searchkick/index.rb +25 -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,40 +95,27 @@ 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:  
     | 
| 
       112 
     | 
    
         
            -
              expires_at: {gt: Time.now},    # lt, gte, lte also available
         
     | 
| 
       113 
     | 
    
         
            -
              orders_count: 1..10,           # equivalent to {gte: 1, lte: 10}
         
     | 
| 
       114 
     | 
    
         
            -
              aisle_id: [25, 30],            # in
         
     | 
| 
       115 
     | 
    
         
            -
              store_id: {not: 2},            # not
         
     | 
| 
       116 
     | 
    
         
            -
              aisle_id: {not: [25, 30]},     # not in
         
     | 
| 
       117 
     | 
    
         
            -
              user_ids: {all: [1, 3]},       # all elements in array
         
     | 
| 
       118 
     | 
    
         
            -
              category: {like: "%frozen%"},  # like
         
     | 
| 
       119 
     | 
    
         
            -
              category: {ilike: "%frozen%"}, # ilike
         
     | 
| 
       120 
     | 
    
         
            -
              category: /frozen .+/,         # regexp
         
     | 
| 
       121 
     | 
    
         
            -
              category: {prefix: "frozen"},  # prefix
         
     | 
| 
       122 
     | 
    
         
            -
              store_id: {exists: true},      # exists
         
     | 
| 
       123 
     | 
    
         
            -
              _not: {store_id: 1},           # negate a condition
         
     | 
| 
       124 
     | 
    
         
            -
              _or: [{in_stock: true}, {backordered: true}],
         
     | 
| 
       125 
     | 
    
         
            -
              _and: [{in_stock: true}, {backordered: true}]
         
     | 
| 
       126 
     | 
    
         
            -
            }
         
     | 
| 
      
 110 
     | 
    
         
            +
            where(store_id: 1, expires_at: Time.now..)
         
     | 
| 
       127 
111 
     | 
    
         
             
            ```
         
     | 
| 
       128 
112 
     | 
    
         | 
| 
      
 113 
     | 
    
         
            +
            [These types of filters are supported](#filtering)
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
       129 
115 
     | 
    
         
             
            Order
         
     | 
| 
       130 
116 
     | 
    
         | 
| 
       131 
117 
     | 
    
         
             
            ```ruby
         
     | 
| 
       132 
     | 
    
         
            -
            order 
     | 
| 
      
 118 
     | 
    
         
            +
            order(_score: :desc) # most relevant first - default
         
     | 
| 
       133 
119 
     | 
    
         
             
            ```
         
     | 
| 
       134 
120 
     | 
    
         | 
| 
       135 
121 
     | 
    
         
             
            [All of these sort options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html)
         
     | 
| 
         @@ -137,13 +123,13 @@ order: {_score: :desc} # most relevant first - default 
     | 
|
| 
       137 
123 
     | 
    
         
             
            Limit / offset
         
     | 
| 
       138 
124 
     | 
    
         | 
| 
       139 
125 
     | 
    
         
             
            ```ruby
         
     | 
| 
       140 
     | 
    
         
            -
            limit 
     | 
| 
      
 126 
     | 
    
         
            +
            limit(20).offset(40)
         
     | 
| 
       141 
127 
     | 
    
         
             
            ```
         
     | 
| 
       142 
128 
     | 
    
         | 
| 
       143 
129 
     | 
    
         
             
            Select
         
     | 
| 
       144 
130 
     | 
    
         | 
| 
       145 
131 
     | 
    
         
             
            ```ruby
         
     | 
| 
       146 
     | 
    
         
            -
            select: 
     | 
| 
      
 132 
     | 
    
         
            +
            select(:name)
         
     | 
| 
       147 
133 
     | 
    
         
             
            ```
         
     | 
| 
       148 
134 
     | 
    
         | 
| 
       149 
135 
     | 
    
         
             
            [These source filtering options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html#source-filtering)
         
     | 
| 
         @@ -162,7 +148,7 @@ results.each { |result| ... } 
     | 
|
| 
       162 
148 
     | 
    
         
             
            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 
149 
     | 
    
         | 
| 
       164 
150 
     | 
    
         
             
            ```ruby
         
     | 
| 
       165 
     | 
    
         
            -
            Product.search("apples" 
     | 
| 
      
 151 
     | 
    
         
            +
            Product.search("apples").load(false)
         
     | 
| 
       166 
152 
     | 
    
         
             
            ```
         
     | 
| 
       167 
153 
     | 
    
         | 
| 
       168 
154 
     | 
    
         
             
            Get total results
         
     | 
| 
         @@ -185,33 +171,113 @@ results.response 
     | 
|
| 
       185 
171 
     | 
    
         | 
| 
       186 
172 
     | 
    
         
             
            **Note:** By default, Elasticsearch and OpenSearch [limit paging](#deep-paging) to the first 10,000 results for performance. This applies to the total count as well.
         
     | 
| 
       187 
173 
     | 
    
         | 
| 
      
 174 
     | 
    
         
            +
            ### Filtering
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
            Equal
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 179 
     | 
    
         
            +
            where(store_id: 1)
         
     | 
| 
      
 180 
     | 
    
         
            +
            ```
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
            Not equal
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 185 
     | 
    
         
            +
            where.not(store_id: 2)
         
     | 
| 
      
 186 
     | 
    
         
            +
            ```
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
            Greater than (`gt`), less than (`lt`), greater than or equal (`gte`), less than or equal (`lte`)
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 191 
     | 
    
         
            +
            where(expires_at: {gt: Time.now})
         
     | 
| 
      
 192 
     | 
    
         
            +
            ```
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
            Range
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 197 
     | 
    
         
            +
            where(orders_count: 1..10)
         
     | 
| 
      
 198 
     | 
    
         
            +
            ```
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
            In
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 203 
     | 
    
         
            +
            where(aisle_id: [25, 30])
         
     | 
| 
      
 204 
     | 
    
         
            +
            ```
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
            Not in
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 209 
     | 
    
         
            +
            where.not(aisle_id: [25, 30])
         
     | 
| 
      
 210 
     | 
    
         
            +
            ```
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
            Contains all
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 215 
     | 
    
         
            +
            where(user_ids: {all: [1, 3]})
         
     | 
| 
      
 216 
     | 
    
         
            +
            ```
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
            Like
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 221 
     | 
    
         
            +
            where(category: {like: "%frozen%"})
         
     | 
| 
      
 222 
     | 
    
         
            +
            ```
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
            Case-insensitive like
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 227 
     | 
    
         
            +
            where(category: {ilike: "%frozen%"})
         
     | 
| 
      
 228 
     | 
    
         
            +
            ```
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
            Regular expression
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 233 
     | 
    
         
            +
            where(category: /frozen .+/)
         
     | 
| 
      
 234 
     | 
    
         
            +
            ```
         
     | 
| 
      
 235 
     | 
    
         
            +
             
     | 
| 
      
 236 
     | 
    
         
            +
            Prefix
         
     | 
| 
      
 237 
     | 
    
         
            +
             
     | 
| 
      
 238 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 239 
     | 
    
         
            +
            where(category: {prefix: "frozen"})
         
     | 
| 
      
 240 
     | 
    
         
            +
            ```
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
      
 242 
     | 
    
         
            +
            Exists
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 245 
     | 
    
         
            +
            where(store_id: {exists: true})
         
     | 
| 
      
 246 
     | 
    
         
            +
            ```
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
            Combine filters with OR
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 251 
     | 
    
         
            +
            where(_or: [{in_stock: true}, {backordered: true}])
         
     | 
| 
      
 252 
     | 
    
         
            +
            ```
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
       188 
254 
     | 
    
         
             
            ### Boosting
         
     | 
| 
       189 
255 
     | 
    
         | 
| 
       190 
256 
     | 
    
         
             
            Boost important fields
         
     | 
| 
       191 
257 
     | 
    
         | 
| 
       192 
258 
     | 
    
         
             
            ```ruby
         
     | 
| 
       193 
     | 
    
         
            -
            fields 
     | 
| 
      
 259 
     | 
    
         
            +
            fields("title^10", "description")
         
     | 
| 
       194 
260 
     | 
    
         
             
            ```
         
     | 
| 
       195 
261 
     | 
    
         | 
| 
       196 
262 
     | 
    
         
             
            Boost by the value of a field (field must be numeric)
         
     | 
| 
       197 
263 
     | 
    
         | 
| 
       198 
264 
     | 
    
         
             
            ```ruby
         
     | 
| 
       199 
     | 
    
         
            -
            boost_by: 
     | 
| 
       200 
     | 
    
         
            -
            boost_by 
     | 
| 
      
 265 
     | 
    
         
            +
            boost_by(:orders_count) # give popular documents a little boost
         
     | 
| 
      
 266 
     | 
    
         
            +
            boost_by(orders_count: {factor: 10}) # default factor is 1
         
     | 
| 
       201 
267 
     | 
    
         
             
            ```
         
     | 
| 
       202 
268 
     | 
    
         | 
| 
       203 
269 
     | 
    
         
             
            Boost matching documents
         
     | 
| 
       204 
270 
     | 
    
         | 
| 
       205 
271 
     | 
    
         
             
            ```ruby
         
     | 
| 
       206 
     | 
    
         
            -
            boost_where 
     | 
| 
       207 
     | 
    
         
            -
            boost_where 
     | 
| 
       208 
     | 
    
         
            -
            boost_where 
     | 
| 
      
 272 
     | 
    
         
            +
            boost_where(user_id: 1)
         
     | 
| 
      
 273 
     | 
    
         
            +
            boost_where(user_id: {value: 1, factor: 100}) # default factor is 1000
         
     | 
| 
      
 274 
     | 
    
         
            +
            boost_where(user_id: [{value: 1, factor: 100}, {value: 2, factor: 200}])
         
     | 
| 
       209 
275 
     | 
    
         
             
            ```
         
     | 
| 
       210 
276 
     | 
    
         | 
| 
       211 
277 
     | 
    
         
             
            Boost by recency
         
     | 
| 
       212 
278 
     | 
    
         | 
| 
       213 
279 
     | 
    
         
             
            ```ruby
         
     | 
| 
       214 
     | 
    
         
            -
            boost_by_recency 
     | 
| 
      
 280 
     | 
    
         
            +
            boost_by_recency(created_at: {scale: "7d", decay: 0.5})
         
     | 
| 
       215 
281 
     | 
    
         
             
            ```
         
     | 
| 
       216 
282 
     | 
    
         | 
| 
       217 
283 
     | 
    
         
             
            You can also boost by:
         
     | 
| 
         @@ -233,7 +299,7 @@ Plays nicely with kaminari and will_paginate. 
     | 
|
| 
       233 
299 
     | 
    
         | 
| 
       234 
300 
     | 
    
         
             
            ```ruby
         
     | 
| 
       235 
301 
     | 
    
         
             
            # controller
         
     | 
| 
       236 
     | 
    
         
            -
            @products = Product.search("milk" 
     | 
| 
      
 302 
     | 
    
         
            +
            @products = Product.search("milk").page(params[:page]).per_page(20)
         
     | 
| 
       237 
303 
     | 
    
         
             
            ```
         
     | 
| 
       238 
304 
     | 
    
         | 
| 
       239 
305 
     | 
    
         
             
            View with kaminari
         
     | 
| 
         @@ -259,7 +325,7 @@ Product.search("fresh honey") # fresh AND honey 
     | 
|
| 
       259 
325 
     | 
    
         
             
            To change this, use:
         
     | 
| 
       260 
326 
     | 
    
         | 
| 
       261 
327 
     | 
    
         
             
            ```ruby
         
     | 
| 
       262 
     | 
    
         
            -
            Product.search("fresh honey" 
     | 
| 
      
 328 
     | 
    
         
            +
            Product.search("fresh honey").operator("or") # fresh OR honey
         
     | 
| 
       263 
329 
     | 
    
         
             
            ```
         
     | 
| 
       264 
330 
     | 
    
         | 
| 
       265 
331 
     | 
    
         
             
            By default, results must match the entire word - `back` will not match `backpack`. You can change this behavior with:
         
     | 
| 
         @@ -273,7 +339,7 @@ end 
     | 
|
| 
       273 
339 
     | 
    
         
             
            And to search (after you reindex):
         
     | 
| 
       274 
340 
     | 
    
         | 
| 
       275 
341 
     | 
    
         
             
            ```ruby
         
     | 
| 
       276 
     | 
    
         
            -
            Product.search("back" 
     | 
| 
      
 342 
     | 
    
         
            +
            Product.search("back").fields(:name).match(:word_start)
         
     | 
| 
       277 
343 
     | 
    
         
             
            ```
         
     | 
| 
       278 
344 
     | 
    
         | 
| 
       279 
345 
     | 
    
         
             
            Available options are:
         
     | 
| 
         @@ -293,7 +359,7 @@ The default is `:word`. The most matches will happen with `:word_middle`. 
     | 
|
| 
       293 
359 
     | 
    
         
             
            To specify different matching for different fields, use:
         
     | 
| 
       294 
360 
     | 
    
         | 
| 
       295 
361 
     | 
    
         
             
            ```ruby
         
     | 
| 
       296 
     | 
    
         
            -
            Product.search(query 
     | 
| 
      
 362 
     | 
    
         
            +
            Product.search(query).fields({name: :word_start}, {brand: :word_middle})
         
     | 
| 
       297 
363 
     | 
    
         
             
            ```
         
     | 
| 
       298 
364 
     | 
    
         | 
| 
       299 
365 
     | 
    
         
             
            ### Exact Matches
         
     | 
| 
         @@ -301,7 +367,7 @@ Product.search(query, fields: [{name: :word_start}, {brand: :word_middle}]) 
     | 
|
| 
       301 
367 
     | 
    
         
             
            To match a field exactly (case-sensitive), use:
         
     | 
| 
       302 
368 
     | 
    
         | 
| 
       303 
369 
     | 
    
         
             
            ```ruby
         
     | 
| 
       304 
     | 
    
         
            -
            Product.search(query 
     | 
| 
      
 370 
     | 
    
         
            +
            Product.search(query).fields({name: :exact})
         
     | 
| 
       305 
371 
     | 
    
         
             
            ```
         
     | 
| 
       306 
372 
     | 
    
         | 
| 
       307 
373 
     | 
    
         
             
            ### Phrase Matches
         
     | 
| 
         @@ -309,7 +375,7 @@ Product.search(query, fields: [{name: :exact}]) 
     | 
|
| 
       309 
375 
     | 
    
         
             
            To only match the exact order, use:
         
     | 
| 
       310 
376 
     | 
    
         | 
| 
       311 
377 
     | 
    
         
             
            ```ruby
         
     | 
| 
       312 
     | 
    
         
            -
            Product.search("fresh honey" 
     | 
| 
      
 378 
     | 
    
         
            +
            Product.search("fresh honey").match(:phrase)
         
     | 
| 
       313 
379 
     | 
    
         
             
            ```
         
     | 
| 
       314 
380 
     | 
    
         | 
| 
       315 
381 
     | 
    
         
             
            ### Stemming and Language
         
     | 
| 
         @@ -385,11 +451,7 @@ search_synonyms: ["lightbulb => halogenlamp"] 
     | 
|
| 
       385 
451 
     | 
    
         | 
| 
       386 
452 
     | 
    
         
             
            ### Dynamic Synonyms
         
     | 
| 
       387 
453 
     | 
    
         | 
| 
       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.
         
     | 
| 
      
 454 
     | 
    
         
            +
            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 
455 
     | 
    
         | 
| 
       394 
456 
     | 
    
         
             
            ```txt
         
     | 
| 
       395 
457 
     | 
    
         
             
            pop, soda
         
     | 
| 
         @@ -410,29 +472,6 @@ And reload with: 
     | 
|
| 
       410 
472 
     | 
    
         
             
            Product.search_index.reload_synonyms
         
     | 
| 
       411 
473 
     | 
    
         
             
            ```
         
     | 
| 
       412 
474 
     | 
    
         | 
| 
       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 
475 
     | 
    
         
             
            ### Misspellings
         
     | 
| 
       437 
476 
     | 
    
         | 
| 
       438 
477 
     | 
    
         
             
            By default, Searchkick handles misspelled queries by returning results with an [edit distance](https://en.wikipedia.org/wiki/Levenshtein_distance) of one.
         
     | 
| 
         @@ -440,13 +479,13 @@ By default, Searchkick handles misspelled queries by returning results with an [ 
     | 
|
| 
       440 
479 
     | 
    
         
             
            You can change this with:
         
     | 
| 
       441 
480 
     | 
    
         | 
| 
       442 
481 
     | 
    
         
             
            ```ruby
         
     | 
| 
       443 
     | 
    
         
            -
            Product.search("zucini" 
     | 
| 
      
 482 
     | 
    
         
            +
            Product.search("zucini").misspellings(edit_distance: 2) # zucchini
         
     | 
| 
       444 
483 
     | 
    
         
             
            ```
         
     | 
| 
       445 
484 
     | 
    
         | 
| 
       446 
485 
     | 
    
         
             
            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 
486 
     | 
    
         | 
| 
       448 
487 
     | 
    
         
             
            ```ruby
         
     | 
| 
       449 
     | 
    
         
            -
            Product.search("zuchini" 
     | 
| 
      
 488 
     | 
    
         
            +
            Product.search("zuchini").misspellings(below: 5)
         
     | 
| 
       450 
489 
     | 
    
         
             
            ```
         
     | 
| 
       451 
490 
     | 
    
         | 
| 
       452 
491 
     | 
    
         
             
            If there are fewer than 5 results, a 2nd search is performed with misspellings enabled. The result of this query is returned.
         
     | 
| 
         @@ -454,13 +493,13 @@ If there are fewer than 5 results, a 2nd search is performed with misspellings e 
     | 
|
| 
       454 
493 
     | 
    
         
             
            Turn off misspellings with:
         
     | 
| 
       455 
494 
     | 
    
         | 
| 
       456 
495 
     | 
    
         
             
            ```ruby
         
     | 
| 
       457 
     | 
    
         
            -
            Product.search("zuchini" 
     | 
| 
      
 496 
     | 
    
         
            +
            Product.search("zuchini").misspellings(false) # no zucchini
         
     | 
| 
       458 
497 
     | 
    
         
             
            ```
         
     | 
| 
       459 
498 
     | 
    
         | 
| 
       460 
499 
     | 
    
         
             
            Specify which fields can include misspellings with:
         
     | 
| 
       461 
500 
     | 
    
         | 
| 
       462 
501 
     | 
    
         
             
            ```ruby
         
     | 
| 
       463 
     | 
    
         
            -
            Product.search("zucini" 
     | 
| 
      
 502 
     | 
    
         
            +
            Product.search("zucini").fields(:name, :color).misspellings(fields: [:name])
         
     | 
| 
       464 
503 
     | 
    
         
             
            ```
         
     | 
| 
       465 
504 
     | 
    
         | 
| 
       466 
505 
     | 
    
         
             
            > When doing this, you must also specify fields to search
         
     | 
| 
         @@ -470,7 +509,7 @@ Product.search("zucini", fields: [:name, :color], misspellings: {fields: [:name] 
     | 
|
| 
       470 
509 
     | 
    
         
             
            If a user searches `butter`, they may also get results for `peanut butter`. To prevent this, use:
         
     | 
| 
       471 
510 
     | 
    
         | 
| 
       472 
511 
     | 
    
         
             
            ```ruby
         
     | 
| 
       473 
     | 
    
         
            -
            Product.search("butter" 
     | 
| 
      
 512 
     | 
    
         
            +
            Product.search("butter").exclude("peanut butter")
         
     | 
| 
       474 
513 
     | 
    
         
             
            ```
         
     | 
| 
       475 
514 
     | 
    
         | 
| 
       476 
515 
     | 
    
         
             
            You can map queries and terms to exclude with:
         
     | 
| 
         @@ -481,13 +520,13 @@ exclude_queries = { 
     | 
|
| 
       481 
520 
     | 
    
         
             
              "cream" => ["ice cream", "whipped cream"]
         
     | 
| 
       482 
521 
     | 
    
         
             
            }
         
     | 
| 
       483 
522 
     | 
    
         | 
| 
       484 
     | 
    
         
            -
            Product.search(query 
     | 
| 
      
 523 
     | 
    
         
            +
            Product.search(query).exclude(exclude_queries[query])
         
     | 
| 
       485 
524 
     | 
    
         
             
            ```
         
     | 
| 
       486 
525 
     | 
    
         | 
| 
       487 
526 
     | 
    
         
             
            You can demote results by boosting by a factor less than one:
         
     | 
| 
       488 
527 
     | 
    
         | 
| 
       489 
528 
     | 
    
         
             
            ```ruby
         
     | 
| 
       490 
     | 
    
         
            -
            Product.search("butter" 
     | 
| 
      
 529 
     | 
    
         
            +
            Product.search("butter").boost_where(category: {value: "pantry", factor: 0.5})
         
     | 
| 
       491 
530 
     | 
    
         
             
            ```
         
     | 
| 
       492 
531 
     | 
    
         | 
| 
       493 
532 
     | 
    
         
             
            ### Emoji
         
     | 
| 
         @@ -503,7 +542,7 @@ gem "gemoji-parser" 
     | 
|
| 
       503 
542 
     | 
    
         
             
            And use:
         
     | 
| 
       504 
543 
     | 
    
         | 
| 
       505 
544 
     | 
    
         
             
            ```ruby
         
     | 
| 
       506 
     | 
    
         
            -
            Product.search("🍨🍰" 
     | 
| 
      
 545 
     | 
    
         
            +
            Product.search("🍨🍰").emoji
         
     | 
| 
       507 
546 
     | 
    
         
             
            ```
         
     | 
| 
       508 
547 
     | 
    
         | 
| 
       509 
548 
     | 
    
         
             
            ## Indexing
         
     | 
| 
         @@ -669,7 +708,7 @@ end 
     | 
|
| 
       669 
708 
     | 
    
         
             
            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 
709 
     | 
    
         | 
| 
       671 
710 
     | 
    
         
             
            ```ruby
         
     | 
| 
       672 
     | 
    
         
            -
            Product.search("apple" 
     | 
| 
      
 711 
     | 
    
         
            +
            Product.search("apple").track(user_id: current_user.id)
         
     | 
| 
       673 
712 
     | 
    
         
             
            ```
         
     | 
| 
       674 
713 
     | 
    
         | 
| 
       675 
714 
     | 
    
         
             
            [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 +722,7 @@ class Product < ApplicationRecord 
     | 
|
| 
       683 
722 
     | 
    
         
             
              has_many :conversions, class_name: "Searchjoy::Conversion", as: :convertable
         
     | 
| 
       684 
723 
     | 
    
         
             
              has_many :searches, class_name: "Searchjoy::Search", through: :conversions
         
     | 
| 
       685 
724 
     | 
    
         | 
| 
       686 
     | 
    
         
            -
              searchkick  
     | 
| 
      
 725 
     | 
    
         
            +
              searchkick conversions_v2: [:conversions] # name of field
         
     | 
| 
       687 
726 
     | 
    
         | 
| 
       688 
727 
     | 
    
         
             
              def search_data
         
     | 
| 
       689 
728 
     | 
    
         
             
                {
         
     | 
| 
         @@ -695,7 +734,7 @@ class Product < ApplicationRecord 
     | 
|
| 
       695 
734 
     | 
    
         
             
            end
         
     | 
| 
       696 
735 
     | 
    
         
             
            ```
         
     | 
| 
       697 
736 
     | 
    
         | 
| 
       698 
     | 
    
         
            -
            Reindex and set up a cron job to add new conversions daily. For zero downtime deployment, temporarily set ` 
     | 
| 
      
 737 
     | 
    
         
            +
            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 
738 
     | 
    
         | 
| 
       700 
739 
     | 
    
         
             
            ### Performant Conversions
         
     | 
| 
       701 
740 
     | 
    
         | 
| 
         @@ -711,7 +750,7 @@ Next, update your model. Create a separate method for conversion data so you can 
     | 
|
| 
       711 
750 
     | 
    
         | 
| 
       712 
751 
     | 
    
         
             
            ```ruby
         
     | 
| 
       713 
752 
     | 
    
         
             
            class Product < ApplicationRecord
         
     | 
| 
       714 
     | 
    
         
            -
              searchkick  
     | 
| 
      
 753 
     | 
    
         
            +
              searchkick conversions_v2: [:conversions]
         
     | 
| 
       715 
754 
     | 
    
         | 
| 
       716 
755 
     | 
    
         
             
              def search_data
         
     | 
| 
       717 
756 
     | 
    
         
             
                {
         
     | 
| 
         @@ -728,7 +767,7 @@ class Product < ApplicationRecord 
     | 
|
| 
       728 
767 
     | 
    
         
             
            end
         
     | 
| 
       729 
768 
     | 
    
         
             
            ```
         
     | 
| 
       730 
769 
     | 
    
         | 
| 
       731 
     | 
    
         
            -
            Deploy and reindex your data. For zero downtime deployment, temporarily set ` 
     | 
| 
      
 770 
     | 
    
         
            +
            Deploy and reindex your data. For zero downtime deployment, temporarily set `conversions_v2(false)` in your search calls until the data is reindexed.
         
     | 
| 
       732 
771 
     | 
    
         | 
| 
       733 
772 
     | 
    
         
             
            ```ruby
         
     | 
| 
       734 
773 
     | 
    
         
             
            Product.reindex
         
     | 
| 
         @@ -743,8 +782,8 @@ class UpdateConversionsJob < ApplicationJob 
     | 
|
| 
       743 
782 
     | 
    
         | 
| 
       744 
783 
     | 
    
         
             
                # get records that have a recent conversion
         
     | 
| 
       745 
784 
     | 
    
         
             
                recently_converted_ids =
         
     | 
| 
       746 
     | 
    
         
            -
                  Searchjoy::Conversion.where(convertable_type: class_name 
     | 
| 
       747 
     | 
    
         
            -
             
     | 
| 
      
 785 
     | 
    
         
            +
                  Searchjoy::Conversion.where(convertable_type: class_name, created_at: since..)
         
     | 
| 
      
 786 
     | 
    
         
            +
                    .order(:convertable_id).distinct.pluck(:convertable_id)
         
     | 
| 
       748 
787 
     | 
    
         | 
| 
       749 
788 
     | 
    
         
             
                # split into batches
         
     | 
| 
       750 
789 
     | 
    
         
             
                recently_converted_ids.in_groups_of(1000, false) do |ids|
         
     | 
| 
         @@ -752,8 +791,8 @@ class UpdateConversionsJob < ApplicationJob 
     | 
|
| 
       752 
791 
     | 
    
         
             
                    # fetch conversions
         
     | 
| 
       753 
792 
     | 
    
         
             
                    conversions =
         
     | 
| 
       754 
793 
     | 
    
         
             
                      Searchjoy::Conversion.where(convertable_id: ids, convertable_type: class_name)
         
     | 
| 
       755 
     | 
    
         
            -
             
     | 
| 
       756 
     | 
    
         
            -
             
     | 
| 
      
 794 
     | 
    
         
            +
                        .joins(:search).where.not(searchjoy_searches: {user_id: nil})
         
     | 
| 
      
 795 
     | 
    
         
            +
                        .group(:convertable_id, :query).distinct.count(:user_id)
         
     | 
| 
       757 
796 
     | 
    
         | 
| 
       758 
797 
     | 
    
         
             
                    # group by record
         
     | 
| 
       759 
798 
     | 
    
         
             
                    conversions_by_record = {}
         
     | 
| 
         @@ -771,7 +810,7 @@ class UpdateConversionsJob < ApplicationJob 
     | 
|
| 
       771 
810 
     | 
    
         | 
| 
       772 
811 
     | 
    
         
             
                  if reindex
         
     | 
| 
       773 
812 
     | 
    
         
             
                    # reindex conversions data
         
     | 
| 
       774 
     | 
    
         
            -
                    model.where(id: ids).reindex(:conversions_data)
         
     | 
| 
      
 813 
     | 
    
         
            +
                    model.where(id: ids).reindex(:conversions_data, ignore_missing: true)
         
     | 
| 
       775 
814 
     | 
    
         
             
                  end
         
     | 
| 
       776 
815 
     | 
    
         
             
                end
         
     | 
| 
       777 
816 
     | 
    
         
             
              end
         
     | 
| 
         @@ -808,7 +847,7 @@ end 
     | 
|
| 
       808 
847 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       809 
848 
     | 
    
         | 
| 
       810 
849 
     | 
    
         
             
            ```ruby
         
     | 
| 
       811 
     | 
    
         
            -
            Product.search("milk" 
     | 
| 
      
 850 
     | 
    
         
            +
            Product.search("milk").boost_where(orderer_ids: current_user.id)
         
     | 
| 
       812 
851 
     | 
    
         
             
            ```
         
     | 
| 
       813 
852 
     | 
    
         | 
| 
       814 
853 
     | 
    
         
             
            ## Instant Search / Autocomplete
         
     | 
| 
         @@ -832,7 +871,7 @@ end 
     | 
|
| 
       832 
871 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       833 
872 
     | 
    
         | 
| 
       834 
873 
     | 
    
         
             
            ```ruby
         
     | 
| 
       835 
     | 
    
         
            -
            Movie.search("jurassic pa" 
     | 
| 
      
 874 
     | 
    
         
            +
            Movie.search("jurassic pa").fields(:title).match(:word_start)
         
     | 
| 
       836 
875 
     | 
    
         
             
            ```
         
     | 
| 
       837 
876 
     | 
    
         | 
| 
       838 
877 
     | 
    
         
             
            Use a front-end library like [typeahead.js](https://twitter.github.io/typeahead.js/) to show the results.
         
     | 
| 
         @@ -844,19 +883,13 @@ First, add a route and controller action. 
     | 
|
| 
       844 
883 
     | 
    
         
             
            ```ruby
         
     | 
| 
       845 
884 
     | 
    
         
             
            class MoviesController < ApplicationController
         
     | 
| 
       846 
885 
     | 
    
         
             
              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)
         
     | 
| 
      
 886 
     | 
    
         
            +
                render json: Movie.search(params[:query]).fields("title^5", "director")
         
     | 
| 
      
 887 
     | 
    
         
            +
                  .match(:word_start).limit(10).load(false).misspellings(below: 5).map(&:title)
         
     | 
| 
       855 
888 
     | 
    
         
             
              end
         
     | 
| 
       856 
889 
     | 
    
         
             
            end
         
     | 
| 
       857 
890 
     | 
    
         
             
            ```
         
     | 
| 
       858 
891 
     | 
    
         | 
| 
       859 
     | 
    
         
            -
            **Note:** Use `load 
     | 
| 
      
 892 
     | 
    
         
            +
            **Note:** Use `load(false)` and `misspellings(below: n)` (or `misspellings(false)`) for best performance.
         
     | 
| 
       860 
893 
     | 
    
         | 
| 
       861 
894 
     | 
    
         
             
            Then add the search box and JavaScript code to a view.
         
     | 
| 
       862 
895 
     | 
    
         | 
| 
         @@ -893,7 +926,7 @@ end 
     | 
|
| 
       893 
926 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       894 
927 
     | 
    
         | 
| 
       895 
928 
     | 
    
         
             
            ```ruby
         
     | 
| 
       896 
     | 
    
         
            -
            products = Product.search("peantu butta" 
     | 
| 
      
 929 
     | 
    
         
            +
            products = Product.search("peantu butta").suggest
         
     | 
| 
       897 
930 
     | 
    
         
             
            products.suggestions # ["peanut butter"]
         
     | 
| 
       898 
931 
     | 
    
         
             
            ```
         
     | 
| 
       899 
932 
     | 
    
         | 
| 
         @@ -904,40 +937,40 @@ products.suggestions # ["peanut butter"] 
     | 
|
| 
       904 
937 
     | 
    
         
             
            
         
     | 
| 
       905 
938 
     | 
    
         | 
| 
       906 
939 
     | 
    
         
             
            ```ruby
         
     | 
| 
       907 
     | 
    
         
            -
            products = Product.search("chuck taylor" 
     | 
| 
      
 940 
     | 
    
         
            +
            products = Product.search("chuck taylor").aggs(:product_type, :gender, :brand)
         
     | 
| 
       908 
941 
     | 
    
         
             
            products.aggs
         
     | 
| 
       909 
942 
     | 
    
         
             
            ```
         
     | 
| 
       910 
943 
     | 
    
         | 
| 
       911 
944 
     | 
    
         
             
            By default, `where` conditions apply to aggregations.
         
     | 
| 
       912 
945 
     | 
    
         | 
| 
       913 
946 
     | 
    
         
             
            ```ruby
         
     | 
| 
       914 
     | 
    
         
            -
            Product.search("wingtips" 
     | 
| 
      
 947 
     | 
    
         
            +
            Product.search("wingtips").where(color: "brandy").aggs(:size)
         
     | 
| 
       915 
948 
     | 
    
         
             
            # aggregations for brandy wingtips are returned
         
     | 
| 
       916 
949 
     | 
    
         
             
            ```
         
     | 
| 
       917 
950 
     | 
    
         | 
| 
       918 
951 
     | 
    
         
             
            Change this with:
         
     | 
| 
       919 
952 
     | 
    
         | 
| 
       920 
953 
     | 
    
         
             
            ```ruby
         
     | 
| 
       921 
     | 
    
         
            -
            Product.search("wingtips" 
     | 
| 
      
 954 
     | 
    
         
            +
            Product.search("wingtips").where(color: "brandy").aggs(:size).smart_aggs(false)
         
     | 
| 
       922 
955 
     | 
    
         
             
            # aggregations for all wingtips are returned
         
     | 
| 
       923 
956 
     | 
    
         
             
            ```
         
     | 
| 
       924 
957 
     | 
    
         | 
| 
       925 
958 
     | 
    
         
             
            Set `where` conditions for each aggregation separately with:
         
     | 
| 
       926 
959 
     | 
    
         | 
| 
       927 
960 
     | 
    
         
             
            ```ruby
         
     | 
| 
       928 
     | 
    
         
            -
            Product.search("wingtips" 
     | 
| 
      
 961 
     | 
    
         
            +
            Product.search("wingtips").aggs(size: {where: {color: "brandy"}})
         
     | 
| 
       929 
962 
     | 
    
         
             
            ```
         
     | 
| 
       930 
963 
     | 
    
         | 
| 
       931 
964 
     | 
    
         
             
            Limit
         
     | 
| 
       932 
965 
     | 
    
         | 
| 
       933 
966 
     | 
    
         
             
            ```ruby
         
     | 
| 
       934 
     | 
    
         
            -
            Product.search("apples" 
     | 
| 
      
 967 
     | 
    
         
            +
            Product.search("apples").aggs(store_id: {limit: 10})
         
     | 
| 
       935 
968 
     | 
    
         
             
            ```
         
     | 
| 
       936 
969 
     | 
    
         | 
| 
       937 
970 
     | 
    
         
             
            Order
         
     | 
| 
       938 
971 
     | 
    
         | 
| 
       939 
972 
     | 
    
         
             
            ```ruby
         
     | 
| 
       940 
     | 
    
         
            -
            Product.search("wingtips" 
     | 
| 
      
 973 
     | 
    
         
            +
            Product.search("wingtips").aggs(color: {order: {"_key" => "asc"}}) # alphabetically
         
     | 
| 
       941 
974 
     | 
    
         
             
            ```
         
     | 
| 
       942 
975 
     | 
    
         | 
| 
       943 
976 
     | 
    
         
             
            [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 +979,31 @@ Ranges 
     | 
|
| 
       946 
979 
     | 
    
         | 
| 
       947 
980 
     | 
    
         
             
            ```ruby
         
     | 
| 
       948 
981 
     | 
    
         
             
            price_ranges = [{to: 20}, {from: 20, to: 50}, {from: 50}]
         
     | 
| 
       949 
     | 
    
         
            -
            Product.search("*" 
     | 
| 
      
 982 
     | 
    
         
            +
            Product.search("*").aggs(price: {ranges: price_ranges})
         
     | 
| 
       950 
983 
     | 
    
         
             
            ```
         
     | 
| 
       951 
984 
     | 
    
         | 
| 
       952 
985 
     | 
    
         
             
            Minimum document count
         
     | 
| 
       953 
986 
     | 
    
         | 
| 
       954 
987 
     | 
    
         
             
            ```ruby
         
     | 
| 
       955 
     | 
    
         
            -
            Product.search("apples" 
     | 
| 
      
 988 
     | 
    
         
            +
            Product.search("apples").aggs(store_id: {min_doc_count: 2})
         
     | 
| 
       956 
989 
     | 
    
         
             
            ```
         
     | 
| 
       957 
990 
     | 
    
         | 
| 
       958 
991 
     | 
    
         
             
            Script support
         
     | 
| 
       959 
992 
     | 
    
         | 
| 
       960 
993 
     | 
    
         
             
            ```ruby
         
     | 
| 
       961 
     | 
    
         
            -
            Product.search("*" 
     | 
| 
      
 994 
     | 
    
         
            +
            Product.search("*").aggs(color: {script: {source: "'Color: ' + _value"}})
         
     | 
| 
       962 
995 
     | 
    
         
             
            ```
         
     | 
| 
       963 
996 
     | 
    
         | 
| 
       964 
997 
     | 
    
         
             
            Date histogram
         
     | 
| 
       965 
998 
     | 
    
         | 
| 
       966 
999 
     | 
    
         
             
            ```ruby
         
     | 
| 
       967 
     | 
    
         
            -
            Product.search("pear" 
     | 
| 
      
 1000 
     | 
    
         
            +
            Product.search("pear").aggs(products_per_year: {date_histogram: {field: :created_at, interval: :year}})
         
     | 
| 
       968 
1001 
     | 
    
         
             
            ```
         
     | 
| 
       969 
1002 
     | 
    
         | 
| 
       970 
1003 
     | 
    
         
             
            For other aggregation types, including sub-aggregations, use `body_options`:
         
     | 
| 
       971 
1004 
     | 
    
         | 
| 
       972 
1005 
     | 
    
         
             
            ```ruby
         
     | 
| 
       973 
     | 
    
         
            -
            Product.search("orange" 
     | 
| 
      
 1006 
     | 
    
         
            +
            Product.search("orange").body_options(aggs: {price: {histogram: {field: :price, interval: 10}}})
         
     | 
| 
       974 
1007 
     | 
    
         
             
            ```
         
     | 
| 
       975 
1008 
     | 
    
         | 
| 
       976 
1009 
     | 
    
         
             
            ## Highlight
         
     | 
| 
         @@ -986,7 +1019,7 @@ end 
     | 
|
| 
       986 
1019 
     | 
    
         
             
            Highlight the search query in the results.
         
     | 
| 
       987 
1020 
     | 
    
         | 
| 
       988 
1021 
     | 
    
         
             
            ```ruby
         
     | 
| 
       989 
     | 
    
         
            -
            bands = Band.search("cinema" 
     | 
| 
      
 1022 
     | 
    
         
            +
            bands = Band.search("cinema").highlight
         
     | 
| 
       990 
1023 
     | 
    
         
             
            ```
         
     | 
| 
       991 
1024 
     | 
    
         | 
| 
       992 
1025 
     | 
    
         
             
            View the highlighted fields with:
         
     | 
| 
         @@ -1000,19 +1033,19 @@ end 
     | 
|
| 
       1000 
1033 
     | 
    
         
             
            To change the tag, use:
         
     | 
| 
       1001 
1034 
     | 
    
         | 
| 
       1002 
1035 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1003 
     | 
    
         
            -
            Band.search("cinema" 
     | 
| 
      
 1036 
     | 
    
         
            +
            Band.search("cinema").highlight(tag: "<strong>")
         
     | 
| 
       1004 
1037 
     | 
    
         
             
            ```
         
     | 
| 
       1005 
1038 
     | 
    
         | 
| 
       1006 
1039 
     | 
    
         
             
            To highlight and search different fields, use:
         
     | 
| 
       1007 
1040 
     | 
    
         | 
| 
       1008 
1041 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1009 
     | 
    
         
            -
            Band.search("cinema" 
     | 
| 
      
 1042 
     | 
    
         
            +
            Band.search("cinema").fields(:name).highlight(fields: [:description])
         
     | 
| 
       1010 
1043 
     | 
    
         
             
            ```
         
     | 
| 
       1011 
1044 
     | 
    
         | 
| 
       1012 
1045 
     | 
    
         
             
            By default, the entire field is highlighted. To get small snippets instead, use:
         
     | 
| 
       1013 
1046 
     | 
    
         | 
| 
       1014 
1047 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1015 
     | 
    
         
            -
            bands = Band.search("cinema" 
     | 
| 
      
 1048 
     | 
    
         
            +
            bands = Band.search("cinema").highlight(fragment_size: 20)
         
     | 
| 
       1016 
1049 
     | 
    
         
             
            bands.with_highlights(multiple: true).each do |band, highlights|
         
     | 
| 
       1017 
1050 
     | 
    
         
             
              highlights[:name].join(" and ")
         
     | 
| 
       1018 
1051 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1021,18 +1054,18 @@ end 
     | 
|
| 
       1021 
1054 
     | 
    
         
             
            Additional options can be specified for each field:
         
     | 
| 
       1022 
1055 
     | 
    
         | 
| 
       1023 
1056 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1024 
     | 
    
         
            -
            Band.search("cinema" 
     | 
| 
      
 1057 
     | 
    
         
            +
            Band.search("cinema").fields(:name).highlight(fields: {name: {fragment_size: 200}})
         
     | 
| 
       1025 
1058 
     | 
    
         
             
            ```
         
     | 
| 
       1026 
1059 
     | 
    
         | 
| 
       1027 
1060 
     | 
    
         
             
            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 
1061 
     | 
    
         | 
| 
       1029 
1062 
     | 
    
         
             
            ## Similar Items
         
     | 
| 
       1030 
1063 
     | 
    
         | 
| 
       1031 
     | 
    
         
            -
            Find similar items 
     | 
| 
      
 1064 
     | 
    
         
            +
            Find similar items
         
     | 
| 
       1032 
1065 
     | 
    
         | 
| 
       1033 
1066 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1034 
1067 
     | 
    
         
             
            product = Product.first
         
     | 
| 
       1035 
     | 
    
         
            -
            product.similar( 
     | 
| 
      
 1068 
     | 
    
         
            +
            product.similar.fields(:name).where(size: "12 oz")
         
     | 
| 
       1036 
1069 
     | 
    
         
             
            ```
         
     | 
| 
       1037 
1070 
     | 
    
         | 
| 
       1038 
1071 
     | 
    
         
             
            ## Geospatial Searches
         
     | 
| 
         @@ -1050,13 +1083,13 @@ end 
     | 
|
| 
       1050 
1083 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       1051 
1084 
     | 
    
         | 
| 
       1052 
1085 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1053 
     | 
    
         
            -
            Restaurant.search("pizza" 
     | 
| 
      
 1086 
     | 
    
         
            +
            Restaurant.search("pizza").where(location: {near: {lat: 37, lon: -114}, within: "100mi"}) # or 160km
         
     | 
| 
       1054 
1087 
     | 
    
         
             
            ```
         
     | 
| 
       1055 
1088 
     | 
    
         | 
| 
       1056 
1089 
     | 
    
         
             
            Bounded by a box
         
     | 
| 
       1057 
1090 
     | 
    
         | 
| 
       1058 
1091 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1059 
     | 
    
         
            -
            Restaurant.search("sushi" 
     | 
| 
      
 1092 
     | 
    
         
            +
            Restaurant.search("sushi").where(location: {top_left: {lat: 38, lon: -123}, bottom_right: {lat: 37, lon: -122}})
         
     | 
| 
       1060 
1093 
     | 
    
         
             
            ```
         
     | 
| 
       1061 
1094 
     | 
    
         | 
| 
       1062 
1095 
     | 
    
         
             
            **Note:** `top_right` and `bottom_left` also work
         
     | 
| 
         @@ -1064,7 +1097,7 @@ Restaurant.search("sushi", where: {location: {top_left: {lat: 38, lon: -123}, bo 
     | 
|
| 
       1064 
1097 
     | 
    
         
             
            Bounded by a polygon
         
     | 
| 
       1065 
1098 
     | 
    
         | 
| 
       1066 
1099 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1067 
     | 
    
         
            -
            Restaurant.search("dessert" 
     | 
| 
      
 1100 
     | 
    
         
            +
            Restaurant.search("dessert").where(location: {geo_polygon: {points: [{lat: 38, lon: -123}, {lat: 39, lon: -123}, {lat: 37, lon: 122}]}})
         
     | 
| 
       1068 
1101 
     | 
    
         
             
            ```
         
     | 
| 
       1069 
1102 
     | 
    
         | 
| 
       1070 
1103 
     | 
    
         
             
            ### Boost By Distance
         
     | 
| 
         @@ -1072,13 +1105,13 @@ Restaurant.search("dessert", where: {location: {geo_polygon: {points: [{lat: 38, 
     | 
|
| 
       1072 
1105 
     | 
    
         
             
            Boost results by distance - closer results are boosted more
         
     | 
| 
       1073 
1106 
     | 
    
         | 
| 
       1074 
1107 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1075 
     | 
    
         
            -
            Restaurant.search("noodles" 
     | 
| 
      
 1108 
     | 
    
         
            +
            Restaurant.search("noodles").boost_by_distance(location: {origin: {lat: 37, lon: -122}})
         
     | 
| 
       1076 
1109 
     | 
    
         
             
            ```
         
     | 
| 
       1077 
1110 
     | 
    
         | 
| 
       1078 
1111 
     | 
    
         
             
            Also supports [additional options](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#function-decay)
         
     | 
| 
       1079 
1112 
     | 
    
         | 
| 
       1080 
1113 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1081 
     | 
    
         
            -
            Restaurant.search("wings" 
     | 
| 
      
 1114 
     | 
    
         
            +
            Restaurant.search("wings").boost_by_distance(location: {origin: {lat: 37, lon: -122}, function: "linear", scale: "30mi", decay: 0.5})
         
     | 
| 
       1082 
1115 
     | 
    
         
             
            ```
         
     | 
| 
       1083 
1116 
     | 
    
         | 
| 
       1084 
1117 
     | 
    
         
             
            ### Geo Shapes
         
     | 
| 
         @@ -1105,19 +1138,19 @@ See the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsea 
     | 
|
| 
       1105 
1138 
     | 
    
         
             
            Find shapes intersecting with the query shape
         
     | 
| 
       1106 
1139 
     | 
    
         | 
| 
       1107 
1140 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1108 
     | 
    
         
            -
            Restaurant.search("soup" 
     | 
| 
      
 1141 
     | 
    
         
            +
            Restaurant.search("soup").where(bounds: {geo_shape: {type: "polygon", coordinates: [[{lat: 38, lon: -123}, ...]]}})
         
     | 
| 
       1109 
1142 
     | 
    
         
             
            ```
         
     | 
| 
       1110 
1143 
     | 
    
         | 
| 
       1111 
1144 
     | 
    
         
             
            Falling entirely within the query shape
         
     | 
| 
       1112 
1145 
     | 
    
         | 
| 
       1113 
1146 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1114 
     | 
    
         
            -
            Restaurant.search("salad" 
     | 
| 
      
 1147 
     | 
    
         
            +
            Restaurant.search("salad").where(bounds: {geo_shape: {type: "circle", relation: "within", coordinates: {lat: 38, lon: -123}, radius: "1km"}})
         
     | 
| 
       1115 
1148 
     | 
    
         
             
            ```
         
     | 
| 
       1116 
1149 
     | 
    
         | 
| 
       1117 
1150 
     | 
    
         
             
            Not touching the query shape
         
     | 
| 
       1118 
1151 
     | 
    
         | 
| 
       1119 
1152 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1120 
     | 
    
         
            -
            Restaurant.search("burger" 
     | 
| 
      
 1153 
     | 
    
         
            +
            Restaurant.search("burger").where(bounds: {geo_shape: {type: "envelope", relation: "disjoint", coordinates: [{lat: 38, lon: -123}, {lat: 37, lon: -122}]}})
         
     | 
| 
       1121 
1154 
     | 
    
         
             
            ```
         
     | 
| 
       1122 
1155 
     | 
    
         | 
| 
       1123 
1156 
     | 
    
         
             
            ## Inheritance
         
     | 
| 
         @@ -1147,9 +1180,9 @@ Dog.reindex # equivalent, all animals reindexed 
     | 
|
| 
       1147 
1180 
     | 
    
         
             
            And to search, use:
         
     | 
| 
       1148 
1181 
     | 
    
         | 
| 
       1149 
1182 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1150 
     | 
    
         
            -
            Animal.search("*") 
     | 
| 
       1151 
     | 
    
         
            -
            Dog.search("*") 
     | 
| 
       1152 
     | 
    
         
            -
            Animal.search("*" 
     | 
| 
      
 1183 
     | 
    
         
            +
            Animal.search("*")                # all animals
         
     | 
| 
      
 1184 
     | 
    
         
            +
            Dog.search("*")                   # just dogs
         
     | 
| 
      
 1185 
     | 
    
         
            +
            Animal.search("*").type(Dog, Cat) # just cats and dogs
         
     | 
| 
       1153 
1186 
     | 
    
         
             
            ```
         
     | 
| 
       1154 
1187 
     | 
    
         | 
| 
       1155 
1188 
     | 
    
         
             
            **Notes:**
         
     | 
| 
         @@ -1157,7 +1190,7 @@ Animal.search("*", type: [Dog, Cat]) # just cats and dogs 
     | 
|
| 
       1157 
1190 
     | 
    
         
             
            1. The `suggest` option retrieves suggestions from the parent at the moment.
         
     | 
| 
       1158 
1191 
     | 
    
         | 
| 
       1159 
1192 
     | 
    
         
             
                ```ruby
         
     | 
| 
       1160 
     | 
    
         
            -
                Dog.search("airbudd" 
     | 
| 
      
 1193 
     | 
    
         
            +
                Dog.search("airbudd").suggest # suggestions for all animals
         
     | 
| 
       1161 
1194 
     | 
    
         
             
                ```
         
     | 
| 
       1162 
1195 
     | 
    
         
             
            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 
1196 
     | 
    
         | 
| 
         @@ -1166,7 +1199,7 @@ Animal.search("*", type: [Dog, Cat]) # just cats and dogs 
     | 
|
| 
       1166 
1199 
     | 
    
         
             
            To help with debugging queries, you can use:
         
     | 
| 
       1167 
1200 
     | 
    
         | 
| 
       1168 
1201 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1169 
     | 
    
         
            -
            Product.search("soap" 
     | 
| 
      
 1202 
     | 
    
         
            +
            Product.search("soap").debug
         
     | 
| 
       1170 
1203 
     | 
    
         
             
            ```
         
     | 
| 
       1171 
1204 
     | 
    
         | 
| 
       1172 
1205 
     | 
    
         
             
            This prints useful info to `stdout`.
         
     | 
| 
         @@ -1174,7 +1207,7 @@ This prints useful info to `stdout`. 
     | 
|
| 
       1174 
1207 
     | 
    
         
             
            See how the search server scores your queries with:
         
     | 
| 
       1175 
1208 
     | 
    
         | 
| 
       1176 
1209 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1177 
     | 
    
         
            -
            Product.search("soap" 
     | 
| 
      
 1210 
     | 
    
         
            +
            Product.search("soap").explain.response
         
     | 
| 
       1178 
1211 
     | 
    
         
             
            ```
         
     | 
| 
       1179 
1212 
     | 
    
         | 
| 
       1180 
1213 
     | 
    
         
             
            See how the search server tokenizes your queries with:
         
     | 
| 
         @@ -1208,37 +1241,42 @@ As you iterate on your search, it’s a good idea to add tests. 
     | 
|
| 
       1208 
1241 
     | 
    
         | 
| 
       1209 
1242 
     | 
    
         
             
            For performance, only enable Searchkick callbacks for the tests that need it.
         
     | 
| 
       1210 
1243 
     | 
    
         | 
| 
       1211 
     | 
    
         
            -
            ###  
     | 
| 
      
 1244 
     | 
    
         
            +
            ### Rails
         
     | 
| 
       1212 
1245 
     | 
    
         | 
| 
       1213 
     | 
    
         
            -
             
     | 
| 
      
 1246 
     | 
    
         
            +
            Add to your `test/test_helper.rb`:
         
     | 
| 
       1214 
1247 
     | 
    
         | 
| 
       1215 
1248 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1216 
     | 
    
         
            -
             
     | 
| 
       1217 
     | 
    
         
            -
               
     | 
| 
       1218 
     | 
    
         
            -
                 
     | 
| 
      
 1249 
     | 
    
         
            +
            module ActiveSupport
         
     | 
| 
      
 1250 
     | 
    
         
            +
              class TestCase
         
     | 
| 
      
 1251 
     | 
    
         
            +
                parallelize_setup do |worker|
         
     | 
| 
      
 1252 
     | 
    
         
            +
                  Searchkick.index_suffix = worker
         
     | 
| 
       1219 
1253 
     | 
    
         | 
| 
       1220 
     | 
    
         
            -
             
     | 
| 
       1221 
     | 
    
         
            -
             
     | 
| 
       1222 
     | 
    
         
            -
             
     | 
| 
       1223 
     | 
    
         
            -
                # and disable callbacks
         
     | 
| 
       1224 
     | 
    
         
            -
                Searchkick.disable_callbacks
         
     | 
| 
      
 1254 
     | 
    
         
            +
                  # reindex models for parallel tests
         
     | 
| 
      
 1255 
     | 
    
         
            +
                  Product.reindex
         
     | 
| 
      
 1256 
     | 
    
         
            +
                end
         
     | 
| 
       1225 
1257 
     | 
    
         
             
              end
         
     | 
| 
       1226 
1258 
     | 
    
         
             
            end
         
     | 
| 
      
 1259 
     | 
    
         
            +
             
     | 
| 
      
 1260 
     | 
    
         
            +
            # reindex models for non-parallel tests
         
     | 
| 
      
 1261 
     | 
    
         
            +
            Product.reindex
         
     | 
| 
      
 1262 
     | 
    
         
            +
             
     | 
| 
      
 1263 
     | 
    
         
            +
            # and disable callbacks
         
     | 
| 
      
 1264 
     | 
    
         
            +
            Searchkick.disable_callbacks
         
     | 
| 
       1227 
1265 
     | 
    
         
             
            ```
         
     | 
| 
       1228 
1266 
     | 
    
         | 
| 
       1229 
1267 
     | 
    
         
             
            And use:
         
     | 
| 
       1230 
1268 
     | 
    
         | 
| 
       1231 
1269 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1232 
1270 
     | 
    
         
             
            class ProductTest < ActiveSupport::TestCase
         
     | 
| 
       1233 
     | 
    
         
            -
               
     | 
| 
      
 1271 
     | 
    
         
            +
              setup do
         
     | 
| 
       1234 
1272 
     | 
    
         
             
                Searchkick.enable_callbacks
         
     | 
| 
       1235 
1273 
     | 
    
         
             
              end
         
     | 
| 
       1236 
1274 
     | 
    
         | 
| 
       1237 
     | 
    
         
            -
               
     | 
| 
      
 1275 
     | 
    
         
            +
              teardown do
         
     | 
| 
       1238 
1276 
     | 
    
         
             
                Searchkick.disable_callbacks
         
     | 
| 
       1239 
1277 
     | 
    
         
             
              end
         
     | 
| 
       1240 
1278 
     | 
    
         | 
| 
       1241 
     | 
    
         
            -
               
     | 
| 
      
 1279 
     | 
    
         
            +
              test "search" do
         
     | 
| 
       1242 
1280 
     | 
    
         
             
                Product.create!(name: "Apple")
         
     | 
| 
       1243 
1281 
     | 
    
         
             
                Product.search_index.refresh
         
     | 
| 
       1244 
1282 
     | 
    
         
             
                assert_equal ["Apple"], Product.search("apple").map(&:name)
         
     | 
| 
         @@ -1314,25 +1352,24 @@ end 
     | 
|
| 
       1314 
1352 
     | 
    
         | 
| 
       1315 
1353 
     | 
    
         
             
            ### Factory Bot
         
     | 
| 
       1316 
1354 
     | 
    
         | 
| 
       1317 
     | 
    
         
            -
             
     | 
| 
      
 1355 
     | 
    
         
            +
            Define a trait for each model:
         
     | 
| 
       1318 
1356 
     | 
    
         | 
| 
       1319 
1357 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1320 
1358 
     | 
    
         
             
            FactoryBot.define do
         
     | 
| 
       1321 
1359 
     | 
    
         
             
              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 
1360 
     | 
    
         
             
                trait :reindex do
         
     | 
| 
       1327 
     | 
    
         
            -
                  after(:create) do |product,  
     | 
| 
      
 1361 
     | 
    
         
            +
                  after(:create) do |product, _|
         
     | 
| 
       1328 
1362 
     | 
    
         
             
                    product.reindex(refresh: true)
         
     | 
| 
       1329 
1363 
     | 
    
         
             
                  end
         
     | 
| 
       1330 
1364 
     | 
    
         
             
                end
         
     | 
| 
       1331 
1365 
     | 
    
         
             
              end
         
     | 
| 
       1332 
1366 
     | 
    
         
             
            end
         
     | 
| 
      
 1367 
     | 
    
         
            +
            ```
         
     | 
| 
       1333 
1368 
     | 
    
         | 
| 
       1334 
     | 
    
         
            -
             
     | 
| 
       1335 
     | 
    
         
            -
             
     | 
| 
      
 1369 
     | 
    
         
            +
            And use:
         
     | 
| 
      
 1370 
     | 
    
         
            +
             
     | 
| 
      
 1371 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1372 
     | 
    
         
            +
            FactoryBot.create(:product, :reindex)
         
     | 
| 
       1336 
1373 
     | 
    
         
             
            ```
         
     | 
| 
       1337 
1374 
     | 
    
         | 
| 
       1338 
1375 
     | 
    
         
             
            ### GitHub Actions
         
     | 
| 
         @@ -1354,8 +1391,8 @@ And [setup-opensearch](https://github.com/ankane/setup-opensearch) for an easy w 
     | 
|
| 
       1354 
1391 
     | 
    
         
             
            For the search server, Searchkick uses `ENV["ELASTICSEARCH_URL"]` for Elasticsearch and `ENV["OPENSEARCH_URL"]` for OpenSearch. This defaults to `http://localhost:9200`.
         
     | 
| 
       1355 
1392 
     | 
    
         | 
| 
       1356 
1393 
     | 
    
         
             
            - [Elastic Cloud](#elastic-cloud)
         
     | 
| 
       1357 
     | 
    
         
            -
            - [Heroku](#heroku)
         
     | 
| 
       1358 
1394 
     | 
    
         
             
            - [Amazon OpenSearch Service](#amazon-opensearch-service)
         
     | 
| 
      
 1395 
     | 
    
         
            +
            - [Heroku](#heroku)
         
     | 
| 
       1359 
1396 
     | 
    
         
             
            - [Self-Hosted and Other](#self-hosted-and-other)
         
     | 
| 
       1360 
1397 
     | 
    
         | 
| 
       1361 
1398 
     | 
    
         
             
            ### Elastic Cloud
         
     | 
| 
         @@ -1372,6 +1409,36 @@ Then deploy and reindex: 
     | 
|
| 
       1372 
1409 
     | 
    
         
             
            rake searchkick:reindex:all
         
     | 
| 
       1373 
1410 
     | 
    
         
             
            ```
         
     | 
| 
       1374 
1411 
     | 
    
         | 
| 
      
 1412 
     | 
    
         
            +
            ### Amazon OpenSearch Service
         
     | 
| 
      
 1413 
     | 
    
         
            +
             
     | 
| 
      
 1414 
     | 
    
         
            +
            Create an initializer `config/initializers/opensearch.rb` with:
         
     | 
| 
      
 1415 
     | 
    
         
            +
             
     | 
| 
      
 1416 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1417 
     | 
    
         
            +
            ENV["OPENSEARCH_URL"] = "https://es-domain-1234.us-east-1.es.amazonaws.com:443"
         
     | 
| 
      
 1418 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1419 
     | 
    
         
            +
             
     | 
| 
      
 1420 
     | 
    
         
            +
            To use signed requests, include in your Gemfile:
         
     | 
| 
      
 1421 
     | 
    
         
            +
             
     | 
| 
      
 1422 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1423 
     | 
    
         
            +
            gem "faraday_middleware-aws-sigv4"
         
     | 
| 
      
 1424 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1425 
     | 
    
         
            +
             
     | 
| 
      
 1426 
     | 
    
         
            +
            and add to your initializer:
         
     | 
| 
      
 1427 
     | 
    
         
            +
             
     | 
| 
      
 1428 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1429 
     | 
    
         
            +
            Searchkick.aws_credentials = {
         
     | 
| 
      
 1430 
     | 
    
         
            +
              access_key_id: ENV["AWS_ACCESS_KEY_ID"],
         
     | 
| 
      
 1431 
     | 
    
         
            +
              secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
         
     | 
| 
      
 1432 
     | 
    
         
            +
              region: "us-east-1"
         
     | 
| 
      
 1433 
     | 
    
         
            +
            }
         
     | 
| 
      
 1434 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1435 
     | 
    
         
            +
             
     | 
| 
      
 1436 
     | 
    
         
            +
            Then deploy and reindex:
         
     | 
| 
      
 1437 
     | 
    
         
            +
             
     | 
| 
      
 1438 
     | 
    
         
            +
            ```sh
         
     | 
| 
      
 1439 
     | 
    
         
            +
            rake searchkick:reindex:all
         
     | 
| 
      
 1440 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1441 
     | 
    
         
            +
             
     | 
| 
       1375 
1442 
     | 
    
         
             
            ### Heroku
         
     | 
| 
       1376 
1443 
     | 
    
         | 
| 
       1377 
1444 
     | 
    
         
             
            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 +1489,6 @@ Then deploy and reindex: 
     | 
|
| 
       1422 
1489 
     | 
    
         
             
            heroku run rake searchkick:reindex:all
         
     | 
| 
       1423 
1490 
     | 
    
         
             
            ```
         
     | 
| 
       1424 
1491 
     | 
    
         | 
| 
       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 
1492 
     | 
    
         
             
            ### Self-Hosted and Other
         
     | 
| 
       1456 
1493 
     | 
    
         | 
| 
       1457 
1494 
     | 
    
         
             
            Create an initializer with:
         
     | 
| 
         @@ -1522,8 +1559,10 @@ And create an initializer with: 
     | 
|
| 
       1522 
1559 
     | 
    
         | 
| 
       1523 
1560 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1524 
1561 
     | 
    
         
             
            class SearchSerializer
         
     | 
| 
      
 1562 
     | 
    
         
            +
              CODER = JSON::Coder.new { |v, _| v.is_a?(Time) ? v.as_json : v }
         
     | 
| 
      
 1563 
     | 
    
         
            +
             
     | 
| 
       1525 
1564 
     | 
    
         
             
              def dump(object)
         
     | 
| 
       1526 
     | 
    
         
            -
                 
     | 
| 
      
 1565 
     | 
    
         
            +
                CODER.generate(object)
         
     | 
| 
       1527 
1566 
     | 
    
         
             
              end
         
     | 
| 
       1528 
1567 
     | 
    
         
             
            end
         
     | 
| 
       1529 
1568 
     | 
    
         | 
| 
         @@ -1682,7 +1721,7 @@ end 
     | 
|
| 
       1682 
1721 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       1683 
1722 
     | 
    
         | 
| 
       1684 
1723 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1685 
     | 
    
         
            -
            Business.search("ice cream" 
     | 
| 
      
 1724 
     | 
    
         
            +
            Business.search("ice cream").routing(params[:city_id])
         
     | 
| 
       1686 
1725 
     | 
    
         
             
            ```
         
     | 
| 
       1687 
1726 
     | 
    
         | 
| 
       1688 
1727 
     | 
    
         
             
            ### Partial Reindexing
         
     | 
| 
         @@ -1713,6 +1752,12 @@ And use: 
     | 
|
| 
       1713 
1752 
     | 
    
         
             
            Product.reindex(:prices_data)
         
     | 
| 
       1714 
1753 
     | 
    
         
             
            ```
         
     | 
| 
       1715 
1754 
     | 
    
         | 
| 
      
 1755 
     | 
    
         
            +
            Ignore errors for missing documents with:
         
     | 
| 
      
 1756 
     | 
    
         
            +
             
     | 
| 
      
 1757 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 1758 
     | 
    
         
            +
            Product.reindex(:prices_data, ignore_missing: true)
         
     | 
| 
      
 1759 
     | 
    
         
            +
            ```
         
     | 
| 
      
 1760 
     | 
    
         
            +
             
     | 
| 
       1716 
1761 
     | 
    
         
             
            ## Advanced
         
     | 
| 
       1717 
1762 
     | 
    
         | 
| 
       1718 
1763 
     | 
    
         
             
            Searchkick makes it easy to use the Elasticsearch or OpenSearch DSL on its own.
         
     | 
| 
         @@ -1745,7 +1790,7 @@ end 
     | 
|
| 
       1745 
1790 
     | 
    
         
             
            And use the `body` option to search:
         
     | 
| 
       1746 
1791 
     | 
    
         | 
| 
       1747 
1792 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1748 
     | 
    
         
            -
            products = Product.search( 
     | 
| 
      
 1793 
     | 
    
         
            +
            products = Product.search.body(query: {match: {name: "milk"}})
         
     | 
| 
       1749 
1794 
     | 
    
         
             
            ```
         
     | 
| 
       1750 
1795 
     | 
    
         | 
| 
       1751 
1796 
     | 
    
         
             
            View the response with:
         
     | 
| 
         @@ -1757,7 +1802,7 @@ products.response 
     | 
|
| 
       1757 
1802 
     | 
    
         
             
            To modify the query generated by Searchkick, use:
         
     | 
| 
       1758 
1803 
     | 
    
         | 
| 
       1759 
1804 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1760 
     | 
    
         
            -
            products = Product.search("milk" 
     | 
| 
      
 1805 
     | 
    
         
            +
            products = Product.search("milk").body_options(min_score: 1)
         
     | 
| 
       1761 
1806 
     | 
    
         
             
            ```
         
     | 
| 
       1762 
1807 
     | 
    
         | 
| 
       1763 
1808 
     | 
    
         
             
            or
         
     | 
| 
         @@ -1796,13 +1841,13 @@ Then use `products` and `coupons` as typical results. 
     | 
|
| 
       1796 
1841 
     | 
    
         
             
            Search across multiple models with:
         
     | 
| 
       1797 
1842 
     | 
    
         | 
| 
       1798 
1843 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1799 
     | 
    
         
            -
            Searchkick.search("milk" 
     | 
| 
      
 1844 
     | 
    
         
            +
            Searchkick.search("milk").models(Product, Category)
         
     | 
| 
       1800 
1845 
     | 
    
         
             
            ```
         
     | 
| 
       1801 
1846 
     | 
    
         | 
| 
       1802 
1847 
     | 
    
         
             
            Boost specific models with:
         
     | 
| 
       1803 
1848 
     | 
    
         | 
| 
       1804 
1849 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1805 
     | 
    
         
            -
            indices_boost 
     | 
| 
      
 1850 
     | 
    
         
            +
            indices_boost(Category => 2, Product => 1)
         
     | 
| 
       1806 
1851 
     | 
    
         
             
            ```
         
     | 
| 
       1807 
1852 
     | 
    
         | 
| 
       1808 
1853 
     | 
    
         
             
            ## Multi-Tenancy
         
     | 
| 
         @@ -1814,7 +1859,7 @@ Check out [this great post](https://www.tiagoamaro.com.br/2014/12/11/multi-tenan 
     | 
|
| 
       1814 
1859 
     | 
    
         
             
            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 
1860 
     | 
    
         | 
| 
       1816 
1861 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1817 
     | 
    
         
            -
            Product.search("*" 
     | 
| 
      
 1862 
     | 
    
         
            +
            Product.search("*").scroll("1m") do |batch|
         
     | 
| 
       1818 
1863 
     | 
    
         
             
              # process batch ...
         
     | 
| 
       1819 
1864 
     | 
    
         
             
            end
         
     | 
| 
       1820 
1865 
     | 
    
         
             
            ```
         
     | 
| 
         @@ -1822,7 +1867,7 @@ end 
     | 
|
| 
       1822 
1867 
     | 
    
         
             
            You can also scroll batches manually.
         
     | 
| 
       1823 
1868 
     | 
    
         | 
| 
       1824 
1869 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1825 
     | 
    
         
            -
            products = Product.search("*" 
     | 
| 
      
 1870 
     | 
    
         
            +
            products = Product.search("*").scroll("1m")
         
     | 
| 
       1826 
1871 
     | 
    
         
             
            while products.any?
         
     | 
| 
       1827 
1872 
     | 
    
         
             
              # process batch ...
         
     | 
| 
       1828 
1873 
     | 
    
         | 
| 
         @@ -1845,7 +1890,7 @@ end 
     | 
|
| 
       1845 
1890 
     | 
    
         
             
            If you just need an accurate total count, you can instead use:
         
     | 
| 
       1846 
1891 
     | 
    
         | 
| 
       1847 
1892 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1848 
     | 
    
         
            -
            Product.search("pears" 
     | 
| 
      
 1893 
     | 
    
         
            +
            Product.search("pears").body_options(track_total_hits: true)
         
     | 
| 
       1849 
1894 
     | 
    
         
             
            ```
         
     | 
| 
       1850 
1895 
     | 
    
         | 
| 
       1851 
1896 
     | 
    
         
             
            ## Nested Data
         
     | 
| 
         @@ -1853,7 +1898,7 @@ Product.search("pears", body_options: {track_total_hits: true}) 
     | 
|
| 
       1853 
1898 
     | 
    
         
             
            To query nested data, use dot notation.
         
     | 
| 
       1854 
1899 
     | 
    
         | 
| 
       1855 
1900 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1856 
     | 
    
         
            -
            Product.search("san" 
     | 
| 
      
 1901 
     | 
    
         
            +
            Product.search("san").fields("store.city").where("store.zip_code" => 12345)
         
     | 
| 
       1857 
1902 
     | 
    
         
             
            ```
         
     | 
| 
       1858 
1903 
     | 
    
         | 
| 
       1859 
1904 
     | 
    
         
             
            ## Nearest Neighbor Search
         
     | 
| 
         @@ -1871,7 +1916,7 @@ Also supports `euclidean` and `inner_product` 
     | 
|
| 
       1871 
1916 
     | 
    
         
             
            Reindex and search with:
         
     | 
| 
       1872 
1917 
     | 
    
         | 
| 
       1873 
1918 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1874 
     | 
    
         
            -
            Product.search( 
     | 
| 
      
 1919 
     | 
    
         
            +
            Product.search.knn(field: :embedding, vector: [1, 2, 3]).limit(10)
         
     | 
| 
       1875 
1920 
     | 
    
         
             
            ```
         
     | 
| 
       1876 
1921 
     | 
    
         | 
| 
       1877 
1922 
     | 
    
         
             
            ### HNSW Options
         
     | 
| 
         @@ -1889,7 +1934,7 @@ end 
     | 
|
| 
       1889 
1934 
     | 
    
         
             
            Specify `ef_search`
         
     | 
| 
       1890 
1935 
     | 
    
         | 
| 
       1891 
1936 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1892 
     | 
    
         
            -
            Product.search( 
     | 
| 
      
 1937 
     | 
    
         
            +
            Product.search.knn(field: :embedding, vector: [1, 2, 3], ef_search: 40).limit(10)
         
     | 
| 
       1893 
1938 
     | 
    
         
             
            ```
         
     | 
| 
       1894 
1939 
     | 
    
         | 
| 
       1895 
1940 
     | 
    
         
             
            ## Semantic Search
         
     | 
| 
         @@ -1924,7 +1969,7 @@ query_embedding = embed.(query_prefix + query, **embed_options) 
     | 
|
| 
       1924 
1969 
     | 
    
         
             
            And perform nearest neighbor search
         
     | 
| 
       1925 
1970 
     | 
    
         | 
| 
       1926 
1971 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1927 
     | 
    
         
            -
            Product.search( 
     | 
| 
      
 1972 
     | 
    
         
            +
            Product.search.knn(field: :embedding, vector: query_embedding).limit(20)
         
     | 
| 
       1928 
1973 
     | 
    
         
             
            ```
         
     | 
| 
       1929 
1974 
     | 
    
         | 
| 
       1930 
1975 
     | 
    
         
             
            See a [full example](examples/semantic.rb)
         
     | 
| 
         @@ -1934,8 +1979,8 @@ See a [full example](examples/semantic.rb) 
     | 
|
| 
       1934 
1979 
     | 
    
         
             
            Perform keyword search and semantic search in parallel
         
     | 
| 
       1935 
1980 
     | 
    
         | 
| 
       1936 
1981 
     | 
    
         
             
            ```ruby
         
     | 
| 
       1937 
     | 
    
         
            -
            keyword_search = Product.search(query 
     | 
| 
       1938 
     | 
    
         
            -
            semantic_search = Product.search( 
     | 
| 
      
 1982 
     | 
    
         
            +
            keyword_search = Product.search(query).limit(20)
         
     | 
| 
      
 1983 
     | 
    
         
            +
            semantic_search = Product.search.knn(field: :embedding, vector: query_embedding).limit(20)
         
     | 
| 
       1939 
1984 
     | 
    
         
             
            Searchkick.multi_search([keyword_search, semantic_search])
         
     | 
| 
       1940 
1985 
     | 
    
         
             
            ```
         
     | 
| 
       1941 
1986 
     | 
    
         | 
| 
         @@ -2023,36 +2068,33 @@ Searchkick.index_prefix = "datakick" 
     | 
|
| 
       2023 
2068 
     | 
    
         
             
            Use a different term for boosting by conversions
         
     | 
| 
       2024 
2069 
     | 
    
         | 
| 
       2025 
2070 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2026 
     | 
    
         
            -
            Product.search("banana" 
     | 
| 
      
 2071 
     | 
    
         
            +
            Product.search("banana").conversions_v2(term: "organic banana")
         
     | 
| 
       2027 
2072 
     | 
    
         
             
            ```
         
     | 
| 
       2028 
2073 
     | 
    
         | 
| 
       2029 
     | 
    
         
            -
             
     | 
| 
      
 2074 
     | 
    
         
            +
            Define multiple conversion fields
         
     | 
| 
       2030 
2075 
     | 
    
         | 
| 
       2031 
2076 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2032 
2077 
     | 
    
         
             
            class Product < ApplicationRecord
         
     | 
| 
       2033 
2078 
     | 
    
         
             
              has_many :searches, class_name: "Searchjoy::Search"
         
     | 
| 
       2034 
2079 
     | 
    
         | 
| 
       2035 
     | 
    
         
            -
               
     | 
| 
       2036 
     | 
    
         
            -
              searchkick conversions: ["unique_user_conversions", "total_conversions"]
         
     | 
| 
      
 2080 
     | 
    
         
            +
              searchkick conversions_v2: ["unique_conversions", "total_conversions"]
         
     | 
| 
       2037 
2081 
     | 
    
         | 
| 
       2038 
2082 
     | 
    
         
             
              def search_data
         
     | 
| 
       2039 
2083 
     | 
    
         
             
                {
         
     | 
| 
       2040 
2084 
     | 
    
         
             
                  name: name,
         
     | 
| 
       2041 
     | 
    
         
            -
                   
     | 
| 
       2042 
     | 
    
         
            -
                  # {"ice cream" => 234, "chocolate" => 67, "cream" => 2}
         
     | 
| 
      
 2085 
     | 
    
         
            +
                  unique_conversions: searches.group(:query).distinct.count(:user_id),
         
     | 
| 
       2043 
2086 
     | 
    
         
             
                  total_conversions: searches.group(:query).count
         
     | 
| 
       2044 
     | 
    
         
            -
                  # {"ice cream" => 412, "chocolate" => 117, "cream" => 6}
         
     | 
| 
       2045 
2087 
     | 
    
         
             
                }
         
     | 
| 
       2046 
2088 
     | 
    
         
             
              end
         
     | 
| 
       2047 
2089 
     | 
    
         
             
            end
         
     | 
| 
       2048 
2090 
     | 
    
         
             
            ```
         
     | 
| 
       2049 
2091 
     | 
    
         | 
| 
       2050 
     | 
    
         
            -
             
     | 
| 
      
 2092 
     | 
    
         
            +
            And specify which to use
         
     | 
| 
       2051 
2093 
     | 
    
         | 
| 
       2052 
2094 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2053 
2095 
     | 
    
         
             
            Product.search("banana") # boost by both fields (default)
         
     | 
| 
       2054 
     | 
    
         
            -
            Product.search("banana" 
     | 
| 
       2055 
     | 
    
         
            -
            Product.search("banana" 
     | 
| 
      
 2096 
     | 
    
         
            +
            Product.search("banana").conversions_v2("total_conversions") # only boost by total_conversions
         
     | 
| 
      
 2097 
     | 
    
         
            +
            Product.search("banana").conversions_v2(false) # no conversion boosting
         
     | 
| 
       2056 
2098 
     | 
    
         
             
            ```
         
     | 
| 
       2057 
2099 
     | 
    
         | 
| 
       2058 
2100 
     | 
    
         
             
            Change timeout
         
     | 
| 
         @@ -2073,28 +2115,56 @@ Change the search method name 
     | 
|
| 
       2073 
2115 
     | 
    
         
             
            Searchkick.search_method_name = :lookup
         
     | 
| 
       2074 
2116 
     | 
    
         
             
            ```
         
     | 
| 
       2075 
2117 
     | 
    
         | 
| 
       2076 
     | 
    
         
            -
            Change  
     | 
| 
      
 2118 
     | 
    
         
            +
            Change the queue name
         
     | 
| 
       2077 
2119 
     | 
    
         | 
| 
       2078 
2120 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2079 
     | 
    
         
            -
            Searchkick.queue_name = :search_reindex
         
     | 
| 
      
 2121 
     | 
    
         
            +
            Searchkick.queue_name = :search_reindex # defaults to :searchkick
         
     | 
| 
      
 2122 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2123 
     | 
    
         
            +
             
     | 
| 
      
 2124 
     | 
    
         
            +
            Change the queue name or priority for a model
         
     | 
| 
      
 2125 
     | 
    
         
            +
             
     | 
| 
      
 2126 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2127 
     | 
    
         
            +
            class Product < ApplicationRecord
         
     | 
| 
      
 2128 
     | 
    
         
            +
              searchkick job_options: {queue: "critical", priority: 10}
         
     | 
| 
      
 2129 
     | 
    
         
            +
            end
         
     | 
| 
      
 2130 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2131 
     | 
    
         
            +
             
     | 
| 
      
 2132 
     | 
    
         
            +
            Change the queue name or priority for a specific call
         
     | 
| 
      
 2133 
     | 
    
         
            +
             
     | 
| 
      
 2134 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2135 
     | 
    
         
            +
            Product.reindex(mode: :async, job_options: {queue: "critical", priority: 10})
         
     | 
| 
      
 2136 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2137 
     | 
    
         
            +
             
     | 
| 
      
 2138 
     | 
    
         
            +
            Change the parent job
         
     | 
| 
      
 2139 
     | 
    
         
            +
             
     | 
| 
      
 2140 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2141 
     | 
    
         
            +
            Searchkick.parent_job = "ApplicationJob" # defaults to "ActiveJob::Base"
         
     | 
| 
       2080 
2142 
     | 
    
         
             
            ```
         
     | 
| 
       2081 
2143 
     | 
    
         | 
| 
       2082 
2144 
     | 
    
         
             
            Eager load associations
         
     | 
| 
       2083 
2145 
     | 
    
         | 
| 
       2084 
2146 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2085 
     | 
    
         
            -
            Product.search("milk" 
     | 
| 
      
 2147 
     | 
    
         
            +
            Product.search("milk").includes(:brand, :stores)
         
     | 
| 
       2086 
2148 
     | 
    
         
             
            ```
         
     | 
| 
       2087 
2149 
     | 
    
         | 
| 
       2088 
2150 
     | 
    
         
             
            Eager load different associations by model
         
     | 
| 
       2089 
2151 
     | 
    
         | 
| 
       2090 
2152 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2091 
     | 
    
         
            -
            Searchkick.search("*" 
     | 
| 
      
 2153 
     | 
    
         
            +
            Searchkick.search("*").models(Product, Store).model_includes(Product => [:store], Store => [:product])
         
     | 
| 
       2092 
2154 
     | 
    
         
             
            ```
         
     | 
| 
       2093 
2155 
     | 
    
         | 
| 
       2094 
2156 
     | 
    
         
             
            Run additional scopes on results
         
     | 
| 
       2095 
2157 
     | 
    
         | 
| 
       2096 
2158 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2097 
     | 
    
         
            -
            Product.search("milk" 
     | 
| 
      
 2159 
     | 
    
         
            +
            Product.search("milk").scope_results(->(r) { r.with_attached_images })
         
     | 
| 
      
 2160 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2161 
     | 
    
         
            +
             
     | 
| 
      
 2162 
     | 
    
         
            +
            Set opaque id for slow logs
         
     | 
| 
      
 2163 
     | 
    
         
            +
             
     | 
| 
      
 2164 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2165 
     | 
    
         
            +
            Product.search("milk").opaque_id("some-id")
         
     | 
| 
      
 2166 
     | 
    
         
            +
            # or
         
     | 
| 
      
 2167 
     | 
    
         
            +
            Searchkick.multi_search(searches, opaque_id: "some-id")
         
     | 
| 
       2098 
2168 
     | 
    
         
             
            ```
         
     | 
| 
       2099 
2169 
     | 
    
         | 
| 
       2100 
2170 
     | 
    
         
             
            Specify default fields to search
         
     | 
| 
         @@ -2159,7 +2229,7 @@ end 
     | 
|
| 
       2159 
2229 
     | 
    
         
             
            Add [request parameters](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html#search-search-api-query-params) like `search_type`
         
     | 
| 
       2160 
2230 
     | 
    
         | 
| 
       2161 
2231 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2162 
     | 
    
         
            -
            Product.search("carrots" 
     | 
| 
      
 2232 
     | 
    
         
            +
            Product.search("carrots").request_params(search_type: "dfs_query_then_fetch")
         
     | 
| 
       2163 
2233 
     | 
    
         
             
            ```
         
     | 
| 
       2164 
2234 
     | 
    
         | 
| 
       2165 
2235 
     | 
    
         
             
            Set options across all models
         
     | 
| 
         @@ -2174,10 +2244,11 @@ Reindex conditionally 
     | 
|
| 
       2174 
2244 
     | 
    
         | 
| 
       2175 
2245 
     | 
    
         
             
            ```ruby
         
     | 
| 
       2176 
2246 
     | 
    
         
             
            class Product < ApplicationRecord
         
     | 
| 
       2177 
     | 
    
         
            -
              searchkick  
     | 
| 
      
 2247 
     | 
    
         
            +
              searchkick callback_options: {if: :search_data_changed?}
         
     | 
| 
       2178 
2248 
     | 
    
         | 
| 
       2179 
     | 
    
         
            -
               
     | 
| 
       2180 
     | 
    
         
            -
             
     | 
| 
      
 2249 
     | 
    
         
            +
              def search_data_changed?
         
     | 
| 
      
 2250 
     | 
    
         
            +
                previous_changes.include?("name")
         
     | 
| 
      
 2251 
     | 
    
         
            +
              end
         
     | 
| 
       2181 
2252 
     | 
    
         
             
            end
         
     | 
| 
       2182 
2253 
     | 
    
         
             
            ```
         
     | 
| 
       2183 
2254 
     | 
    
         | 
| 
         @@ -2190,13 +2261,7 @@ rake searchkick:reindex:all 
     | 
|
| 
       2190 
2261 
     | 
    
         
             
            Turn on misspellings after a certain number of characters
         
     | 
| 
       2191 
2262 
     | 
    
         | 
| 
       2192 
2263 
     | 
    
         
             
            ```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
         
     | 
| 
      
 2264 
     | 
    
         
            +
            Product.search("api").misspellings(prefix_length: 2) # api, apt, no ahi
         
     | 
| 
       2200 
2265 
     | 
    
         
             
            ```
         
     | 
| 
       2201 
2266 
     | 
    
         | 
| 
       2202 
2267 
     | 
    
         
             
            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 +2299,53 @@ end 
     | 
|
| 
       2234 
2299 
     | 
    
         | 
| 
       2235 
2300 
     | 
    
         
             
            For convenience, this is set by default in the test environment.
         
     | 
| 
       2236 
2301 
     | 
    
         | 
| 
      
 2302 
     | 
    
         
            +
            ## Upgrading
         
     | 
| 
      
 2303 
     | 
    
         
            +
             
     | 
| 
      
 2304 
     | 
    
         
            +
            ### 6.0
         
     | 
| 
      
 2305 
     | 
    
         
            +
             
     | 
| 
      
 2306 
     | 
    
         
            +
            Searchkick 6 brings a new query builder API:
         
     | 
| 
      
 2307 
     | 
    
         
            +
             
     | 
| 
      
 2308 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2309 
     | 
    
         
            +
            Product.search("apples").where(in_stock: true).limit(10).offset(50)
         
     | 
| 
      
 2310 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2311 
     | 
    
         
            +
             
     | 
| 
      
 2312 
     | 
    
         
            +
            All existing options can be used as methods, or you can continue to use the existing API.
         
     | 
| 
      
 2313 
     | 
    
         
            +
             
     | 
| 
      
 2314 
     | 
    
         
            +
            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`:
         
     | 
| 
      
 2315 
     | 
    
         
            +
             
     | 
| 
      
 2316 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2317 
     | 
    
         
            +
            class Product < ApplicationRecord
         
     | 
| 
      
 2318 
     | 
    
         
            +
              searchkick conversions: [:conversions], conversions_v2: [:conversions_v2]
         
     | 
| 
      
 2319 
     | 
    
         
            +
             
     | 
| 
      
 2320 
     | 
    
         
            +
              def search_data
         
     | 
| 
      
 2321 
     | 
    
         
            +
                conversions = searches.group(:query).distinct.count(:user_id)
         
     | 
| 
      
 2322 
     | 
    
         
            +
                {
         
     | 
| 
      
 2323 
     | 
    
         
            +
                  conversions: conversions,
         
     | 
| 
      
 2324 
     | 
    
         
            +
                  conversions_v2: conversions
         
     | 
| 
      
 2325 
     | 
    
         
            +
                }
         
     | 
| 
      
 2326 
     | 
    
         
            +
              end
         
     | 
| 
      
 2327 
     | 
    
         
            +
            end
         
     | 
| 
      
 2328 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2329 
     | 
    
         
            +
             
     | 
| 
      
 2330 
     | 
    
         
            +
            Reindex, then remove `conversions`:
         
     | 
| 
      
 2331 
     | 
    
         
            +
             
     | 
| 
      
 2332 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 2333 
     | 
    
         
            +
            class Product < ApplicationRecord
         
     | 
| 
      
 2334 
     | 
    
         
            +
              searchkick conversions_v2: [:conversions_v2]
         
     | 
| 
      
 2335 
     | 
    
         
            +
             
     | 
| 
      
 2336 
     | 
    
         
            +
              def search_data
         
     | 
| 
      
 2337 
     | 
    
         
            +
                {
         
     | 
| 
      
 2338 
     | 
    
         
            +
                  conversions_v2: searches.group(:query).distinct.count(:user_id)
         
     | 
| 
      
 2339 
     | 
    
         
            +
                }
         
     | 
| 
      
 2340 
     | 
    
         
            +
              end
         
     | 
| 
      
 2341 
     | 
    
         
            +
            end
         
     | 
| 
      
 2342 
     | 
    
         
            +
            ```
         
     | 
| 
      
 2343 
     | 
    
         
            +
             
     | 
| 
      
 2344 
     | 
    
         
            +
            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.
         
     | 
| 
      
 2345 
     | 
    
         
            +
             
     | 
| 
       2237 
2346 
     | 
    
         
             
            ## History
         
     | 
| 
       2238 
2347 
     | 
    
         | 
| 
       2239 
     | 
    
         
            -
            View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md) 
     | 
| 
      
 2348 
     | 
    
         
            +
            View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md)
         
     | 
| 
       2240 
2349 
     | 
    
         | 
| 
       2241 
2350 
     | 
    
         
             
            ## Thanks
         
     | 
| 
       2242 
2351 
     | 
    
         |