searchkick 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 755a0e68ea42b18bb5d3ab111bec8d7f95a0bce2
4
- data.tar.gz: 5ed049addff00159cfe9a4c7db03ea81cb9383ca
2
+ SHA256:
3
+ metadata.gz: eb9e19c9a72da12faf603c3fddbdd2fac429058a0774553c0afb874f0a1bc0de
4
+ data.tar.gz: a80890252fd573c07fa1f63b5cdbbaaec7dd1848ad0941e09160e973a0ad8ea2
5
5
  SHA512:
6
- metadata.gz: cab627210801732d119796bce578d42289d808199a1f3a3510343ded21c4513ecad2f2e7259a435fc4c31db13e89995bf659195fa783c5d5747c0c0e554ee76f
7
- data.tar.gz: 4b06af47509a66eafc9f8bc6080e28a72fbd03998c4b7307b4e26cb950a5d52417ace6261a9b974dcf809fb535af75c5789a2e1ae96c45c66dd630dd9e0b76fc
6
+ metadata.gz: 1faf5e1622ec798d6ffca925d7831c55265973d8e58260a7dd8f98b249bd20cb9a3a903c9a12a870dfcd4eb723caf21459a259bd197e84d99771fed96a2d65ed
7
+ data.tar.gz: 796a19163cbc93d9fdbd235d29bbd86beedc3c583ea214c91406b6252f9d778d4f8ee710ae3ad1f1f85d91a7ab30571c3a01567ce8486a042d8e8d4ce3a63635
data/.travis.yml CHANGED
@@ -15,12 +15,13 @@ notifications:
15
15
  on_failure: change
16
16
  gemfile:
17
17
  - Gemfile
18
+ - test/gemfiles/activerecord51.gemfile
18
19
  - test/gemfiles/activerecord50.gemfile
19
20
  - test/gemfiles/activerecord42.gemfile
20
21
  - test/gemfiles/mongoid5.gemfile
21
22
  - test/gemfiles/mongoid6.gemfile
22
23
  env:
23
- - ELASTICSEARCH_VERSION=6.2.2
24
+ - ELASTICSEARCH_VERSION=6.2.3
24
25
  jdk: oraclejdk8
25
26
  matrix:
26
27
  include:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 3.0.3
2
+
3
+ - Added support for pagination with `body` option
4
+ - Added `boost_by_recency` option
5
+ - Fixed "Model Search Data" output for `debug` option
6
+ - Fixed `reindex_status` error
7
+ - Fixed error with optional operators in Ruby regexp
8
+ - Fixed deprecation warnings for Elasticsearch 6.2+
9
+
1
10
  ## 3.0.2
2
11
 
3
12
  - Added support for Korean and Vietnamese
data/README.md CHANGED
@@ -201,7 +201,16 @@ boost_where: {user_id: {value: 1, factor: 100}} # default factor is 1000
201
201
  boost_where: {user_id: [{value: 1, factor: 100}, {value: 2, factor: 200}]}
202
202
  ```
203
203
 
204
- [Conversions](#keep-getting-better) are also a great way to boost.
204
+ Boost by recency
205
+
206
+ ```ruby
207
+ boost_by_recency: {created_at: {scale: "7d", decay: 0.5}}
208
+ ```
209
+
210
+ You can also boost by:
211
+
212
+ - [Conversions](#keep-getting-better)
213
+ - [Distance](#boost-by-distance)
205
214
 
206
215
  ### Get Everything
207
216
 
data/lib/searchkick.rb CHANGED
@@ -95,10 +95,11 @@ module Searchkick
95
95
  end
96
96
 
97
97
  def self.server_below?(version)
98
- Gem::Version.new(server_version.sub("-", ".")) < Gem::Version.new(version.sub("-", "."))
98
+ Gem::Version.new(server_version.split("-")[0]) < Gem::Version.new(version.split("-")[0])
99
99
  end
100
100
 
101
101
  def self.search(term = "*", model: nil, **options, &block)
102
+ options = options.dup
102
103
  klass = model
103
104
 
104
105
  # make Searchkick.search(index_name: [Product]) and Product.search equivalent
@@ -106,6 +107,7 @@ module Searchkick
106
107
  index_name = Array(options[:index_name])
107
108
  if index_name.size == 1 && index_name.first.respond_to?(:searchkick_index)
108
109
  klass = index_name.first
110
+ options.delete(:index_name)
109
111
  end
110
112
  end
111
113
 
@@ -51,6 +51,10 @@ module Searchkick
51
51
  Searchkick.indexer.queue(records.map { |r| RecordData.new(index, r).update_data(method_name) })
52
52
  end
53
53
 
54
+ def batches_left
55
+ Searchkick.with_redis { |r| r.scard(batches_key) }
56
+ end
57
+
54
58
  private
55
59
 
56
60
  def import_or_update(records, method_name, async)
@@ -134,12 +138,12 @@ module Searchkick
134
138
  end
135
139
 
136
140
  def bulk_reindex_job(scope, batch_id, options)
141
+ Searchkick.with_redis { |r| r.sadd(batches_key, batch_id) }
137
142
  Searchkick::BulkReindexJob.perform_later({
138
143
  class_name: scope.model_name.name,
139
144
  index_name: index.name,
140
145
  batch_id: batch_id
141
146
  }.merge(options))
142
- Searchkick.with_redis { |r| r.sadd(batches_key, batch_id) }
143
147
  end
144
148
 
145
149
  def with_retries
@@ -156,10 +160,6 @@ module Searchkick
156
160
  end
157
161
  end
158
162
 
159
- def batches_left
160
- Searchkick.with_redis { |r| r.scard(batches_key) }
161
- end
162
-
163
163
  def batches_key
164
164
  "searchkick:reindex:#{index.name}:batches"
165
165
  end
@@ -17,7 +17,7 @@ module Searchkick
17
17
  end
18
18
 
19
19
  def delete
20
- if !Searchkick.server_below?("6.0.0-alpha1") && alias_exists?
20
+ if !Searchkick.server_below?("6.0.0") && alias_exists?
21
21
  # can't call delete directly on aliases in ES 6
22
22
  indices = client.indices.get_alias(name: name).keys
23
23
  client.indices.delete index: indices
@@ -11,7 +11,9 @@ module Searchkick
11
11
  settings = options[:settings] || {}
12
12
  mappings = options[:mappings]
13
13
  else
14
- below60 = Searchkick.server_below?("6.0.0-alpha1")
14
+ below60 = Searchkick.server_below?("6.0.0")
15
+ below62 = Searchkick.server_below?("6.2.0")
16
+
15
17
  default_type = "text"
16
18
  default_analyzer = :searchkick_index
17
19
  keyword_mapping = {type: "keyword"}
@@ -216,6 +218,13 @@ module Searchkick
216
218
  settings[:similarity] = {default: {type: options[:similarity]}}
217
219
  end
218
220
 
221
+ unless below62
222
+ settings[:index] = {
223
+ max_ngram_diff: 49,
224
+ max_shingle_diff: 4
225
+ }
226
+ end
227
+
219
228
  settings.deep_merge!(options[:settings] || {})
220
229
 
221
230
  # synonyms
@@ -16,7 +16,7 @@ module Searchkick
16
16
 
17
17
  def initialize(klass, term = "*", **options)
18
18
  unknown_keywords = options.keys - [:aggs, :body, :body_options, :boost,
19
- :boost_by, :boost_by_distance, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :execute, :explain,
19
+ :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :debug, :emoji, :exclude, :execute, :explain,
20
20
  :fields, :highlight, :includes, :index_name, :indices_boost, :limit, :load,
21
21
  :match, :misspellings, :model_includes, :offset, :operator, :order, :padding, :page, :per_page, :profile,
22
22
  :request_params, :routing, :scope_results, :select, :similar, :smart_aggs, :suggest, :track, :type, :where]
@@ -114,7 +114,8 @@ module Searchkick
114
114
  highlighted_fields: @highlighted_fields || [],
115
115
  misspellings: @misspellings,
116
116
  term: term,
117
- scope_results: options[:scope_results]
117
+ scope_results: options[:scope_results],
118
+ index_name: options[:index_name]
118
119
  }
119
120
 
120
121
  if options[:debug]
@@ -135,7 +136,7 @@ module Searchkick
135
136
  if searchkick_index
136
137
  puts "Model Search Data"
137
138
  begin
138
- pp(klass.first(3).map { |r| {index: searchkick_index.record_data(r).merge(data: searchkick_index.send(:search_data, r))}})
139
+ pp klass.limit(3).map { |r| RecordData.new(searchkick_index, r).index_data }
139
140
  rescue => e
140
141
  puts "#{e.class.name}: #{e.message}"
141
142
  end
@@ -224,9 +225,9 @@ module Searchkick
224
225
  @json = options[:body]
225
226
  if @json
226
227
  ignored_options = options.keys & [:aggs, :boost,
227
- :boost_by, :boost_by_distance, :boost_where, :conversions, :conversions_term, :exclude, :explain,
228
- :fields, :highlight, :indices_boost, :limit, :match, :misspellings, :offset, :operator, :order,
229
- :padding, :page, :per_page, :profile, :select, :smart_aggs, :suggest, :where]
228
+ :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :exclude, :explain,
229
+ :fields, :highlight, :indices_boost, :match, :misspellings, :operator, :order,
230
+ :profile, :select, :smart_aggs, :suggest, :where]
230
231
  raise ArgumentError, "Options incompatible with body option: #{ignored_options.join(", ")}" if ignored_options.any?
231
232
  payload = @json
232
233
  else
@@ -381,10 +382,7 @@ module Searchkick
381
382
  query = payload
382
383
  end
383
384
 
384
- payload = {
385
- size: per_page,
386
- from: offset
387
- }
385
+ payload = {}
388
386
 
389
387
  # type when inheritance
390
388
  where = (options[:where] || {}).dup
@@ -409,6 +407,7 @@ module Searchkick
409
407
  set_boost_by(multiply_filters, custom_filters)
410
408
  set_boost_where(custom_filters)
411
409
  set_boost_by_distance(custom_filters) if options[:boost_by_distance]
410
+ set_boost_by_recency(custom_filters) if options[:boost_by_recency]
412
411
 
413
412
  payload[:query] = build_query(query, filters, should, must_not, custom_filters, multiply_filters)
414
413
 
@@ -444,6 +443,13 @@ module Searchkick
444
443
  end
445
444
  end
446
445
 
446
+ # pagination
447
+ pagination_options = options[:page] || options[:limit] || options[:per_page] || options[:offset] || options[:padding]
448
+ if !options[:body] || pagination_options
449
+ payload[:size] = per_page
450
+ payload[:from] = offset
451
+ end
452
+
447
453
  # type
448
454
  if !searchkick_options[:inheritance] && (options[:type] || (klass != searchkick_klass && searchkick_index))
449
455
  @type = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v) }
@@ -588,6 +594,19 @@ module Searchkick
588
594
  end
589
595
  end
590
596
 
597
+ def set_boost_by_recency(custom_filters)
598
+ options[:boost_by_recency].each do |field, attributes|
599
+ attributes = {function: :gauss, origin: Time.now}.merge(attributes)
600
+
601
+ custom_filters << {
602
+ weight: attributes[:factor] || 1,
603
+ attributes[:function] => {
604
+ field => attributes.except(:factor, :function)
605
+ }
606
+ }
607
+ end
608
+ end
609
+
591
610
  def set_boost_by(multiply_filters, custom_filters)
592
611
  boost_by = options[:boost_by] || {}
593
612
  if boost_by.is_a?(Array)
@@ -900,7 +919,7 @@ module Searchkick
900
919
  elsif value.nil?
901
920
  {bool: {must_not: {exists: {field: field}}}}
902
921
  elsif value.is_a?(Regexp)
903
- {regexp: {field => {value: value.source}}}
922
+ {regexp: {field => {value: value.source, flags: "NONE"}}}
904
923
  else
905
924
  {term: {field => value}}
906
925
  end
@@ -963,11 +982,11 @@ module Searchkick
963
982
  end
964
983
 
965
984
  def below60?
966
- Searchkick.server_below?("6.0.0-alpha1")
985
+ Searchkick.server_below?("6.0.0")
967
986
  end
968
987
 
969
988
  def below61?
970
- Searchkick.server_below?("6.1.0-alpha1")
989
+ Searchkick.server_below?("6.1.0")
971
990
  end
972
991
  end
973
992
  end
@@ -22,7 +22,8 @@ module Searchkick
22
22
  results = {}
23
23
 
24
24
  hits.group_by { |hit, _| hit["_type"] }.each do |type, grouped_hits|
25
- results[type] = results_query(type.camelize.constantize, grouped_hits).to_a.index_by { |r| r.id.to_s }
25
+ klass = (!options[:index_name] && @klass) || type.camelize.constantize
26
+ results[type] = results_query(klass, grouped_hits).to_a.index_by { |r| r.id.to_s }
26
27
  end
27
28
 
28
29
  # sort
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "3.0.2"
2
+ VERSION = "3.0.3"
3
3
  end
data/searchkick.gemspec CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features|benchmark)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.required_ruby_version = ">= 2.2.0"
22
+
21
23
  spec.add_dependency "activemodel", ">= 4.2"
22
24
  spec.add_dependency "elasticsearch", ">= 5"
23
25
  spec.add_dependency "hashie"
data/test/boost_test.rb CHANGED
@@ -149,6 +149,24 @@ class BoostTest < Minitest::Test
149
149
  assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], boost_where: {user_ids: [{value: 1, factor: 10}, {value: 3, factor: 20}]}
150
150
  end
151
151
 
152
+ def test_boost_by_recency
153
+ store [
154
+ {name: "Article 1", created_at: 2.days.ago},
155
+ {name: "Article 2", created_at: 1.day.ago},
156
+ {name: "Article 3", created_at: Time.now}
157
+ ]
158
+ assert_order "article", ["Article 3", "Article 2", "Article 1"], boost_by_recency: {created_at: {scale: "7d", decay: 0.5}}
159
+ end
160
+
161
+ def test_boost_by_recency_origin
162
+ store [
163
+ {name: "Article 1", created_at: 2.days.ago},
164
+ {name: "Article 2", created_at: 1.day.ago},
165
+ {name: "Article 3", created_at: Time.now}
166
+ ]
167
+ assert_order "article", ["Article 1", "Article 2", "Article 3"], boost_by_recency: {created_at: {origin: 2.days.ago, scale: "7d", decay: 0.5}}
168
+ end
169
+
152
170
  def test_boost_by_distance
153
171
  store [
154
172
  {name: "San Francisco", latitude: 37.7833, longitude: -122.4167},
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in searchkick.gemspec
4
+ gemspec path: "../../"
5
+
6
+ gem "sqlite3"
7
+ gem "activerecord", "~> 5.1.0"
@@ -43,6 +43,32 @@ class PaginationTest < Minitest::Test
43
43
  assert products.any?
44
44
  end
45
45
 
46
+ def test_pagination_body
47
+ store_names ["Product A", "Product B", "Product C", "Product D", "Product E", "Product F"]
48
+ products = Product.search("product", body: {query: {match_all: {}}, sort: [{name: "asc"}]}, page: 2, per_page: 2, padding: 1)
49
+ assert_equal ["Product D", "Product E"], products.map(&:name)
50
+ assert_equal "product", products.entry_name
51
+ assert_equal 2, products.current_page
52
+ assert_equal 1, products.padding
53
+ assert_equal 2, products.per_page
54
+ assert_equal 2, products.size
55
+ assert_equal 2, products.length
56
+ assert_equal 3, products.total_pages
57
+ assert_equal 6, products.total_count
58
+ assert_equal 6, products.total_entries
59
+ assert_equal 2, products.limit_value
60
+ assert_equal 3, products.offset_value
61
+ assert_equal 3, products.offset
62
+ assert_equal 3, products.next_page
63
+ assert_equal 1, products.previous_page
64
+ assert_equal 1, products.prev_page
65
+ assert !products.first_page?
66
+ assert !products.last_page?
67
+ assert !products.empty?
68
+ assert !products.out_of_range?
69
+ assert products.any?
70
+ end
71
+
46
72
  def test_pagination_nil_page
47
73
  store_names ["Product A", "Product B", "Product C", "Product D", "Product E"]
48
74
  products = Product.search("product", order: {name: :asc}, page: nil, per_page: 2)
data/test/query_test.rb CHANGED
@@ -32,4 +32,12 @@ class QueryTest < Minitest::Test
32
32
  def test_request_params
33
33
  assert_equal "dfs_query_then_fetch", Product.search("*", request_params: {search_type: "dfs_query_then_fetch"}, execute: false).params[:search_type]
34
34
  end
35
+
36
+ def test_debug
37
+ store_names ["Milk"]
38
+ out, _ = capture_io do
39
+ assert_search "milk", ["Milk"], debug: true
40
+ end
41
+ refute_includes out, "Error"
42
+ end
35
43
  end
data/test/reindex_test.rb CHANGED
@@ -28,7 +28,7 @@ class ReindexTest < Minitest::Test
28
28
  end
29
29
 
30
30
  def test_async
31
- skip if !defined?(ActiveJob)
31
+ skip unless defined?(ActiveJob)
32
32
 
33
33
  Searchkick.callbacks(false) do
34
34
  store_names ["Product A"]
@@ -40,12 +40,30 @@ class ReindexTest < Minitest::Test
40
40
  index.refresh
41
41
  assert_equal 1, index.total_docs
42
42
 
43
+ if defined?(Redis)
44
+ assert Searchkick.reindex_status(reindex[:name])
45
+ end
46
+
43
47
  Product.searchkick_index.promote(reindex[:index_name])
44
48
  assert_search "product", ["Product A"]
45
49
  end
46
50
 
51
+ def test_async_wait
52
+ skip unless defined?(ActiveJob) && defined?(Redis)
53
+
54
+ Searchkick.callbacks(false) do
55
+ store_names ["Product A"]
56
+ end
57
+
58
+ capture_io do
59
+ Product.reindex(async: {wait: true})
60
+ end
61
+
62
+ assert_search "product", ["Product A"]
63
+ end
64
+
47
65
  def test_async_non_integer_pk
48
- skip if !defined?(ActiveJob)
66
+ skip unless defined?(ActiveJob)
49
67
 
50
68
  Sku.create(id: SecureRandom.hex, name: "Test")
51
69
  reindex = Sku.reindex(async: true)
data/test/where_test.rb CHANGED
@@ -79,6 +79,11 @@ class WhereTest < Minitest::Test
79
79
  assert_search "*", ["Product A"], where: {name: {regexp: "Pro.+"}}
80
80
  end
81
81
 
82
+ def test_special_regexp
83
+ store_names ["Product <A>", "Item <B>"]
84
+ assert_search "*", ["Product <A>"], where: {name: /Pro.+<.+/}
85
+ end
86
+
82
87
  def test_where_string
83
88
  store [
84
89
  {name: "Product A", color: "RED"}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchkick
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-26 00:00:00.000000000 Z
11
+ date: 2018-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -144,6 +144,7 @@ files:
144
144
  - test/errors_test.rb
145
145
  - test/gemfiles/activerecord42.gemfile
146
146
  - test/gemfiles/activerecord50.gemfile
147
+ - test/gemfiles/activerecord51.gemfile
147
148
  - test/gemfiles/apartment.gemfile
148
149
  - test/gemfiles/cequel.gemfile
149
150
  - test/gemfiles/mongoid5.gemfile
@@ -188,7 +189,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
188
189
  requirements:
189
190
  - - ">="
190
191
  - !ruby/object:Gem::Version
191
- version: '0'
192
+ version: 2.2.0
192
193
  required_rubygems_version: !ruby/object:Gem::Requirement
193
194
  requirements:
194
195
  - - ">="
@@ -196,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
197
  version: '0'
197
198
  requirements: []
198
199
  rubyforge_project:
199
- rubygems_version: 2.6.13
200
+ rubygems_version: 2.7.6
200
201
  signing_key:
201
202
  specification_version: 4
202
203
  summary: Searchkick learns what your users are looking for. As more people search,
@@ -214,6 +215,7 @@ test_files:
214
215
  - test/errors_test.rb
215
216
  - test/gemfiles/activerecord42.gemfile
216
217
  - test/gemfiles/activerecord50.gemfile
218
+ - test/gemfiles/activerecord51.gemfile
217
219
  - test/gemfiles/apartment.gemfile
218
220
  - test/gemfiles/cequel.gemfile
219
221
  - test/gemfiles/mongoid5.gemfile