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