searchkick 1.2.0 → 1.2.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 +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
|