searchkick 3.0.0 → 3.0.1

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
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