searchkick 0.9.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -9
  3. data/CHANGELOG.md +14 -0
  4. data/README.md +190 -14
  5. data/lib/searchkick.rb +1 -0
  6. data/lib/searchkick/index.rb +8 -8
  7. data/lib/searchkick/logging.rb +4 -2
  8. data/lib/searchkick/model.rb +13 -10
  9. data/lib/searchkick/query.rb +107 -39
  10. data/lib/searchkick/reindex_job.rb +0 -2
  11. data/lib/searchkick/reindex_v2_job.rb +0 -1
  12. data/lib/searchkick/results.rb +18 -1
  13. data/lib/searchkick/tasks.rb +0 -2
  14. data/lib/searchkick/version.rb +1 -1
  15. data/test/aggs_test.rb +102 -0
  16. data/test/autocomplete_test.rb +1 -3
  17. data/test/boost_test.rb +1 -3
  18. data/{ci → test/ci}/before_install.sh +4 -3
  19. data/test/facets_test.rb +4 -6
  20. data/{gemfiles → test/gemfiles}/activerecord31.gemfile +1 -1
  21. data/{gemfiles → test/gemfiles}/activerecord32.gemfile +1 -1
  22. data/{gemfiles → test/gemfiles}/activerecord40.gemfile +1 -1
  23. data/{gemfiles → test/gemfiles}/activerecord41.gemfile +1 -1
  24. data/{gemfiles → test/gemfiles}/mongoid2.gemfile +1 -1
  25. data/{gemfiles → test/gemfiles}/mongoid3.gemfile +1 -1
  26. data/{gemfiles → test/gemfiles}/mongoid4.gemfile +1 -1
  27. data/test/gemfiles/mongoid5.gemfile +7 -0
  28. data/{gemfiles → test/gemfiles}/nobrainer.gemfile +1 -1
  29. data/test/highlight_test.rb +2 -4
  30. data/test/index_test.rb +20 -9
  31. data/test/inheritance_test.rb +1 -3
  32. data/test/match_test.rb +8 -7
  33. data/test/model_test.rb +2 -4
  34. data/test/query_test.rb +1 -3
  35. data/test/records_test.rb +0 -2
  36. data/test/reindex_job_test.rb +1 -3
  37. data/test/reindex_v2_job_test.rb +1 -3
  38. data/test/routing_test.rb +4 -3
  39. data/test/should_index_test.rb +1 -3
  40. data/test/similar_test.rb +1 -3
  41. data/test/sql_test.rb +7 -9
  42. data/test/suggest_test.rb +1 -3
  43. data/test/synonyms_test.rb +1 -3
  44. data/test/test_helper.rb +23 -10
  45. metadata +24 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0227952cfe570c64d03eacd4db89689ec4a0b2d5
4
- data.tar.gz: 14d2061a47667de91432894f8ef220b1a73bec3c
3
+ metadata.gz: 49cbfbcd21df174e93c80f8960ed0771182c0bd6
4
+ data.tar.gz: 7f02c54a57a145744797000aae4d48c6c3fd5219
5
5
  SHA512:
6
- metadata.gz: d9e12c3156a856a53d7b5157f40b167dce9ff05949f5312f34100222b6769801ab602baba3ca10aec2e80db21e2ae0410229120d865f0682a7167827b4bb7b82
7
- data.tar.gz: cbfbbaca6c443d5f319a97342d1969f85d94749b5662f86b3da557d310a9bd6ed02fc1d7af54e8f2ca0be703bf35c8bb49164bf4d13195e348c221708e8e9793
6
+ metadata.gz: 322e05f51de0e35bc8636f92838ae37ac482c518fbe9163e4a1c91b1f60e607e56e1c9e0fc1271ffd83ef082d77c23f316b5dd9494ca12899696fba9cd03e60b
7
+ data.tar.gz: 387c81af6e19c8faeaa0fe2904d41b13f375ab970bff47b15814b1e874a149dabd0f9a48928773f6bddc94e4c21429f3cf89a1f4be5b3c3a4445702109d476f9
@@ -4,7 +4,7 @@ services:
4
4
  - elasticsearch
5
5
  - mongodb
6
6
  before_install:
7
- - ./ci/before_install.sh
7
+ - ./test/ci/before_install.sh
8
8
  script: bundle exec rake test
9
9
  before_script:
10
10
  - psql -c 'create database searchkick_test;' -U postgres
@@ -14,14 +14,15 @@ notifications:
14
14
  on_failure: change
15
15
  gemfile:
16
16
  - Gemfile
17
- - gemfiles/activerecord41.gemfile
18
- - gemfiles/activerecord40.gemfile
19
- - gemfiles/activerecord32.gemfile
20
- - gemfiles/activerecord31.gemfile
21
- - gemfiles/mongoid2.gemfile
22
- - gemfiles/mongoid3.gemfile
23
- - gemfiles/mongoid4.gemfile
17
+ - test/gemfiles/activerecord41.gemfile
18
+ - test/gemfiles/activerecord40.gemfile
19
+ - test/gemfiles/activerecord32.gemfile
20
+ - test/gemfiles/activerecord31.gemfile
21
+ - test/gemfiles/mongoid2.gemfile
22
+ - test/gemfiles/mongoid3.gemfile
23
+ - test/gemfiles/mongoid4.gemfile
24
+ - test/gemfiles/mongoid5.gemfile
24
25
  matrix:
25
26
  include:
26
- - gemfile: gemfiles/nobrainer.gemfile
27
+ - gemfile: test/gemfiles/nobrainer.gemfile
27
28
  env: NOBRAINER=true
@@ -1,3 +1,17 @@
1
+ ## 1.0.0
2
+
3
+ - Added support for Elasticsearch 2.0
4
+ - Added support for aggregations
5
+ - Added ability to use misspellings for partial matches
6
+ - Added `fragment_size` option for highlight
7
+ - Added `took` method to results
8
+
9
+ Breaking changes
10
+
11
+ - Raise `Searchkick::DangerousOperation` error when calling reindex with scope
12
+ - Enabled misspellings by default for partial matches
13
+ - Enabled transpositions by default for misspellings
14
+
1
15
  ## 0.9.1
2
16
 
3
17
  - `and` now matches `&`
data/README.md CHANGED
@@ -21,6 +21,8 @@ Plus:
21
21
  - “Did you mean” suggestions
22
22
  - works with ActiveRecord, Mongoid, and NoBrainer
23
23
 
24
+ **Searchkick 1.0 was just released!** See [instructions for upgrading](#100)
25
+
24
26
  :speech_balloon: Get [handcrafted updates](http://chartkick.us7.list-manage.com/subscribe?u=952c861f99eb43084e0a49f98&id=6ea6541e8e&group[0][4]=true) for new features
25
27
 
26
28
  :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
@@ -45,7 +47,7 @@ Add this line to your application’s Gemfile:
45
47
  gem 'searchkick'
46
48
  ```
47
49
 
48
- For Elasticsearch 0.90, use version `0.6.3` and [this readme](https://github.com/ankane/searchkick/blob/v0.6.3/README.md).
50
+ For Elasticsearch 2.0, use the version `1.0` and above. For Elasticsearch 0.90, use version `0.6.3` and [this readme](https://github.com/ankane/searchkick/blob/v0.6.3/README.md).
49
51
 
50
52
  Add searchkick to models you want to search.
51
53
 
@@ -117,6 +119,35 @@ Limit / offset
117
119
  limit: 20, offset: 40
118
120
  ```
119
121
 
122
+ ### Results
123
+
124
+ Searches return a `Searchkick::Results` object. This responds like an array to most methods.
125
+
126
+ ```ruby
127
+ results = Product.search("milk")
128
+ results.size
129
+ results.any?
130
+ results.each { ... }
131
+ ```
132
+
133
+ Get total results
134
+
135
+ ```ruby
136
+ results.total_count
137
+ ```
138
+
139
+ Get the time the search took (in milliseconds)
140
+
141
+ ```ruby
142
+ results.took
143
+ ```
144
+
145
+ Get the full response from Elasticsearch
146
+
147
+ ```ruby
148
+ results.response
149
+ ```
150
+
120
151
  ### Boosting
121
152
 
122
153
  Boost important fields
@@ -229,17 +260,19 @@ Searchkick defaults to English for stemming. To change this, use:
229
260
 
230
261
  ```ruby
231
262
  class Product < ActiveRecord::Base
232
- searchkick language: "German"
263
+ searchkick stemmer: "german"
233
264
  end
234
265
  ```
235
266
 
236
- [See the list of languages](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/analysis-snowball-tokenfilter.html)
267
+ [See the list of stemmers](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stemmer-tokenfilter.html)
237
268
 
238
269
  ### Synonyms
239
270
 
240
271
  ```ruby
241
272
  class Product < ActiveRecord::Base
242
273
  searchkick synonyms: [["scallion", "green onion"], ["qtip", "cotton swab"]]
274
+ # or
275
+ # searchkick synonyms: -> { CSV.read("/some/path/synonyms.csv") }
243
276
  end
244
277
  ```
245
278
 
@@ -282,14 +315,6 @@ Or turn off misspellings with:
282
315
  Product.search "zuchini", misspellings: false # no zucchini
283
316
  ```
284
317
 
285
- Swapping two letters counts as two edits. To count the [transposition of two adjacent characters as a single edit](https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance), use:
286
-
287
- ```ruby
288
- Product.search "mikl", misspellings: {transpositions: true} # milk
289
- ```
290
-
291
- This is planned to be the default in Searchkick 1.0.
292
-
293
318
  ### Indexing
294
319
 
295
320
  Control what data is indexed with the `search_data` method. Call `Product.reindex` after changing this method.
@@ -516,11 +541,110 @@ products = Product.search "peantu butta", suggest: true
516
541
  products.suggestions # ["peanut butter"]
517
542
  ```
518
543
 
519
- ### Facets
544
+ ### Aggregations
545
+
546
+ [Aggregations](http://www.elasticsearch.org/guide/reference/api/search/facets/) provide aggregated search data.
547
+
548
+ ![Aggregations](http://ankane.github.io/searchkick/facets.png)
549
+
550
+ ```ruby
551
+ products = Product.search "chuck taylor", aggs: [:product_type, :gender, :brand]
552
+ products.aggs
553
+ ```
554
+
555
+ By default, `where` conditions apply to aggregations.
556
+
557
+ ```ruby
558
+ Product.search "wingtips", where: {color: "brandy"}, aggs: [:size]
559
+ # aggregations for brandy wingtips are returned
560
+ ```
561
+
562
+ Change this with:
563
+
564
+ ```ruby
565
+ Product.search "wingtips", where: {color: "brandy"}, aggs: [:size], smart_aggs: false
566
+ # aggregations for all wingtips are returned
567
+ ```
568
+
569
+ Set `where` conditions for each aggregation separately with:
570
+
571
+ ```ruby
572
+ Product.search "wingtips", aggs: {size: {where: {color: "brandy"}}}
573
+ ```
574
+
575
+ Limit
576
+
577
+ ```ruby
578
+ Product.search "apples", aggs: {store_id: {limit: 10}}
579
+ ```
580
+
581
+ Ranges
582
+
583
+ ```ruby
584
+ price_ranges = [{to: 20}, {from: 20, to: 50}, {from: 50}]
585
+ Product.search "*", aggs: {price: {ranges: price_ranges}}
586
+ ```
587
+
588
+ #### Moving From Facets
589
+
590
+ 1. Replace `facets` with `aggs` in searches. **Note:** Stats facets are not supported at this time.
520
591
 
521
- [Facets](http://www.elasticsearch.org/guide/reference/api/search/facets/) provide aggregated search data.
592
+ ```ruby
593
+ products = Product.search "chuck taylor", facets: [:brand]
594
+ # to
595
+ products = Product.search "chuck taylor", aggs: [:brand]
596
+ ```
597
+
598
+ 2. Replace the `facets` method with `aggs` for results.
599
+
600
+ ```ruby
601
+ products.facets
602
+ # to
603
+ products.aggs
604
+ ```
605
+
606
+ The keys in results differ slightly. Instead of:
607
+
608
+ ```json
609
+ {
610
+ "_type":"terms",
611
+ "missing":0,
612
+ "total":45,
613
+ "other":34,
614
+ "terms":[
615
+ {"term":14.0,"count":11}
616
+ ]
617
+ }
618
+ ```
619
+
620
+ You get:
621
+
622
+ ```json
623
+ {
624
+ "doc_count":45,
625
+ "doc_count_error_upper_bound":0,
626
+ "sum_other_doc_count":34,
627
+ "buckets":[
628
+ {"key":14.0,"doc_count":11}
629
+ ]
630
+ }
631
+ ```
632
+
633
+ Update your application to handle this.
522
634
 
523
- ![Facets](http://ankane.github.io/searchkick/facets.png)
635
+ 3. By default, `where` conditions apply to aggregations. This is equivalent to `smart_facets: true`. If you have `smart_facets: true`, you can remove it. If this is not desired, set `smart_aggs: false`.
636
+
637
+ 4. If you have any range facets with dates, change the key from `ranges` to `date_ranges`.
638
+
639
+ ```ruby
640
+ facets: {date_field: {ranges: date_ranges}}
641
+ # to
642
+ aggs: {date_field: {date_ranges: date_ranges}}
643
+ ```
644
+
645
+ ### Facets [deprecated]
646
+
647
+ Facets have been deprecated in favor of aggregations as of Searchkick 0.9.2. See [how to upgrade](#moving-from-facets).
524
648
 
525
649
  ```ruby
526
650
  products = Product.search "chuck taylor", facets: [:product_type, :gender, :brand]
@@ -662,6 +786,8 @@ City.search "san", boost_by_distance: {field: :location, origin: [37, -122], fun
662
786
 
663
787
  Searchkick supports [Elasticsearch’s routing feature](https://www.elastic.co/blog/customizing-your-document-routing).
664
788
 
789
+ **Note:** Routing is not yet supported for Elasticsearch 2.0.
790
+
665
791
  ```ruby
666
792
  class Contact < ActiveRecord::Base
667
793
  searchkick routing: :user_id
@@ -706,6 +832,12 @@ Dog.search "airbudd", suggest: true # suggestions for all animals
706
832
 
707
833
  ## Debugging Queries
708
834
 
835
+ See how Elasticsearch scores your queries with:
836
+
837
+ ```ruby
838
+ Product.search("soap", explain: true).response
839
+ ```
840
+
709
841
  See how Elasticsearch tokenizes your queries with:
710
842
 
711
843
  ```ruby
@@ -759,6 +891,28 @@ Then deploy and reindex:
759
891
  heroku run rake searchkick:reindex CLASS=Product
760
892
  ```
761
893
 
894
+ ### Amazon Elasticsearch Service
895
+
896
+ You must use an [IP-based access policy](http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg-search.html) for Searchkick to work.
897
+
898
+ Include `elasticsearch 1.0.14` or greater in your Gemfile.
899
+
900
+ ```ruby
901
+ gem "elasticsearch", ">= 1.0.14"
902
+ ```
903
+
904
+ Create an initializer `config/initializers/elasticsearch.rb` with:
905
+
906
+ ```ruby
907
+ ENV["ELASTICSEARCH_URL"] = "http://es-domain-1234.us-east-1.es.amazonaws.com"
908
+ ```
909
+
910
+ Then deploy and reindex:
911
+
912
+ ```sh
913
+ rake searchkick:reindex CLASS=Product
914
+ ```
915
+
762
916
  ### Other
763
917
 
764
918
  Create an initializer `config/initializers/elasticsearch.rb` with:
@@ -1085,6 +1239,28 @@ View the [changelog](https://github.com/ankane/searchkick/blob/master/CHANGELOG.
1085
1239
 
1086
1240
  Important notes are listed below.
1087
1241
 
1242
+ ### 1.0.0
1243
+
1244
+ - Added support for Elasticsearch 2.0
1245
+ - Facets are deprecated in favor of [aggregations](#aggregations) - see [how to upgrade](#moving-from-facets)
1246
+
1247
+ #### Breaking Changes
1248
+
1249
+ - **ActiveRecord 4.1+ and Mongoid 3+:** Attempting to reindex with a scope now throws a `Searchkick::DangerousOperation` error to keep your from accidentally recreating your index with only a few records.
1250
+
1251
+ ```ruby
1252
+ Product.where(color: "brandy").reindex # error!
1253
+ ```
1254
+
1255
+ If this is what you intend to do, use:
1256
+
1257
+ ```ruby
1258
+ Product.where(color: "brandy").reindex(accept_danger: true)
1259
+ ```
1260
+
1261
+ - Misspellings are enabled by default for [partial matches](#partial-matches). Use `misspellings: false` to disable.
1262
+ - [Transpositions](https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance) are enabled by default for misspellings. Use `misspellings: {transpositions: false}` to disable.
1263
+
1088
1264
  ### 0.6.0 and 0.7.0
1089
1265
 
1090
1266
  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.
@@ -22,6 +22,7 @@ module Searchkick
22
22
  class MissingIndexError < StandardError; end
23
23
  class UnsupportedVersionError < StandardError; end
24
24
  class InvalidQueryError < Elasticsearch::Transport::Transport::Errors::BadRequest; end
25
+ class DangerousOperation < StandardError; end
25
26
 
26
27
  class << self
27
28
  attr_accessor :search_method_name
@@ -100,7 +100,7 @@ module Searchkick
100
100
 
101
101
  def similar_record(record, options = {})
102
102
  like_text = retrieve(record).to_hash
103
- .keep_if { |k, v| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
103
+ .keep_if { |k, _| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
104
104
  .values.compact.join(" ")
105
105
 
106
106
  # TODO deep merge method
@@ -118,9 +118,7 @@ module Searchkick
118
118
 
119
119
  def search_model(searchkick_klass, term = nil, options = {}, &block)
120
120
  query = Searchkick::Query.new(searchkick_klass, term, options)
121
- if block
122
- block.call(query.body)
123
- end
121
+ block.call(query.body) if block
124
122
  if options[:execute] == false
125
123
  query
126
124
  else
@@ -349,6 +347,9 @@ module Searchkick
349
347
 
350
348
  # synonyms
351
349
  synonyms = options[:synonyms] || []
350
+
351
+ synonyms = synonyms.call if synonyms.respond_to?(:call)
352
+
352
353
  if synonyms.any?
353
354
  settings[:analysis][:filter][:searchkick_synonym] = {
354
355
  type: "synonym",
@@ -379,7 +380,7 @@ module Searchkick
379
380
  end
380
381
 
381
382
  if options[:special_characters] == false
382
- settings[:analysis][:analyzer].each do |analyzer, analyzer_settings|
383
+ settings[:analysis][:analyzer].each do |_, analyzer_settings|
383
384
  analyzer_settings[:filter].reject! { |f| f == "asciifolding" }
384
385
  end
385
386
  end
@@ -387,8 +388,8 @@ module Searchkick
387
388
  mapping = {}
388
389
 
389
390
  # conversions
390
- if options[:conversions]
391
- mapping[:conversions] = {
391
+ if (conversions_field = options[:conversions])
392
+ mapping[conversions_field] = {
392
393
  type: "nested",
393
394
  properties: {
394
395
  query: {type: "string", analyzer: "searchkick_keyword"},
@@ -561,6 +562,5 @@ module Searchkick
561
562
  obj
562
563
  end
563
564
  end
564
-
565
565
  end
566
566
  end
@@ -62,7 +62,8 @@ module Searchkick
62
62
  end
63
63
 
64
64
  def self.reset_runtime
65
- rt, self.runtime = runtime, 0
65
+ rt = runtime
66
+ self.runtime = 0
66
67
  rt
67
68
  end
68
69
 
@@ -122,7 +123,8 @@ module Searchkick
122
123
 
123
124
  module ClassMethods
124
125
  def log_process_action(payload)
125
- messages, runtime = super, payload[:searchkick_runtime]
126
+ messages = super
127
+ runtime = payload[:searchkick_runtime]
126
128
  messages << ("Searchkick: %.1fms" % runtime.to_f) if runtime.to_f > 0
127
129
  messages
128
130
  end
@@ -2,7 +2,6 @@ module Searchkick
2
2
  module Reindex; end # legacy for Searchjoy
3
3
 
4
4
  module Model
5
-
6
5
  def searchkick(options = {})
7
6
  raise "Only call searchkick once per model" if respond_to?(:searchkick_index)
8
7
 
@@ -18,12 +17,11 @@ module Searchkick
18
17
  class_variable_set :@@searchkick_callbacks, callbacks
19
18
  class_variable_set :@@searchkick_index, options[:index_name] || [options[:index_prefix], model_name.plural, Searchkick.env].compact.join("_")
20
19
 
21
- define_singleton_method(Searchkick.search_method_name) do |term = nil, options = {}, &block|
22
- searchkick_index.search_model(self, term, options, &block)
23
- end
24
- extend Searchkick::Reindex # legacy for Searchjoy
25
-
26
20
  class << self
21
+ def searchkick_search(term = nil, options = {}, &block)
22
+ searchkick_index.search_model(self, term, options, &block)
23
+ end
24
+ alias_method Searchkick.search_method_name, :searchkick_search
27
25
 
28
26
  def searchkick_index
29
27
  index = class_variable_get :@@searchkick_index
@@ -43,9 +41,16 @@ module Searchkick
43
41
  class_variable_get(:@@searchkick_callbacks) && Searchkick.callbacks?
44
42
  end
45
43
 
46
- def reindex(options = {})
44
+ def searchkick_reindex(options = {})
45
+ unless options[:accept_danger]
46
+ if (respond_to?(:current_scope) && respond_to?(:default_scoped) && current_scope && current_scope.to_sql != default_scoped.to_sql) ||
47
+ (respond_to?(:queryable) && queryable != unscoped.with_default_scope)
48
+ raise Searchkick::DangerousOperation, "Only call reindex on models, not relations. Pass `accept_danger: true` if this is your intention."
49
+ end
50
+ end
47
51
  searchkick_index.reindex_scope(searchkick_klass, options)
48
52
  end
53
+ alias_method :reindex, :searchkick_reindex unless method_defined?(:reindex)
49
54
 
50
55
  def clean_indices
51
56
  searchkick_index.clean_indices
@@ -62,8 +67,8 @@ module Searchkick
62
67
  def searchkick_index_options
63
68
  searchkick_index.index_options
64
69
  end
65
-
66
70
  end
71
+ extend Searchkick::Reindex # legacy for Searchjoy
67
72
 
68
73
  if callbacks
69
74
  callback_name = callbacks == :async ? :reindex_async : :reindex
@@ -94,9 +99,7 @@ module Searchkick
94
99
  def should_index?
95
100
  true
96
101
  end unless method_defined?(:should_index?)
97
-
98
102
  end
99
103
  end
100
-
101
104
  end
102
105
  end