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 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