searchkick 0.9.1 → 1.0.0

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