search_flip 4.0.0.beta5 → 4.0.0.beta9

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