searchkick-sinneduy 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.travis.yml +28 -0
  4. data/CHANGELOG.md +272 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +1109 -0
  8. data/Rakefile +8 -0
  9. data/ci/before_install.sh +14 -0
  10. data/gemfiles/activerecord31.gemfile +7 -0
  11. data/gemfiles/activerecord32.gemfile +7 -0
  12. data/gemfiles/activerecord40.gemfile +8 -0
  13. data/gemfiles/activerecord41.gemfile +8 -0
  14. data/gemfiles/mongoid2.gemfile +7 -0
  15. data/gemfiles/mongoid3.gemfile +6 -0
  16. data/gemfiles/mongoid4.gemfile +7 -0
  17. data/gemfiles/nobrainer.gemfile +6 -0
  18. data/lib/searchkick.rb +72 -0
  19. data/lib/searchkick/index.rb +550 -0
  20. data/lib/searchkick/logging.rb +136 -0
  21. data/lib/searchkick/model.rb +102 -0
  22. data/lib/searchkick/query.rb +567 -0
  23. data/lib/searchkick/reindex_job.rb +28 -0
  24. data/lib/searchkick/reindex_v2_job.rb +24 -0
  25. data/lib/searchkick/results.rb +158 -0
  26. data/lib/searchkick/tasks.rb +35 -0
  27. data/lib/searchkick/version.rb +3 -0
  28. data/searchkick.gemspec +28 -0
  29. data/test/autocomplete_test.rb +67 -0
  30. data/test/boost_test.rb +126 -0
  31. data/test/facets_test.rb +91 -0
  32. data/test/highlight_test.rb +58 -0
  33. data/test/index_test.rb +119 -0
  34. data/test/inheritance_test.rb +80 -0
  35. data/test/match_test.rb +163 -0
  36. data/test/model_test.rb +38 -0
  37. data/test/query_test.rb +14 -0
  38. data/test/reindex_job_test.rb +33 -0
  39. data/test/reindex_v2_job_test.rb +34 -0
  40. data/test/routing_test.rb +14 -0
  41. data/test/should_index_test.rb +34 -0
  42. data/test/similar_test.rb +20 -0
  43. data/test/sql_test.rb +327 -0
  44. data/test/suggest_test.rb +82 -0
  45. data/test/synonyms_test.rb +50 -0
  46. data/test/test_helper.rb +276 -0
  47. metadata +194 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 648263ec8fedddc97a0b01805c49898b847facda
4
+ data.tar.gz: 3573039526b4352830cb7c1e579e31f455bbf41a
5
+ SHA512:
6
+ metadata.gz: 0230e401447e2d5e0bb11017484eb260c346a396a137b2ff02bb1a01df16bc4428bfb4e06d28d7c5f2ee66bd0b917a31bf15998207697ff88c66b6c8b2415cd0
7
+ data.tar.gz: c0bcfb57b47bae01982f31e2e0c66420d0ded25c9002105eb745fcc271f5e0a77917d98bd6cc979bc8dbc9881dddff9b5045a60ca7a529c025386fe1db1012bf
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ *.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.log
19
+ .DS_Store
20
+ .ruby-*
@@ -0,0 +1,28 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1
4
+ services:
5
+ - elasticsearch
6
+ - mongodb
7
+ before_install:
8
+ - ./ci/before_install.sh
9
+ script: bundle exec rake test
10
+ before_script:
11
+ - psql -c 'create database searchkick_test;' -U postgres
12
+ notifications:
13
+ email:
14
+ on_success: never
15
+ on_failure: change
16
+ gemfile:
17
+ - Gemfile
18
+ - gemfiles/activerecord41.gemfile
19
+ - gemfiles/activerecord40.gemfile
20
+ - gemfiles/activerecord32.gemfile
21
+ - gemfiles/activerecord31.gemfile
22
+ - gemfiles/mongoid2.gemfile
23
+ - gemfiles/mongoid3.gemfile
24
+ - gemfiles/mongoid4.gemfile
25
+ matrix:
26
+ include:
27
+ - gemfile: gemfiles/nobrainer.gemfile
28
+ env: NOBRAINER=true
@@ -0,0 +1,272 @@
1
+ ## 0.9.0
2
+
3
+ - Much better performance for where queries if no facets
4
+ - Added basic support for regex
5
+ - Added support for routing
6
+ - Made `Searchkick.disable_callbacks` thread-safe
7
+
8
+ ## 0.8.7
9
+
10
+ - Fixed Mongoid import
11
+
12
+ ## 0.8.6
13
+
14
+ - Added support for NoBrainer
15
+ - Added `stem_conversions: false` option
16
+ - Added support for multiple `boost_where` values on the same field
17
+ - Added support for array of values for `boost_where`
18
+ - Fixed suggestions with partial match boost
19
+ - Fixed redefining existing instance methods in models
20
+
21
+ ## 0.8.5
22
+
23
+ - Added support for Elasticsearch 1.4
24
+ - Added `unsearchable` option
25
+ - Added `select: true` option
26
+ - Added `body` option
27
+
28
+ ## 0.8.4
29
+
30
+ - Added `boost_by_distance`
31
+ - More flexible highlight options
32
+ - Better `env` logic
33
+
34
+ ## 0.8.3
35
+
36
+ - Added support for ActiveJob
37
+ - Added `timeout` setting
38
+ - Fixed import with no records
39
+
40
+ ## 0.8.2
41
+
42
+ - Added `async` to `callbacks` option
43
+ - Added `wordnet` option
44
+ - Added `edit_distance` option to eventually replace `distance` option
45
+ - Catch misspelling of `misspellings` option
46
+ - Improved logging
47
+
48
+ ## 0.8.1
49
+
50
+ - Added `search_method_name` option
51
+ - Fixed `order` for array of hashes
52
+ - Added support for Mongoid 2
53
+
54
+ ## 0.8.0
55
+
56
+ - Added support for Elasticsearch 1.2
57
+
58
+ ## 0.7.9
59
+
60
+ - Added `tokens` method
61
+ - Added `json` option
62
+ - Added exact matches
63
+ - Added `prev_page` for Kaminari pagination
64
+ - Added `import` option to reindex
65
+
66
+ ## 0.7.8
67
+
68
+ - Added `boost_by` and `boost_where` options
69
+ - Added ability to boost fields - `name^10`
70
+ - Added `select` option for `load: false`
71
+
72
+ ## 0.7.7
73
+
74
+ - Added support for automatic failover
75
+ - Fixed `operator` option (and default) for partial matches
76
+
77
+ ## 0.7.6
78
+
79
+ - Added `stats` option to facets
80
+ - Added `padding` option
81
+
82
+ ## 0.7.5
83
+
84
+ - Do not throw errors when index becomes out of sync with database
85
+ - Added custom exception types
86
+ - Fixed `offset` and `offset_value`
87
+
88
+ ## 0.7.4
89
+
90
+ - Fixed reindex with inheritance
91
+
92
+ ## 0.7.3
93
+
94
+ - Fixed multi-index searches
95
+ - Fixed suggestions for partial matches
96
+ - Added `offset` and `length` for improved pagination
97
+
98
+ ## 0.7.2
99
+
100
+ - Added smart facets
101
+ - Added more fields to `load: false` result
102
+ - Fixed logging for multi-index searches
103
+ - Added `first_page?` and `last_page?` for improved Kaminari support
104
+
105
+ ## 0.7.1
106
+
107
+ - Fixed huge issue w/ zero-downtime reindexing on 0.90
108
+
109
+ ## 0.7.0
110
+
111
+ - Added support for Elasticsearch 1.1
112
+ - Dropped support for Elasticsearch below 0.90.4 (unfortunate side effect of above)
113
+
114
+ ## 0.6.3
115
+
116
+ - Removed patron since no support for Windows
117
+ - Added error if `searchkick` is called multiple times
118
+
119
+ ## 0.6.2
120
+
121
+ - Added logging
122
+ - Fixed index_name option
123
+ - Added ability to use proc as the index name
124
+
125
+ ## 0.6.1
126
+
127
+ - Fixed huge issue w/ zero-downtime reindexing on 0.90 and elasticsearch-ruby 1.0
128
+ - Restore load: false behavior
129
+ - Restore total_entries method
130
+
131
+ ## 0.6.0
132
+
133
+ - Moved to elasticsearch-ruby
134
+ - Added support for modifying the query and viewing the response
135
+ - Added support for page_entries_info method
136
+
137
+ ## 0.5.3
138
+
139
+ - Fixed bug w/ word_* queries
140
+
141
+ ## 0.5.2
142
+
143
+ - Use after_commit hook for ActiveRecord to prevent data inconsistencies
144
+
145
+ ## 0.5.1
146
+
147
+ - Replaced stop words with common terms query
148
+ - Added language option
149
+ - Fixed bug with empty array in where clause
150
+ - Fixed bug with MongoDB integer _id
151
+ - Fixed reindex bug when callbacks disabled
152
+
153
+ ## 0.5.0
154
+
155
+ - Better control over partial matches
156
+ - Added merge_mappings option
157
+ - Added batch_size option
158
+ - Fixed bug with nil where clauses
159
+
160
+ ## 0.4.2
161
+
162
+ - Added `should_index?` method to control which records are indexed
163
+ - Added ability to temporarily disable callbacks
164
+ - Added custom mappings
165
+
166
+ ## 0.4.1
167
+
168
+ - Fixed issue w/ inheritance mapping
169
+
170
+ ## 0.4.0
171
+
172
+ - Added support for Mongoid 4
173
+ - Added support for multiple locations
174
+
175
+ ## 0.3.5
176
+
177
+ - Added facet ranges
178
+ - Added all operator
179
+
180
+ ## 0.3.4
181
+
182
+ - Added highlighting
183
+ - Added :distance option to misspellings
184
+ - Fixed issue w/ BigDecimal serialization
185
+
186
+ ## 0.3.3
187
+
188
+ - Better error messages
189
+ - Added where: {field: nil} queries
190
+
191
+ ## 0.3.2
192
+
193
+ - Added support for single table inheritance
194
+ - Removed Tire::Model::Search
195
+
196
+ ## 0.3.1
197
+
198
+ - Added index_prefix option
199
+ - Fixed ES issue with incorrect facet counts
200
+ - Added option to turn off special characters
201
+
202
+ ## 0.3.0
203
+
204
+ - Fixed reversed coordinates
205
+ - Added bounded by a box queries
206
+ - Expanded `or` queries
207
+
208
+ ## 0.2.8
209
+
210
+ - Added option to disable callbacks
211
+ - Fixed bug with facets with Elasticsearch 0.90.5
212
+
213
+ ## 0.2.7
214
+
215
+ - Added limit to facet
216
+ - Improved similar items
217
+
218
+ ## 0.2.6
219
+
220
+ - Added option to disable misspellings
221
+
222
+ ## 0.2.5
223
+
224
+ - Added geospartial searches
225
+ - Create alias before importing document if no alias exists
226
+ - Fixed exception when :per_page option is a string
227
+ - Check `RAILS_ENV` if `RACK_ENV` is not set
228
+
229
+ ## 0.2.4
230
+
231
+ - Use `to_hash` instead of `as_json` for default `search_data` method
232
+ - Works for Mongoid 1.3
233
+ - Use one shard in test environment for consistent scores
234
+
235
+ ## 0.2.3
236
+
237
+ - Setup Travis
238
+ - Clean old indices before reindex
239
+ - Search for `*` returns all results
240
+ - Fixed pagination
241
+ - Added `similar` method
242
+
243
+ ## 0.2.2
244
+
245
+ - Clean old indices after reindex
246
+ - More expansions for fuzzy queries
247
+
248
+ ## 0.2.1
249
+
250
+ - Added Rails logger
251
+ - Only fetch ids when `load: true`
252
+
253
+ ## 0.2.0
254
+
255
+ - Added autocomplete
256
+ - Added “Did you mean” suggestions
257
+ - Added personalized searches
258
+
259
+ ## 0.1.4
260
+
261
+ - Bug fix
262
+
263
+ ## 0.1.3
264
+
265
+ - Changed edit distance to one for misspellings
266
+ - Raise errors when indexing fails
267
+ - Fixed pagination
268
+ - Fixed :include option
269
+
270
+ ## 0.1.2
271
+
272
+ - Launch
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in searchkick.gemspec
4
+ gemspec
5
+
6
+ gem "sqlite3"
7
+ gem "activerecord", "~> 4.2.0"
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andrew Kane
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,1109 @@
1
+ # Searchkick
2
+
3
+ :rocket: Intelligent search made easy
4
+
5
+ Searchkick learns what **your users** are looking for. As more people search, it gets smarter and the results get better. It’s friendly for developers - and magical for your users.
6
+
7
+ Searchkick handles:
8
+
9
+ - stemming - `tomatoes` matches `tomato`
10
+ - special characters - `jalapeno` matches `jalapeño`
11
+ - extra whitespace - `dishwasher` matches `dish washer`
12
+ - misspellings - `zuchini` matches `zucchini`
13
+ - custom synonyms - `qtip` matches `cotton swab`
14
+
15
+ Plus:
16
+
17
+ - query like SQL - no need to learn a new query language
18
+ - reindex without downtime
19
+ - easily personalize results for each user
20
+ - autocomplete
21
+ - “Did you mean” suggestions
22
+ - works with ActiveRecord, Mongoid, and NoBrainer
23
+
24
+ :speech_balloon: Get [handcrafted updates](http://chartkick.us7.list-manage.com/subscribe?u=952c861f99eb43084e0a49f98&id=6ea6541e8e&group[0][4]=true) for new features
25
+
26
+ :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
27
+
28
+ [![Build Status](https://travis-ci.org/ankane/searchkick.svg?branch=master)](https://travis-ci.org/ankane/searchkick)
29
+
30
+ We highly recommend tracking queries and conversions
31
+
32
+ :zap: [Searchjoy](https://github.com/ankane/searchjoy) makes it easy
33
+
34
+ ## Get Started
35
+
36
+ [Install Elasticsearch](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup.html). For Homebrew, use:
37
+
38
+ ```sh
39
+ brew install elasticsearch
40
+ ```
41
+
42
+ Add this line to your application’s Gemfile:
43
+
44
+ ```ruby
45
+ gem 'searchkick'
46
+ ```
47
+
48
+ For Elasticsearch 0.90, use version `0.6.3` and [this readme](https://github.com/ankane/searchkick/blob/v0.6.3/README.md).
49
+
50
+ Add searchkick to models you want to search.
51
+
52
+ ```ruby
53
+ class Product < ActiveRecord::Base
54
+ searchkick
55
+ end
56
+ ```
57
+
58
+ Add data to the search index.
59
+
60
+ ```ruby
61
+ Product.reindex
62
+ ```
63
+
64
+ And to query, use:
65
+
66
+ ```ruby
67
+ products = Product.search "2% Milk"
68
+ products.each do |product|
69
+ puts product.name
70
+ end
71
+ ```
72
+
73
+ Searchkick supports the complete [Elasticsearch Search API](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-search.html). As your search becomes more advanced, we recommend you use the [Elasticsearch DSL](#advanced) for maximum flexibility.
74
+
75
+ ### Queries
76
+
77
+ Query like SQL
78
+
79
+ ```ruby
80
+ Product.search "2% Milk", where: {in_stock: true}, limit: 10, offset: 50
81
+ ```
82
+
83
+ Search specific fields
84
+
85
+ ```ruby
86
+ fields: [:name, :brand]
87
+ ```
88
+
89
+ Where
90
+
91
+ ```ruby
92
+ where: {
93
+ expires_at: {gt: Time.now}, # lt, gte, lte also available
94
+ orders_count: 1..10, # equivalent to {gte: 1, lte: 10}
95
+ aisle_id: [25, 30], # in
96
+ store_id: {not: 2}, # not
97
+ aisle_id: {not: [25, 30]}, # not in
98
+ user_ids: {all: [1, 3]}, # all elements in array
99
+ or: [
100
+ [{in_stock: true}, {backordered: true}]
101
+ ]
102
+ }
103
+ ```
104
+
105
+ Order
106
+
107
+ ```ruby
108
+ order: {_score: :desc} # most relevant first - default
109
+ ```
110
+
111
+ [All of these sort options are supported](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html)
112
+
113
+ Limit / offset
114
+
115
+ ```ruby
116
+ limit: 20, offset: 40
117
+ ```
118
+
119
+ ### Boosting
120
+
121
+ Boost important fields
122
+
123
+ ```ruby
124
+ fields: ["title^10", "description"]
125
+ ```
126
+
127
+ Boost by the value of a field (field must be numeric)
128
+
129
+ ```ruby
130
+ boost_by: [:orders_count] # give popular documents a little boost
131
+ boost_by: {orders_count: {factor: 10}} # default factor is 1
132
+ ```
133
+
134
+ Boost matching documents
135
+
136
+ ```ruby
137
+ boost_where: {user_id: 1}
138
+ boost_where: {user_id: {value: 1, factor: 100}} # default factor is 1000
139
+ boost_where: {user_id: [{value: 1, factor: 100}, {value: 2, factor: 200}]}
140
+ ```
141
+
142
+ [Conversions](#keep-getting-better) are also a great way to boost.
143
+
144
+ ### Get Everything
145
+
146
+ Use a `*` for the query.
147
+
148
+ ```ruby
149
+ Product.search "*"
150
+ ```
151
+
152
+ ### Pagination
153
+
154
+ Plays nicely with kaminari and will_paginate.
155
+
156
+ ```ruby
157
+ # controller
158
+ @products = Product.search "milk", page: params[:page], per_page: 20
159
+ ```
160
+
161
+ View with kaminari
162
+
163
+ ```erb
164
+ <%= paginate @products %>
165
+ ```
166
+
167
+ View with will_paginate
168
+
169
+ ```erb
170
+ <%= will_paginate @products %>
171
+ ```
172
+
173
+ ### Partial Matches
174
+
175
+ By default, results must match all words in the query.
176
+
177
+ ```ruby
178
+ Product.search "fresh honey" # fresh AND honey
179
+ ```
180
+
181
+ To change this, use:
182
+
183
+ ```ruby
184
+ Product.search "fresh honey", operator: "or" # fresh OR honey
185
+ ```
186
+
187
+ By default, results must match the entire word - `back` will not match `backpack`. You can change this behavior with:
188
+
189
+ ```ruby
190
+ class Product < ActiveRecord::Base
191
+ searchkick word_start: [:name]
192
+ end
193
+ ```
194
+
195
+ And to search (after you reindex):
196
+
197
+ ```ruby
198
+ Product.search "back", fields: [{name: :word_start}]
199
+ ```
200
+
201
+ Available options are:
202
+
203
+ ```ruby
204
+ :word # default
205
+ :word_start
206
+ :word_middle
207
+ :word_end
208
+ :text_start
209
+ :text_middle
210
+ :text_end
211
+ ```
212
+
213
+ To boost fields, use:
214
+
215
+ ```ruby
216
+ fields: [{"name^2" => :word_start}] # better interface on the way
217
+ ```
218
+
219
+ ### Exact Matches
220
+
221
+ ```ruby
222
+ User.search "hi@searchkick.org", fields: [{email: :exact}, :name]
223
+ ```
224
+
225
+ ### Language
226
+
227
+ Searchkick defaults to English for stemming. To change this, use:
228
+
229
+ ```ruby
230
+ class Product < ActiveRecord::Base
231
+ searchkick language: "German"
232
+ end
233
+ ```
234
+
235
+ [See the list of languages](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/analysis-snowball-tokenfilter.html)
236
+
237
+ ### Synonyms
238
+
239
+ ```ruby
240
+ class Product < ActiveRecord::Base
241
+ searchkick synonyms: [["scallion", "green onion"], ["qtip", "cotton swab"]]
242
+ end
243
+ ```
244
+
245
+ Call `Product.reindex` after changing synonyms.
246
+
247
+ ### WordNet
248
+
249
+ Prepopulate English synonyms with the [WordNet database](http://en.wikipedia.org/wiki/WordNet).
250
+
251
+ Download [WordNet 3.0](http://wordnetcode.princeton.edu/3.0/WNprolog-3.0.tar.gz) to each Elasticsearch server and move `wn_s.pl` to the `/var/lib` directory.
252
+
253
+ ```sh
254
+ cd /tmp
255
+ curl -o wordnet.tar.gz http://wordnetcode.princeton.edu/3.0/WNprolog-3.0.tar.gz
256
+ tar -zxvf wordnet.tar.gz
257
+ mv prolog/wn_s.pl /var/lib
258
+ ```
259
+
260
+ Tell each model to use it:
261
+
262
+ ```ruby
263
+ class Product < ActiveRecord::Base
264
+ searchkick wordnet: true
265
+ end
266
+ ```
267
+
268
+ ### Misspellings
269
+
270
+ By default, Searchkick handles misspelled queries by returning results with an [edit distance](http://en.wikipedia.org/wiki/Levenshtein_distance) of one. To turn off this feature, use:
271
+
272
+ ```ruby
273
+ Product.search "zuchini", misspellings: false # no zucchini
274
+ ```
275
+
276
+ You can also change the edit distance with:
277
+
278
+ ```ruby
279
+ Product.search "zucini", misspellings: {edit_distance: 2} # zucchini
280
+ ```
281
+
282
+ ### Indexing
283
+
284
+ Control what data is indexed with the `search_data` method. Call `Product.reindex` after changing this method.
285
+
286
+ ```ruby
287
+ class Product < ActiveRecord::Base
288
+ def search_data
289
+ as_json only: [:name, :active]
290
+ # or equivalently
291
+ {
292
+ name: name,
293
+ active: active
294
+ }
295
+ end
296
+ end
297
+ ```
298
+
299
+ Searchkick uses `find_in_batches` to import documents. To eager load associations, use the `search_import` scope.
300
+
301
+ ```ruby
302
+ class Product < ActiveRecord::Base
303
+ scope :search_import, -> { includes(:searches) }
304
+ end
305
+ ```
306
+
307
+ By default, all records are indexed. To control which records are indexed, use the `should_index?` method.
308
+
309
+ ```ruby
310
+ class Product < ActiveRecord::Base
311
+ def should_index?
312
+ active # only index active records
313
+ end
314
+ end
315
+ ```
316
+
317
+ ### To Reindex, or Not to Reindex
318
+
319
+ #### Reindex
320
+
321
+ - when you install or upgrade searchkick
322
+ - change the `search_data` method
323
+ - change the `searchkick` method
324
+
325
+ #### No need to reindex
326
+
327
+ - App starts
328
+
329
+ ### Stay Synced
330
+
331
+ There are three strategies for keeping the index synced with your database.
332
+
333
+ 1. Immediate (default)
334
+
335
+ Anytime a record is inserted, updated, or deleted
336
+
337
+ 2. Asynchronous
338
+
339
+ Use background jobs for better performance
340
+
341
+ ```ruby
342
+ class Product < ActiveRecord::Base
343
+ searchkick callbacks: :async
344
+ end
345
+ ```
346
+
347
+ And [install Active Job](https://github.com/ankane/activejob_backport) for Rails 4.1 and below
348
+
349
+ 3. Manual
350
+
351
+ Turn off automatic syncing
352
+
353
+ ```ruby
354
+ class Product < ActiveRecord::Base
355
+ searchkick callbacks: false
356
+ end
357
+ ```
358
+
359
+ #### Associations
360
+
361
+ Data is **not** automatically synced when an association is updated. If this is desired, add a callback to reindex:
362
+
363
+ ```ruby
364
+ class Image < ActiveRecord::Base
365
+ belongs_to :product
366
+
367
+ after_commit :reindex_product
368
+
369
+ def reindex_product
370
+ product.reindex # or reindex_async
371
+ end
372
+ end
373
+ ```
374
+
375
+ ### Keep Getting Better
376
+
377
+ Searchkick uses conversion data to learn what users are looking for. If a user searches for “ice cream” and adds Ben & Jerry’s Chunky Monkey to the cart (our conversion metric at Instacart), that item gets a little more weight for similar searches.
378
+
379
+ The first step is to define your conversion metric and start tracking conversions. The database works well for low volume, but feel free to use Redis or another datastore.
380
+
381
+ ```ruby
382
+ class Search < ActiveRecord::Base
383
+ belongs_to :product
384
+ # fields: id, query, searched_at, converted_at, product_id
385
+ end
386
+ ```
387
+
388
+ You do **not** need to clean up the search queries. Searchkick automatically treats `apple` and `APPLES` the same.
389
+
390
+ Next, add conversions to the index.
391
+
392
+ ```ruby
393
+ class Product < ActiveRecord::Base
394
+ has_many :searches
395
+
396
+ searchkick conversions: "conversions" # name of field
397
+
398
+ def search_data
399
+ {
400
+ name: name,
401
+ conversions: searches.group("query").count
402
+ # {"ice cream" => 234, "chocolate" => 67, "cream" => 2}
403
+ }
404
+ end
405
+ end
406
+ ```
407
+
408
+ Reindex and set up a cron job to add new conversions daily.
409
+
410
+ ```ruby
411
+ rake searchkick:reindex CLASS=Product
412
+ ```
413
+
414
+ ### Personalized Results
415
+
416
+ Order results differently for each user. For example, show a user’s previously purchased products before other results.
417
+
418
+ ```ruby
419
+ class Product < ActiveRecord::Base
420
+
421
+ def search_data
422
+ {
423
+ name: name,
424
+ orderer_ids: orders.pluck(:user_id) # boost this product for these users
425
+ }
426
+ end
427
+
428
+ end
429
+ ```
430
+
431
+ Reindex and search with:
432
+
433
+ ```ruby
434
+ Product.search "milk", boost_where: {orderer_ids: current_user.id}
435
+ ```
436
+
437
+ ### Autocomplete
438
+
439
+ Autocomplete predicts what a user will type, making the search experience faster and easier.
440
+
441
+ ![Autocomplete](http://ankane.github.io/searchkick/autocomplete.png)
442
+
443
+ **Note:** If you only have a few thousand records, don’t use Searchkick for autocomplete. It’s *much* faster to load all records into JavaScript and autocomplete there (eliminates network requests).
444
+
445
+ First, specify which fields use this feature. This is necessary since autocomplete can increase the index size significantly, but don’t worry - this gives you blazing faster queries.
446
+
447
+ ```ruby
448
+ class City < ActiveRecord::Base
449
+ searchkick text_start: [:name]
450
+ end
451
+ ```
452
+
453
+ Reindex and search with:
454
+
455
+ ```ruby
456
+ City.search "san fr", fields: [{name: :text_start}]
457
+ ```
458
+
459
+ Typically, you want to use a JavaScript library like [typeahead.js](http://twitter.github.io/typeahead.js/) or [jQuery UI](http://jqueryui.com/autocomplete/).
460
+
461
+ #### Here’s how to make it work with Rails
462
+
463
+ First, add a route and controller action.
464
+
465
+ ```ruby
466
+ # app/controllers/cities_controller.rb
467
+ class CitiesController < ApplicationController
468
+
469
+ def autocomplete
470
+ render json: City.search(params[:query], fields: [{name: :text_start}], limit: 10).map(&:name)
471
+ end
472
+
473
+ end
474
+ ```
475
+
476
+ Then add the search box and JavaScript code to a view.
477
+
478
+ ```html
479
+ <input type="text" id="query" name="query" />
480
+
481
+ <script src="jquery.js"></script>
482
+ <script src="typeahead.js"></script>
483
+ <script>
484
+ $("#query").typeahead({
485
+ name: "city",
486
+ remote: "/cities/autocomplete?query=%QUERY"
487
+ });
488
+ </script>
489
+ ```
490
+
491
+ ### Suggestions
492
+
493
+ ![Suggest](http://ankane.github.io/searchkick/recursion.png)
494
+
495
+ ```ruby
496
+ class Product < ActiveRecord::Base
497
+ searchkick suggest: ["name"] # fields to generate suggestions
498
+ end
499
+ ```
500
+
501
+ Reindex and search with:
502
+
503
+ ```ruby
504
+ products = Product.search "peantu butta", suggest: true
505
+ products.suggestions # ["peanut butter"]
506
+ ```
507
+
508
+ ### Facets
509
+
510
+ [Facets](http://www.elasticsearch.org/guide/reference/api/search/facets/) provide aggregated search data.
511
+
512
+ ![Facets](http://ankane.github.io/searchkick/facets.png)
513
+
514
+ ```ruby
515
+ products = Product.search "chuck taylor", facets: [:product_type, :gender, :brand]
516
+ p products.facets
517
+ ```
518
+
519
+ By default, `where` conditions are not applied to facets (for backward compatibility).
520
+
521
+ ```ruby
522
+ Product.search "wingtips", where: {color: "brandy"}, facets: [:size]
523
+ # facets *not* filtered by color :(
524
+ ```
525
+
526
+ Change this with:
527
+
528
+ ```ruby
529
+ Product.search "wingtips", where: {color: "brandy"}, facets: [:size], smart_facets: true
530
+ ```
531
+
532
+ or set `where` conditions for each facet separately:
533
+
534
+ ```ruby
535
+ Product.search "wingtips", facets: {size: {where: {color: "brandy"}}}
536
+ ```
537
+
538
+ Limit
539
+
540
+ ```ruby
541
+ Product.search "2% Milk", facets: {store_id: {limit: 10}}
542
+ ```
543
+
544
+ Ranges
545
+
546
+ ```ruby
547
+ price_ranges = [{to: 20}, {from: 20, to: 50}, {from: 50}]
548
+ Product.search "*", facets: {price: {ranges: price_ranges}}
549
+ ```
550
+
551
+ Use the `stats` option to get to max, min, mean, and total scores for each facet
552
+
553
+ ```ruby
554
+ Product.search "*", facets: {store_id: {stats: true}}
555
+ ```
556
+
557
+ ### Highlight
558
+
559
+ Specify which fields to index with highlighting.
560
+
561
+ ```ruby
562
+ class Product < ActiveRecord::Base
563
+ searchkick highlight: [:name]
564
+ end
565
+ ```
566
+
567
+ Highlight the search query in the results.
568
+
569
+ ```ruby
570
+ bands = Band.search "cinema", fields: [:name], highlight: true
571
+ ```
572
+
573
+ **Note:** The `fields` option is required, unless highlight options are given - see below.
574
+
575
+ View the highlighted fields with:
576
+
577
+ ```ruby
578
+ bands.with_details.each do |band, details|
579
+ puts details[:highlight][:name] # "Two Door <em>Cinema</em> Club"
580
+ end
581
+ ```
582
+
583
+ To change the tag, use:
584
+
585
+ ```ruby
586
+ Band.search "cinema", fields: [:name], highlight: {tag: "<strong>"}
587
+ ```
588
+
589
+ To highlight and search different fields, use:
590
+
591
+ ```ruby
592
+ Band.search "cinema", fields: [:name], highlight: {fields: [:description]}
593
+ ```
594
+
595
+ Additional options, including fragment size, can be specified for each field:
596
+
597
+ ```ruby
598
+ Band.search "cinema", fields: [:name], highlight: {fields: {name: {fragment_size: 200}}}
599
+ ```
600
+
601
+ You can find available highlight options in the [Elasticsearch reference](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html#_highlighted_fragments).
602
+
603
+ ### Similar Items
604
+
605
+ Find similar items.
606
+
607
+ ```ruby
608
+ product = Product.first
609
+ product.similar(fields: ["name"], where: {size: "12 oz"})
610
+ ```
611
+
612
+ ### Geospatial Searches
613
+
614
+ ```ruby
615
+ class City < ActiveRecord::Base
616
+ searchkick locations: ["location"]
617
+
618
+ def search_data
619
+ attributes.merge location: [latitude, longitude]
620
+ end
621
+ end
622
+ ```
623
+
624
+ Reindex and search with:
625
+
626
+ ```ruby
627
+ City.search "san", where: {location: {near: [37, -114], within: "100mi"}} # or 160km
628
+ ```
629
+
630
+ Bounded by a box
631
+
632
+ ```ruby
633
+ City.search "san", where: {location: {top_left: [38, -123], bottom_right: [37, -122]}}
634
+ ```
635
+
636
+ ### Boost By Distance
637
+
638
+ Boost results by distance - closer results are boosted more
639
+
640
+ ```ruby
641
+ City.search "san", boost_by_distance: {field: :location, origin: [37, -122]}
642
+ ```
643
+
644
+ Also supports [additional options](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_decay_functions)
645
+
646
+ ```ruby
647
+ City.search "san", boost_by_distance: {field: :location, origin: [37, -122], function: :linear, scale: "30mi", decay: 0.5}
648
+ ```
649
+
650
+ ### Routing
651
+
652
+ Searchkick supports [Elasticsearch’s routing feature](https://www.elastic.co/blog/customizing-your-document-routing).
653
+
654
+ ```ruby
655
+ class Contact < ActiveRecord::Base
656
+ searchkick routing: :user_id
657
+ end
658
+ ```
659
+
660
+ Reindex and search with:
661
+
662
+ ```ruby
663
+ Contact.search "John", routing: current_user.id
664
+ ```
665
+
666
+ ## Inheritance
667
+
668
+ Searchkick supports single table inheritance.
669
+
670
+ ```ruby
671
+ class Dog < Animal
672
+ end
673
+ ```
674
+
675
+ The parent and child model can both reindex.
676
+
677
+ ```ruby
678
+ Animal.reindex
679
+ Dog.reindex # equivalent
680
+ ```
681
+
682
+ And to search, use:
683
+
684
+ ```ruby
685
+ Animal.search "*" # all animals
686
+ Dog.search "*" # just dogs
687
+ Animal.search "*", type: [Dog, Cat] # just cats and dogs
688
+ ```
689
+
690
+ **Note:** The `suggest` option retrieves suggestions from the parent at the moment.
691
+
692
+ ```ruby
693
+ Dog.search "airbudd", suggest: true # suggestions for all animals
694
+ ```
695
+
696
+ ## Debugging Queries
697
+
698
+ See how Elasticsearch tokenizes your queries with:
699
+
700
+ ```ruby
701
+ Product.searchkick_index.tokens("Dish Washer Soap", analyzer: "default_index")
702
+ # ["dish", "dishwash", "washer", "washersoap", "soap"]
703
+
704
+ Product.searchkick_index.tokens("dishwasher soap", analyzer: "searchkick_search")
705
+ # ["dishwashersoap"] - no match
706
+
707
+ Product.searchkick_index.tokens("dishwasher soap", analyzer: "searchkick_search2")
708
+ # ["dishwash", "soap"] - match!!
709
+ ```
710
+
711
+ Partial matches
712
+
713
+ ```ruby
714
+ Product.searchkick_index.tokens("San Diego", analyzer: "searchkick_word_start_index")
715
+ # ["s", "sa", "san", "d", "di", "die", "dieg", "diego"]
716
+
717
+ Product.searchkick_index.tokens("dieg", analyzer: "searchkick_word_search")
718
+ # ["dieg"] - match!!
719
+ ```
720
+
721
+ See the [complete list of analyzers](lib/searchkick/index.rb#L209).
722
+
723
+ ## Deployment
724
+
725
+ Searchkick uses `ENV["ELASTICSEARCH_URL"]` for the Elasticsearch server. This defaults to `http://localhost:9200`.
726
+
727
+ ### Heroku
728
+
729
+ Choose an add-on: [SearchBox](https://addons.heroku.com/searchbox), [Bonsai](https://addons.heroku.com/bonsai), or [Found](https://addons.heroku.com/foundelasticsearch).
730
+
731
+ ```sh
732
+ # SearchBox
733
+ heroku addons:add searchbox:starter
734
+ heroku config:add ELASTICSEARCH_URL=`heroku config:get SEARCHBOX_URL`
735
+
736
+ # Bonsai
737
+ heroku addons:add bonsai
738
+ heroku config:add ELASTICSEARCH_URL=`heroku config:get BONSAI_URL`
739
+
740
+ # Found
741
+ heroku addons:add foundelasticsearch
742
+ heroku config:add ELASTICSEARCH_URL=`heroku config:get FOUNDELASTICSEARCH_URL`
743
+ ```
744
+
745
+ Then deploy and reindex:
746
+
747
+ ```sh
748
+ heroku run rake searchkick:reindex CLASS=Product
749
+ ```
750
+
751
+ ### Other
752
+
753
+ Create an initializer `config/initializers/elasticsearch.rb` with:
754
+
755
+ ```ruby
756
+ ENV["ELASTICSEARCH_URL"] = "http://username:password@api.searchbox.io"
757
+ ```
758
+
759
+ Then deploy and reindex:
760
+
761
+ ```sh
762
+ rake searchkick:reindex CLASS=Product
763
+ ```
764
+
765
+ ### Performance
766
+
767
+ For the best performance, add [Typhoeus](https://github.com/typhoeus/typhoeus) to your Gemfile.
768
+
769
+ ```ruby
770
+ gem 'typhoeus'
771
+ ```
772
+
773
+ And create an initializer with:
774
+
775
+ ```ruby
776
+ require "typhoeus/adapters/faraday"
777
+ Ethon.logger = Logger.new("/dev/null")
778
+ ```
779
+
780
+ **Note:** Typhoeus is not available for Windows.
781
+
782
+ ### Automatic Failover
783
+
784
+ Create an initializer `config/initializers/elasticsearch.rb` with multiple hosts:
785
+
786
+ ```ruby
787
+ Searchkick.client = Elasticsearch::Client.new(hosts: ["localhost:9200", "localhost:9201"], retry_on_failure: true)
788
+ ```
789
+
790
+ See [elasticsearch-transport](https://github.com/elasticsearch/elasticsearch-ruby/blob/master/elasticsearch-transport) for a complete list of options.
791
+
792
+ ### Lograge
793
+
794
+ Add the following to `config/environments/production.rb`:
795
+
796
+ ```ruby
797
+ config.lograge.custom_options = lambda do |event|
798
+ options = {}
799
+ options[:search] = event.payload[:searchkick_runtime] if event.payload[:searchkick_runtime].to_f > 0
800
+ options
801
+ end
802
+ ```
803
+
804
+ See [Production Rails](https://github.com/ankane/production_rails) for other good practices.
805
+
806
+ ## Advanced
807
+
808
+ Prefer to use the [Elasticsearch DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-queries.html) but still want awesome features like zero-downtime reindexing?
809
+
810
+ ### Advanced Mapping
811
+
812
+ Create a custom mapping:
813
+
814
+ ```ruby
815
+ class Product < ActiveRecord::Base
816
+ searchkick mappings: {
817
+ product: {
818
+ properties: {
819
+ name: {type: "string", analyzer: "keyword"}
820
+ }
821
+ }
822
+ }
823
+ end
824
+ ```
825
+
826
+ To keep the mappings and settings generated by Searchkick, use:
827
+
828
+ ```ruby
829
+ class Product < ActiveRecord::Base
830
+ searchkick merge_mappings: true, mappings: {...}
831
+ end
832
+ ```
833
+
834
+ ### Advanced Search
835
+
836
+ And use the `body` option to search:
837
+
838
+ ```ruby
839
+ products = Product.search body: {match: {name: "milk"}}
840
+ ```
841
+
842
+ View the response with:
843
+
844
+ ```ruby
845
+ products.response
846
+ ```
847
+
848
+ To modify the query generated by Searchkick, use:
849
+
850
+ ```ruby
851
+ products =
852
+ Product.search "2% Milk" do |body|
853
+ body[:query] = {match_all: {}}
854
+ end
855
+ ```
856
+
857
+ ## Reference
858
+
859
+ Reindex one record
860
+
861
+ ```ruby
862
+ product = Product.find 10
863
+ product.reindex
864
+ # or to reindex in the background
865
+ product.reindex_async
866
+ ```
867
+
868
+ Reindex more than one record without recreating the index
869
+
870
+ ```ruby
871
+ # do this ...
872
+ some_company.products.each { |p| p.reindex }
873
+ # or this ...
874
+ Product.searchkick_index.import(some_company.products)
875
+ # don't do the following as it will recreate the index with some_company's products only
876
+ some_company.products.reindex
877
+ ```
878
+
879
+ Reindex large set of records in batches
880
+
881
+ ```ruby
882
+ Product.where("id > 100000").find_in_batches do |batch|
883
+ Product.searchkick_index.import(batch)
884
+ end
885
+ ```
886
+
887
+ Remove old indices
888
+
889
+ ```ruby
890
+ Product.clean_indices
891
+ ```
892
+
893
+ Use a different index name
894
+
895
+ ```ruby
896
+ class Product < ActiveRecord::Base
897
+ searchkick index_name: "products_v2"
898
+ end
899
+ ```
900
+
901
+ Prefix the index name
902
+
903
+ ```ruby
904
+ class Product < ActiveRecord::Base
905
+ searchkick index_prefix: "datakick"
906
+ end
907
+ ```
908
+
909
+ Turn off callbacks temporarily
910
+
911
+ ```ruby
912
+ Product.disable_search_callbacks # or use Searchkick.disable_callbacks for all models
913
+ ExpensiveProductsTask.execute
914
+ Product.enable_search_callbacks # or use Searchkick.enable_callbacks for all models
915
+ Product.reindex
916
+ ```
917
+
918
+ Change timeout
919
+
920
+ ```ruby
921
+ Searchkick.timeout = 5 # defaults to 10
922
+ ```
923
+
924
+ Change the search method name in `config/initializers/searchkick.rb`
925
+
926
+ ```ruby
927
+ Searchkick.search_method_name = :lookup
928
+ ```
929
+
930
+ Eager load associations
931
+
932
+ ```ruby
933
+ Product.search "milk", include: [:brand, :stores]
934
+ ```
935
+
936
+ Do not load models
937
+
938
+ ```ruby
939
+ Product.search "milk", load: false
940
+ ```
941
+
942
+ Turn off special characters
943
+
944
+ ```ruby
945
+ class Product < ActiveRecord::Base
946
+ # A will not match Ä
947
+ searchkick special_characters: false
948
+ end
949
+ ```
950
+
951
+ Change import batch size
952
+
953
+ ```ruby
954
+ class Product < ActiveRecord::Base
955
+ searchkick batch_size: 200 # defaults to 1000
956
+ end
957
+ ```
958
+
959
+ Create index without importing
960
+
961
+ ```ruby
962
+ Product.reindex(import: false)
963
+ ```
964
+
965
+ Make fields unsearchable but include in the source
966
+
967
+ ```ruby
968
+ class Product < ActiveRecord::Base
969
+ searchkick unsearchable: [:color]
970
+ end
971
+ ```
972
+
973
+ Reindex conditionally
974
+
975
+ **Note:** With ActiveRecord, use this feature with caution - [transaction rollbacks can cause data inconstencies](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-model/README.md#custom-callbacks)
976
+
977
+ ```ruby
978
+ class Product < ActiveRecord::Base
979
+ searchkick callbacks: false
980
+
981
+ # add the callbacks manually
982
+ after_save :reindex, if: proc{|model| model.name_changed? } # use your own condition
983
+ after_destroy :reindex
984
+ end
985
+ ```
986
+
987
+ Reindex all models - Rails only
988
+
989
+ ```sh
990
+ rake searchkick:reindex:all
991
+ ```
992
+
993
+ ## Large Data Sets
994
+
995
+ For large data sets, check out [Keeping Elasticsearch in Sync](https://www.found.no/foundation/keeping-elasticsearch-in-sync/). Searchkick will make this easy in the future.
996
+
997
+ ## Testing
998
+
999
+ This section could use some love.
1000
+
1001
+ ### RSpec
1002
+
1003
+ ```ruby
1004
+ describe Product do
1005
+ it "searches" do
1006
+ Product.reindex
1007
+ Product.searchkick_index.refresh # don't forget this
1008
+ # test goes here...
1009
+ end
1010
+ end
1011
+ ```
1012
+
1013
+ ### Factory Girl
1014
+
1015
+ ```ruby
1016
+ product = FactoryGirl.create(:product)
1017
+ product.reindex # don't forget this
1018
+ Product.searchkick_index.refresh # or this
1019
+ ```
1020
+
1021
+ ## Migrating from Tire
1022
+
1023
+ 1. Change `search` methods to `tire.search` and add index name in existing search calls
1024
+
1025
+ ```ruby
1026
+ Product.search "fruit"
1027
+ ```
1028
+
1029
+ should be replaced with
1030
+
1031
+ ```ruby
1032
+ Product.tire.search "fruit", index: "products"
1033
+ ```
1034
+
1035
+ 2. Replace tire mapping w/ searchkick method
1036
+
1037
+ ```ruby
1038
+ class Product < ActiveRecord::Base
1039
+ searchkick
1040
+ end
1041
+ ```
1042
+
1043
+ 3. Deploy and reindex
1044
+
1045
+ ```ruby
1046
+ rake searchkick:reindex CLASS=Product # or Product.reindex in the console
1047
+ ```
1048
+
1049
+ 4. Once it finishes, replace search calls w/ searchkick calls
1050
+
1051
+ ## Upgrading
1052
+
1053
+ View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.md).
1054
+
1055
+ Important notes are listed below.
1056
+
1057
+ ### 0.6.0 and 0.7.0
1058
+
1059
+ If running Searchkick `0.6.0` or `0.7.0` and Elasticsearch `0.90`, we recommend upgrading to Searchkick `0.6.1` or `0.7.1` to fix an issue that causes downtime when reindexing.
1060
+
1061
+ ### 0.3.0
1062
+
1063
+ Before `0.3.0`, locations were indexed incorrectly. When upgrading, be sure to reindex immediately.
1064
+
1065
+ ## Elasticsearch Gotchas
1066
+
1067
+ ### Inconsistent Scores
1068
+
1069
+ Due to the distributed nature of Elasticsearch, you can get incorrect results when the number of documents in the index is low. You can [read more about it here](http://www.elasticsearch.org/blog/understanding-query-then-fetch-vs-dfs-query-then-fetch/). To fix this, do:
1070
+
1071
+ ```ruby
1072
+ class Product < ActiveRecord::Base
1073
+ searchkick settings: {number_of_shards: 1}
1074
+ end
1075
+ ```
1076
+
1077
+ For convenience, this is set by default in the test environment.
1078
+
1079
+ ## Thanks
1080
+
1081
+ Thanks to Karel Minarik for [Elasticsearch Ruby](https://github.com/elasticsearch/elasticsearch-ruby) and [Tire](https://github.com/karmi/tire), Jaroslav Kalistsuk for [zero downtime reindexing](https://gist.github.com/jarosan/3124884), and Alex Leschenko for [Elasticsearch autocomplete](https://github.com/leschenko/elasticsearch_autocomplete).
1082
+
1083
+ ## Roadmap
1084
+
1085
+ - More features for large data sets
1086
+ - Improve section on testing
1087
+ - Semantic search features
1088
+ - Search multiple fields for different terms
1089
+ - Search across models
1090
+ - Search nested objects
1091
+ - Much finer customization
1092
+
1093
+ ## Contributing
1094
+
1095
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
1096
+
1097
+ - [Report bugs](https://github.com/ankane/searchkick/issues)
1098
+ - Fix bugs and [submit pull requests](https://github.com/ankane/searchkick/pulls)
1099
+ - Write, clarify, or fix documentation
1100
+ - Suggest or add new features
1101
+
1102
+ To get started with development and testing:
1103
+
1104
+ ```sh
1105
+ git clone https://github.com/ankane/searchkick.git
1106
+ cd searchkick
1107
+ bundle install
1108
+ rake test
1109
+ ```