searchkick 0.8.7 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -1
- data/Gemfile +1 -1
- data/README.md +68 -4
- data/Rakefile +1 -1
- data/gemfiles/nobrainer.gemfile +1 -1
- data/lib/searchkick/index.rb +33 -24
- data/lib/searchkick/logging.rb +1 -1
- data/lib/searchkick/model.rb +5 -5
- data/lib/searchkick/query.rb +58 -38
- data/lib/searchkick/reindex_job.rb +1 -1
- data/lib/searchkick/reindex_v2_job.rb +1 -1
- data/lib/searchkick/results.rb +12 -12
- data/lib/searchkick/tasks.rb +3 -3
- data/lib/searchkick/version.rb +1 -1
- data/lib/searchkick.rb +5 -7
- data/searchkick.gemspec +5 -5
- data/test/facets_test.rb +8 -2
- data/test/index_test.rb +13 -5
- data/test/model_test.rb +3 -0
- data/test/reindex_v2_job_test.rb +1 -1
- data/test/routing_test.rb +14 -0
- data/test/sql_test.rb +10 -5
- data/test/suggest_test.rb +2 -2
- data/test/test_helper.rb +14 -9
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 086a7f84d9047b486d127d05cc3d20b7b6d66b04
|
4
|
+
data.tar.gz: f0896d8115143f72cf088f69f82c923c6e4f67cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f084aa390d924d64c610ea86845433bb521b8ad319e50350936f56d57f3db770e01e87c34c55d38f88b58599b03d6176a8af6e4fee8793fc5f1b8fb01cfeea6e
|
7
|
+
data.tar.gz: e26a6809754329de5cf67a473f3df2d5b8b19b1981bdb22c041421b4916a1efcf5d4f78501cd9656ba297ccdcb0e3fe1742d0d57ffee1e0bf8475104045367fb
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
+
## 0.9.0
|
2
|
+
|
3
|
+
- Much better performance for where queries if no facets
|
4
|
+
- Added basic support for regex
|
5
|
+
- Added support for routing
|
6
|
+
- Made `Searchkick.disable_callbacks` thread-safe
|
7
|
+
|
1
8
|
## 0.8.7
|
2
9
|
|
3
|
-
- Fixed
|
10
|
+
- Fixed Mongoid import
|
4
11
|
|
5
12
|
## 0.8.6
|
6
13
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -23,9 +23,9 @@ Plus:
|
|
23
23
|
|
24
24
|
:speech_balloon: Get [handcrafted updates](http://chartkick.us7.list-manage.com/subscribe?u=952c861f99eb43084e0a49f98&id=6ea6541e8e&group[0][4]=true) for new features
|
25
25
|
|
26
|
-
:tangerine: Battle-tested at [Instacart](https://www.instacart.com)
|
26
|
+
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
27
27
|
|
28
|
-
[![Build Status](https://travis-ci.org/ankane/searchkick.
|
28
|
+
[![Build Status](https://travis-ci.org/ankane/searchkick.svg?branch=master)](https://travis-ci.org/ankane/searchkick)
|
29
29
|
|
30
30
|
We highly recommend tracking queries and conversions
|
31
31
|
|
@@ -606,7 +606,7 @@ Find similar items.
|
|
606
606
|
|
607
607
|
```ruby
|
608
608
|
product = Product.first
|
609
|
-
product.similar(fields: ["name"])
|
609
|
+
product.similar(fields: ["name"], where: {size: "12 oz"})
|
610
610
|
```
|
611
611
|
|
612
612
|
### Geospatial Searches
|
@@ -647,6 +647,22 @@ Also supports [additional options](http://www.elasticsearch.org/guide/en/elastic
|
|
647
647
|
City.search "san", boost_by_distance: {field: :location, origin: [37, -122], function: :linear, scale: "30mi", decay: 0.5}
|
648
648
|
```
|
649
649
|
|
650
|
+
### Routing
|
651
|
+
|
652
|
+
Searchkick supports [Elasticsearch’s routing feature](https://www.elastic.co/blog/customizing-your-document-routing).
|
653
|
+
|
654
|
+
```ruby
|
655
|
+
class Contact < ActiveRecord::Base
|
656
|
+
searchkick routing: :user_id
|
657
|
+
end
|
658
|
+
```
|
659
|
+
|
660
|
+
Reindex and search with:
|
661
|
+
|
662
|
+
```ruby
|
663
|
+
Contact.search "John", routing: current_user.id
|
664
|
+
```
|
665
|
+
|
650
666
|
## Inheritance
|
651
667
|
|
652
668
|
Searchkick supports single table inheritance.
|
@@ -849,6 +865,25 @@ product.reindex
|
|
849
865
|
product.reindex_async
|
850
866
|
```
|
851
867
|
|
868
|
+
Reindex more than one record without recreating the index
|
869
|
+
|
870
|
+
```ruby
|
871
|
+
# do this ...
|
872
|
+
some_company.products.each { |p| p.reindex }
|
873
|
+
# or this ...
|
874
|
+
Product.searchkick_index.import(some_company.products)
|
875
|
+
# don't do the following as it will recreate the index with some_company's products only
|
876
|
+
some_company.products.reindex
|
877
|
+
```
|
878
|
+
|
879
|
+
Reindex large set of records in batches
|
880
|
+
|
881
|
+
```ruby
|
882
|
+
Product.where("id > 100000").find_in_batches do |batch|
|
883
|
+
Product.searchkick_index.import(batch)
|
884
|
+
end
|
885
|
+
```
|
886
|
+
|
852
887
|
Remove old indices
|
853
888
|
|
854
889
|
```ruby
|
@@ -955,6 +990,34 @@ Reindex all models - Rails only
|
|
955
990
|
rake searchkick:reindex:all
|
956
991
|
```
|
957
992
|
|
993
|
+
## Large Data Sets
|
994
|
+
|
995
|
+
For large data sets, check out [Keeping Elasticsearch in Sync](https://www.found.no/foundation/keeping-elasticsearch-in-sync/). Searchkick will make this easy in the future.
|
996
|
+
|
997
|
+
## Testing
|
998
|
+
|
999
|
+
This section could use some love.
|
1000
|
+
|
1001
|
+
### RSpec
|
1002
|
+
|
1003
|
+
```ruby
|
1004
|
+
describe Product do
|
1005
|
+
it "searches" do
|
1006
|
+
Product.reindex
|
1007
|
+
Product.searchkick_index.refresh # don't forget this
|
1008
|
+
# test goes here...
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
```
|
1012
|
+
|
1013
|
+
### Factory Girl
|
1014
|
+
|
1015
|
+
```ruby
|
1016
|
+
product = FactoryGirl.create(:product)
|
1017
|
+
product.reindex # don't forget this
|
1018
|
+
Product.searchkick_index.refresh # or this
|
1019
|
+
```
|
1020
|
+
|
958
1021
|
## Migrating from Tire
|
959
1022
|
|
960
1023
|
1. Change `search` methods to `tire.search` and add index name in existing search calls
|
@@ -1019,11 +1082,12 @@ Thanks to Karel Minarik for [Elasticsearch Ruby](https://github.com/elasticsearc
|
|
1019
1082
|
|
1020
1083
|
## Roadmap
|
1021
1084
|
|
1085
|
+
- More features for large data sets
|
1086
|
+
- Improve section on testing
|
1022
1087
|
- Semantic search features
|
1023
1088
|
- Search multiple fields for different terms
|
1024
1089
|
- Search across models
|
1025
1090
|
- Search nested objects
|
1026
|
-
- Add section on testing
|
1027
1091
|
- Much finer customization
|
1028
1092
|
|
1029
1093
|
## Contributing
|
data/Rakefile
CHANGED
data/gemfiles/nobrainer.gemfile
CHANGED
data/lib/searchkick/index.rb
CHANGED
@@ -34,7 +34,7 @@ module Searchkick
|
|
34
34
|
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
35
35
|
[]
|
36
36
|
end
|
37
|
-
actions = old_indices.map{|old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
|
37
|
+
actions = old_indices.map { |old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
|
38
38
|
client.indices.update_aliases body: {actions: actions}
|
39
39
|
end
|
40
40
|
|
@@ -50,19 +50,22 @@ module Searchkick
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def remove(record)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
id = search_id(record)
|
54
|
+
unless id.blank?
|
55
|
+
client.delete(
|
56
|
+
index: name,
|
57
|
+
type: document_type(record),
|
58
|
+
id: id
|
59
|
+
)
|
60
|
+
end
|
58
61
|
end
|
59
62
|
|
60
63
|
def import(records)
|
61
|
-
records.group_by{|r| document_type(r) }.each do |type, batch|
|
64
|
+
records.group_by { |r| document_type(r) }.each do |type, batch|
|
62
65
|
client.bulk(
|
63
66
|
index: name,
|
64
67
|
type: type,
|
65
|
-
body: batch.map{|r| {index: {_id: search_id(r), data: search_data(r)}} }
|
68
|
+
body: batch.map { |r| {index: {_id: search_id(r), data: search_data(r)}} }
|
66
69
|
)
|
67
70
|
end
|
68
71
|
end
|
@@ -71,12 +74,12 @@ module Searchkick
|
|
71
74
|
client.get(
|
72
75
|
index: name,
|
73
76
|
type: document_type(record),
|
74
|
-
id: record
|
77
|
+
id: search_id(record)
|
75
78
|
)["_source"]
|
76
79
|
end
|
77
80
|
|
78
81
|
def reindex_record(record)
|
79
|
-
if record.destroyed?
|
82
|
+
if record.destroyed? || !record.should_index?
|
80
83
|
begin
|
81
84
|
remove(record)
|
82
85
|
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
@@ -89,7 +92,7 @@ module Searchkick
|
|
89
92
|
|
90
93
|
def reindex_record_async(record)
|
91
94
|
if defined?(Searchkick::ReindexV2Job)
|
92
|
-
|
95
|
+
Searchkick::ReindexV2Job.perform_later(record.class.name, record.id.to_s)
|
93
96
|
else
|
94
97
|
Delayed::Job.enqueue Searchkick::ReindexJob.new(record.class.name, record.id.to_s)
|
95
98
|
end
|
@@ -97,7 +100,7 @@ module Searchkick
|
|
97
100
|
|
98
101
|
def similar_record(record, options = {})
|
99
102
|
like_text = retrieve(record).to_hash
|
100
|
-
.keep_if{|k,v| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
|
103
|
+
.keep_if { |k, v| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
|
101
104
|
.values.compact.join(" ")
|
102
105
|
|
103
106
|
# TODO deep merge method
|
@@ -136,7 +139,7 @@ module Searchkick
|
|
136
139
|
# remove old indices that start w/ index_name
|
137
140
|
def clean_indices
|
138
141
|
all_indices = client.indices.get_aliases
|
139
|
-
indices = all_indices.select{|k, v| (v.empty? || v["aliases"].empty?) && k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
|
142
|
+
indices = all_indices.select { |k, v| (v.empty? || v["aliases"].empty?) && k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
|
140
143
|
indices.each do |index|
|
141
144
|
Searchkick::Index.new(index).delete
|
142
145
|
end
|
@@ -180,7 +183,7 @@ module Searchkick
|
|
180
183
|
scope = scope.search_import if scope.respond_to?(:search_import)
|
181
184
|
if scope.respond_to?(:find_in_batches)
|
182
185
|
scope.find_in_batches batch_size: batch_size do |batch|
|
183
|
-
import batch.select
|
186
|
+
import batch.select(&:should_index?)
|
184
187
|
end
|
185
188
|
else
|
186
189
|
# https://github.com/karmi/tire/blob/master/lib/tire/model/import.rb
|
@@ -200,7 +203,7 @@ module Searchkick
|
|
200
203
|
def index_options
|
201
204
|
options = @options
|
202
205
|
|
203
|
-
if options[:mappings]
|
206
|
+
if options[:mappings] && !options[:merge_mappings]
|
204
207
|
settings = options[:settings] || {}
|
205
208
|
mappings = options[:mappings]
|
206
209
|
else
|
@@ -333,7 +336,7 @@ module Searchkick
|
|
333
336
|
if synonyms.any?
|
334
337
|
settings[:analysis][:filter][:searchkick_synonym] = {
|
335
338
|
type: "synonym",
|
336
|
-
synonyms: synonyms.select{|s| s.size > 1 }.map{|s| s.join(",") }
|
339
|
+
synonyms: synonyms.select { |s| s.size > 1 }.map { |s| s.join(",") }
|
337
340
|
}
|
338
341
|
# choosing a place for the synonym filter when stemming is not easy
|
339
342
|
# https://groups.google.com/forum/#!topic/elasticsearch/p7qcQlgHdB8
|
@@ -361,7 +364,7 @@ module Searchkick
|
|
361
364
|
|
362
365
|
if options[:special_characters] == false
|
363
366
|
settings[:analysis][:analyzer].each do |analyzer, analyzer_settings|
|
364
|
-
analyzer_settings[:filter].reject!{|f| f == "asciifolding" }
|
367
|
+
analyzer_settings[:filter].reject! { |f| f == "asciifolding" }
|
365
368
|
end
|
366
369
|
end
|
367
370
|
|
@@ -380,7 +383,7 @@ module Searchkick
|
|
380
383
|
|
381
384
|
mapping_options = Hash[
|
382
385
|
[:autocomplete, :suggest, :text_start, :text_middle, :text_end, :word_start, :word_middle, :word_end, :highlight]
|
383
|
-
.map{|type| [type, (options[type] || []).map(&:to_s)] }
|
386
|
+
.map { |type| [type, (options[type] || []).map(&:to_s)] }
|
384
387
|
]
|
385
388
|
|
386
389
|
mapping_options.values.flatten.uniq.each do |field|
|
@@ -420,9 +423,15 @@ module Searchkick
|
|
420
423
|
}
|
421
424
|
end
|
422
425
|
|
426
|
+
routing = {}
|
427
|
+
if options[:routing]
|
428
|
+
routing = {required: true, path: options[:routing].to_s}
|
429
|
+
end
|
430
|
+
|
423
431
|
mappings = {
|
424
432
|
_default_: {
|
425
433
|
properties: mapping,
|
434
|
+
_routing: routing,
|
426
435
|
# https://gist.github.com/kimchy/2898285
|
427
436
|
dynamic_templates: [
|
428
437
|
{
|
@@ -457,7 +466,7 @@ module Searchkick
|
|
457
466
|
# other
|
458
467
|
|
459
468
|
def tokens(text, options = {})
|
460
|
-
client.indices.analyze({text: text, index: name}.merge(options))["tokens"].map{|t| t["token"] }
|
469
|
+
client.indices.analyze({text: text, index: name}.merge(options))["tokens"].map { |t| t["token"] }
|
461
470
|
end
|
462
471
|
|
463
472
|
def klass_document_type(klass)
|
@@ -488,24 +497,24 @@ module Searchkick
|
|
488
497
|
|
489
498
|
# stringify fields
|
490
499
|
# remove _id since search_id is used instead
|
491
|
-
source = source.inject({}){|memo,(k,v)| memo[k.to_s] = v; memo}.except("_id")
|
500
|
+
source = source.inject({}) { |memo, (k, v)| memo[k.to_s] = v; memo }.except("_id")
|
492
501
|
|
493
502
|
# conversions
|
494
503
|
conversions_field = options[:conversions]
|
495
|
-
if conversions_field
|
496
|
-
source[conversions_field] = source[conversions_field].map{|k, v| {query: k, count: v} }
|
504
|
+
if conversions_field && source[conversions_field]
|
505
|
+
source[conversions_field] = source[conversions_field].map { |k, v| {query: k, count: v} }
|
497
506
|
end
|
498
507
|
|
499
508
|
# hack to prevent generator field doesn't exist error
|
500
509
|
(options[:suggest] || []).map(&:to_s).each do |field|
|
501
|
-
source[field] = nil
|
510
|
+
source[field] = nil unless source[field]
|
502
511
|
end
|
503
512
|
|
504
513
|
# locations
|
505
514
|
(options[:locations] || []).map(&:to_s).each do |field|
|
506
515
|
if source[field]
|
507
516
|
if source[field].first.is_a?(Array) # array of arrays
|
508
|
-
source[field] = source[field].map{|a| a.map(&:to_f).reverse }
|
517
|
+
source[field] = source[field].map { |a| a.map(&:to_f).reverse }
|
509
518
|
else
|
510
519
|
source[field] = source[field].map(&:to_f).reverse
|
511
520
|
end
|
data/lib/searchkick/logging.rb
CHANGED
@@ -77,7 +77,7 @@ module Searchkick
|
|
77
77
|
|
78
78
|
# no easy way to tell which host the client will use
|
79
79
|
host = Searchkick.client.transport.hosts.first
|
80
|
-
debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map{|t| CGI.escape(t) }.join(
|
80
|
+
debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map { |t| CGI.escape(t) }.join(',')}" : ''}/_search?pretty -d '#{payload[:query][:body].to_json}'"
|
81
81
|
end
|
82
82
|
|
83
83
|
def request(event)
|
data/lib/searchkick/model.rb
CHANGED
@@ -11,14 +11,14 @@ module Searchkick
|
|
11
11
|
class_eval do
|
12
12
|
cattr_reader :searchkick_options, :searchkick_klass
|
13
13
|
|
14
|
-
callbacks = options.
|
14
|
+
callbacks = options.key?(:callbacks) ? options[:callbacks] : true
|
15
15
|
|
16
16
|
class_variable_set :@@searchkick_options, options.dup
|
17
17
|
class_variable_set :@@searchkick_klass, self
|
18
18
|
class_variable_set :@@searchkick_callbacks, callbacks
|
19
19
|
class_variable_set :@@searchkick_index, options[:index_name] || [options[:index_prefix], model_name.plural, Searchkick.env].compact.join("_")
|
20
20
|
|
21
|
-
define_singleton_method(Searchkick.search_method_name) do |term = nil, options={}, &block|
|
21
|
+
define_singleton_method(Searchkick.search_method_name) do |term = nil, options = {}, &block|
|
22
22
|
searchkick_index.search_model(self, term, options, &block)
|
23
23
|
end
|
24
24
|
extend Searchkick::Reindex # legacy for Searchjoy
|
@@ -68,10 +68,10 @@ module Searchkick
|
|
68
68
|
if callbacks
|
69
69
|
callback_name = callbacks == :async ? :reindex_async : :reindex
|
70
70
|
if respond_to?(:after_commit)
|
71
|
-
after_commit callback_name, if: proc{ self.class.search_callbacks? }
|
71
|
+
after_commit callback_name, if: proc { self.class.search_callbacks? }
|
72
72
|
else
|
73
|
-
after_save callback_name, if: proc{ self.class.search_callbacks? }
|
74
|
-
after_destroy callback_name, if: proc{ self.class.search_callbacks? }
|
73
|
+
after_save callback_name, if: proc { self.class.search_callbacks? }
|
74
|
+
after_destroy callback_name, if: proc { self.class.search_callbacks? }
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
data/lib/searchkick/query.rb
CHANGED
@@ -15,26 +15,26 @@ module Searchkick
|
|
15
15
|
@term = term
|
16
16
|
@options = options
|
17
17
|
|
18
|
-
below12 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.2")
|
19
|
-
below14 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.4")
|
18
|
+
below12 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.2.0")
|
19
|
+
below14 = Gem::Version.new(Searchkick.server_version) < Gem::Version.new("1.4.0")
|
20
20
|
|
21
21
|
boost_fields = {}
|
22
22
|
fields =
|
23
23
|
if options[:fields]
|
24
24
|
if options[:autocomplete]
|
25
|
-
options[:fields].map{|f| "#{f}.autocomplete" }
|
25
|
+
options[:fields].map { |f| "#{f}.autocomplete" }
|
26
26
|
else
|
27
27
|
options[:fields].map do |value|
|
28
28
|
k, v = value.is_a?(Hash) ? value.to_a.first : [value, :word]
|
29
29
|
k2, boost = k.to_s.split("^", 2)
|
30
|
-
field = "#{k2}.#{v == :word ?
|
30
|
+
field = "#{k2}.#{v == :word ? 'analyzed' : v}"
|
31
31
|
boost_fields[field] = boost.to_f if boost
|
32
32
|
field
|
33
33
|
end
|
34
34
|
end
|
35
35
|
else
|
36
36
|
if options[:autocomplete]
|
37
|
-
(searchkick_options[:autocomplete] || []).map{|f| "#{f}.autocomplete" }
|
37
|
+
(searchkick_options[:autocomplete] || []).map { |f| "#{f}.autocomplete" }
|
38
38
|
else
|
39
39
|
["_all"]
|
40
40
|
end
|
@@ -44,7 +44,7 @@ module Searchkick
|
|
44
44
|
|
45
45
|
# pagination
|
46
46
|
page = [options[:page].to_i, 1].max
|
47
|
-
per_page = (options[:limit] || options[:per_page] ||
|
47
|
+
per_page = (options[:limit] || options[:per_page] || 100_000).to_i
|
48
48
|
padding = [options[:padding].to_i, 0].max
|
49
49
|
offset = options[:offset] || (page - 1) * per_page + padding
|
50
50
|
|
@@ -98,13 +98,13 @@ module Searchkick
|
|
98
98
|
boost: factor
|
99
99
|
}
|
100
100
|
|
101
|
-
if field == "_all"
|
101
|
+
if field == "_all" || field.end_with?(".analyzed")
|
102
102
|
shared_options[:cutoff_frequency] = 0.001 unless operator == "and"
|
103
103
|
qs.concat [
|
104
104
|
shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search"),
|
105
105
|
shared_options.merge(boost: 10 * factor, analyzer: "searchkick_search2")
|
106
106
|
]
|
107
|
-
misspellings = options.
|
107
|
+
misspellings = options.key?(:misspellings) ? options[:misspellings] : options[:mispellings] # why not?
|
108
108
|
if misspellings != false
|
109
109
|
edit_distance = (misspellings.is_a?(Hash) && (misspellings[:edit_distance] || misspellings[:distance])) || 1
|
110
110
|
qs.concat [
|
@@ -120,7 +120,7 @@ module Searchkick
|
|
120
120
|
qs << shared_options.merge(analyzer: analyzer)
|
121
121
|
end
|
122
122
|
|
123
|
-
queries.concat(qs.map{|q| {match: {field => q}} })
|
123
|
+
queries.concat(qs.map { |q| {match: {field => q}} })
|
124
124
|
end
|
125
125
|
|
126
126
|
payload = {
|
@@ -130,7 +130,7 @@ module Searchkick
|
|
130
130
|
}
|
131
131
|
end
|
132
132
|
|
133
|
-
if conversions_field
|
133
|
+
if conversions_field && options[:conversions] != false
|
134
134
|
# wrap payload in a bool query
|
135
135
|
script_score =
|
136
136
|
if below12
|
@@ -167,7 +167,7 @@ module Searchkick
|
|
167
167
|
|
168
168
|
boost_by = options[:boost_by] || {}
|
169
169
|
if boost_by.is_a?(Array)
|
170
|
-
boost_by = Hash[
|
170
|
+
boost_by = Hash[boost_by.map { |f| [f, {factor: 1}] }]
|
171
171
|
end
|
172
172
|
if options[:boost]
|
173
173
|
boost_by[options[:boost]] = {factor: 1}
|
@@ -191,14 +191,14 @@ module Searchkick
|
|
191
191
|
end
|
192
192
|
|
193
193
|
boost_where = options[:boost_where] || {}
|
194
|
-
if options[:user_id]
|
194
|
+
if options[:user_id] && personalize_field
|
195
195
|
boost_where[personalize_field] = options[:user_id]
|
196
196
|
end
|
197
197
|
if options[:personalize]
|
198
198
|
boost_where = boost_where.merge(options[:personalize])
|
199
199
|
end
|
200
200
|
boost_where.each do |field, value|
|
201
|
-
if value.is_a?(Array)
|
201
|
+
if value.is_a?(Array) && value.first.is_a?(Hash)
|
202
202
|
value.each do |value_factor|
|
203
203
|
value, factor = value_factor[:value], value_factor[:factor]
|
204
204
|
custom_filters << custom_filter(field, value, factor)
|
@@ -215,10 +215,10 @@ module Searchkick
|
|
215
215
|
boost_by_distance = options[:boost_by_distance]
|
216
216
|
if boost_by_distance
|
217
217
|
boost_by_distance = {function: :gauss, scale: "5mi"}.merge(boost_by_distance)
|
218
|
-
if !boost_by_distance[:field]
|
218
|
+
if !boost_by_distance[:field] || !boost_by_distance[:origin]
|
219
219
|
raise ArgumentError, "boost_by_distance requires :field and :origin"
|
220
220
|
end
|
221
|
-
function_params = boost_by_distance.select{|k,v| [:origin, :scale, :offset, :decay].include?(k) }
|
221
|
+
function_params = boost_by_distance.select { |k, v| [:origin, :scale, :offset, :decay].include?(k) }
|
222
222
|
function_params[:origin] = function_params[:origin].reverse
|
223
223
|
custom_filters << {
|
224
224
|
boost_by_distance[:function] => {
|
@@ -248,29 +248,41 @@ module Searchkick
|
|
248
248
|
if options[:order]
|
249
249
|
order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
|
250
250
|
# TODO id transformation for arrays
|
251
|
-
payload[:sort] = order.is_a?(Array) ? order : Hash[
|
251
|
+
payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? :_id : k, v] }]
|
252
252
|
end
|
253
253
|
|
254
254
|
# filters
|
255
255
|
filters = where_filters(options[:where])
|
256
256
|
if filters.any?
|
257
|
-
|
258
|
-
|
259
|
-
|
257
|
+
if options[:facets]
|
258
|
+
payload[:filter] = {
|
259
|
+
and: filters
|
260
|
+
}
|
261
|
+
else
|
262
|
+
# more efficient query if no facets
|
263
|
+
payload[:query] = {
|
264
|
+
filtered: {
|
265
|
+
query: payload[:query],
|
266
|
+
filter: {
|
267
|
+
and: filters
|
268
|
+
}
|
269
|
+
}
|
270
|
+
}
|
271
|
+
end
|
260
272
|
end
|
261
273
|
|
262
274
|
# facets
|
263
275
|
if options[:facets]
|
264
276
|
facets = options[:facets] || {}
|
265
277
|
if facets.is_a?(Array) # convert to more advanced syntax
|
266
|
-
facets = Hash[
|
278
|
+
facets = Hash[facets.map { |f| [f, {}] }]
|
267
279
|
end
|
268
280
|
|
269
281
|
payload[:facets] = {}
|
270
282
|
facets.each do |field, facet_options|
|
271
283
|
# ask for extra facets due to
|
272
284
|
# https://github.com/elasticsearch/elasticsearch/issues/1305
|
273
|
-
size = facet_options[:limit] ? facet_options[:limit] + 150 :
|
285
|
+
size = facet_options[:limit] ? facet_options[:limit] + 150 : 100_000
|
274
286
|
|
275
287
|
if facet_options[:ranges]
|
276
288
|
payload[:facets][field] = {
|
@@ -289,7 +301,7 @@ module Searchkick
|
|
289
301
|
else
|
290
302
|
payload[:facets][field] = {
|
291
303
|
terms: {
|
292
|
-
field: field,
|
304
|
+
field: facet_options[:field] || field,
|
293
305
|
size: size
|
294
306
|
}
|
295
307
|
}
|
@@ -300,7 +312,7 @@ module Searchkick
|
|
300
312
|
# offset is not possible
|
301
313
|
# http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html
|
302
314
|
|
303
|
-
facet_options.deep_merge!(where: options[:where].reject{|k| k == field}) if options[:smart_facets] == true
|
315
|
+
facet_options.deep_merge!(where: options[:where].reject { |k| k == field }) if options[:smart_facets] == true
|
304
316
|
facet_filters = where_filters(facet_options[:where])
|
305
317
|
if facet_filters.any?
|
306
318
|
payload[:facets][field][:facet_filter] = {
|
@@ -318,7 +330,7 @@ module Searchkick
|
|
318
330
|
|
319
331
|
# intersection
|
320
332
|
if options[:fields]
|
321
|
-
suggest_fields
|
333
|
+
suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first }
|
322
334
|
end
|
323
335
|
|
324
336
|
if suggest_fields.any?
|
@@ -336,11 +348,11 @@ module Searchkick
|
|
336
348
|
# highlight
|
337
349
|
if options[:highlight]
|
338
350
|
payload[:highlight] = {
|
339
|
-
fields: Hash[
|
351
|
+
fields: Hash[fields.map { |f| [f, {}] }]
|
340
352
|
}
|
341
353
|
|
342
354
|
if options[:highlight].is_a?(Hash)
|
343
|
-
if tag = options[:highlight][:tag]
|
355
|
+
if (tag = options[:highlight][:tag])
|
344
356
|
payload[:highlight][:pre_tags] = [tag]
|
345
357
|
payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")]
|
346
358
|
end
|
@@ -364,8 +376,13 @@ module Searchkick
|
|
364
376
|
payload[:fields] = []
|
365
377
|
end
|
366
378
|
|
367
|
-
if options[:type]
|
368
|
-
@type = [options[:type] || klass].flatten.map{|v| searchkick_index.klass_document_type(v) }
|
379
|
+
if options[:type] || klass != searchkick_klass
|
380
|
+
@type = [options[:type] || klass].flatten.map { |v| searchkick_index.klass_document_type(v) }
|
381
|
+
end
|
382
|
+
|
383
|
+
# routing
|
384
|
+
if options[:routing]
|
385
|
+
@routing = options[:routing]
|
369
386
|
end
|
370
387
|
end
|
371
388
|
|
@@ -395,6 +412,7 @@ module Searchkick
|
|
395
412
|
body: body
|
396
413
|
}
|
397
414
|
params.merge!(type: @type) if @type
|
415
|
+
params.merge!(routing: @routing) if @routing
|
398
416
|
params
|
399
417
|
end
|
400
418
|
|
@@ -405,10 +423,10 @@ module Searchkick
|
|
405
423
|
status_code = e.message[1..3].to_i
|
406
424
|
if status_code == 404
|
407
425
|
raise MissingIndexError, "Index missing - run #{searchkick_klass.name}.reindex"
|
408
|
-
elsif status_code == 500
|
409
|
-
e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]")
|
410
|
-
e.message.include?("No query registered for [multi_match]")
|
411
|
-
e.message.include?("[match] query does not support [cutoff_frequency]]")
|
426
|
+
elsif status_code == 500 && (
|
427
|
+
e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") ||
|
428
|
+
e.message.include?("No query registered for [multi_match]") ||
|
429
|
+
e.message.include?("[match] query does not support [cutoff_frequency]]") ||
|
412
430
|
e.message.include?("No query registered for [function_score]]")
|
413
431
|
)
|
414
432
|
|
@@ -430,7 +448,7 @@ module Searchkick
|
|
430
448
|
field = field.to_s
|
431
449
|
facet = response["facets"][field]
|
432
450
|
response["facets"][field]["terms"] = facet["terms"].first(limit)
|
433
|
-
response["facets"][field]["other"] = facet["total"] - facet["terms"].sum{|term| term["count"] }
|
451
|
+
response["facets"][field]["other"] = facet["total"] - facet["terms"].sum { |term| term["count"] }
|
434
452
|
end
|
435
453
|
|
436
454
|
opts = {
|
@@ -453,7 +471,7 @@ module Searchkick
|
|
453
471
|
|
454
472
|
if field == :or
|
455
473
|
value.each do |or_clause|
|
456
|
-
filters << {or: or_clause.map{|or_statement| {and: where_filters(or_statement)} }}
|
474
|
+
filters << {or: or_clause.map { |or_statement| {and: where_filters(or_statement)} }}
|
457
475
|
end
|
458
476
|
else
|
459
477
|
# expand ranges
|
@@ -507,7 +525,7 @@ module Searchkick
|
|
507
525
|
raise "Unknown where operator"
|
508
526
|
end
|
509
527
|
# issue 132
|
510
|
-
if existing = filters.find{ |f| f[:range] && f[:range][field] }
|
528
|
+
if (existing = filters.find { |f| f[:range] && f[:range][field] })
|
511
529
|
existing[:range][field].merge!(range_query)
|
512
530
|
else
|
513
531
|
filters << {range: {field => range_query}}
|
@@ -524,13 +542,15 @@ module Searchkick
|
|
524
542
|
|
525
543
|
def term_filters(field, value)
|
526
544
|
if value.is_a?(Array) # in query
|
527
|
-
if value.any?
|
528
|
-
{or:
|
545
|
+
if value.any?(&:nil?)
|
546
|
+
{or: [term_filters(field, nil), term_filters(field, value.compact)]}
|
529
547
|
else
|
530
|
-
{
|
548
|
+
{in: {field => value}}
|
531
549
|
end
|
532
550
|
elsif value.nil?
|
533
551
|
{missing: {"field" => field, existence: true, null_value: true}}
|
552
|
+
elsif value.is_a?(Regexp)
|
553
|
+
{regexp: {field => {value: value.source}}}
|
534
554
|
else
|
535
555
|
{term: {field => value}}
|
536
556
|
end
|
@@ -10,7 +10,7 @@ module Searchkick
|
|
10
10
|
model = @klass.constantize
|
11
11
|
record = model.find(@id) rescue nil # TODO fix lazy coding
|
12
12
|
index = model.searchkick_index
|
13
|
-
if !record
|
13
|
+
if !record || !record.should_index?
|
14
14
|
# hacky
|
15
15
|
record ||= model.new
|
16
16
|
record.id = @id
|
@@ -6,7 +6,7 @@ module Searchkick
|
|
6
6
|
model = klass.constantize
|
7
7
|
record = model.find(id) rescue nil # TODO fix lazy coding
|
8
8
|
index = model.searchkick_index
|
9
|
-
if !record
|
9
|
+
if !record || !record.should_index?
|
10
10
|
# hacky
|
11
11
|
record ||= model.new
|
12
12
|
record.id = id
|
data/lib/searchkick/results.rb
CHANGED
@@ -21,10 +21,10 @@ module Searchkick
|
|
21
21
|
# results can have different types
|
22
22
|
results = {}
|
23
23
|
|
24
|
-
hits.group_by{|hit, i| hit["_type"] }.each do |type, grouped_hits|
|
24
|
+
hits.group_by { |hit, i| hit["_type"] }.each do |type, grouped_hits|
|
25
25
|
records = type.camelize.constantize
|
26
26
|
if options[:includes]
|
27
|
-
if defined?(NoBrainer::Document)
|
27
|
+
if defined?(NoBrainer::Document) && records < NoBrainer::Document
|
28
28
|
records = records.preload(options[:includes])
|
29
29
|
else
|
30
30
|
records = records.includes(options[:includes])
|
@@ -35,7 +35,7 @@ module Searchkick
|
|
35
35
|
|
36
36
|
# sort
|
37
37
|
hits.map do |hit|
|
38
|
-
results[hit["_type"]].find{|r| r.id.to_s == hit["_id"].to_s }
|
38
|
+
results[hit["_type"]].find { |r| r.id.to_s == hit["_id"].to_s }
|
39
39
|
end.compact
|
40
40
|
else
|
41
41
|
hits.map do |hit|
|
@@ -54,7 +54,7 @@ module Searchkick
|
|
54
54
|
|
55
55
|
def suggestions
|
56
56
|
if response["suggest"]
|
57
|
-
response["suggest"].values.flat_map{|v| v.first["options"] }.sort_by{|o| -o["score"] }.map{|o| o["text"] }.uniq
|
57
|
+
response["suggest"].values.flat_map { |v| v.first["options"] }.sort_by { |o| -o["score"] }.map { |o| o["text"] }.uniq
|
58
58
|
else
|
59
59
|
raise "Pass `suggest: true` to the search method for suggestions"
|
60
60
|
end
|
@@ -68,7 +68,7 @@ module Searchkick
|
|
68
68
|
each_with_hit.map do |model, hit|
|
69
69
|
details = {}
|
70
70
|
if hit["highlight"]
|
71
|
-
details[:highlight] = Hash[
|
71
|
+
details[:highlight] = Hash[hit["highlight"].map { |k, v| [(options[:json] ? k : k.sub(/\.analyzed\z/, "")).to_sym, v.first] }]
|
72
72
|
end
|
73
73
|
[model, details]
|
74
74
|
end
|
@@ -138,18 +138,18 @@ module Searchkick
|
|
138
138
|
private
|
139
139
|
|
140
140
|
def results_query(records, grouped_hits)
|
141
|
-
if records.respond_to?(:primary_key)
|
141
|
+
if records.respond_to?(:primary_key) && records.primary_key
|
142
142
|
# ActiveRecord
|
143
|
-
records.where(records.primary_key => grouped_hits.map{|hit| hit["_id"] }).to_a
|
144
|
-
elsif records.respond_to?(:all)
|
143
|
+
records.where(records.primary_key => grouped_hits.map { |hit| hit["_id"] }).to_a
|
144
|
+
elsif records.respond_to?(:all) && records.all.respond_to?(:for_ids)
|
145
145
|
# Mongoid 2
|
146
|
-
records.all.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
|
146
|
+
records.all.for_ids(grouped_hits.map { |hit| hit["_id"] }).to_a
|
147
147
|
elsif records.respond_to?(:queryable)
|
148
148
|
# Mongoid 3+
|
149
|
-
records.queryable.for_ids(grouped_hits.map{|hit| hit["_id"] }).to_a
|
150
|
-
elsif records.respond_to?(:unscoped)
|
149
|
+
records.queryable.for_ids(grouped_hits.map { |hit| hit["_id"] }).to_a
|
150
|
+
elsif records.respond_to?(:unscoped) && records.all.respond_to?(:preload)
|
151
151
|
# Nobrainer
|
152
|
-
records.unscoped.where(:id.in => grouped_hits.map{|hit| hit["_id"] }).to_a
|
152
|
+
records.unscoped.where(:id.in => grouped_hits.map { |hit| hit["_id"] }).to_a
|
153
153
|
else
|
154
154
|
raise "Not sure how to load records"
|
155
155
|
end
|
data/lib/searchkick/tasks.rb
CHANGED
@@ -3,13 +3,13 @@ require "rake"
|
|
3
3
|
namespace :searchkick do
|
4
4
|
|
5
5
|
desc "reindex model"
|
6
|
-
task :
|
6
|
+
task reindex: :environment do
|
7
7
|
if ENV["CLASS"]
|
8
8
|
klass = ENV["CLASS"].constantize rescue nil
|
9
9
|
if klass
|
10
10
|
klass.reindex
|
11
11
|
else
|
12
|
-
abort "Could not find class: #{ENV[
|
12
|
+
abort "Could not find class: #{ENV['CLASS']}"
|
13
13
|
end
|
14
14
|
else
|
15
15
|
abort "USAGE: rake searchkick:reindex CLASS=Product"
|
@@ -20,7 +20,7 @@ namespace :searchkick do
|
|
20
20
|
|
21
21
|
namespace :reindex do
|
22
22
|
desc "reindex all models"
|
23
|
-
task :
|
23
|
+
task all: :environment do
|
24
24
|
Rails.application.eager_load!
|
25
25
|
Searchkick.models.each do |model|
|
26
26
|
puts "Reindexing #{model.name}..."
|
data/lib/searchkick/version.rb
CHANGED
data/lib/searchkick.rb
CHANGED
@@ -24,13 +24,11 @@ module Searchkick
|
|
24
24
|
class InvalidQueryError < Elasticsearch::Transport::Transport::Errors::BadRequest; end
|
25
25
|
|
26
26
|
class << self
|
27
|
-
attr_accessor :callbacks
|
28
27
|
attr_accessor :search_method_name
|
29
28
|
attr_accessor :wordnet_path
|
30
29
|
attr_accessor :timeout
|
31
30
|
attr_accessor :models
|
32
31
|
end
|
33
|
-
self.callbacks = true
|
34
32
|
self.search_method_name = :search
|
35
33
|
self.wordnet_path = "/var/lib/wn_s.pl"
|
36
34
|
self.timeout = 10
|
@@ -44,8 +42,8 @@ module Searchkick
|
|
44
42
|
)
|
45
43
|
end
|
46
44
|
|
47
|
-
|
48
|
-
|
45
|
+
class << self
|
46
|
+
attr_writer :client
|
49
47
|
end
|
50
48
|
|
51
49
|
def self.server_version
|
@@ -53,15 +51,15 @@ module Searchkick
|
|
53
51
|
end
|
54
52
|
|
55
53
|
def self.enable_callbacks
|
56
|
-
|
54
|
+
Thread.current[:searchkick_callbacks_enabled] = true
|
57
55
|
end
|
58
56
|
|
59
57
|
def self.disable_callbacks
|
60
|
-
|
58
|
+
Thread.current[:searchkick_callbacks_enabled] = false
|
61
59
|
end
|
62
60
|
|
63
61
|
def self.callbacks?
|
64
|
-
|
62
|
+
Thread.current[:searchkick_callbacks_enabled].nil? || Thread.current[:searchkick_callbacks_enabled]
|
65
63
|
end
|
66
64
|
|
67
65
|
def self.env
|
data/searchkick.gemspec
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require "searchkick/version"
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "searchkick"
|
8
8
|
spec.version = Searchkick::VERSION
|
9
9
|
spec.authors = ["Andrew Kane"]
|
10
10
|
spec.email = ["andrew@chartkick.com"]
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
11
|
+
spec.description = "Intelligent search made easy"
|
12
|
+
spec.summary = "Searchkick learns what your users are looking for. As more people search, it gets smarter and the results get better. It’s friendly for developers - and magical for your users."
|
13
13
|
spec.homepage = "https://github.com/ankane/searchkick"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
-
spec.files = `git ls-files`.split(
|
16
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
data/test/facets_test.rb
CHANGED
@@ -21,6 +21,12 @@ class TestFacets < Minitest::Test
|
|
21
21
|
assert_equal ({1 => 1}), store_facet(facets: {store_id: {where: {in_stock: true}}})
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_field
|
25
|
+
assert_equal ({1 => 1, 2 => 2}), store_facet(facets: {store_id: {}})
|
26
|
+
assert_equal ({1 => 1, 2 => 2}), store_facet(facets: {store_id: {field: "store_id"}})
|
27
|
+
assert_equal ({1 => 1, 2 => 2}), store_facet({facets: {store_id_new: {field: "store_id"}}}, "store_id_new")
|
28
|
+
end
|
29
|
+
|
24
30
|
def test_limit
|
25
31
|
facet = Product.search("Product", facets: {store_id: {limit: 1}}).facets["store_id"]
|
26
32
|
assert_equal 1, facet["terms"].size
|
@@ -78,8 +84,8 @@ class TestFacets < Minitest::Test
|
|
78
84
|
|
79
85
|
protected
|
80
86
|
|
81
|
-
def store_facet(options)
|
82
|
-
Hash[
|
87
|
+
def store_facet(options, facet_key="store_id")
|
88
|
+
Hash[Product.search("Product", options).facets[facet_key]["terms"].map { |v| [v["term"], v["count"]] }]
|
83
89
|
end
|
84
90
|
|
85
91
|
end
|
data/test/index_test.rb
CHANGED
@@ -75,24 +75,32 @@ class TestIndex < Minitest::Test
|
|
75
75
|
def test_bad_mapping
|
76
76
|
Product.searchkick_index.delete
|
77
77
|
store_names ["Product A"]
|
78
|
-
assert_raises(Searchkick::InvalidQueryError){ Product.search "test" }
|
78
|
+
assert_raises(Searchkick::InvalidQueryError) { Product.search "test" }
|
79
|
+
ensure
|
80
|
+
Product.reindex
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_remove_blank_id
|
84
|
+
store_names ["Product A"]
|
85
|
+
Product.searchkick_index.remove(OpenStruct.new)
|
86
|
+
assert_search "product", ["Product A"]
|
79
87
|
ensure
|
80
88
|
Product.reindex
|
81
89
|
end
|
82
90
|
|
83
91
|
def test_missing_index
|
84
|
-
assert_raises(Searchkick::MissingIndexError){ Product.search "test", index_name: "not_found" }
|
92
|
+
assert_raises(Searchkick::MissingIndexError) { Product.search "test", index_name: "not_found" }
|
85
93
|
end
|
86
94
|
|
87
95
|
def test_unsupported_version
|
88
|
-
raises_exception =
|
96
|
+
raises_exception = ->(s) { raise Elasticsearch::Transport::Transport::Error.new("[500] No query registered for [multi_match]") }
|
89
97
|
Searchkick.client.stub :search, raises_exception do
|
90
|
-
assert_raises(Searchkick::UnsupportedVersionError){ Product.search("test") }
|
98
|
+
assert_raises(Searchkick::UnsupportedVersionError) { Product.search("test") }
|
91
99
|
end
|
92
100
|
end
|
93
101
|
|
94
102
|
def test_invalid_query
|
95
|
-
assert_raises(Searchkick::InvalidQueryError){ Product.search(query: {}) }
|
103
|
+
assert_raises(Searchkick::InvalidQueryError) { Product.search(query: {}) }
|
96
104
|
end
|
97
105
|
|
98
106
|
if defined?(ActiveRecord)
|
data/test/model_test.rb
CHANGED
data/test/reindex_v2_job_test.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class TestRouting < Minitest::Test
|
4
|
+
|
5
|
+
def test_routing_query
|
6
|
+
query = Store.search("Dollar Tree", routing: "Dollar Tree", execute: false)
|
7
|
+
assert_equal query.params[:routing], "Dollar Tree"
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_routing_mappings
|
11
|
+
index_options = Store.searchkick_index.index_options
|
12
|
+
assert_equal index_options[:mappings][:_default_][:_routing], {required: true, path: "name"}
|
13
|
+
end
|
14
|
+
end
|
data/test/sql_test.rb
CHANGED
@@ -8,7 +8,7 @@ class TestSql < Minitest::Test
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def test_no_limit
|
11
|
-
names = 20.times.map{|i| "Product #{i}" }
|
11
|
+
names = 20.times.map { |i| "Product #{i}" }
|
12
12
|
store_names names
|
13
13
|
assert_search "product", names
|
14
14
|
end
|
@@ -57,7 +57,7 @@ class TestSql < Minitest::Test
|
|
57
57
|
{name: "Product A", store_id: 1, in_stock: true, backordered: true, created_at: now, orders_count: 4, user_ids: [1, 2, 3]},
|
58
58
|
{name: "Product B", store_id: 2, in_stock: true, backordered: false, created_at: now - 1, orders_count: 3, user_ids: [1]},
|
59
59
|
{name: "Product C", store_id: 3, in_stock: false, backordered: true, created_at: now - 2, orders_count: 2, user_ids: [1, 3]},
|
60
|
-
{name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1}
|
60
|
+
{name: "Product D", store_id: 4, in_stock: false, backordered: false, created_at: now - 3, orders_count: 1}
|
61
61
|
]
|
62
62
|
assert_search "product", ["Product A", "Product B"], where: {in_stock: true}
|
63
63
|
# date
|
@@ -85,7 +85,7 @@ class TestSql < Minitest::Test
|
|
85
85
|
assert_search "product", ["Product A", "Product C"], where: {user_ids: {all: [1, 3]}}
|
86
86
|
assert_search "product", [], where: {user_ids: {all: [1, 2, 3, 4]}}
|
87
87
|
# any / nested terms
|
88
|
-
assert_search "product", ["Product B", "Product C"], where: {user_ids: {not: [2], in: [1,3]}}
|
88
|
+
assert_search "product", ["Product B", "Product C"], where: {user_ids: {not: [2], in: [1, 3]}}
|
89
89
|
# not / exists
|
90
90
|
assert_search "product", ["Product D"], where: {user_ids: nil}
|
91
91
|
assert_search "product", ["Product A", "Product B", "Product C"], where: {user_ids: {not: nil}}
|
@@ -93,6 +93,11 @@ class TestSql < Minitest::Test
|
|
93
93
|
assert_search "product", ["Product B"], where: {user_ids: {not: [3, nil]}}
|
94
94
|
end
|
95
95
|
|
96
|
+
def test_regexp
|
97
|
+
store_names ["Product A"]
|
98
|
+
assert_search "*", ["Product A"], where: {name: /Pro.+/}
|
99
|
+
end
|
100
|
+
|
96
101
|
def test_where_string
|
97
102
|
store [
|
98
103
|
{name: "Product A", color: "RED"}
|
@@ -288,7 +293,7 @@ class TestSql < Minitest::Test
|
|
288
293
|
def test_select
|
289
294
|
store [{name: "Product A", store_id: 1}]
|
290
295
|
result = Product.search("product", load: false, select: [:name, :store_id]).first
|
291
|
-
assert_equal %w[id name store_id], result.keys.reject{|k| k.start_with?("_") }.sort
|
296
|
+
assert_equal %w[id name store_id], result.keys.reject { |k| k.start_with?("_") }.sort
|
292
297
|
assert_equal ["Product A"], result.name # this is not great
|
293
298
|
end
|
294
299
|
|
@@ -312,7 +317,7 @@ class TestSql < Minitest::Test
|
|
312
317
|
end
|
313
318
|
|
314
319
|
# TODO see if Mongoid is loaded
|
315
|
-
unless defined?(Mongoid)
|
320
|
+
unless defined?(Mongoid) || defined?(NoBrainer)
|
316
321
|
def test_include
|
317
322
|
store_names ["Product A"]
|
318
323
|
assert Product.search("product", include: [:store]).first.association(:store).loaded?
|
data/test/suggest_test.rb
CHANGED
@@ -19,13 +19,13 @@ class TestSuggest < Minitest::Test
|
|
19
19
|
|
20
20
|
def test_without_option
|
21
21
|
store_names ["hi"] # needed to prevent ElasticsearchException - seed 668
|
22
|
-
assert_raises(RuntimeError){ Product.search("hi").suggestions }
|
22
|
+
assert_raises(RuntimeError) { Product.search("hi").suggestions }
|
23
23
|
end
|
24
24
|
|
25
25
|
def test_multiple_fields
|
26
26
|
store [
|
27
27
|
{name: "Shark", color: "Sharp"},
|
28
|
-
{name: "Shark", color: "Sharp"}
|
28
|
+
{name: "Shark", color: "Sharp"}
|
29
29
|
]
|
30
30
|
assert_suggest_all "shar", ["shark", "sharp"]
|
31
31
|
end
|
data/test/test_helper.rb
CHANGED
@@ -8,9 +8,11 @@ ENV["RACK_ENV"] = "test"
|
|
8
8
|
|
9
9
|
Minitest::Test = Minitest::Unit::TestCase unless defined?(Minitest::Test)
|
10
10
|
|
11
|
-
File.delete("elasticsearch.log") if File.
|
11
|
+
File.delete("elasticsearch.log") if File.exist?("elasticsearch.log")
|
12
12
|
Searchkick.client.transport.logger = Logger.new("elasticsearch.log")
|
13
13
|
|
14
|
+
puts "Running against Elasticsearch #{Searchkick.server_version}"
|
15
|
+
|
14
16
|
I18n.config.enforce_available_locales = true
|
15
17
|
|
16
18
|
ActiveJob::Base.logger = nil if defined?(ActiveJob)
|
@@ -26,7 +28,7 @@ if defined?(Mongoid)
|
|
26
28
|
module BSON
|
27
29
|
class ObjectId
|
28
30
|
def <=>(other)
|
29
|
-
|
31
|
+
data <=> other.data
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|
@@ -210,12 +212,15 @@ class Product
|
|
210
212
|
end
|
211
213
|
|
212
214
|
class Store
|
213
|
-
searchkick
|
214
|
-
|
215
|
-
|
216
|
-
|
215
|
+
searchkick \
|
216
|
+
routing: :name,
|
217
|
+
merge_mappings: true,
|
218
|
+
mappings: {
|
219
|
+
store: {
|
220
|
+
properties: {
|
221
|
+
name: {type: "string", analyzer: "keyword"},
|
222
|
+
}
|
217
223
|
}
|
218
|
-
}
|
219
224
|
}
|
220
225
|
end
|
221
226
|
|
@@ -223,7 +228,7 @@ class Animal
|
|
223
228
|
searchkick \
|
224
229
|
autocomplete: [:name],
|
225
230
|
suggest: [:name],
|
226
|
-
index_name: -> { "#{
|
231
|
+
index_name: -> { "#{name.tableize}-#{Date.today.year}" }
|
227
232
|
# wordnet: true
|
228
233
|
end
|
229
234
|
|
@@ -252,7 +257,7 @@ class Minitest::Test
|
|
252
257
|
end
|
253
258
|
|
254
259
|
def store_names(names, klass = Product)
|
255
|
-
store names.map{|name| {name: name} }, klass
|
260
|
+
store names.map { |name| {name: name} }, klass
|
256
261
|
end
|
257
262
|
|
258
263
|
# no order
|
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: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -139,6 +139,7 @@ files:
|
|
139
139
|
- test/query_test.rb
|
140
140
|
- test/reindex_job_test.rb
|
141
141
|
- test/reindex_v2_job_test.rb
|
142
|
+
- test/routing_test.rb
|
142
143
|
- test/should_index_test.rb
|
143
144
|
- test/similar_test.rb
|
144
145
|
- test/sql_test.rb
|
@@ -183,9 +184,11 @@ test_files:
|
|
183
184
|
- test/query_test.rb
|
184
185
|
- test/reindex_job_test.rb
|
185
186
|
- test/reindex_v2_job_test.rb
|
187
|
+
- test/routing_test.rb
|
186
188
|
- test/should_index_test.rb
|
187
189
|
- test/similar_test.rb
|
188
190
|
- test/sql_test.rb
|
189
191
|
- test/suggest_test.rb
|
190
192
|
- test/synonyms_test.rb
|
191
193
|
- test/test_helper.rb
|
194
|
+
has_rdoc:
|