searchkick 3.0.2 → 3.0.3

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