searchkick 1.2.0 → 1.2.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 +8 -0
- data/README.md +29 -11
- data/lib/searchkick.rb +11 -1
- data/lib/searchkick/index.rb +23 -5
- data/lib/searchkick/logging.rb +56 -7
- data/lib/searchkick/model.rb +2 -2
- data/lib/searchkick/query.rb +67 -44
- data/lib/searchkick/results.rb +8 -2
- data/lib/searchkick/version.rb +1 -1
- data/test/aggs_test.rb +1 -1
- data/test/multi_search_test.rb +22 -0
- data/test/routing_test.rb +1 -3
- data/test/sql_test.rb +81 -0
- data/test/test_helper.rb +11 -1
- 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: 750ced99cb1b0929710eba206bde31907f0d9544
|
4
|
+
data.tar.gz: 454d078f8900948def90b1a88b40bde8aec561b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5139a9a1f326fa97f58f09fc52d5988ff14661e8e4ec9a3240c6e4c10160c577d5b6231caab980101951a37cbdbbf5631e4df70c5060f5a21bd5beb9ae58dfec
|
7
|
+
data.tar.gz: 841981f0feaafcbeb39295282b018d4672bc41c115f31ce3d1b63964345197942082aac4eeff5b43190669ef81ad5a0fd3b0ac8afb5f271154058f0045fb3760
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 1.2.1
|
2
|
+
|
3
|
+
- Added `multi_search` method
|
4
|
+
- Added support for routing for Elasticsearch 2
|
5
|
+
- Added support for `search_document_id` and `search_document_type` in models
|
6
|
+
- Fixed error with instrumentation for searching multiple models
|
7
|
+
- Fixed instrumentation for bulk updates
|
8
|
+
|
1
9
|
## 1.2.0
|
2
10
|
|
3
11
|
- Fixed deprecation warnings with `alias_method_chain`
|
data/README.md
CHANGED
@@ -338,12 +338,13 @@ Control what data is indexed with the `search_data` method. Call `Product.reinde
|
|
338
338
|
|
339
339
|
```ruby
|
340
340
|
class Product < ActiveRecord::Base
|
341
|
+
belongs_to :department
|
342
|
+
|
341
343
|
def search_data
|
342
|
-
as_json only: [:name, :active]
|
343
|
-
# or equivalently
|
344
344
|
{
|
345
345
|
name: name,
|
346
|
-
|
346
|
+
department_name: department.name,
|
347
|
+
on_sale: sale_price.present?
|
347
348
|
}
|
348
349
|
end
|
349
350
|
end
|
@@ -353,7 +354,7 @@ Searchkick uses `find_in_batches` to import documents. To eager load associatio
|
|
353
354
|
|
354
355
|
```ruby
|
355
356
|
class Product < ActiveRecord::Base
|
356
|
-
scope :search_import, -> { includes(:
|
357
|
+
scope :search_import, -> { includes(:department) }
|
357
358
|
end
|
358
359
|
```
|
359
360
|
|
@@ -833,18 +834,20 @@ City.search "san", boost_by_distance: {field: :location, origin: {lat: 37, lon:
|
|
833
834
|
|
834
835
|
Searchkick supports [Elasticsearch’s routing feature](https://www.elastic.co/blog/customizing-your-document-routing).
|
835
836
|
|
836
|
-
**Note:** Routing is not yet supported for Elasticsearch 2.0.
|
837
|
-
|
838
837
|
```ruby
|
839
|
-
class
|
840
|
-
searchkick routing:
|
838
|
+
class Business < ActiveRecord::Base
|
839
|
+
searchkick routing: true
|
840
|
+
|
841
|
+
def searchkick_routing
|
842
|
+
city_id
|
843
|
+
end
|
841
844
|
end
|
842
845
|
```
|
843
846
|
|
844
847
|
Reindex and search with:
|
845
848
|
|
846
849
|
```ruby
|
847
|
-
|
850
|
+
Business.search "ice cream", routing: params[:city_id]
|
848
851
|
```
|
849
852
|
|
850
853
|
## Inheritance
|
@@ -961,6 +964,7 @@ gem 'faraday_middleware-aws-signers-v4'
|
|
961
964
|
and add to your initializer:
|
962
965
|
|
963
966
|
```ruby
|
967
|
+
require "faraday_middleware/aws_signers_v4"
|
964
968
|
Searchkick.client =
|
965
969
|
Elasticsearch::Client.new(
|
966
970
|
url: ENV["ELASTICSEARCH_URL"],
|
@@ -1088,6 +1092,20 @@ products =
|
|
1088
1092
|
end
|
1089
1093
|
```
|
1090
1094
|
|
1095
|
+
### Multi Search
|
1096
|
+
|
1097
|
+
To batch search requests for performance, use:
|
1098
|
+
|
1099
|
+
```ruby
|
1100
|
+
fresh_products = Product.search("fresh", execute: false)
|
1101
|
+
frozen_products = Product.search("frozen", execute: false)
|
1102
|
+
Searchkick.multi_search([fresh_products, frozen_products])
|
1103
|
+
```
|
1104
|
+
|
1105
|
+
Then use `fresh_products` and `frozen_products` as typical results.
|
1106
|
+
|
1107
|
+
**Note:** Errors are not raised as with single requests. Use the `error` method on each query to check for errors. Also, the `below` option for misspellings is ignored.
|
1108
|
+
|
1091
1109
|
## Reference
|
1092
1110
|
|
1093
1111
|
Reindex one record
|
@@ -1234,7 +1252,7 @@ end
|
|
1234
1252
|
|
1235
1253
|
Reindex conditionally
|
1236
1254
|
|
1237
|
-
**Note:** With ActiveRecord, use this feature with caution - [transaction rollbacks can cause data
|
1255
|
+
**Note:** With ActiveRecord, use this feature with caution - [transaction rollbacks can cause data inconsistencies](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-model/README.md#custom-callbacks)
|
1238
1256
|
|
1239
1257
|
```ruby
|
1240
1258
|
class Product < ActiveRecord::Base
|
@@ -1246,7 +1264,7 @@ class Product < ActiveRecord::Base
|
|
1246
1264
|
end
|
1247
1265
|
```
|
1248
1266
|
|
1249
|
-
Search multiple models
|
1267
|
+
Search multiple models
|
1250
1268
|
|
1251
1269
|
```ruby
|
1252
1270
|
Searchkick.search "milk", index_name: [Product, Category]
|
data/lib/searchkick.rb
CHANGED
@@ -9,7 +9,7 @@ require "searchkick/reindex_job"
|
|
9
9
|
require "searchkick/model"
|
10
10
|
require "searchkick/tasks"
|
11
11
|
require "searchkick/middleware"
|
12
|
-
require "searchkick/logging" if defined?(
|
12
|
+
require "searchkick/logging" if defined?(ActiveSupport::Notifications)
|
13
13
|
|
14
14
|
# background jobs
|
15
15
|
begin
|
@@ -138,6 +138,16 @@ module Searchkick
|
|
138
138
|
query.execute
|
139
139
|
end
|
140
140
|
end
|
141
|
+
|
142
|
+
def self.multi_search(queries)
|
143
|
+
if queries.any?
|
144
|
+
responses = client.msearch(body: queries.flat_map { |q| [q.params.except(:body), q.body] })["responses"]
|
145
|
+
queries.each_with_index do |query, i|
|
146
|
+
query.handle_response(responses[i])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
nil
|
150
|
+
end
|
141
151
|
end
|
142
152
|
|
143
153
|
# TODO find better ActiveModel hook
|
data/lib/searchkick/index.rb
CHANGED
@@ -53,14 +53,24 @@ module Searchkick
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def bulk_delete(records)
|
56
|
-
Searchkick.queue_items(records.reject { |r| r.id.blank? }.map { |r| {delete:
|
56
|
+
Searchkick.queue_items(records.reject { |r| r.id.blank? }.map { |r| {delete: record_data(r)} })
|
57
57
|
end
|
58
58
|
|
59
59
|
def bulk_index(records)
|
60
|
-
Searchkick.queue_items(records.map { |r| {index:
|
60
|
+
Searchkick.queue_items(records.map { |r| {index: record_data(r).merge(data: search_data(r))} })
|
61
61
|
end
|
62
62
|
alias_method :import, :bulk_index
|
63
63
|
|
64
|
+
def record_data(r)
|
65
|
+
data = {
|
66
|
+
_index: name,
|
67
|
+
_id: search_id(r),
|
68
|
+
_type: document_type(r)
|
69
|
+
}
|
70
|
+
data[:_routing] = r.search_routing if r.respond_to?(:search_routing)
|
71
|
+
data
|
72
|
+
end
|
73
|
+
|
64
74
|
def retrieve(record)
|
65
75
|
client.get(
|
66
76
|
index: name,
|
@@ -463,7 +473,10 @@ module Searchkick
|
|
463
473
|
|
464
474
|
routing = {}
|
465
475
|
if options[:routing]
|
466
|
-
routing = {required: true
|
476
|
+
routing = {required: true}
|
477
|
+
unless options[:routing] == true
|
478
|
+
routing[:path] = options[:routing].to_s
|
479
|
+
end
|
467
480
|
end
|
468
481
|
|
469
482
|
dynamic_fields = {
|
@@ -533,11 +546,16 @@ module Searchkick
|
|
533
546
|
end
|
534
547
|
|
535
548
|
def document_type(record)
|
536
|
-
|
549
|
+
if record.respond_to?(:search_document_type)
|
550
|
+
record.search_document_type
|
551
|
+
else
|
552
|
+
klass_document_type(record.class)
|
553
|
+
end
|
537
554
|
end
|
538
555
|
|
539
556
|
def search_id(record)
|
540
|
-
record.
|
557
|
+
id = record.respond_to?(:search_document_id) ? record.search_document_id : record.id
|
558
|
+
id.is_a?(Numeric) ? id : id.to_s
|
541
559
|
end
|
542
560
|
|
543
561
|
def search_data(record)
|
data/lib/searchkick/logging.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# based on https://gist.github.com/mnutt/566725
|
2
|
+
require "active_support/core_ext/module/attr_internal"
|
2
3
|
|
3
4
|
module Searchkick
|
4
5
|
module QueryWithInstrumentation
|
5
6
|
def execute_search
|
7
|
+
name = searchkick_klass ? "#{searchkick_klass.name} Search" : "Search"
|
6
8
|
event = {
|
7
|
-
name:
|
9
|
+
name: name,
|
8
10
|
query: params
|
9
11
|
}
|
10
12
|
ActiveSupport::Notifications.instrument("search.searchkick", event) do
|
@@ -19,19 +21,27 @@ module Searchkick
|
|
19
21
|
name: "#{record.searchkick_klass.name} Store",
|
20
22
|
id: search_id(record)
|
21
23
|
}
|
22
|
-
|
23
|
-
super
|
24
|
+
if Searchkick.callbacks_value == :bulk
|
25
|
+
super
|
26
|
+
else
|
27
|
+
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
28
|
+
super
|
29
|
+
end
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
27
|
-
|
28
33
|
def remove(record)
|
34
|
+
name = record && record.searchkick_klass ? "#{record.searchkick_klass.name} Remove" : "Remove"
|
29
35
|
event = {
|
30
|
-
name:
|
36
|
+
name: name,
|
31
37
|
id: search_id(record)
|
32
38
|
}
|
33
|
-
|
34
|
-
super
|
39
|
+
if Searchkick.callbacks_value == :bulk
|
40
|
+
super
|
41
|
+
else
|
42
|
+
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
43
|
+
super
|
44
|
+
end
|
35
45
|
end
|
36
46
|
end
|
37
47
|
|
@@ -48,6 +58,32 @@ module Searchkick
|
|
48
58
|
end
|
49
59
|
end
|
50
60
|
|
61
|
+
module SearchkickWithInstrumentation
|
62
|
+
def multi_search(searches)
|
63
|
+
event = {
|
64
|
+
name: "Multi Search",
|
65
|
+
body: searches.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join
|
66
|
+
}
|
67
|
+
ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
|
68
|
+
super
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def perform_items(items)
|
73
|
+
if callbacks_value == :bulk
|
74
|
+
event = {
|
75
|
+
name: "Bulk",
|
76
|
+
count: items.size
|
77
|
+
}
|
78
|
+
ActiveSupport::Notifications.instrument("request.searchkick", event) do
|
79
|
+
super
|
80
|
+
end
|
81
|
+
else
|
82
|
+
super
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
51
87
|
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/log_subscriber.rb
|
52
88
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
53
89
|
def self.runtime=(value)
|
@@ -87,6 +123,18 @@ module Searchkick
|
|
87
123
|
|
88
124
|
debug " #{color(name, YELLOW, true)} #{payload.except(:name).to_json}"
|
89
125
|
end
|
126
|
+
|
127
|
+
def multi_search(event)
|
128
|
+
self.class.runtime += event.duration
|
129
|
+
return unless logger.debug?
|
130
|
+
|
131
|
+
payload = event.payload
|
132
|
+
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
133
|
+
|
134
|
+
# no easy way to tell which host the client will use
|
135
|
+
host = Searchkick.client.transport.hosts.first
|
136
|
+
debug " #{color(name, YELLOW, true)} curl #{host[:protocol]}://#{host[:host]}:#{host[:port]}/_msearch?pretty -d '#{payload[:body]}'"
|
137
|
+
end
|
90
138
|
end
|
91
139
|
|
92
140
|
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/railties/controller_runtime.rb
|
@@ -130,6 +178,7 @@ module Searchkick
|
|
130
178
|
end
|
131
179
|
Searchkick::Query.send(:prepend, Searchkick::QueryWithInstrumentation)
|
132
180
|
Searchkick::Index.send(:prepend, Searchkick::IndexWithInstrumentation)
|
181
|
+
Searchkick.singleton_class.send(:prepend, Searchkick::SearchkickWithInstrumentation)
|
133
182
|
Searchkick::LogSubscriber.attach_to :searchkick
|
134
183
|
ActiveSupport.on_load(:action_controller) do
|
135
184
|
include Searchkick::ControllerRuntime
|
data/lib/searchkick/model.rb
CHANGED
@@ -21,7 +21,7 @@ module Searchkick
|
|
21
21
|
def searchkick_search(term = nil, options = {}, &block)
|
22
22
|
searchkick_index.search_model(self, term, options, &block)
|
23
23
|
end
|
24
|
-
alias_method Searchkick.search_method_name, :searchkick_search
|
24
|
+
alias_method Searchkick.search_method_name, :searchkick_search if Searchkick.search_method_name
|
25
25
|
|
26
26
|
def searchkick_index
|
27
27
|
index = class_variable_get :@@searchkick_index
|
@@ -73,7 +73,7 @@ module Searchkick
|
|
73
73
|
callback_name = callbacks == :async ? :reindex_async : :reindex
|
74
74
|
if respond_to?(:after_commit)
|
75
75
|
after_commit callback_name, if: proc { self.class.search_callbacks? }
|
76
|
-
|
76
|
+
elsif respond_to?(:after_save)
|
77
77
|
after_save callback_name, if: proc { self.class.search_callbacks? }
|
78
78
|
after_destroy callback_name, if: proc { self.class.search_callbacks? }
|
79
79
|
end
|
data/lib/searchkick/query.rb
CHANGED
@@ -5,7 +5,13 @@ module Searchkick
|
|
5
5
|
attr_reader :klass, :term, :options
|
6
6
|
attr_accessor :body
|
7
7
|
|
8
|
-
def_delegators :execute, :map, :each, :any?, :empty?, :size, :length, :slice, :[], :to_ary
|
8
|
+
def_delegators :execute, :map, :each, :any?, :empty?, :size, :length, :slice, :[], :to_ary,
|
9
|
+
:records, :results, :suggestions, :each_with_hit, :with_details, :facets, :aggregations, :aggs,
|
10
|
+
:took, :error, :model_name, :entry_name, :total_count, :total_entries,
|
11
|
+
:current_page, :per_page, :limit_value, :padding, :total_pages, :num_pages,
|
12
|
+
:offset_value, :offset, :previous_page, :prev_page, :next_page, :first_page?, :last_page?,
|
13
|
+
:out_of_range?, :hits
|
14
|
+
|
9
15
|
|
10
16
|
def initialize(klass, term, options = {})
|
11
17
|
if term.is_a?(Hash)
|
@@ -67,48 +73,9 @@ module Searchkick
|
|
67
73
|
response = execute_search
|
68
74
|
end
|
69
75
|
rescue => e # TODO rescue type
|
70
|
-
|
71
|
-
if status_code == 404
|
72
|
-
raise MissingIndexError, "Index missing - run #{reindex_command}"
|
73
|
-
elsif status_code == 500 && (
|
74
|
-
e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") ||
|
75
|
-
e.message.include?("No query registered for [multi_match]") ||
|
76
|
-
e.message.include?("[match] query does not support [cutoff_frequency]]") ||
|
77
|
-
e.message.include?("No query registered for [function_score]]")
|
78
|
-
)
|
79
|
-
|
80
|
-
raise UnsupportedVersionError, "This version of Searchkick requires Elasticsearch 1.0 or greater"
|
81
|
-
elsif status_code == 400
|
82
|
-
if e.message.include?("[multi_match] analyzer [searchkick_search] not found")
|
83
|
-
raise InvalidQueryError, "Bad mapping - run #{reindex_command}"
|
84
|
-
else
|
85
|
-
raise InvalidQueryError, e.message
|
86
|
-
end
|
87
|
-
else
|
88
|
-
raise e
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# apply facet limit in client due to
|
93
|
-
# https://github.com/elasticsearch/elasticsearch/issues/1305
|
94
|
-
@facet_limits.each do |field, limit|
|
95
|
-
field = field.to_s
|
96
|
-
facet = response["facets"][field]
|
97
|
-
response["facets"][field]["terms"] = facet["terms"].first(limit)
|
98
|
-
response["facets"][field]["other"] = facet["total"] - facet["terms"].sum { |term| term["count"] }
|
76
|
+
handle_error(e)
|
99
77
|
end
|
100
|
-
|
101
|
-
opts = {
|
102
|
-
page: @page,
|
103
|
-
per_page: @per_page,
|
104
|
-
padding: @padding,
|
105
|
-
load: @load,
|
106
|
-
includes: options[:include] || options[:includes],
|
107
|
-
json: !options[:json].nil?,
|
108
|
-
match_suffix: @match_suffix,
|
109
|
-
highlighted_fields: @highlighted_fields || []
|
110
|
-
}
|
111
|
-
Searchkick::Results.new(searchkick_klass, response, opts)
|
78
|
+
handle_response(response)
|
112
79
|
end
|
113
80
|
end
|
114
81
|
|
@@ -123,8 +90,56 @@ module Searchkick
|
|
123
90
|
"curl #{host[:protocol]}://#{credentials}#{host[:host]}:#{host[:port]}/#{CGI.escape(index)}#{type ? "/#{type.map { |t| CGI.escape(t) }.join(',')}" : ''}/_search?pretty -d '#{query[:body].to_json}'"
|
124
91
|
end
|
125
92
|
|
93
|
+
def handle_response(response)
|
94
|
+
# apply facet limit in client due to
|
95
|
+
# https://github.com/elasticsearch/elasticsearch/issues/1305
|
96
|
+
@facet_limits.each do |field, limit|
|
97
|
+
field = field.to_s
|
98
|
+
facet = response["facets"][field]
|
99
|
+
response["facets"][field]["terms"] = facet["terms"].first(limit)
|
100
|
+
response["facets"][field]["other"] = facet["total"] - facet["terms"].sum { |term| term["count"] }
|
101
|
+
end
|
102
|
+
|
103
|
+
opts = {
|
104
|
+
page: @page,
|
105
|
+
per_page: @per_page,
|
106
|
+
padding: @padding,
|
107
|
+
load: @load,
|
108
|
+
includes: options[:include] || options[:includes],
|
109
|
+
json: !options[:json].nil?,
|
110
|
+
match_suffix: @match_suffix,
|
111
|
+
highlighted_fields: @highlighted_fields || []
|
112
|
+
}
|
113
|
+
|
114
|
+
# set execute for multi search
|
115
|
+
@execute = Searchkick::Results.new(searchkick_klass, response, opts)
|
116
|
+
end
|
117
|
+
|
126
118
|
private
|
127
119
|
|
120
|
+
def handle_error(e)
|
121
|
+
status_code = e.message[1..3].to_i
|
122
|
+
if status_code == 404
|
123
|
+
raise MissingIndexError, "Index missing - run #{reindex_command}"
|
124
|
+
elsif status_code == 500 && (
|
125
|
+
e.message.include?("IllegalArgumentException[minimumSimilarity >= 1]") ||
|
126
|
+
e.message.include?("No query registered for [multi_match]") ||
|
127
|
+
e.message.include?("[match] query does not support [cutoff_frequency]]") ||
|
128
|
+
e.message.include?("No query registered for [function_score]]")
|
129
|
+
)
|
130
|
+
|
131
|
+
raise UnsupportedVersionError, "This version of Searchkick requires Elasticsearch 1.0 or greater"
|
132
|
+
elsif status_code == 400
|
133
|
+
if e.message.include?("[multi_match] analyzer [searchkick_search] not found")
|
134
|
+
raise InvalidQueryError, "Bad mapping - run #{reindex_command}"
|
135
|
+
else
|
136
|
+
raise InvalidQueryError, e.message
|
137
|
+
end
|
138
|
+
else
|
139
|
+
raise e
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
128
143
|
def reindex_command
|
129
144
|
searchkick_klass ? "#{searchkick_klass.name}.reindex" : "reindex"
|
130
145
|
end
|
@@ -567,10 +582,18 @@ module Searchkick
|
|
567
582
|
end
|
568
583
|
|
569
584
|
# An empty array will cause only the _id and _type for each hit to be returned
|
570
|
-
# http://www.elasticsearch.org/guide/reference/api/search/fields/
|
585
|
+
# doc for :select - http://www.elasticsearch.org/guide/reference/api/search/fields/
|
586
|
+
# doc for :select_v2 - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-source-filtering.html
|
571
587
|
if options[:select]
|
572
588
|
payload[:fields] = options[:select] if options[:select] != true
|
589
|
+
elsif options[:select_v2]
|
590
|
+
if options[:select_v2] == []
|
591
|
+
payload[:fields] = [] # intuitively [] makes sense to return no fields, but ES by default returns all fields
|
592
|
+
else
|
593
|
+
payload[:_source] = options[:select_v2]
|
594
|
+
end
|
573
595
|
elsif load
|
596
|
+
# don't need any fields since we're going to load them from the DB anyways
|
574
597
|
payload[:fields] = []
|
575
598
|
end
|
576
599
|
|
@@ -650,7 +673,7 @@ module Searchkick
|
|
650
673
|
when :lte
|
651
674
|
{to: op_value, include_upper: true}
|
652
675
|
else
|
653
|
-
raise "Unknown where operator"
|
676
|
+
raise "Unknown where operator: #{op.inspect}"
|
654
677
|
end
|
655
678
|
# issue 132
|
656
679
|
if (existing = filters.find { |f| f[:range] && f[:range][field] })
|
data/lib/searchkick/results.rb
CHANGED
@@ -39,13 +39,15 @@ module Searchkick
|
|
39
39
|
result =
|
40
40
|
if hit["_source"]
|
41
41
|
hit.except("_source").merge(hit["_source"])
|
42
|
-
|
42
|
+
elsif hit["fields"]
|
43
43
|
hit.except("fields").merge(hit["fields"])
|
44
|
+
else
|
45
|
+
hit
|
44
46
|
end
|
45
47
|
|
46
48
|
if hit["highlight"]
|
47
49
|
highlight = Hash[hit["highlight"].map { |k, v| [base_field(k), v.first] }]
|
48
|
-
options[:highlighted_fields].map{ |k| base_field(k) }.each do |k|
|
50
|
+
options[:highlighted_fields].map { |k| base_field(k) }.each do |k|
|
49
51
|
result["highlighted_#{k}"] ||= (highlight[k] || result[k])
|
50
52
|
end
|
51
53
|
end
|
@@ -106,6 +108,10 @@ module Searchkick
|
|
106
108
|
response["took"]
|
107
109
|
end
|
108
110
|
|
111
|
+
def error
|
112
|
+
response["error"]
|
113
|
+
end
|
114
|
+
|
109
115
|
def model_name
|
110
116
|
klass.model_name
|
111
117
|
end
|
data/lib/searchkick/version.rb
CHANGED
data/test/aggs_test.rb
CHANGED
@@ -21,7 +21,7 @@ class AggsTest < Minitest::Test
|
|
21
21
|
|
22
22
|
def test_order
|
23
23
|
agg = Product.search("Product", aggs: {color: {order: {"_term" => "desc"}}}).aggs["color"]
|
24
|
-
assert_equal %w
|
24
|
+
assert_equal %w(red green blue), agg["buckets"].map { |b| b["key"] }
|
25
25
|
end
|
26
26
|
|
27
27
|
def test_field
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class MultiSearchTest < Minitest::Test
|
4
|
+
def test_basic
|
5
|
+
store_names ["Product A"]
|
6
|
+
store_names ["Store A"], Store
|
7
|
+
products = Product.search("*", execute: false)
|
8
|
+
stores = Store.search("*", execute: false)
|
9
|
+
Searchkick.multi_search([products, stores])
|
10
|
+
assert_equal ["Product A"], products.map(&:name)
|
11
|
+
assert_equal ["Store A"], stores.map(&:name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_error
|
15
|
+
store_names ["Product A"]
|
16
|
+
products = Product.search("*", execute: false)
|
17
|
+
stores = Store.search("*", order: [:bad_field], execute: false)
|
18
|
+
Searchkick.multi_search([products, stores])
|
19
|
+
assert !products.error
|
20
|
+
assert stores.error
|
21
|
+
end
|
22
|
+
end
|
data/test/routing_test.rb
CHANGED
@@ -2,14 +2,12 @@ require_relative "test_helper"
|
|
2
2
|
|
3
3
|
class RoutingTest < Minitest::Test
|
4
4
|
def test_routing_query
|
5
|
-
skip if elasticsearch2?
|
6
5
|
query = Store.search("Dollar Tree", routing: "Dollar Tree", execute: false)
|
7
6
|
assert_equal query.params[:routing], "Dollar Tree"
|
8
7
|
end
|
9
8
|
|
10
9
|
def test_routing_mappings
|
11
|
-
skip if elasticsearch2?
|
12
10
|
index_options = Store.searchkick_index.index_options
|
13
|
-
assert_equal index_options[:mappings][:_default_][:_routing], required: true
|
11
|
+
assert_equal index_options[:mappings][:_default_][:_routing], required: true
|
14
12
|
end
|
15
13
|
end
|
data/test/sql_test.rb
CHANGED
@@ -81,6 +81,7 @@ class SqlTest < Minitest::Test
|
|
81
81
|
result = Product.search("product", load: false, select: [:name, :store_id]).first
|
82
82
|
assert_equal %w(id name store_id), result.keys.reject { |k| k.start_with?("_") }.sort
|
83
83
|
assert_equal ["Product A"], result.name # this is not great
|
84
|
+
assert_equal [1], result.store_id
|
84
85
|
end
|
85
86
|
|
86
87
|
def test_select_array
|
@@ -89,6 +90,14 @@ class SqlTest < Minitest::Test
|
|
89
90
|
assert_equal [1, 2], result.user_ids
|
90
91
|
end
|
91
92
|
|
93
|
+
def test_select_single_field
|
94
|
+
store [{name: "Product A", store_id: 1}]
|
95
|
+
result = Product.search("product", load: false, select: :name).first
|
96
|
+
assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
|
97
|
+
assert_equal ["Product A"], result.name
|
98
|
+
assert_nil result.store_id
|
99
|
+
end
|
100
|
+
|
92
101
|
def test_select_all
|
93
102
|
store [{name: "Product A", user_ids: [1, 2]}]
|
94
103
|
hit = Product.search("product", select: true).hits.first
|
@@ -96,6 +105,78 @@ class SqlTest < Minitest::Test
|
|
96
105
|
assert_equal hit["_source"]["user_ids"], [1, 2]
|
97
106
|
end
|
98
107
|
|
108
|
+
def test_select_none
|
109
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
110
|
+
hit = Product.search("product", select: []).hits.first
|
111
|
+
assert_nil hit["_source"]
|
112
|
+
end
|
113
|
+
|
114
|
+
# select_v2
|
115
|
+
|
116
|
+
def test_select_v2
|
117
|
+
store [{name: "Product A", store_id: 1}]
|
118
|
+
result = Product.search("product", load: false, select_v2: [:name, :store_id]).first
|
119
|
+
assert_equal %w(id name store_id), result.keys.reject { |k| k.start_with?("_") }.sort
|
120
|
+
assert_equal "Product A", result.name
|
121
|
+
assert_equal 1, result.store_id
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_select_v2_array
|
125
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
126
|
+
result = Product.search("product", load: false, select_v2: [:user_ids]).first
|
127
|
+
assert_equal [1, 2], result.user_ids
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_select_v2_single_field
|
131
|
+
store [{name: "Product A", store_id: 1}]
|
132
|
+
result = Product.search("product", load: false, select_v2: :name).first
|
133
|
+
assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
|
134
|
+
assert_equal "Product A", result.name
|
135
|
+
assert_nil result.store_id
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_select_v2_all
|
139
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
140
|
+
hit = Product.search("product", select_v2: true).hits.first
|
141
|
+
assert_equal hit["_source"]["name"], "Product A"
|
142
|
+
assert_equal hit["_source"]["user_ids"], [1, 2]
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_select_v2_none
|
146
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
147
|
+
hit = Product.search("product", select_v2: []).hits.first
|
148
|
+
assert_nil hit["_source"]
|
149
|
+
hit = Product.search("product", select_v2: false).hits.first
|
150
|
+
assert_nil hit["_source"]
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_select_v2_include
|
154
|
+
store [{name: "Product A", user_ids: [1, 2]}]
|
155
|
+
result = Product.search("product", load: false, select_v2: {include: [:name]}).first
|
156
|
+
assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
|
157
|
+
assert_equal "Product A", result.name
|
158
|
+
assert_nil result.store_id
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_select_v2_exclude
|
162
|
+
store [{name: "Product A", user_ids: [1, 2], store_id: 1}]
|
163
|
+
result = Product.search("product", load: false, select_v2: {exclude: [:name]}).first
|
164
|
+
assert_nil result.name
|
165
|
+
assert_equal [1, 2], result.user_ids
|
166
|
+
assert_equal 1, result.store_id
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_select_v2_include_and_exclude
|
170
|
+
# let's take this to the next level
|
171
|
+
store [{name: "Product A", user_ids: [1, 2], store_id: 1}]
|
172
|
+
result = Product.search("product", load: false, select_v2: {include: [:store_id], exclude: [:name]}).first
|
173
|
+
assert_equal 1, result.store_id
|
174
|
+
assert_nil result.name
|
175
|
+
assert_nil result.user_ids
|
176
|
+
end
|
177
|
+
|
178
|
+
# other tests
|
179
|
+
|
99
180
|
def test_nested_object
|
100
181
|
aisle = {"id" => 1, "name" => "Frozen"}
|
101
182
|
store [{name: "Product A", aisle: aisle}]
|
data/test/test_helper.rb
CHANGED
@@ -4,6 +4,7 @@ require "minitest/autorun"
|
|
4
4
|
require "minitest/pride"
|
5
5
|
require "logger"
|
6
6
|
require "active_support/core_ext" if defined?(NoBrainer)
|
7
|
+
require "active_support/notifications"
|
7
8
|
|
8
9
|
ENV["RACK_ENV"] = "test"
|
9
10
|
|
@@ -18,6 +19,7 @@ puts "Running against Elasticsearch #{Searchkick.server_version}"
|
|
18
19
|
I18n.config.enforce_available_locales = true
|
19
20
|
|
20
21
|
ActiveJob::Base.logger = nil if defined?(ActiveJob)
|
22
|
+
ActiveSupport::LogSubscriber.logger = Logger.new(STDOUT) if ENV["NOTIFICATIONS"]
|
21
23
|
|
22
24
|
def elasticsearch2?
|
23
25
|
Searchkick.server_version.starts_with?("2.")
|
@@ -244,7 +246,7 @@ end
|
|
244
246
|
|
245
247
|
class Store
|
246
248
|
searchkick \
|
247
|
-
routing:
|
249
|
+
routing: true,
|
248
250
|
merge_mappings: true,
|
249
251
|
mappings: {
|
250
252
|
store: {
|
@@ -253,6 +255,14 @@ class Store
|
|
253
255
|
}
|
254
256
|
}
|
255
257
|
}
|
258
|
+
|
259
|
+
def search_document_id
|
260
|
+
id
|
261
|
+
end
|
262
|
+
|
263
|
+
def search_routing
|
264
|
+
name
|
265
|
+
end
|
256
266
|
end
|
257
267
|
|
258
268
|
class Animal
|
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: 1.2.
|
4
|
+
version: 1.2.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: 2016-02-
|
11
|
+
date: 2016-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -142,6 +142,7 @@ files:
|
|
142
142
|
- test/match_test.rb
|
143
143
|
- test/misspellings_test.rb
|
144
144
|
- test/model_test.rb
|
145
|
+
- test/multi_search_test.rb
|
145
146
|
- test/order_test.rb
|
146
147
|
- test/pagination_test.rb
|
147
148
|
- test/query_test.rb
|
@@ -205,6 +206,7 @@ test_files:
|
|
205
206
|
- test/match_test.rb
|
206
207
|
- test/misspellings_test.rb
|
207
208
|
- test/model_test.rb
|
209
|
+
- test/multi_search_test.rb
|
208
210
|
- test/order_test.rb
|
209
211
|
- test/pagination_test.rb
|
210
212
|
- test/query_test.rb
|