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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 49a9ad19b612e5079b3c97ec4ff756e62b8e2333
4
- data.tar.gz: 2e9133f49f0287cabf5bc88fe2fd28e6594ffa27
3
+ metadata.gz: 750ced99cb1b0929710eba206bde31907f0d9544
4
+ data.tar.gz: 454d078f8900948def90b1a88b40bde8aec561b5
5
5
  SHA512:
6
- metadata.gz: 4e15ce0859a66945f0675226bb5492d67db7612f23e480d5cec930903db151c2622380d30192e1eace1feb146b455f56b5cb3f55c07129580ac8117da224f42e
7
- data.tar.gz: 87c387df771fcf4dcec72b46f779169515acef02bd29ff12565c1c52374fc99a13d54a7d5a38ff0ef57cb7beb58412b0c354abf2beb0860342f2ff545605130a
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
- active: active
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(:searches) }
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 Contact < ActiveRecord::Base
840
- searchkick routing: :user_id
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
- Contact.search "John", routing: current_user.id
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 inconstencies](https://github.com/elasticsearch/elasticsearch-rails/blob/master/elasticsearch-model/README.md#custom-callbacks)
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 [master]
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?(Rails)
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
@@ -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: {_index: name, _type: document_type(r), _id: search_id(r)}} })
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: {_index: name, _type: document_type(r), _id: search_id(r), data: search_data(r)}} })
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, path: options[:routing].to_s}
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
- klass_document_type(record.class)
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.id.is_a?(Numeric) ? record.id : record.id.to_s
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)
@@ -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: "#{searchkick_klass.name} Search",
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
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
23
- super(record)
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: "#{record.searchkick_klass.name} Remove",
36
+ name: name,
31
37
  id: search_id(record)
32
38
  }
33
- ActiveSupport::Notifications.instrument("request.searchkick", event) do
34
- super(record)
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
@@ -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
- else
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
@@ -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
- status_code = e.message[1..3].to_i
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] })
@@ -39,13 +39,15 @@ module Searchkick
39
39
  result =
40
40
  if hit["_source"]
41
41
  hit.except("_source").merge(hit["_source"])
42
- else
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
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.1"
3
3
  end
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[red green blue], agg["buckets"].map { |b| b["key"] }
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, path: "name"
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: elasticsearch2? ? false : "name",
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.0
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-04 00:00:00.000000000 Z
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