searchkick 3.0.0 → 3.0.1

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
2
  SHA1:
3
- metadata.gz: 0114d21ec3a6707c49408e641fa27a5591cc9eca
4
- data.tar.gz: d723af7de81aa5ff40818fcd229029724a0fcda7
3
+ metadata.gz: bf72e82ba04fdb0dad02352ce84d8d7844b9097d
4
+ data.tar.gz: 53739b3ac5968665fb53d73f6966b63e2c9bf3c3
5
5
  SHA512:
6
- metadata.gz: 04b026c1acdd413476a2099f7b0b335c207f286e4aadd9d323f0f5d8f5bc18cd15da2219040830a12f258dba497c9412510815fd2af70682e3579239caeaabba
7
- data.tar.gz: e41240ef1831b869311c588a3a6d87c054607fedd2d3da3d56db4d220ae7c1038a317cb703f220b36f06c2ada8b99f2a34cd8e3b7f7c046d6ad2fb2115004a3c
6
+ metadata.gz: f1e26f41eda3b67dae70f0fe0502f46bef521565bdf69b70d1762430292e941f1fb581f6a485779705afe869f2728d9bd5e18abcd7537de1bcb2307831a84c75
7
+ data.tar.gz: 03c79e33d55fd712d4b75ff9830d00c9fa78acdc65aeb80a78cc23931055ecec0a3e892d239491236ecedc7184c8d37a062a0ece6a45779b1244e9117eed28d9
@@ -1,6 +1,12 @@
1
+ ## 3.0.1
2
+
3
+ - Added `scope` option for partial reindex
4
+ - Added support for Japanese, Polish, and Ukranian
5
+
1
6
  ## 3.0.0
2
7
 
3
8
  - Added support for Chinese
9
+ - No longer requires fields to query for Elasticsearch 6
4
10
  - Results can be marshaled by default (unless using `highlight` option)
5
11
 
6
12
  Breaking changes
data/Gemfile CHANGED
@@ -4,13 +4,13 @@ source "https://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  gem "sqlite3"
7
- gem "activerecord", "~> 5.2.0.rc1"
7
+ gem "activerecord"
8
8
  gem "gemoji-parser"
9
9
  gem "typhoeus"
10
- gem "activejob", "~> 5.2.0.rc1"
10
+ gem "activejob"
11
11
  gem "redis"
12
12
  gem "connection_pool"
13
13
 
14
14
  # kaminari
15
- gem "actionpack", "~> 5.2.0.rc1"
15
+ gem "actionpack"
16
16
  gem "kaminari"
data/README.md CHANGED
@@ -19,6 +19,7 @@ Plus:
19
19
  - easily personalize results for each user
20
20
  - autocomplete
21
21
  - “Did you mean” suggestions
22
+ - supports many languages
22
23
  - works with ActiveRecord, Mongoid, and NoBrainer
23
24
 
24
25
  :speech_balloon: Get [handcrafted updates](http://chartkick.us7.list-manage.com/subscribe?u=952c861f99eb43084e0a49f98&id=6ea6541e8e&group[0][4]=true) for new features
@@ -299,13 +300,12 @@ end
299
300
 
300
301
  [See the list of stemmers](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stemmer-tokenfilter.html)
301
302
 
302
- For Chinese, install the [elasticsearch-analysis-ik plugin](https://github.com/medcl/elasticsearch-analysis-ik) and use:
303
+ A few languages require plugins:
303
304
 
304
- ```ruby
305
- class Product < ApplicationRecord
306
- searchkick language: "chinese"
307
- end
308
- ```
305
+ - `chinese` - [elasticsearch-analysis-ik plugin](https://github.com/medcl/elasticsearch-analysis-ik)
306
+ - `japanese` - [analysis-kuromoji plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/6.2/analysis-kuromoji.html)
307
+ - `polish` - [analysis-stempel plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/6.2/analysis-stempel.html)
308
+ - `ukrainian` - [analysis-ukrainian plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/6.2/analysis-ukrainian.html)
309
309
 
310
310
  ### Synonyms
311
311
 
@@ -1344,7 +1344,7 @@ end
1344
1344
  Create a job to update the cache and reindex records with new conversions.
1345
1345
 
1346
1346
  ```ruby
1347
- class ReindexConversionsJob < ActiveJob::Base
1347
+ class ReindexConversionsJob < ApplicationJob
1348
1348
  def perform(class_name)
1349
1349
  # get records that have a recent conversion
1350
1350
  recently_converted_ids =
@@ -6,31 +6,34 @@ module Searchkick
6
6
  @index = index
7
7
  end
8
8
 
9
- def import_scope(scope, resume: false, method_name: nil, async: false, batch: false, batch_id: nil, full: false)
10
- # use scope for import
11
- scope = scope.search_import if scope.respond_to?(:search_import)
9
+ def import_scope(relation, resume: false, method_name: nil, async: false, batch: false, batch_id: nil, full: false, scope: nil)
10
+ if scope
11
+ relation = relation.send(scope)
12
+ elsif relation.respond_to?(:search_import)
13
+ relation = relation.search_import
14
+ end
12
15
 
13
16
  if batch
14
- import_or_update scope.to_a, method_name, async
17
+ import_or_update relation.to_a, method_name, async
15
18
  Searchkick.with_redis { |r| r.srem(batches_key, batch_id) } if batch_id
16
19
  elsif full && async
17
- full_reindex_async(scope)
18
- elsif scope.respond_to?(:find_in_batches)
20
+ full_reindex_async(relation)
21
+ elsif relation.respond_to?(:find_in_batches)
19
22
  if resume
20
23
  # use total docs instead of max id since there's not a great way
21
24
  # to get the max _id without scripting since it's a string
22
25
 
23
26
  # TODO use primary key and prefix with table name
24
- scope = scope.where("id > ?", total_docs)
27
+ relation = relation.where("id > ?", total_docs)
25
28
  end
26
29
 
27
- scope = scope.select("id").except(:includes, :preload) if async
30
+ relation = relation.select("id").except(:includes, :preload) if async
28
31
 
29
- scope.find_in_batches batch_size: batch_size do |items|
32
+ relation.find_in_batches batch_size: batch_size do |items|
30
33
  import_or_update items, method_name, async
31
34
  end
32
35
  else
33
- each_batch(scope) do |items|
36
+ each_batch(relation) do |items|
34
37
  import_or_update items, method_name, async
35
38
  end
36
39
  end
@@ -178,22 +178,22 @@ module Searchkick
178
178
 
179
179
  # reindex
180
180
 
181
- def reindex(scope, method_name, scoped:, full: false, **options)
181
+ def reindex(relation, method_name, scoped:, full: false, scope: nil, **options)
182
182
  refresh = options.fetch(:refresh, !scoped)
183
183
 
184
184
  if method_name
185
185
  # update
186
- import_scope(scope, method_name: method_name)
186
+ import_scope(relation, method_name: method_name, scope: scope)
187
187
  self.refresh if refresh
188
188
  true
189
189
  elsif scoped && !full
190
190
  # reindex association
191
- import_scope(scope)
191
+ import_scope(relation, scope: scope)
192
192
  self.refresh if refresh
193
193
  true
194
194
  else
195
195
  # full reindex
196
- reindex_scope(scope, options)
196
+ reindex_scope(relation, scope: scope, **options)
197
197
  end
198
198
  end
199
199
 
@@ -204,8 +204,8 @@ module Searchkick
204
204
  index
205
205
  end
206
206
 
207
- def import_scope(scope, **options)
208
- bulk_indexer.import_scope(scope, **options)
207
+ def import_scope(relation, **options)
208
+ bulk_indexer.import_scope(relation, **options)
209
209
  end
210
210
 
211
211
  def batches_left
@@ -257,7 +257,7 @@ module Searchkick
257
257
 
258
258
  # https://gist.github.com/jarosan/3124884
259
259
  # http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/
260
- def reindex_scope(scope, import: true, resume: false, retain: false, async: false, refresh_interval: nil)
260
+ def reindex_scope(relation, import: true, resume: false, retain: false, async: false, refresh_interval: nil, scope: nil)
261
261
  if resume
262
262
  index_name = all_indices.sort.last
263
263
  raise Searchkick::Error, "No index to resume" unless index_name
@@ -265,16 +265,23 @@ module Searchkick
265
265
  else
266
266
  clean_indices unless retain
267
267
 
268
- index_options = scope.searchkick_index_options
268
+ index_options = relation.searchkick_index_options
269
269
  index_options.deep_merge!(settings: {index: {refresh_interval: refresh_interval}}) if refresh_interval
270
270
  index = create_index(index_options: index_options)
271
271
  end
272
272
 
273
+ import_options = {
274
+ resume: resume,
275
+ async: async,
276
+ full: true,
277
+ scope: scope
278
+ }
279
+
273
280
  # check if alias exists
274
281
  alias_exists = alias_exists?
275
282
  if alias_exists
276
283
  # import before promotion
277
- index.import_scope(scope, resume: resume, async: async, full: true) if import
284
+ index.import_scope(relation, **import_options) if import
278
285
 
279
286
  # get existing indices to remove
280
287
  unless async
@@ -286,7 +293,7 @@ module Searchkick
286
293
  promote(index.name, update_refresh_interval: !refresh_interval.nil?)
287
294
 
288
295
  # import after promotion
289
- index.import_scope(scope, resume: resume, async: async, full: true) if import
296
+ index.import_scope(relation, **import_options) if import
290
297
  end
291
298
 
292
299
  if async
@@ -142,7 +142,8 @@ module Searchkick
142
142
  }
143
143
  }
144
144
 
145
- if language == "chinese"
145
+ case language
146
+ when "chinese"
146
147
  settings[:analysis][:analyzer].merge!(
147
148
  default_analyzer => {
148
149
  type: "ik_smart"
@@ -156,6 +157,30 @@ module Searchkick
156
157
  )
157
158
 
158
159
  settings[:analysis][:filter].delete(:searchkick_stemmer)
160
+ when "japanese"
161
+ settings[:analysis][:analyzer].merge!(
162
+ default_analyzer => {
163
+ type: "kuromoji"
164
+ },
165
+ searchkick_search: {
166
+ type: "kuromoji"
167
+ },
168
+ searchkick_search2: {
169
+ type: "kuromoji"
170
+ }
171
+ )
172
+ when "polish", "ukrainian", "smartcn"
173
+ settings[:analysis][:analyzer].merge!(
174
+ default_analyzer => {
175
+ type: language
176
+ },
177
+ searchkick_search: {
178
+ type: language
179
+ },
180
+ searchkick_search2: {
181
+ type: language
182
+ }
183
+ )
159
184
  end
160
185
 
161
186
  if Searchkick.env == "test"
@@ -314,10 +314,12 @@ module Searchkick
314
314
 
315
315
  if field == "_all" || field.end_with?(".analyzed")
316
316
  shared_options[:cutoff_frequency] = 0.001 unless operator.to_s == "and" || misspellings == false
317
- qs.concat [
318
- shared_options.merge(analyzer: "searchkick_search"),
319
- shared_options.merge(analyzer: "searchkick_search2")
320
- ]
317
+ qs << shared_options.merge(analyzer: "searchkick_search")
318
+
319
+ # searchkick_search and searchkick_search2 are the same for ukrainian
320
+ unless %w(japanese polish ukrainian).include?(searchkick_options[:language])
321
+ qs << shared_options.merge(analyzer: "searchkick_search2")
322
+ end
321
323
  exclude_analyzer = "searchkick_search2"
322
324
  elsif field.end_with?(".exact")
323
325
  f = field.split(".")[0..-2].join(".")
@@ -850,7 +852,7 @@ module Searchkick
850
852
  }
851
853
  when :regexp # support for regexp queries without using a regexp ruby object
852
854
  filters << {regexp: {field => {value: op_value}}}
853
- when :not # not equal
855
+ when :not, :_not # not equal
854
856
  filters << {bool: {must_not: term_filters(field, op_value)}}
855
857
  when :all
856
858
  op_value.each do |val|
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "3.0.0"
2
+ VERSION = "3.0.1"
3
3
  end
@@ -2,15 +2,59 @@ require_relative "test_helper"
2
2
 
3
3
  class LanguageTest < Minitest::Test
4
4
  def setup
5
+ skip unless ENV["LANGUAGE"]
6
+
5
7
  Song.destroy_all
6
8
  end
7
9
 
8
10
  def test_chinese
9
- skip unless ENV["CHINESE"]
10
- Song.reindex
11
- store_names ["中华人民共和国国歌"], Song
12
- assert_search "中华人民共和国", ["中华人民共和国国歌"], {}, Song
13
- assert_search "国歌", ["中华人民共和国国歌"], {}, Song
14
- assert_search "人", [], {}, Song
11
+ # requires https://github.com/medcl/elasticsearch-analysis-ik
12
+ with_options(Song, language: "chinese") do
13
+ store_names ["中华人民共和国国歌"], Song
14
+ assert_language_search "中华人民共和国", ["中华人民共和国国歌"]
15
+ assert_language_search "国歌", ["中华人民共和国国歌"]
16
+ assert_language_search "人", []
17
+ end
18
+ end
19
+
20
+ # experimental
21
+ def test_smartcn
22
+ # requires https://www.elastic.co/guide/en/elasticsearch/plugins/6.2/analysis-smartcn.html
23
+ with_options(Song, language: "smartcn") do
24
+ store_names ["中华人民共和国国歌"], Song
25
+ assert_language_search "中华人民共和国", ["中华人民共和国国歌"]
26
+ # assert_language_search "国歌", ["中华人民共和国国歌"]
27
+ assert_language_search "人", []
28
+ end
29
+ end
30
+
31
+ def test_japanese
32
+ # requires https://www.elastic.co/guide/en/elasticsearch/plugins/6.2/analysis-kuromoji.html
33
+ with_options(Song, language: "japanese") do
34
+ store_names ["JR新宿駅の近くにビールを飲みに行こうか"], Song
35
+ assert_language_search "飲む", ["JR新宿駅の近くにビールを飲みに行こうか"]
36
+ assert_language_search "jr", ["JR新宿駅の近くにビールを飲みに行こうか"]
37
+ assert_language_search "新", []
38
+ end
39
+ end
40
+
41
+ def test_polish
42
+ # requires https://www.elastic.co/guide/en/elasticsearch/plugins/6.2/analysis-stempel.html
43
+ with_options(Song, language: "polish") do
44
+ store_names ["polski"], Song
45
+ assert_language_search "polskimi", ["polski"]
46
+ end
47
+ end
48
+
49
+ def test_ukrainian
50
+ # requires https://www.elastic.co/guide/en/elasticsearch/plugins/6.2/analysis-ukrainian.html
51
+ with_options(Song, language: "ukrainian") do
52
+ store_names ["ресторани"], Song
53
+ assert_language_search "ресторан", ["ресторани"]
54
+ end
55
+ end
56
+
57
+ def assert_language_search(term, expected)
58
+ assert_search term, expected, {misspellings: false}, Song
15
59
  end
16
60
  end
@@ -1,6 +1,11 @@
1
1
  require_relative "test_helper"
2
2
 
3
3
  class ReindexTest < Minitest::Test
4
+ def setup
5
+ super
6
+ Sku.destroy_all
7
+ end
8
+
4
9
  def test_scoped
5
10
  skip if nobrainer? || cequel?
6
11
 
@@ -47,7 +47,9 @@ class SynonymsTest < Minitest::Test
47
47
  end
48
48
 
49
49
  def test_wordnet
50
- skip unless ENV["TEST_WORDNET"]
50
+ # requires WordNet
51
+ skip unless ENV["WORDNET"]
52
+
51
53
  store_names ["Creature", "Beast", "Dragon"], Animal
52
54
  assert_search "animal", ["Creature", "Beast"], {}, Animal
53
55
  end
@@ -528,7 +528,7 @@ class Sku
528
528
  end
529
529
 
530
530
  class Song
531
- searchkick language: "chinese"
531
+ searchkick
532
532
  end
533
533
 
534
534
  Product.searchkick_index.delete if Product.searchkick_index.exists?
@@ -547,7 +547,6 @@ class Minitest::Test
547
547
  Store.destroy_all
548
548
  Animal.destroy_all
549
549
  Speaker.destroy_all
550
- Sku.destroy_all
551
550
  end
552
551
 
553
552
  protected
@@ -579,4 +578,16 @@ class Minitest::Test
579
578
  def assert_first(term, expected, options = {}, klass = Product)
580
579
  assert_equal expected, klass.search(term, options).map(&:name).first
581
580
  end
581
+
582
+ def with_options(klass, options)
583
+ previous_options = klass.searchkick_options.dup
584
+ begin
585
+ klass.searchkick_options.merge!(options)
586
+ klass.reindex
587
+ yield
588
+ ensure
589
+ klass.searchkick_options.clear
590
+ klass.searchkick_options.merge!(previous_options)
591
+ end
592
+ end
582
593
  end
@@ -31,7 +31,9 @@ class WhereTest < Minitest::Test
31
31
  assert_search "product", ["Product A"], where: {store_id: 1...2}
32
32
  assert_search "product", ["Product A", "Product B"], where: {store_id: [1, 2]}
33
33
  assert_search "product", ["Product B", "Product C", "Product D"], where: {store_id: {not: 1}}
34
+ assert_search "product", ["Product B", "Product C", "Product D"], where: {store_id: {_not: 1}}
34
35
  assert_search "product", ["Product C", "Product D"], where: {store_id: {not: [1, 2]}}
36
+ assert_search "product", ["Product C", "Product D"], where: {store_id: {_not: [1, 2]}}
35
37
  assert_search "product", ["Product A"], where: {user_ids: {lte: 2, gte: 2}}
36
38
 
37
39
  # or
@@ -56,12 +58,15 @@ class WhereTest < Minitest::Test
56
58
 
57
59
  # any / nested terms
58
60
  assert_search "product", ["Product B", "Product C"], where: {user_ids: {not: [2], in: [1, 3]}}
61
+ assert_search "product", ["Product B", "Product C"], where: {user_ids: {_not: [2], in: [1, 3]}}
59
62
 
60
63
  # not / exists
61
64
  assert_search "product", ["Product D"], where: {user_ids: nil}
62
65
  assert_search "product", ["Product A", "Product B", "Product C"], where: {user_ids: {not: nil}}
66
+ assert_search "product", ["Product A", "Product B", "Product C"], where: {user_ids: {_not: nil}}
63
67
  assert_search "product", ["Product A", "Product C", "Product D"], where: {user_ids: [3, nil]}
64
68
  assert_search "product", ["Product B"], where: {user_ids: {not: [3, nil]}}
69
+ assert_search "product", ["Product B"], where: {user_ids: {_not: [3, nil]}}
65
70
  end
66
71
 
67
72
  def test_regexp
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.0
4
+ version: 3.0.1
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-03 00:00:00.000000000 Z
11
+ date: 2018-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel