search_flip 3.0.0.beta3 → 3.1.0

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
  SHA256:
3
- metadata.gz: 417bf711946ace4812485ea2efdfefdeb36414e7e8e8d8ed24b10b4807050be9
4
- data.tar.gz: c8a9f7392d77f15c4714c40756c032a2bb3bc12c363a9e956a6b9e184c8e4c17
3
+ metadata.gz: f4e3ca349bd68555b2826d84c17b192335752947babe02ee51c5501cb04f79ca
4
+ data.tar.gz: e95021068918513669da2730d613157ad7151995c4a25f532f4274c3a848710f
5
5
  SHA512:
6
- metadata.gz: c62c6d3bc17ec18da5b304dfbddbafc29e35013bcc6b0b906858380b01a263b2e534986cd41f9e90d13de99820f9cbf11a173802910737bf33d8ae5653230af1
7
- data.tar.gz: 3b6ee6d16086e451f6f6e6d7c718d6a30c30115d91c6ba9e69113faa19b9abc35d04c8c179dc9ca5ac49418877c0c5828435d71c1dab7b71d4afe2e31da88762
6
+ metadata.gz: dcb46f99778d2243e6fb4187f85eb9a1db9aedc25a5f2b8287c3cdb8dcd259253bc3430e1e36521ea5794a2a9188f91473ae8d8572edc8c775ac0d234ac2e284
7
+ data.tar.gz: 1dbb3ba32c9c2a16f1c2b30aba7257dffc39b708f3e68ec8552c6d6d8ac6a751027a2899a3865051f7d1fdd7a6acd7a1f456eba552c7b50f4c2956292f41d413
@@ -1,3 +1,6 @@
1
+ AllCops:
2
+ NewCops: enable
3
+
1
4
  Style/HashTransformValues:
2
5
  Enabled: false
3
6
 
@@ -5,9 +5,11 @@ env:
5
5
  - ES_IMAGE=elasticsearch:5.4
6
6
  - ES_IMAGE=docker.elastic.co/elasticsearch/elasticsearch:6.7.0
7
7
  - ES_IMAGE=docker.elastic.co/elasticsearch/elasticsearch:7.0.0
8
- - ES_IMAGE=docker.elastic.co/elasticsearch/elasticsearch:7.4.0
8
+ - ES_IMAGE=docker.elastic.co/elasticsearch/elasticsearch:7.6.0
9
9
  rvm:
10
+ - ruby-2.5.3
10
11
  - ruby-2.6.2
12
+ - ruby-2.7.1
11
13
  before_install:
12
14
  - docker-compose up -d
13
15
  - sleep 10
@@ -1,6 +1,12 @@
1
1
 
2
2
  # CHANGELOG
3
3
 
4
+ ## v3.1.0
5
+
6
+ * Added plugin support in `SearchFlip::HTTPClient`
7
+ * Added `SearchFlip::AwsSigv4Plugin` to be able to use AWS Elasticsearch with
8
+ signed requests
9
+
4
10
  ## v3.0.0
5
11
 
6
12
  * Added `Criteria#to_query`, which returns a raw query including all queries
@@ -23,6 +29,11 @@
23
29
  * Added `SearchFlip::Result.from_hit`
24
30
  * Added support for `source`, `sort`, `page`, `per`, `paginate`, `explain`, and
25
31
  `highlight` to aggregations
32
+ * Added support for instrumentation
33
+
34
+ ## v2.3.2
35
+
36
+ * Remove ruby 2.7 warnings
26
37
 
27
38
  ## v2.3.1
28
39
 
data/README.md CHANGED
@@ -274,7 +274,7 @@ CommentIndex.create(Comment.first, { bulk_max_mb: 100 }, routing: "routing_key")
274
274
  CommentIndex.update(Comment.first, ...)
275
275
  ```
276
276
 
277
- Checkout the elasticsearch [Bulk API] docs for more info as well as
277
+ Checkout the Elasticsearch [Bulk API] docs for more info as well as
278
278
  [SearchFlip::Bulk](http://www.rubydoc.info/github/mrkamel/search_flip/SearchFlip/Bulk)
279
279
  for a complete list of available options to control the bulk indexing of
280
280
  SearchFlip.
@@ -475,8 +475,8 @@ end
475
475
  ```
476
476
 
477
477
  Generally, aggregation results returned by Elasticsearch are returned as a
478
- `SearchFlip::Result`, which basically is `Hashie::Mash`such that you can access
479
- them via:
478
+ `SearchFlip::Result`, which basically is a `Hashie::Mash`, such that you can
479
+ access them via:
480
480
 
481
481
  ```ruby
482
482
  query.aggregations(:username)["mrkamel"].revenue.value
@@ -769,6 +769,41 @@ http_client = http_client.headers(key: "value")
769
769
  SearchFlip::Connection.new(base_url: "...", http_client: http_client)
770
770
  ```
771
771
 
772
+ ## AWS Elasticsearch / Signed Requests
773
+
774
+ To use SearchFlip with AWS Elasticsearch and signed requests, you have to add
775
+ `aws-sdk-core` to your Gemfile and tell SearchFlip to use the
776
+ `SearchFlip::AwsSigv4Plugin`:
777
+
778
+ ```ruby
779
+ require "search_flip/aws_sigv4_plugin"
780
+
781
+ MyConnection = SearchFlip::Connection.new(
782
+ base_url: "https://your-elasticsearch-cluster.es.amazonaws.com",
783
+ http_client: SearchFlip::HTTPClient.new(
784
+ plugins: [
785
+ SearchFlip::AwsSigv4Plugin.new(
786
+ region: "...",
787
+ access_key_id: "...",
788
+ secret_access_key: "..."
789
+ )
790
+ ]
791
+ )
792
+ )
793
+ ```
794
+
795
+ Again, in your index you need to specify this connection:
796
+
797
+ ```ruby
798
+ class MyIndex
799
+ include SearchFlip::Index
800
+
801
+ def self.connection
802
+ MyConnection
803
+ end
804
+ end
805
+ ```
806
+
772
807
  ## Routing and other index-time options
773
808
 
774
809
  Override `index_options` in case you want to use routing or pass other
@@ -790,6 +825,30 @@ end
790
825
 
791
826
  These options will be passed whenever records get indexed, deleted, etc.
792
827
 
828
+ ## Instrumentation
829
+
830
+ SearchFlip supports instrumentation for request execution via
831
+ `ActiveSupport::Notifications` compatible instrumenters to e.g. allow global
832
+ performance tracing, etc.
833
+
834
+ To use instrumentation, configure the instrumenter:
835
+
836
+ ```ruby
837
+ SearchFlip::Config[:instrumenter] = ActiveSupport::Notifications.notifier
838
+ ```
839
+
840
+ Subsequently, you can subscribe to notifcations for `request.search_flip`:
841
+
842
+ ```ruby
843
+ ActiveSupport::Notifications.subscribe("request.search_flip") do |name, start, finish, id, payload|
844
+ payload[:index] # the index class
845
+ payload[:request] # the request hash sent to Elasticsearch
846
+ payload[:response] # the SearchFlip::Response object or nil in case of errors
847
+ end
848
+ ```
849
+
850
+ A notification will be send for every request that is sent to Elasticsearch.
851
+
793
852
  ## Non-ActiveRecord models
794
853
 
795
854
  SearchFlip ships with built-in support for ActiveRecord models, but using
@@ -1,3 +1,4 @@
1
+ require "ruby2_keywords"
1
2
  require "forwardable"
2
3
  require "http"
3
4
  require "hashie"
@@ -6,6 +7,7 @@ require "oj"
6
7
  require "set"
7
8
 
8
9
  require "search_flip/version"
10
+ require "search_flip/null_instrumenter"
9
11
  require "search_flip/helper"
10
12
  require "search_flip/exceptions"
11
13
  require "search_flip/json"
@@ -58,10 +58,11 @@ module SearchFlip
58
58
  aggregation = yield(SearchFlip::Aggregation.new(target: target))
59
59
 
60
60
  if field_or_hash.is_a?(Hash)
61
- value = field_or_hash.values.first
62
- value = value.values.first if value.is_a?(Hash) && !value.empty?
61
+ aggregation_hash = field_or_hash.values.first
62
+ aggregation_hash = aggregation_hash[:top_hits] if aggregation_hash.is_a?(Hash) && aggregation_hash.key?(:top_hits)
63
+ aggregation_hash = aggregation_hash["top_hits"] if aggregation_hash.is_a?(Hash) && aggregation_hash.key?("top_hits")
63
64
 
64
- value.merge!(aggregation.to_hash)
65
+ aggregation_hash.merge!(aggregation.to_hash)
65
66
  else
66
67
  hash[field_or_hash].merge!(aggregation.to_hash)
67
68
  end
@@ -73,7 +73,8 @@ module SearchFlip
73
73
  unsupported_methods = [
74
74
  :profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :scroll_args,
75
75
  :suggest_values, :includes_values, :preload_values, :eager_load_values, :post_must_values,
76
- :post_must_not_values, :post_filter_values, :preference_value, :search_type_value, :routing_value
76
+ :post_must_not_values, :post_filter_values, :preference_value, :search_type_value,
77
+ :routing_value
77
78
  ]
78
79
 
79
80
  unsupported_methods.each do |unsupported_method|
@@ -111,6 +112,8 @@ module SearchFlip
111
112
  end
112
113
  end
113
114
 
115
+ ruby2_keywords :method_missing
116
+
114
117
  # @api private
115
118
  #
116
119
  # Simply dups the object for api compatability.
@@ -0,0 +1,47 @@
1
+ require "aws-sdk-core"
2
+ require "uri"
3
+
4
+ module SearchFlip
5
+ # The SearchFlip::AwsSigV4Plugin is a plugin for the SearchFlip::HTTPClient
6
+ # to be used with AWS Elasticsearch to sign requests, i.e. add signed
7
+ # headers, before sending the request to Elasticsearch.
8
+ #
9
+ # @example
10
+ # MyConnection = SearchFlip::Connection.new(
11
+ # base_url: "https://your-elasticsearch-cluster.es.amazonaws.com",
12
+ # http_client: SearchFlip::HTTPClient.new(
13
+ # plugins: [
14
+ # SearchFlip::AwsSigv4Plugin.new(
15
+ # region: "...",
16
+ # access_key_id: "...",
17
+ # secret_access_key: "..."
18
+ # )
19
+ # ]
20
+ # )
21
+ # )
22
+
23
+ class AwsSigv4Plugin
24
+ attr_accessor :signer
25
+
26
+ def initialize(options = {})
27
+ self.signer = Aws::Sigv4::Signer.new({ service: "es" }.merge(options))
28
+ end
29
+
30
+ def call(request, method, uri, options = {})
31
+ full_uri = URI.parse(uri)
32
+ full_uri.query = URI.encode_www_form(options[:params]) if options[:params]
33
+
34
+ signature_request = {
35
+ http_method: method.to_s.upcase,
36
+ url: full_uri.to_s
37
+ }
38
+
39
+ signature_request[:body] = options[:body] if options.key?(:body)
40
+ signature_request[:body] = options[:json].respond_to?(:to_str) ? options[:json] : JSON.generate(options[:json]) if options[:json]
41
+
42
+ signature = signer.sign_request(signature_request)
43
+
44
+ request.headers(signature.headers)
45
+ end
46
+ end
47
+ end
@@ -77,7 +77,7 @@ module SearchFlip
77
77
  # and versioning
78
78
 
79
79
  def index(id, object, options = {})
80
- perform :index, id, SearchFlip::JSON.generate(object), options
80
+ perform(:index, id, SearchFlip::JSON.generate(object), options)
81
81
  end
82
82
 
83
83
  # @api private
@@ -86,7 +86,7 @@ module SearchFlip
86
86
  #
87
87
  # @see #index
88
88
 
89
- def import(*args)
89
+ ruby2_keywords def import(*args)
90
90
  index(*args)
91
91
  end
92
92
 
@@ -100,7 +100,7 @@ module SearchFlip
100
100
  # and versioning
101
101
 
102
102
  def create(id, object, options = {})
103
- perform :create, id, SearchFlip::JSON.generate(object), options
103
+ perform(:create, id, SearchFlip::JSON.generate(object), options)
104
104
  end
105
105
 
106
106
  # @api private
@@ -113,7 +113,7 @@ module SearchFlip
113
113
  # and versioning
114
114
 
115
115
  def update(id, object, options = {})
116
- perform :update, id, SearchFlip::JSON.generate(object), options
116
+ perform(:update, id, SearchFlip::JSON.generate(object), options)
117
117
  end
118
118
 
119
119
  # @api private
@@ -125,7 +125,7 @@ module SearchFlip
125
125
  # and versioning
126
126
 
127
127
  def delete(id, options = {})
128
- perform :delete, id, nil, options
128
+ perform(:delete, id, nil, options)
129
129
  end
130
130
 
131
131
  private
@@ -4,6 +4,7 @@ module SearchFlip
4
4
  base_url: "http://127.0.0.1:9200",
5
5
  bulk_limit: 1_000,
6
6
  bulk_max_mb: 100,
7
- auto_refresh: false
7
+ auto_refresh: false,
8
+ instrumenter: NullInstrumenter.new
8
9
  }
9
10
  end
@@ -206,7 +206,7 @@ module SearchFlip
206
206
  #
207
207
  # @return [SearchFlip::Criteria] Simply returns self
208
208
 
209
- def with_settings(*args)
209
+ ruby2_keywords def with_settings(*args)
210
210
  fresh.tap do |criteria|
211
211
  criteria.target = target.with_settings(*args)
212
212
  end
@@ -219,45 +219,47 @@ module SearchFlip
219
219
  # @return [Hash] The generated request object
220
220
 
221
221
  def request
222
- res = {}
223
-
224
- if must_values || must_not_values || filter_values
225
- res[:query] = {
226
- bool: {
227
- must: must_values.to_a,
228
- must_not: must_not_values.to_a,
229
- filter: filter_values.to_a
230
- }.reject { |_, value| value.empty? }
231
- }
232
- end
222
+ @request ||= begin
223
+ res = {}
224
+
225
+ if must_values || must_not_values || filter_values
226
+ res[:query] = {
227
+ bool: {
228
+ must: must_values.to_a,
229
+ must_not: must_not_values.to_a,
230
+ filter: filter_values.to_a
231
+ }.reject { |_, value| value.empty? }
232
+ }
233
+ end
233
234
 
234
- res.update(from: offset_value_with_default, size: limit_value_with_default)
235
-
236
- res[:track_total_hits] = track_total_hits_value unless track_total_hits_value.nil?
237
- res[:explain] = explain_value unless explain_value.nil?
238
- res[:timeout] = timeout_value if timeout_value
239
- res[:terminate_after] = terminate_after_value if terminate_after_value
240
- res[:highlight] = highlight_values if highlight_values
241
- res[:suggest] = suggest_values if suggest_values
242
- res[:sort] = sort_values if sort_values
243
- res[:aggregations] = aggregation_values if aggregation_values
244
-
245
- if post_must_values || post_must_not_values || post_filter_values
246
- res[:post_filter] = {
247
- bool: {
248
- must: post_must_values.to_a,
249
- must_not: post_must_not_values.to_a,
250
- filter: post_filter_values.to_a
251
- }.reject { |_, value| value.empty? }
252
- }
253
- end
235
+ res.update(from: offset_value_with_default, size: limit_value_with_default)
236
+
237
+ res[:track_total_hits] = track_total_hits_value unless track_total_hits_value.nil?
238
+ res[:explain] = explain_value unless explain_value.nil?
239
+ res[:timeout] = timeout_value if timeout_value
240
+ res[:terminate_after] = terminate_after_value if terminate_after_value
241
+ res[:highlight] = highlight_values if highlight_values
242
+ res[:suggest] = suggest_values if suggest_values
243
+ res[:sort] = sort_values if sort_values
244
+ res[:aggregations] = aggregation_values if aggregation_values
245
+
246
+ if post_must_values || post_must_not_values || post_filter_values
247
+ res[:post_filter] = {
248
+ bool: {
249
+ must: post_must_values.to_a,
250
+ must_not: post_must_not_values.to_a,
251
+ filter: post_filter_values.to_a
252
+ }.reject { |_, value| value.empty? }
253
+ }
254
+ end
254
255
 
255
- res[:_source] = source_value unless source_value.nil?
256
- res[:profile] = true if profile_value
256
+ res[:_source] = source_value unless source_value.nil?
257
+ res[:profile] = true if profile_value
257
258
 
258
- res.update(custom_value) if custom_value
259
+ res.update(custom_value) if custom_value
259
260
 
260
- res
261
+ res
262
+ end
261
263
  end
262
264
 
263
265
  # Adds a suggestion section with the given name to the request.
@@ -520,30 +522,13 @@ module SearchFlip
520
522
 
521
523
  def execute
522
524
  @response ||= begin
523
- http_request = connection.http_client.headers(accept: "application/json")
524
-
525
- http_response =
526
- if scroll_args && scroll_args[:id]
527
- http_request.post(
528
- "#{connection.base_url}/_search/scroll",
529
- params: request_params,
530
- json: { scroll: scroll_args[:timeout], scroll_id: scroll_args[:id] }
531
- )
532
- elsif scroll_args
533
- http_request.post(
534
- "#{target.type_url}/_search",
535
- params: request_params.merge(scroll: scroll_args[:timeout]),
536
- json: request
537
- )
538
- else
539
- http_request.post("#{target.type_url}/_search", params: request_params, json: request)
540
- end
541
-
542
- SearchFlip::Response.new(self, http_response.parse)
543
- rescue SearchFlip::ConnectionError, SearchFlip::ResponseError => e
544
- raise e unless failsafe_value
545
-
546
- SearchFlip::Response.new(self, "took" => 0, "hits" => { "total" => 0, "hits" => [] })
525
+ Config[:instrumenter].instrument("request.search_flip", index: target, request: request) do |payload|
526
+ response = execute!
527
+
528
+ payload[:response] = response
529
+
530
+ response
531
+ end
547
532
  end
548
533
  end
549
534
 
@@ -585,6 +570,7 @@ module SearchFlip
585
570
 
586
571
  def fresh
587
572
  dup.tap do |criteria|
573
+ criteria.instance_variable_set(:@request, nil)
588
574
  criteria.instance_variable_set(:@response, nil)
589
575
  end
590
576
  end
@@ -601,6 +587,8 @@ module SearchFlip
601
587
  end
602
588
  end
603
589
 
590
+ ruby2_keywords :method_missing
591
+
604
592
  def_delegators :response, :total_entries, :total_count, :current_page, :previous_page,
605
593
  :prev_page, :next_page, :first_page?, :last_page?, :out_of_range?, :total_pages,
606
594
  :hits, :ids, :count, :size, :length, :took, :aggregations, :suggestions,
@@ -610,6 +598,33 @@ module SearchFlip
610
598
 
611
599
  private
612
600
 
601
+ def execute!
602
+ http_request = connection.http_client.headers(accept: "application/json")
603
+
604
+ http_response =
605
+ if scroll_args && scroll_args[:id]
606
+ http_request.post(
607
+ "#{connection.base_url}/_search/scroll",
608
+ params: request_params,
609
+ json: { scroll: scroll_args[:timeout], scroll_id: scroll_args[:id] }
610
+ )
611
+ elsif scroll_args
612
+ http_request.post(
613
+ "#{target.type_url}/_search",
614
+ params: request_params.merge(scroll: scroll_args[:timeout]),
615
+ json: request
616
+ )
617
+ else
618
+ http_request.post("#{target.type_url}/_search", params: request_params, json: request)
619
+ end
620
+
621
+ SearchFlip::Response.new(self, http_response.parse)
622
+ rescue SearchFlip::ConnectionError, SearchFlip::ResponseError => e
623
+ raise e unless failsafe_value
624
+
625
+ SearchFlip::Response.new(self, "took" => 0, "hits" => { "total" => 0, "hits" => [] })
626
+ end
627
+
613
628
  def yield_in_batches(options = {})
614
629
  return enum_for(:yield_in_batches, options) unless block_given?
615
630
 
@@ -1,15 +1,14 @@
1
1
  module SearchFlip
2
- # @api private
3
- #
4
2
  # The SearchFlip::HTTPClient class wraps the http gem, is for internal use
5
3
  # and responsible for the http request/response handling, ie communicating
6
4
  # with Elasticsearch.
7
5
 
8
6
  class HTTPClient
9
- attr_accessor :request
7
+ attr_accessor :request, :plugins
10
8
 
11
- def initialize
9
+ def initialize(plugins: [])
12
10
  self.request = HTTP
11
+ self.plugins = plugins
13
12
  end
14
13
 
15
14
  class << self
@@ -25,18 +24,21 @@ module SearchFlip
25
24
  client.request = request.send(method, *args)
26
25
  end
27
26
  end
27
+
28
+ ruby2_keywords method
28
29
  end
29
30
 
30
31
  [:get, :post, :put, :delete, :head].each do |method|
31
- define_method method do |*args|
32
- execute(method, *args)
32
+ define_method(method) do |uri, options = {}|
33
+ execute(method, uri, options)
33
34
  end
34
35
  end
35
36
 
36
37
  private
37
38
 
38
- def execute(method, *args)
39
- response = request.send(method, *args)
39
+ def execute(method, uri, options = {})
40
+ final_request = plugins.inject(self) { |res, cur| cur.call(res, method, uri, options) }
41
+ response = final_request.request.send(method, uri, options)
40
42
 
41
43
  raise SearchFlip::ResponseError.new(code: response.code, body: response.body.to_s) unless response.status.success?
42
44
 
@@ -498,7 +498,7 @@ module SearchFlip
498
498
  #
499
499
  # @see #index See #index for more details
500
500
 
501
- def import(*args)
501
+ ruby2_keywords def import(*args)
502
502
  index(*args)
503
503
  end
504
504
 
@@ -0,0 +1,21 @@
1
+ module SearchFlip
2
+ class NullInstrumenter
3
+ def instrument(name, payload = {})
4
+ start(name, payload)
5
+
6
+ begin
7
+ yield(payload) if block_given?
8
+ ensure
9
+ finish(name, payload)
10
+ end
11
+ end
12
+
13
+ def start(_name, _payload)
14
+ true
15
+ end
16
+
17
+ def finish(_name, _payload)
18
+ true
19
+ end
20
+ end
21
+ end
@@ -222,7 +222,7 @@ module SearchFlip
222
222
  #
223
223
  # @return [Array] An array of database records
224
224
 
225
- def records(options = {})
225
+ def records
226
226
  @records ||= begin
227
227
  sort_map = ids.each_with_index.each_with_object({}) { |(id, index), hash| hash[id.to_s] = index }
228
228
 
@@ -1,3 +1,3 @@
1
1
  module SearchFlip
2
- VERSION = "3.0.0.beta3"
2
+ VERSION = "3.1.0"
3
3
  end
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  MESSAGE
26
26
 
27
27
  spec.add_development_dependency "activerecord", ">= 3.0"
28
+ spec.add_development_dependency "aws-sdk-core"
28
29
  spec.add_development_dependency "bundler"
29
30
  spec.add_development_dependency "factory_bot"
30
31
  spec.add_development_dependency "rake"
@@ -37,4 +38,5 @@ Gem::Specification.new do |spec|
37
38
  spec.add_dependency "hashie"
38
39
  spec.add_dependency "http"
39
40
  spec.add_dependency "oj"
41
+ spec.add_dependency "ruby2_keywords"
40
42
  end
@@ -0,0 +1,41 @@
1
+ require File.expand_path("../spec_helper", __dir__)
2
+ require "search_flip/aws_sigv4_plugin"
3
+
4
+ RSpec.describe SearchFlip::AwsSigv4Plugin do
5
+ describe "#call" do
6
+ subject(:plugin) do
7
+ SearchFlip::AwsSigv4Plugin.new(
8
+ region: "us-east-1",
9
+ access_key_id: "access key",
10
+ secret_access_key: "secret access key"
11
+ )
12
+ end
13
+
14
+ let(:client) { SearchFlip::HTTPClient.new }
15
+
16
+ it "adds the signed headers to the request" do
17
+ Timecop.freeze Time.parse("2020-01-01 12:00:00 UTC") do
18
+ expect(client).to receive(:headers).with(
19
+ "host" => "localhost",
20
+ "authorization" => /.*/,
21
+ "x-amz-content-sha256" => /.*/,
22
+ "x-amz-date" => /20200101T120000Z/
23
+ )
24
+
25
+ plugin.call(client, :get, "http://localhost/index")
26
+ end
27
+ end
28
+
29
+ it "feeds the http method, full url and body to the signer" do
30
+ signing_request = {
31
+ http_method: "GET",
32
+ url: "http://localhost/index?param=value",
33
+ body: JSON.generate(key: "value")
34
+ }
35
+
36
+ expect(plugin.signer).to receive(:sign_request).with(signing_request).and_call_original
37
+
38
+ plugin.call(client, :get, "http://localhost/index", params: { param: "value" }, json: { key: "value" })
39
+ end
40
+ end
41
+ end
@@ -1198,9 +1198,12 @@ RSpec.describe SearchFlip::Criteria do
1198
1198
 
1199
1199
  query = ProductIndex.criteria.tap(&:records)
1200
1200
 
1201
+ expect(query.instance_variable_get(:@request)).not_to be_nil
1201
1202
  expect(query.instance_variable_get(:@response)).not_to be_nil
1202
1203
 
1203
1204
  expect(query.object_id).not_to eq(query.fresh.object_id)
1205
+
1206
+ expect(query.fresh.instance_variable_get(:@request)).to be_nil
1204
1207
  expect(query.fresh.instance_variable_get(:@response)).to be_nil
1205
1208
  end
1206
1209
  end
@@ -1234,10 +1237,49 @@ RSpec.describe SearchFlip::Criteria do
1234
1237
  end
1235
1238
  end
1236
1239
 
1240
+ describe "#execute" do
1241
+ around do |example|
1242
+ default_instrumenter = SearchFlip::Config[:instrumenter]
1243
+
1244
+ SearchFlip::Config[:instrumenter] = ActiveSupport::Notifications.instrumenter
1245
+
1246
+ begin
1247
+ example.run
1248
+ ensure
1249
+ SearchFlip::Config[:instrumenter] = default_instrumenter
1250
+ end
1251
+ end
1252
+
1253
+ let(:notifications) { [] }
1254
+
1255
+ let!(:subscriber) do
1256
+ ActiveSupport::Notifications.subscribe("request.search_flip") do |*args|
1257
+ notifications << args
1258
+ end
1259
+ end
1260
+
1261
+ after { ActiveSupport::Notifications.unsubscribe(subscriber) }
1262
+
1263
+ it "instruments the request" do
1264
+ ProductIndex.match_all.execute
1265
+
1266
+ expect(notifications).to be_present
1267
+ end
1268
+
1269
+ it "passes the index, request and response" do
1270
+ ProductIndex.match_all.execute
1271
+
1272
+ expect(notifications.first[4][:index]).to eq(ProductIndex)
1273
+ expect(notifications.first[4][:request]).to be_present
1274
+ expect(notifications.first[4][:response]).to be_present
1275
+ end
1276
+ end
1277
+
1237
1278
  describe "#track_total_hits" do
1238
1279
  it "is added to the request" do
1239
1280
  if ProductIndex.connection.version.to_i >= 7
1240
1281
  query = ProductIndex.track_total_hits(false)
1282
+
1241
1283
  expect(query.request[:track_total_hits]).to eq(false)
1242
1284
  expect { query.execute }.not_to raise_error
1243
1285
  end
@@ -39,6 +39,23 @@ RSpec.describe SearchFlip::HTTPClient do
39
39
  end
40
40
  end
41
41
 
42
+ describe "plugins" do
43
+ subject do
44
+ SearchFlip::HTTPClient.new(
45
+ plugins: [
46
+ ->(request, _method, _uri, _options = {}) { request.headers("First-Header" => "Value") },
47
+ ->(request, _method, _uri, _options = {}) { request.headers("Second-Header" => "Value") }
48
+ ]
49
+ )
50
+ end
51
+
52
+ it "injects the plugins and uses their result in the request" do
53
+ stub_request(:get, "http://localhost/path").with(query: { key: "value" }, headers: { "First-Header" => "Value", "Second-Header" => "Value" }).and_return(body: "success")
54
+
55
+ expect(subject.get("http://localhost/path", params: { key: "value" }).body.to_s).to eq("success")
56
+ end
57
+ end
58
+
42
59
  [:via, :basic_auth, :auth].each do |method|
43
60
  describe "##{method}" do
44
61
  it "creates a dupped instance" do
@@ -0,0 +1,43 @@
1
+ require File.expand_path("../spec_helper", __dir__)
2
+
3
+ RSpec.describe SearchFlip::NullInstrumenter do
4
+ subject { described_class.new }
5
+
6
+ describe "#instrument" do
7
+ it "calls start" do
8
+ allow(subject).to receive(:start)
9
+
10
+ subject.instrument("name", { key: "value" }) {}
11
+
12
+ expect(subject).to have_received(:start)
13
+ end
14
+
15
+ it "calls finish" do
16
+ allow(subject).to receive(:finish)
17
+
18
+ subject.instrument("name", { key: "value" }) {}
19
+
20
+ expect(subject).to have_received(:finish)
21
+ end
22
+
23
+ it "yields and passes the payload" do
24
+ yielded_payload = nil
25
+
26
+ subject.instrument("name", { key: "value" }) { |payload| yielded_payload = payload }
27
+
28
+ expect(yielded_payload).to eq(key: "value")
29
+ end
30
+ end
31
+
32
+ describe "#start" do
33
+ it "returns true" do
34
+ expect(subject.start("name", { key: "value" })).to eq(true)
35
+ end
36
+ end
37
+
38
+ describe "#finish" do
39
+ it "returns true" do
40
+ expect(subject.finish("name", { key: "value" })).to eq(true)
41
+ end
42
+ end
43
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search_flip
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.beta3
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Vetter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-22 00:00:00.000000000 Z
11
+ date: 2020-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-core
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +192,20 @@ dependencies:
178
192
  - - ">="
179
193
  - !ruby/object:Gem::Version
180
194
  version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: ruby2_keywords
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :runtime
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
181
209
  description: Full-Featured Elasticsearch Ruby Client with a Chainable DSL
182
210
  email:
183
211
  - vetter@flakks.com
@@ -198,6 +226,7 @@ files:
198
226
  - lib/search_flip.rb
199
227
  - lib/search_flip/aggregatable.rb
200
228
  - lib/search_flip/aggregation.rb
229
+ - lib/search_flip/aws_sigv4_plugin.rb
201
230
  - lib/search_flip/bulk.rb
202
231
  - lib/search_flip/config.rb
203
232
  - lib/search_flip/connection.rb
@@ -212,6 +241,7 @@ files:
212
241
  - lib/search_flip/index.rb
213
242
  - lib/search_flip/json.rb
214
243
  - lib/search_flip/model.rb
244
+ - lib/search_flip/null_instrumenter.rb
215
245
  - lib/search_flip/paginatable.rb
216
246
  - lib/search_flip/post_filterable.rb
217
247
  - lib/search_flip/response.rb
@@ -223,12 +253,14 @@ files:
223
253
  - search_flip.gemspec
224
254
  - spec/delegate_matcher.rb
225
255
  - spec/search_flip/aggregation_spec.rb
256
+ - spec/search_flip/aws_sigv4_plugin_spec.rb
226
257
  - spec/search_flip/bulk_spec.rb
227
258
  - spec/search_flip/connection_spec.rb
228
259
  - spec/search_flip/criteria_spec.rb
229
260
  - spec/search_flip/http_client_spec.rb
230
261
  - spec/search_flip/index_spec.rb
231
262
  - spec/search_flip/model_spec.rb
263
+ - spec/search_flip/null_instrumenter_spec.rb
232
264
  - spec/search_flip/response_spec.rb
233
265
  - spec/search_flip/result_spec.rb
234
266
  - spec/search_flip/to_json_spec.rb
@@ -251,9 +283,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
251
283
  version: '0'
252
284
  required_rubygems_version: !ruby/object:Gem::Requirement
253
285
  requirements:
254
- - - ">"
286
+ - - ">="
255
287
  - !ruby/object:Gem::Version
256
- version: 1.3.1
288
+ version: '0'
257
289
  requirements: []
258
290
  rubygems_version: 3.0.3
259
291
  signing_key:
@@ -262,12 +294,14 @@ summary: Full-Featured Elasticsearch Ruby Client with a Chainable DSL
262
294
  test_files:
263
295
  - spec/delegate_matcher.rb
264
296
  - spec/search_flip/aggregation_spec.rb
297
+ - spec/search_flip/aws_sigv4_plugin_spec.rb
265
298
  - spec/search_flip/bulk_spec.rb
266
299
  - spec/search_flip/connection_spec.rb
267
300
  - spec/search_flip/criteria_spec.rb
268
301
  - spec/search_flip/http_client_spec.rb
269
302
  - spec/search_flip/index_spec.rb
270
303
  - spec/search_flip/model_spec.rb
304
+ - spec/search_flip/null_instrumenter_spec.rb
271
305
  - spec/search_flip/response_spec.rb
272
306
  - spec/search_flip/result_spec.rb
273
307
  - spec/search_flip/to_json_spec.rb