search_flip 4.0.0.beta5 → 4.0.0.beta9

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: 938e1745f8cfe0173c76f26a4f15ab7e62781fd627dcccb2c2523db7f8ca51bf
4
- data.tar.gz: 2a8980bc0de4db9cb081c5186ed94bbee489a470ea30992a9e8a9c248563d3e6
3
+ metadata.gz: 1850108a3752d6b01de4e2c23453220ac0cf8cd1e4dd61339faeba1c66856960
4
+ data.tar.gz: 71b704481fa26e973929c19c9c7df25685b83b65e5ef470ed032531e7fb3fec6
5
5
  SHA512:
6
- metadata.gz: 2d622f6d843b9975a66af4ee3d937f1eb60b255d6c5017479dae583543d8f4dbba9608209513da94ba95a3aa3323714a0c1a9b21e594f1f4a2be61844864da3c
7
- data.tar.gz: b7835e3adfd35074111e481e3ba54272e59a764a5478c569bc516aa4a8455c939618347a194802a9d6030a91bc52430bf7fac72b00c3ebca1eb09cb13021f21a
6
+ metadata.gz: 0d57dc4c3aa5c7ffd9f287bf0ed2b097eb21671a32721fea1daad735c647f8d9d8d01e78c00e9c63d222d587be19246f8cbed85c2fbbc54b2f3a32bcc447b545
7
+ data.tar.gz: fc2b76b6f04b94a3843d3f0f8d10a131bc88ea576de7576879d9f6a5d490b2cb0e84b8ff8fee87107b1cf5f7c4a3c8ff84ade40bcadbdb4811bbf0f05088b69c
@@ -13,9 +13,9 @@ jobs:
13
13
  - docker.elastic.co/elasticsearch/elasticsearch:7.0.0
14
14
  - docker.elastic.co/elasticsearch/elasticsearch:7.11.2
15
15
  ruby:
16
- - 2.5
17
16
  - 2.6
18
17
  - 2.7
18
+ - 3.0
19
19
  services:
20
20
  elasticsearch:
21
21
  image: ${{ matrix.elasticsearch }}
data/CHANGELOG.md CHANGED
@@ -11,6 +11,21 @@
11
11
  * Added `SearchFlip::Connection#get_cluster_settings` and
12
12
  `#update_cluster_settings`
13
13
 
14
+ ## v3.5.0
15
+
16
+ * Add `SearchFlip::Criteria#http_timeout` to allow specifying timeouts on
17
+ a query level
18
+
19
+ ## v3.4.0
20
+
21
+ * Expose `Http#timeout` via `SearchFlip::HTTPClient`
22
+
23
+ ## v3.3.0
24
+
25
+ * Update httprb
26
+ * Changed oj default options
27
+ * Allow to set oj json options
28
+
14
29
  ## v3.2.1
15
30
 
16
31
  * Fix `refresh` having a empty body breaking in elasticsearch 7.11
data/README.md CHANGED
@@ -51,7 +51,7 @@ CommentIndex.search("hello world").where(available: true).sort(id: "desc").aggre
51
51
 
52
52
  ```
53
53
 
54
- Finally, SearchFlip comes with a minimal set of dependencies (http-rb and oj only).
54
+ Finally, SearchFlip comes with a minimal set of dependencies.
55
55
 
56
56
  ## Reference Docs
57
57
 
@@ -698,6 +698,14 @@ Specify a timeout to limit query processing time:
698
698
  CommentIndex.timeout("3s").execute
699
699
  ```
700
700
 
701
+ * `http_timeout`
702
+
703
+ Specify a http timeout for the request which will be send to Elasticsearch:
704
+
705
+ ```ruby
706
+ CommentIndex.http_timeout(3).execute
707
+ ```
708
+
701
709
  * `terminate_after`
702
710
 
703
711
  Activate early query termination to stop query processing after the specified
@@ -756,7 +764,7 @@ end
756
764
  This allows to use different clusters per index e.g. when migrating indices to
757
765
  new versions of Elasticsearch.
758
766
 
759
- You can specify basic auth, additional headers, etc via:
767
+ You can specify basic auth, additional headers, request timeouts, etc via:
760
768
 
761
769
  ```ruby
762
770
  http_client = SearchFlip::HTTPClient.new
@@ -773,6 +781,9 @@ http_client = http_client.via("proxy.host", 8080)
773
781
  # Custom headers
774
782
  http_client = http_client.headers(key: "value")
775
783
 
784
+ # Timeouts
785
+ http_client = http_client.timeout(20)
786
+
776
787
  SearchFlip::Connection.new(base_url: "...", http_client: http_client)
777
788
  ```
778
789
 
@@ -882,6 +893,47 @@ Thus, if your ORM supports `.find_each`, `#id` and `#where` you are already
882
893
  good to go. Otherwise, simply add your custom implementation of those methods
883
894
  that work with whatever ORM you use.
884
895
 
896
+ ## JSON
897
+
898
+ SearchFlip is using the [Oj gem](https://github.com/ohler55/oj) to generate
899
+ JSON. More concretely, SearchFlip is using:
900
+
901
+ ```ruby
902
+ Oj.dump({ key: "value" }, mode: :custom, use_to_json: true, time_format: :xmlschema, bigdecimal_as_decimal: false)
903
+ ```
904
+
905
+ The `use_to_json` option is used for maximum compatibility, most importantly
906
+ when using rails `ActiveSupport::TimeWithZone` timestamps, which `oj` can not
907
+ serialize natively. However, `use_to_json` adds performance overhead. You can
908
+ change the json options via:
909
+
910
+ ```ruby
911
+ SearchFlip::Config[:json_options] = {
912
+ mode: :custom,
913
+ use_to_json: false,
914
+ time_format: :xmlschema,
915
+ bigdecimal_as_decimal: false
916
+ }
917
+ ```
918
+
919
+ However, you then have to convert timestamps manually for indexation via e.g.:
920
+
921
+ ```ruby
922
+ class MyIndex
923
+ # ...
924
+
925
+ def self.serialize(model)
926
+ {
927
+ # ...
928
+
929
+ created_at: model.created_at.to_time
930
+ }
931
+ end
932
+ end
933
+ ```
934
+
935
+ Please check out the oj docs for more details.
936
+
885
937
  ## Feature Support
886
938
 
887
939
  * for Elasticsearch 2.x, the delete-by-query plugin is required to delete
data/lib/search_flip.rb CHANGED
@@ -2,6 +2,7 @@ require "ruby2_keywords"
2
2
  require "forwardable"
3
3
  require "http"
4
4
  require "thread"
5
+ require "json"
5
6
  require "oj"
6
7
  require "set"
7
8
 
@@ -31,10 +32,15 @@ require "search_flip/index"
31
32
  require "search_flip/model"
32
33
 
33
34
  module SearchFlip
34
- class NotSupportedError < StandardError; end
35
- class ConnectionError < StandardError; end
35
+ class Error < StandardError; end
36
36
 
37
- class ResponseError < StandardError
37
+ class NotSupportedError < Error; end
38
+
39
+ class HttpError < Error; end
40
+ class ConnectionError < HttpError; end
41
+ class TimeoutError < HttpError; end
42
+
43
+ class ResponseError < Error
38
44
  attr_reader :code, :body
39
45
 
40
46
  def initialize(code:, body:)
@@ -5,6 +5,12 @@ module SearchFlip
5
5
  bulk_limit: 1_000,
6
6
  bulk_max_mb: 100,
7
7
  auto_refresh: false,
8
- instrumenter: NullInstrumenter.new
8
+ instrumenter: NullInstrumenter.new,
9
+ json_options: {
10
+ mode: :custom,
11
+ use_to_json: true,
12
+ time_format: :xmlschema,
13
+ bigdecimal_as_decimal: false
14
+ }
9
15
  }
10
16
  end
@@ -26,7 +26,8 @@ module SearchFlip
26
26
 
27
27
  attr_accessor :target, :profile_value, :source_value, :suggest_values, :includes_values,
28
28
  :eager_load_values, :preload_values, :failsafe_value, :scroll_args, :terminate_after_value,
29
- :timeout_value, :preference_value, :search_type_value, :routing_value, :track_total_hits_value
29
+ :timeout_value, :preference_value, :search_type_value, :routing_value, :track_total_hits_value,
30
+ :http_timeout_value
30
31
 
31
32
  # Creates a new criteria while merging the attributes (constraints,
32
33
  # settings, etc) of the current criteria with the attributes of another one
@@ -47,7 +48,7 @@ module SearchFlip
47
48
  [
48
49
  :profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :offset_value,
49
50
  :limit_value, :scroll_args, :source_value, :preference_value, :search_type_value,
50
- :routing_value, :track_total_hits_value, :explain_value
51
+ :routing_value, :track_total_hits_value, :explain_value, :http_timeout_value
51
52
  ].each do |name|
52
53
  criteria.send(:"#{name}=", other.send(name)) unless other.send(name).nil?
53
54
  end
@@ -148,6 +149,22 @@ module SearchFlip
148
149
  end
149
150
  end
150
151
 
152
+ # Specifies a http timeout, such that a SearchFlip::TimeoutError will be
153
+ # thrown when the request times out.
154
+ #
155
+ # @example
156
+ # ProductIndex.http_timeout(3).search("hello world")
157
+ #
158
+ # @param value [Fixnum] The timeout value
159
+ #
160
+ # @return [SearchFlip::Criteria] A newly created extended criteria
161
+
162
+ def http_timeout(value)
163
+ fresh.tap do |criteria|
164
+ criteria.http_timeout_value = value
165
+ end
166
+ end
167
+
151
168
  # Specifies early query termination, such that the processing will be
152
169
  # stopped after the specified number of results has been accumulated.
153
170
  #
@@ -330,10 +347,13 @@ module SearchFlip
330
347
  dupped_request.delete(:from)
331
348
  dupped_request.delete(:size)
332
349
 
350
+ http_request = connection.http_client
351
+ http_request = http_request.timeout(http_timeout_value) if http_timeout_value
352
+
333
353
  if connection.version.to_i >= 5
334
- connection.http_client.post("#{target.type_url}/_delete_by_query", params: request_params.merge(params), json: dupped_request)
354
+ http_request.post("#{target.type_url}/_delete_by_query", params: request_params.merge(params), json: dupped_request)
335
355
  else
336
- connection.http_client.delete("#{target.type_url}/_query", params: request_params.merge(params), json: dupped_request)
356
+ http_request.delete("#{target.type_url}/_query", params: request_params.merge(params), json: dupped_request)
337
357
  end
338
358
 
339
359
  target.refresh if SearchFlip::Config[:auto_refresh]
@@ -501,8 +521,8 @@ module SearchFlip
501
521
  end
502
522
 
503
523
  # Executes the search request for the current criteria, ie sends the
504
- # request to Elasticsearch and returns the response. Connection and
505
- # response errors will be rescued if you specify the criteria to be
524
+ # request to Elasticsearch and returns the response. Connection, timeout
525
+ # and response errors will be rescued if you specify the criteria to be
506
526
  # #failsafe, such that an empty response is returned instead.
507
527
  #
508
528
  # @example
@@ -590,6 +610,7 @@ module SearchFlip
590
610
 
591
611
  def execute!
592
612
  http_request = connection.http_client.headers(accept: "application/json")
613
+ http_request = http_request.timeout(http_timeout_value) if http_timeout_value
593
614
 
594
615
  http_response =
595
616
  if scroll_args && scroll_args[:id]
@@ -609,7 +630,7 @@ module SearchFlip
609
630
  end
610
631
 
611
632
  SearchFlip::Response.new(self, SearchFlip::JSON.parse(http_response.to_s))
612
- rescue SearchFlip::ConnectionError, SearchFlip::ResponseError => e
633
+ rescue SearchFlip::ConnectionError, SearchFlip::TimeoutError, SearchFlip::ResponseError => e
613
634
  raise e unless failsafe_value
614
635
 
615
636
  SearchFlip::Response.new(self, "took" => 0, "hits" => { "total" => 0, "hits" => [] })
@@ -1,7 +1,28 @@
1
1
  module SearchFlip
2
- # The SearchFlip::HTTPClient class wraps the http gem, is for internal use
3
- # and responsible for the http request/response handling, ie communicating
4
- # with Elasticsearch.
2
+ # The SearchFlip::HTTPClient class wraps the http gem and responsible for the
3
+ # http request/response handling, ie communicating with Elasticsearch. You
4
+ # only need to use it directly if you need authentication to communicate with
5
+ # Elasticsearch or if you want to set some custom http settings.
6
+ #
7
+ # @example
8
+ # http_client = SearchFlip::HTTPClient.new
9
+ #
10
+ # # Basic Auth
11
+ # http_client = http_client.basic_auth(user: "username", pass: "password")
12
+ #
13
+ # # Raw Auth Header
14
+ # http_client = http_client.auth("Bearer VGhlIEhUVFAgR2VtLCBST0NLUw")
15
+ #
16
+ # # Proxy Settings
17
+ # http_client = http_client.via("proxy.host", 8080)
18
+ #
19
+ # # Custom headers
20
+ # http_client = http_client.headers(key: "value")
21
+ #
22
+ # # Timeouts
23
+ # http_client = http_client.timeout(20)
24
+ #
25
+ # SearchFlip::Connection.new(base_url: "...", http_client: http_client)
5
26
 
6
27
  class HTTPClient
7
28
  attr_accessor :request, :plugins
@@ -14,11 +35,11 @@ module SearchFlip
14
35
  class << self
15
36
  extend Forwardable
16
37
 
17
- def_delegators :new, :headers, :via, :basic_auth, :auth
38
+ def_delegators :new, :headers, :via, :basic_auth, :auth, :timeout
18
39
  def_delegators :new, :get, :post, :put, :delete, :head
19
40
  end
20
41
 
21
- [:headers, :via, :basic_auth, :auth].each do |method|
42
+ [:headers, :via, :basic_auth, :auth, :timeout].each do |method|
22
43
  define_method method do |*args|
23
44
  dup.tap do |client|
24
45
  client.request = request.send(method, *args)
@@ -45,6 +66,10 @@ module SearchFlip
45
66
  response
46
67
  rescue HTTP::ConnectionError => e
47
68
  raise SearchFlip::ConnectionError, e.message
69
+ rescue HTTP::TimeoutError => e
70
+ raise SearchFlip::TimeoutError, e.message
71
+ rescue HTTP::Error => e
72
+ raise SearchFlip::HttpError, e.message
48
73
  end
49
74
  end
50
75
  end
@@ -254,7 +254,7 @@ module SearchFlip
254
254
  :page, :per, :search, :highlight, :suggest, :custom, :find_in_batches, :find_results_in_batches,
255
255
  :find_each, :find_each_result, :failsafe, :total_entries, :total_count, :timeout, :terminate_after,
256
256
  :records, :results, :must, :must_not, :should, :preference, :search_type, :routing,
257
- :track_total_hits, :explain
257
+ :track_total_hits, :explain, :http_timeout
258
258
 
259
259
  # Override to specify the type name used within Elasticsearch. Recap,
260
260
  # this gem uses an individual index for each index class, because
@@ -1,22 +1,11 @@
1
1
  module SearchFlip
2
2
  class JSON
3
- @default_options = {
4
- mode: :custom,
5
- use_to_json: true,
6
- time_format: :xmlschema,
7
- bigdecimal_as_decimal: false
8
- }
9
-
10
- def self.default_options
11
- @default_options
12
- end
13
-
14
3
  def self.generate(obj)
15
- Oj.dump(obj, default_options)
4
+ Oj.dump(obj, SearchFlip::Config[:json_options])
16
5
  end
17
6
 
18
7
  def self.parse(json)
19
- Oj.load(json, default_options)
8
+ ::JSON.parse(json)
20
9
  end
21
10
  end
22
11
  end
@@ -1,3 +1,3 @@
1
1
  module SearchFlip
2
- VERSION = "4.0.0.beta5"
2
+ VERSION = "4.0.0.beta9"
3
3
  end
data/search_flip.gemspec CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_development_dependency "webmock"
37
37
 
38
38
  spec.add_dependency "http"
39
+ spec.add_dependency "json"
39
40
  spec.add_dependency "oj"
40
41
  spec.add_dependency "ruby2_keywords"
41
42
  end
@@ -97,7 +97,8 @@ RSpec.describe SearchFlip::Criteria do
97
97
  methods = [
98
98
  :profile_value, :failsafe_value, :terminate_after_value, :timeout_value,
99
99
  :offset_value, :limit_value, :scroll_args, :source_value, :preference_value,
100
- :search_type_value, :routing_value, :track_total_hits_value, :explain_value
100
+ :search_type_value, :routing_value, :track_total_hits_value, :explain_value,
101
+ :http_timeout_value
101
102
  ]
102
103
 
103
104
  methods.each do |method|
@@ -191,6 +192,22 @@ RSpec.describe SearchFlip::Criteria do
191
192
  end
192
193
  end
193
194
 
195
+ describe "#http_timeout" do
196
+ it "sets the query timeout" do
197
+ http_client = double("client").as_null_object
198
+ allow(http_client).to receive(:timeout).and_return(http_client)
199
+ allow(http_client).to receive(:post).and_raise(SearchFlip::TimeoutError)
200
+ allow(ProductIndex.connection).to receive(:http_client).and_return(http_client)
201
+
202
+ expect { ProductIndex.http_timeout(1).execute }.to raise_error(SearchFlip::TimeoutError)
203
+ expect(http_client).to have_received(:timeout).with(1)
204
+ end
205
+
206
+ it "executes without errors" do
207
+ expect { ProductIndex.http_timeout(1).execute }.not_to raise_error
208
+ end
209
+ end
210
+
194
211
  describe "#terminate_after" do
195
212
  it "sets the terminate after value" do
196
213
  query = ProductIndex.terminate_after(1)
@@ -1204,13 +1221,19 @@ RSpec.describe SearchFlip::Criteria do
1204
1221
  end
1205
1222
 
1206
1223
  describe "#failsafe" do
1207
- it "prevents query syntax exceptions" do
1208
- expect { ProductIndex.search("syntax/error").records }.to raise_error(SearchFlip::ResponseError)
1224
+ [SearchFlip::ConnectionError, SearchFlip::TimeoutError, SearchFlip::ResponseError.new(code: "code", body: "body")].each do |error|
1225
+ it "prevents #{error}" do
1226
+ http_client = double("client").as_null_object
1227
+ allow(http_client).to receive(:post).and_raise(error)
1228
+ allow(ProductIndex.connection).to receive(:http_client).and_return(http_client)
1229
+
1230
+ expect { ProductIndex.all.execute }.to raise_error(error)
1209
1231
 
1210
- query = ProductIndex.failsafe(true).search("syntax/error")
1232
+ query = ProductIndex.failsafe(true)
1211
1233
 
1212
- expect(query.records).to eq([])
1213
- expect(query.total_entries).to eq(0)
1234
+ expect(query.records).to eq([])
1235
+ expect(query.total_entries).to eq(0)
1236
+ end
1214
1237
  end
1215
1238
  end
1216
1239
 
@@ -7,7 +7,7 @@ class HttpTestRequest
7
7
  self.calls = []
8
8
  end
9
9
 
10
- [:via, :basic_auth, :auth].each do |method|
10
+ [:headers, :via, :basic_auth, :auth, :timeout].each do |method|
11
11
  define_method method do |*args|
12
12
  dup.tap do |request|
13
13
  request.calls = calls + [[method, args]]
@@ -20,7 +20,7 @@ RSpec.describe SearchFlip::HTTPClient do
20
20
  describe "delegation" do
21
21
  subject { SearchFlip::HTTPClient }
22
22
 
23
- [:headers, :via, :basic_auth, :auth].each do |method|
23
+ [:headers, :via, :basic_auth, :auth, :timeout].each do |method|
24
24
  it { should delegate(method).to(:new) }
25
25
  end
26
26
 
@@ -56,8 +56,12 @@ RSpec.describe SearchFlip::HTTPClient do
56
56
  end
57
57
  end
58
58
 
59
- [:via, :basic_auth, :auth].each do |method|
59
+ [:headers, :via, :basic_auth, :auth, :timeout].each do |method|
60
60
  describe "##{method}" do
61
+ it "is understood by HTTP" do
62
+ expect(HTTP.respond_to?(method)).to eq(true)
63
+ end
64
+
61
65
  it "creates a dupped instance" do
62
66
  client = SearchFlip::HTTPClient.new
63
67
  client.request = HttpTestRequest.new
@@ -14,7 +14,7 @@ RSpec.describe SearchFlip::Index do
14
14
  :total_entries, :total_count, :terminate_after, :timeout, :records, :results,
15
15
  :must, :must_not, :should, :find_each_result,
16
16
  :find_results_in_batches, :preference, :search_type, :routing,
17
- :track_total_hits, :explain
17
+ :track_total_hits, :explain, :http_timeout
18
18
  ]
19
19
 
20
20
  methods.each do |method|
@@ -32,14 +32,14 @@ RSpec.describe SearchFlip::JSON do
32
32
  expect(described_class.parse('{"key":"value"}')).to eq("key" => "value")
33
33
  end
34
34
 
35
- it "delegates to Oj" do
36
- allow(Oj).to receive(:load)
35
+ it "delegates to JSON" do
36
+ allow(JSON).to receive(:parse)
37
37
 
38
38
  payload = '{"key":"value"}'
39
39
 
40
40
  described_class.parse(payload)
41
41
 
42
- expect(Oj).to have_received(:load).with(payload, mode: :custom, use_to_json: true, time_format: :xmlschema, bigdecimal_as_decimal: false)
42
+ expect(JSON).to have_received(:parse).with(payload)
43
43
  end
44
44
  end
45
45
  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: 4.0.0.beta5
4
+ version: 4.0.0.beta9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Vetter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-18 00:00:00.000000000 Z
11
+ date: 2021-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: json
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: oj
169
183
  requirement: !ruby/object:Gem::Requirement