searchkick-sinneduy 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ ```