searchkick 2.3.0 → 2.3.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 +4 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile +4 -0
- data/README.md +11 -3
- data/lib/searchkick/index.rb +19 -8
- data/lib/searchkick/query.rb +16 -8
- data/lib/searchkick/results.rb +8 -2
- data/lib/searchkick/version.rb +1 -1
- data/test/boost_test.rb +12 -0
- data/test/gemfiles/mongoid6.gemfile +4 -0
- data/test/index_test.rb +5 -0
- data/test/pagination_test.rb +17 -0
- data/test/reindex_test.rb +12 -0
- data/test/suggest_test.rb +10 -0
- data/test/support/kaminari.yml +21 -0
- data/test/test_helper.rb +32 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75ff5cc71f668744faa82225c281a7a636d2a774
|
4
|
+
data.tar.gz: 3acfb221ce185cd58e4e56907a4b175556c1da53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c329befd3fdad5beb444e0db1c6dabc138ace231cb66f6f8e6abe880bd06fb949aca2ab59fbd135e2516c052d753e743aadac8269a4904fcfc968d7bd6ce626c
|
7
|
+
data.tar.gz: 9943486403fc4a18e6fbf1a2ba3dcda15a85517afaacd8717ffa88dd2d47badb803c85eae9b0aac80b075e6afba2896404ffaeddb22d69c4546a0a8a1e579c4a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## 2.3.1
|
2
|
+
|
3
|
+
- Added support for `reindex(async: true)` for non-numeric primary keys
|
4
|
+
- Added `conversions_term` option
|
5
|
+
- Added support for passing fields to `suggest` option
|
6
|
+
- Fixed `page_view_entries` for Kaminari
|
7
|
+
|
1
8
|
## 2.3.0
|
2
9
|
|
3
10
|
- Fixed analyzer on dynamically mapped fields
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -629,7 +629,7 @@ end
|
|
629
629
|
Reindex and search with:
|
630
630
|
|
631
631
|
```ruby
|
632
|
-
Movie.search "jurassic pa", match: :word_start
|
632
|
+
Movie.search "jurassic pa", fields: [:title], match: :word_start
|
633
633
|
```
|
634
634
|
|
635
635
|
Typically, you want to use a JavaScript library like [typeahead.js](http://twitter.github.io/typeahead.js/) or [jQuery UI](http://jqueryui.com/autocomplete/).
|
@@ -1177,14 +1177,16 @@ end
|
|
1177
1177
|
|
1178
1178
|
### Filterable Fields
|
1179
1179
|
|
1180
|
-
By default, all fields are filterable (can be used in `where` option). Speed up indexing and reduce index size by only making some fields filterable.
|
1180
|
+
By default, all string fields are filterable (can be used in `where` option). Speed up indexing and reduce index size by only making some fields filterable.
|
1181
1181
|
|
1182
1182
|
```ruby
|
1183
1183
|
class Product < ActiveRecord::Base
|
1184
|
-
searchkick filterable: [:
|
1184
|
+
searchkick filterable: [:brand]
|
1185
1185
|
end
|
1186
1186
|
```
|
1187
1187
|
|
1188
|
+
**Note:** Non-string fields will always be filterable and should not be passed to this option.
|
1189
|
+
|
1188
1190
|
### Parallel Reindexing
|
1189
1191
|
|
1190
1192
|
For large data sets, you can use background jobs to parallelize reindexing.
|
@@ -1563,6 +1565,12 @@ class Product < ActiveRecord::Base
|
|
1563
1565
|
end
|
1564
1566
|
```
|
1565
1567
|
|
1568
|
+
Use a different term for boosting by conversions
|
1569
|
+
|
1570
|
+
```ruby
|
1571
|
+
Product.search("banana", conversions_term: "organic banana")
|
1572
|
+
```
|
1573
|
+
|
1566
1574
|
Multiple conversion fields
|
1567
1575
|
|
1568
1576
|
```ruby
|
data/lib/searchkick/index.rb
CHANGED
@@ -420,14 +420,25 @@ module Searchkick
|
|
420
420
|
if scope.respond_to?(:primary_key)
|
421
421
|
# TODO expire Redis key
|
422
422
|
primary_key = scope.primary_key
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
423
|
+
|
424
|
+
starting_id = scope.minimum(primary_key)
|
425
|
+
if starting_id.nil?
|
426
|
+
# no records, do nothing
|
427
|
+
elsif starting_id.is_a?(Numeric)
|
428
|
+
max_id = scope.maximum(primary_key)
|
429
|
+
batches_count = ((max_id - starting_id + 1) / batch_size.to_f).ceil
|
430
|
+
|
431
|
+
batches_count.times do |i|
|
432
|
+
batch_id = i + 1
|
433
|
+
min_id = starting_id + (i * batch_size)
|
434
|
+
bulk_reindex_job scope, batch_id, min_id: min_id, max_id: min_id + batch_size - 1
|
435
|
+
end
|
436
|
+
else
|
437
|
+
scope.find_in_batches(batch_size: batch_size).each_with_index do |batch, i|
|
438
|
+
batch_id = i + 1
|
439
|
+
|
440
|
+
bulk_reindex_job scope, batch_id, record_ids: batch.map { |record| record.id.to_s }
|
441
|
+
end
|
431
442
|
end
|
432
443
|
else
|
433
444
|
batch_id = 1
|
data/lib/searchkick/query.rb
CHANGED
@@ -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, :debug, :emoji, :exclude, :execute, :explain,
|
19
|
+
:boost_by, :boost_by_distance, :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, :offset, :operator, :order, :padding, :page, :per_page, :profile,
|
22
22
|
:request_params, :routing, :select, :similar, :smart_aggs, :suggest, :track, :type, :where]
|
@@ -381,7 +381,7 @@ module Searchkick
|
|
381
381
|
boost_mode: "replace",
|
382
382
|
query: {
|
383
383
|
match: {
|
384
|
-
"#{conversions_field}.query" => term
|
384
|
+
"#{conversions_field}.query" => options[:conversions_term] || term
|
385
385
|
}
|
386
386
|
}
|
387
387
|
}.merge(script_score)
|
@@ -447,7 +447,7 @@ module Searchkick
|
|
447
447
|
set_aggregations(payload) if options[:aggs]
|
448
448
|
|
449
449
|
# suggestions
|
450
|
-
set_suggestions(payload) if options[:suggest]
|
450
|
+
set_suggestions(payload, options[:suggest]) if options[:suggest]
|
451
451
|
|
452
452
|
# highlight
|
453
453
|
set_highlights(payload, fields) if options[:highlight]
|
@@ -575,12 +575,18 @@ module Searchkick
|
|
575
575
|
payload[:indices_boost] = indices_boost
|
576
576
|
end
|
577
577
|
|
578
|
-
def set_suggestions(payload)
|
579
|
-
suggest_fields =
|
578
|
+
def set_suggestions(payload, suggest)
|
579
|
+
suggest_fields = nil
|
580
580
|
|
581
|
-
|
582
|
-
|
583
|
-
|
581
|
+
if suggest.is_a?(Array)
|
582
|
+
suggest_fields = suggest
|
583
|
+
else
|
584
|
+
suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s)
|
585
|
+
|
586
|
+
# intersection
|
587
|
+
if options[:fields]
|
588
|
+
suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
|
589
|
+
end
|
584
590
|
end
|
585
591
|
|
586
592
|
if suggest_fields.any?
|
@@ -592,6 +598,8 @@ module Searchkick
|
|
592
598
|
}
|
593
599
|
}
|
594
600
|
end
|
601
|
+
else
|
602
|
+
raise ArgumentError, "Must pass fields to suggest option"
|
595
603
|
end
|
596
604
|
end
|
597
605
|
|
data/lib/searchkick/results.rb
CHANGED
@@ -127,8 +127,14 @@ module Searchkick
|
|
127
127
|
klass.model_name
|
128
128
|
end
|
129
129
|
|
130
|
-
def entry_name
|
131
|
-
|
130
|
+
def entry_name(options = {})
|
131
|
+
if options.empty?
|
132
|
+
# backward compatibility
|
133
|
+
model_name.human.downcase
|
134
|
+
else
|
135
|
+
default = options[:count] == 1 ? model_name.human : model_name.human.pluralize
|
136
|
+
model_name.human(options.reverse_merge(default: default))
|
137
|
+
end
|
132
138
|
end
|
133
139
|
|
134
140
|
def total_count
|
data/lib/searchkick/version.rb
CHANGED
data/test/boost_test.rb
CHANGED
@@ -28,6 +28,18 @@ class BoostTest < Minitest::Test
|
|
28
28
|
assert_order "speaker", ["Speaker A", "Speaker B", "Speaker C"], {conversions: "conversions_b"}, Speaker
|
29
29
|
end
|
30
30
|
|
31
|
+
def test_multiple_conversions_with_boost_term
|
32
|
+
store [
|
33
|
+
{name: "Speaker A", conversions_a: {"speaker" => 4, "speaker_1" => 1}},
|
34
|
+
{name: "Speaker B", conversions_a: {"speaker" => 3, "speaker_1" => 2}},
|
35
|
+
{name: "Speaker C", conversions_a: {"speaker" => 2, "speaker_1" => 3}},
|
36
|
+
{name: "Speaker D", conversions_a: {"speaker" => 1, "speaker_1" => 4}}
|
37
|
+
], Speaker
|
38
|
+
|
39
|
+
assert_order "speaker", ["Speaker A", "Speaker B", "Speaker C", "Speaker D"], {conversions: "conversions_a"}, Speaker
|
40
|
+
assert_order "speaker", ["Speaker D", "Speaker C", "Speaker B", "Speaker A"], {conversions: "conversions_a", conversions_term: "speaker_1"}, Speaker
|
41
|
+
end
|
42
|
+
|
31
43
|
def test_conversions_stemmed
|
32
44
|
store [
|
33
45
|
{name: "Tomato A", conversions: {"tomato" => 1, "tomatos" => 1, "Tomatoes" => 1}},
|
data/test/index_test.rb
CHANGED
@@ -132,6 +132,11 @@ class IndexTest < Minitest::Test
|
|
132
132
|
assert_search "*", [], where: {alt_description: "Hello"}
|
133
133
|
end
|
134
134
|
|
135
|
+
def test_filterable_non_string
|
136
|
+
store [{name: "Product A", store_id: 1}]
|
137
|
+
assert_search "*", ["Product A"], where: {store_id: 1}
|
138
|
+
end
|
139
|
+
|
135
140
|
def test_large_value
|
136
141
|
skip if nobrainer?
|
137
142
|
large_value = 1000.times.map { "hello" }.join(" ")
|
data/test/pagination_test.rb
CHANGED
@@ -50,4 +50,21 @@ class PaginationTest < Minitest::Test
|
|
50
50
|
assert_equal 1, products.current_page
|
51
51
|
assert products.first_page?
|
52
52
|
end
|
53
|
+
|
54
|
+
def test_kaminari
|
55
|
+
skip unless defined?(Kaminari)
|
56
|
+
|
57
|
+
require "action_view"
|
58
|
+
|
59
|
+
I18n.load_path = Dir["test/support/kaminari.yml"]
|
60
|
+
I18n.backend.load_translations
|
61
|
+
|
62
|
+
view = ActionView::Base.new
|
63
|
+
|
64
|
+
store_names ["Product A"]
|
65
|
+
assert_equal "Displaying <b>1</b> product", view.page_entries_info(Product.search("product"))
|
66
|
+
|
67
|
+
store_names ["Product B"]
|
68
|
+
assert_equal "Displaying <b>all 2</b> products", view.page_entries_info(Product.search("product"))
|
69
|
+
end
|
53
70
|
end
|
data/test/reindex_test.rb
CHANGED
@@ -39,6 +39,18 @@ class ReindexTest < Minitest::Test
|
|
39
39
|
assert_search "product", ["Product A"]
|
40
40
|
end
|
41
41
|
|
42
|
+
def test_async_non_integer_pk
|
43
|
+
skip if !defined?(ActiveJob)
|
44
|
+
|
45
|
+
Sku.create(id: SecureRandom.hex, name: "Test")
|
46
|
+
reindex = Sku.reindex(async: true)
|
47
|
+
assert_search "sku", [], conversions: false
|
48
|
+
|
49
|
+
index = Searchkick::Index.new(reindex[:index_name])
|
50
|
+
index.refresh
|
51
|
+
assert_equal 1, index.total_docs
|
52
|
+
end
|
53
|
+
|
42
54
|
def test_refresh_interval
|
43
55
|
reindex = Product.reindex(refresh_interval: "30s", async: true, import: false)
|
44
56
|
index = Searchkick::Index.new(reindex[:index_name])
|
data/test/suggest_test.rb
CHANGED
@@ -67,6 +67,16 @@ class SuggestTest < Minitest::Test
|
|
67
67
|
assert_suggest "How Big is a Tigre Shar", "how big is a tiger shark", fields: [{"name^2" => :word_start}]
|
68
68
|
end
|
69
69
|
|
70
|
+
def test_multiple_models
|
71
|
+
store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
|
72
|
+
assert_equal "how big is a tiger shark", Searchkick.search("How Big is a Tigre Shar", suggest: [:name]).suggestions.first
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_multiple_models_no_fields
|
76
|
+
store_names ["Great White Shark", "Hammerhead Shark", "Tiger Shark"]
|
77
|
+
assert_raises(ArgumentError) { Searchkick.search("How Big is a Tigre Shar", suggest: true) }
|
78
|
+
end
|
79
|
+
|
70
80
|
protected
|
71
81
|
|
72
82
|
def assert_suggest(term, expected, options = {})
|
@@ -0,0 +1,21 @@
|
|
1
|
+
en:
|
2
|
+
views:
|
3
|
+
pagination:
|
4
|
+
first: "« First"
|
5
|
+
last: "Last »"
|
6
|
+
previous: "‹ Prev"
|
7
|
+
next: "Next ›"
|
8
|
+
truncate: "…"
|
9
|
+
helpers:
|
10
|
+
page_entries_info:
|
11
|
+
entry:
|
12
|
+
zero: "entries"
|
13
|
+
one: "entry"
|
14
|
+
other: "entries"
|
15
|
+
one_page:
|
16
|
+
display_entries:
|
17
|
+
zero: "No %{entry_name} found"
|
18
|
+
one: "Displaying <b>1</b> %{entry_name}"
|
19
|
+
other: "Displaying <b>all %{count}</b> %{entry_name}"
|
20
|
+
more_pages:
|
21
|
+
display_entries: "Displaying %{entry_name} <b>%{first} - %{last}</b> of <b>%{total}</b> in total"
|
data/test/test_helper.rb
CHANGED
@@ -111,6 +111,12 @@ if defined?(Mongoid)
|
|
111
111
|
|
112
112
|
class Cat < Animal
|
113
113
|
end
|
114
|
+
|
115
|
+
class Sku
|
116
|
+
include Mongoid::Document
|
117
|
+
|
118
|
+
field :name
|
119
|
+
end
|
114
120
|
elsif defined?(NoBrainer)
|
115
121
|
NoBrainer.configure do |config|
|
116
122
|
config.app_name = :searchkick
|
@@ -171,6 +177,13 @@ elsif defined?(NoBrainer)
|
|
171
177
|
|
172
178
|
class Cat < Animal
|
173
179
|
end
|
180
|
+
|
181
|
+
class Sku
|
182
|
+
include NoBrainer::Document
|
183
|
+
|
184
|
+
field :id, type: String
|
185
|
+
field :name, type: String
|
186
|
+
end
|
174
187
|
elsif defined?(Cequel)
|
175
188
|
cequel =
|
176
189
|
Cequel.connect(
|
@@ -252,6 +265,13 @@ elsif defined?(Cequel)
|
|
252
265
|
class Cat < Animal
|
253
266
|
end
|
254
267
|
|
268
|
+
class Sku
|
269
|
+
include Cequel::Record
|
270
|
+
|
271
|
+
key :id, :uuid
|
272
|
+
column :name, :text
|
273
|
+
end
|
274
|
+
|
255
275
|
[Product, Store, Region, Speaker, Animal].each(&:synchronize_schema)
|
256
276
|
else
|
257
277
|
require "active_record"
|
@@ -339,6 +359,10 @@ else
|
|
339
359
|
t.string :type
|
340
360
|
end
|
341
361
|
|
362
|
+
ActiveRecord::Migration.create_table :skus, id: :uuid do |t|
|
363
|
+
t.string :name
|
364
|
+
end
|
365
|
+
|
342
366
|
class Product < ActiveRecord::Base
|
343
367
|
belongs_to :store
|
344
368
|
end
|
@@ -361,6 +385,9 @@ else
|
|
361
385
|
|
362
386
|
class Cat < Animal
|
363
387
|
end
|
388
|
+
|
389
|
+
class Sku < ActiveRecord::Base
|
390
|
+
end
|
364
391
|
end
|
365
392
|
|
366
393
|
class Product
|
@@ -477,6 +504,10 @@ class Animal
|
|
477
504
|
# wordnet: true
|
478
505
|
end
|
479
506
|
|
507
|
+
class Sku
|
508
|
+
searchkick callbacks: defined?(ActiveJob) ? :async : true
|
509
|
+
end
|
510
|
+
|
480
511
|
Product.searchkick_index.delete if Product.searchkick_index.exists?
|
481
512
|
Product.reindex
|
482
513
|
Product.reindex # run twice for both index paths
|
@@ -493,6 +524,7 @@ class Minitest::Test
|
|
493
524
|
Store.destroy_all
|
494
525
|
Animal.destroy_all
|
495
526
|
Speaker.destroy_all
|
527
|
+
Sku.destroy_all
|
496
528
|
end
|
497
529
|
|
498
530
|
protected
|
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: 2.3.
|
4
|
+
version: 2.3.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: 2017-
|
11
|
+
date: 2017-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -170,6 +170,7 @@ files:
|
|
170
170
|
- test/similar_test.rb
|
171
171
|
- test/sql_test.rb
|
172
172
|
- test/suggest_test.rb
|
173
|
+
- test/support/kaminari.yml
|
173
174
|
- test/synonyms_test.rb
|
174
175
|
- test/test_helper.rb
|
175
176
|
- test/where_test.rb
|
@@ -245,6 +246,7 @@ test_files:
|
|
245
246
|
- test/similar_test.rb
|
246
247
|
- test/sql_test.rb
|
247
248
|
- test/suggest_test.rb
|
249
|
+
- test/support/kaminari.yml
|
248
250
|
- test/synonyms_test.rb
|
249
251
|
- test/test_helper.rb
|
250
252
|
- test/where_test.rb
|