caoutsearch 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,14 +4,24 @@ module Caoutsearch
4
4
  module Search
5
5
  module Batch
6
6
  module SearchAfter
7
- def search_after(keep_alive: "1m", batch_size: 1000, &block)
8
- pit_id = open_point_in_time(keep_alive: keep_alive)
7
+ def search_after(pit: nil, keep_alive: nil, batch_size: 1000, &block)
8
+ if pit
9
+ external_pit = true
10
+
11
+ warn(<<~MESSAGE) if keep_alive.nil?
12
+ A `pit` was passed to batch records without a `keep_alive` argument.
13
+ You may need it to extend the PIT on each request.
14
+ MESSAGE
15
+ end
16
+
17
+ keep_alive ||= "1m"
18
+ pit ||= open_point_in_time(keep_alive: keep_alive)
9
19
  search = per(batch_size).track_total_hits
10
20
 
11
21
  request_payload = {
12
22
  body: search.build.to_h.merge(
13
23
  pit: {
14
- id: pit_id,
24
+ id: pit,
15
25
  keep_alive: keep_alive
16
26
  }
17
27
  )
@@ -26,7 +36,7 @@ module Caoutsearch
26
36
  loop do
27
37
  requested_at = Time.current
28
38
 
29
- results = instrument(:search_after, pit: pit_id) do |event_payload|
39
+ results = instrument(:search_after, pit: pit) do |event_payload|
30
40
  response = client.search(request_payload)
31
41
  last_response_time = Time.current
32
42
 
@@ -40,27 +50,37 @@ module Caoutsearch
40
50
 
41
51
  response
42
52
  rescue Elastic::Transport::Transport::Errors::NotFound => e
43
- raise_enhance_message_when_pit_failed(e, keep_alive, requested_at, last_response_time)
53
+ if external_pit && progress.zero?
54
+ raise_enhance_message_on_missing_pit(e)
55
+ else
56
+ raise_enhance_message_on_pit_failure(e, keep_alive, requested_at, last_response_time)
57
+ end
44
58
  end
45
59
 
46
60
  hits = results["hits"]["hits"]
47
- pit_id = results["pit_id"]
61
+ pit = results["pit_id"]
48
62
  break if hits.empty?
49
63
 
50
64
  yield hits
51
65
  break if progress >= total
52
66
 
53
67
  request_payload[:body].tap do |body|
54
- body[:pit][:id] = pit_id
68
+ body[:pit][:id] = pit
55
69
  body[:search_after] = hits.last["sort"]
56
70
  body.delete(:track_total_hits)
57
71
  end
58
72
  end
59
73
  ensure
60
- close_point_in_time(pit_id) if pit_id
74
+ close_point_in_time(pit) if pit && !external_pit
75
+ end
76
+
77
+ private
78
+
79
+ def raise_enhance_message_on_missing_pit(error)
80
+ raise error.exception "PIT was not found. #{error.message}"
61
81
  end
62
82
 
63
- def raise_enhance_message_when_pit_failed(error, keep_alive, requested_at, last_response_time)
83
+ def raise_enhance_message_on_pit_failure(error, keep_alive, requested_at, last_response_time)
64
84
  elapsed = (requested_at - last_response_time).round(1).seconds
65
85
 
66
86
  raise error.exception("PIT registered for #{keep_alive}, #{elapsed.inspect} elapsed between. #{error.message}")
@@ -5,7 +5,7 @@ module Caoutsearch
5
5
  module Inspect
6
6
  PROPERTIES_TO_INSPECT = %i[
7
7
  search_criteria
8
- current_context
8
+ current_contexts
9
9
  current_order
10
10
  current_page
11
11
  current_limit
@@ -15,13 +15,13 @@ module Caoutsearch
15
15
  # self.config[:filter][key] = ...
16
16
  #
17
17
  class_attribute :config, default: {
18
- contexts: ActiveSupport::HashWithIndifferentAccess.new,
19
18
  filters: ActiveSupport::HashWithIndifferentAccess.new,
20
19
  defaults: ActiveSupport::HashWithIndifferentAccess.new,
21
20
  suggestions: ActiveSupport::HashWithIndifferentAccess.new,
22
21
  sorts: ActiveSupport::HashWithIndifferentAccess.new
23
22
  }
24
23
 
24
+ class_attribute :contexts, instance_accessor: false, default: {}
25
25
  class_attribute :aggregations, instance_accessor: false, default: {}
26
26
  class_attribute :transformations, instance_accessor: false, default: {}
27
27
  end
@@ -32,7 +32,7 @@ module Caoutsearch
32
32
  config[:match_all] = block
33
33
  end
34
34
 
35
- %w[context default].each do |method|
35
+ %w[default].each do |method|
36
36
  config_attribute = method.pluralize.to_sym
37
37
 
38
38
  define_method method do |name = nil, &block|
@@ -68,6 +68,11 @@ module Caoutsearch
68
68
  sort(new_name) { |direction| sort_by(old_name, direction) }
69
69
  end
70
70
 
71
+ def has_context(name, &block)
72
+ self.contexts = contexts.dup
73
+ contexts[name.to_s] = Caoutsearch::Search::DSL::Item.new(name, &block)
74
+ end
75
+
71
76
  def has_aggregation(name, definition = {}, &block)
72
77
  raise ArgumentError, "has_aggregation accepts Hash definition or block but not both" if block && definition.any?
73
78
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module QueryBuilder
6
+ module Contexts
7
+ private
8
+
9
+ def build_contexts
10
+ call_contexts(*current_contexts) if current_contexts
11
+ end
12
+
13
+ def call_contexts(*args)
14
+ args.each do |arg|
15
+ call_context(arg)
16
+ end
17
+ end
18
+
19
+ def call_context(name)
20
+ name = name.to_s
21
+
22
+ if self.class.contexts.include?(name)
23
+ item = self.class.contexts[name]
24
+ call_context_item(item)
25
+ end
26
+ end
27
+
28
+ def call_context_item(item)
29
+ instance_exec(&item.block)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -5,6 +5,7 @@ module Caoutsearch
5
5
  module QueryBuilder
6
6
  extend ActiveSupport::Concern
7
7
  include QueryBuilder::Aggregations
8
+ include QueryBuilder::Contexts
8
9
 
9
10
  def build
10
11
  reset_variable(:@elasticsearch_query)
@@ -13,7 +14,7 @@ module Caoutsearch
13
14
  run_callbacks :build do
14
15
  build_prepend_hash
15
16
  build_search_criteria
16
- build_context
17
+ build_contexts
17
18
  build_defaults
18
19
  build_limits
19
20
  build_orders
@@ -35,13 +36,6 @@ module Caoutsearch
35
36
  search_by(search_criteria)
36
37
  end
37
38
 
38
- def build_context
39
- return unless current_context
40
-
41
- item = config[:contexts][current_context.to_s]
42
- instance_exec(&item.block) if item
43
- end
44
-
45
39
  def build_defaults
46
40
  keys = search_criteria_keys.map(&:to_s)
47
41
 
@@ -5,7 +5,7 @@ module Caoutsearch
5
5
  module SearchMethods
6
6
  extend ActiveSupport::Concern
7
7
 
8
- attr_reader :current_context, :current_order, :current_aggregations,
8
+ attr_reader :current_contexts, :current_order, :current_aggregations,
9
9
  :current_suggestions, :current_fields, :current_source
10
10
 
11
11
  # Public API
@@ -97,8 +97,9 @@ module Caoutsearch
97
97
  self
98
98
  end
99
99
 
100
- def context!(value)
101
- @current_context = value
100
+ def context!(*values)
101
+ @current_contexts ||= []
102
+ @current_contexts += values.flatten
102
103
  self
103
104
  end
104
105
 
@@ -173,7 +174,7 @@ module Caoutsearch
173
174
  "aggregations" => :@current_aggregations,
174
175
  "suggest" => :@current_suggestions,
175
176
  "suggestions" => :@current_suggestions,
176
- "context" => :@current_context,
177
+ "context" => :@current_contexts,
177
178
  "order" => :@current_order,
178
179
  "page" => :@current_page,
179
180
  "offset" => :@current_offset,
@@ -189,7 +190,7 @@ module Caoutsearch
189
190
  self
190
191
  end
191
192
 
192
- # Getters
193
+ # Getters and predicates
193
194
  # ------------------------------------------------------------------------
194
195
  def search_criteria
195
196
  @search_criteria ||= []
@@ -213,6 +214,10 @@ module Caoutsearch
213
214
  end
214
215
  end
215
216
 
217
+ def current_context?(name)
218
+ @current_contexts&.map(&:to_s)&.include?(name.to_s)
219
+ end
220
+
216
221
  # Criteria handlers
217
222
  # ------------------------------------------------------------------------
218
223
  def find_criterion(key)
@@ -7,26 +7,13 @@ module Caoutsearch
7
7
  module Testing
8
8
  module MockRequests
9
9
  def stub_elasticsearch_request(verb, pattern)
10
- transport = Caoutsearch.client.transport
11
- host = transport.__full_url(transport.hosts[0])
12
-
13
- # Elasticsearch::Client is verify the connection
14
- #
15
- unless @subbed_verify
16
- root_url = URI.join(host, "/").to_s
17
- body = +MultiJson.dump({version: {number: "8.4.1"}})
18
- @subbed_verify = stub_request(:get, root_url).to_return(
19
- headers: {"Content-Type" => "application/json", "X-Elastic-Product" => "Elasticsearch"},
20
- status: 200,
21
- body: body
22
- )
23
- end
10
+ stub_elasticsearch_validation_request
24
11
 
25
12
  case pattern
26
13
  when String
27
- pattern = URI.join(host, pattern).to_s
14
+ pattern = URI.join(elasticsearch_client_host, pattern).to_s
28
15
  when Regexp
29
- pattern = URI.join(host, pattern.source).to_s
16
+ pattern = URI.join(elasticsearch_client_host, pattern.source).to_s
30
17
  pattern = Regexp.new(pattern)
31
18
  else
32
19
  raise TypeError, "wrong type received for URL pattern"
@@ -100,6 +87,45 @@ module Caoutsearch
100
87
 
101
88
  search_request
102
89
  end
90
+
91
+ private
92
+
93
+ def elasticsearch_client_host
94
+ @client_host ||= begin
95
+ transport = Caoutsearch.client.transport
96
+ transport.__full_url(transport.hosts[0])
97
+ end
98
+ end
99
+
100
+ # Elasticsearch::Client is verifying the connection to ES when calling
101
+ # the first request on the client.
102
+ #
103
+ # Prior to version 8.9, it sent a request to "/" before the first request
104
+ # and match the the "X-Elastic-Product" header and version returned.
105
+ #
106
+ # Since 8.9, it matches only the header on the first emitted request.
107
+ # Because we cannot we cannot edit all the responses headers configured
108
+ # after calling `stub_elasticsearch_request`, we better have to
109
+ # call the stubbed request to '/' before any request.
110
+ #
111
+ def stub_elasticsearch_validation_request
112
+ @stubbed_elasticsearch_validation_request ||= begin
113
+ root_url = URI.join(elasticsearch_client_host, "/").to_s
114
+ body = +MultiJson.dump({version: {number: Elasticsearch::VERSION}})
115
+
116
+ stubbed_request = stub_request(:get, root_url).to_return(
117
+ headers: {"Content-Type" => "application/json", "X-Elastic-Product" => "Elasticsearch"},
118
+ status: 200,
119
+ body: body
120
+ )
121
+
122
+ if Gem::Version.new(Elasticsearch::VERSION) >= Gem::Version.new("8.9.0")
123
+ Caoutsearch.client.perform_request("GET", "/")
124
+ end
125
+
126
+ stubbed_request
127
+ end
128
+ end
103
129
  end
104
130
  end
105
131
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caoutsearch
4
- VERSION = "0.0.5"
4
+ VERSION = "0.0.7"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caoutsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Savater Sebastien
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-01-30 00:00:00.000000000 Z
12
+ date: 2023-08-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -81,6 +81,216 @@ dependencies:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
83
  version: '2.6'
84
+ - !ruby/object:Gem::Dependency
85
+ name: activerecord
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: amazing_print
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: appraisal
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: database_cleaner-active_record
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: rake
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ - !ruby/object:Gem::Dependency
155
+ name: rspec
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: rubocop
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ - !ruby/object:Gem::Dependency
183
+ name: rubocop-rake
184
+ requirement: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ type: :development
190
+ prerelease: false
191
+ version_requirements: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: '0'
196
+ - !ruby/object:Gem::Dependency
197
+ name: rubocop-rspec
198
+ requirement: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ type: :development
204
+ prerelease: false
205
+ version_requirements: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ - !ruby/object:Gem::Dependency
211
+ name: rubocop-performance
212
+ requirement: !ruby/object:Gem::Requirement
213
+ requirements:
214
+ - - ">="
215
+ - !ruby/object:Gem::Version
216
+ version: '0'
217
+ type: :development
218
+ prerelease: false
219
+ version_requirements: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ - !ruby/object:Gem::Dependency
225
+ name: simplecov
226
+ requirement: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: '0'
231
+ type: :development
232
+ prerelease: false
233
+ version_requirements: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
238
+ - !ruby/object:Gem::Dependency
239
+ name: standard
240
+ requirement: !ruby/object:Gem::Requirement
241
+ requirements:
242
+ - - ">="
243
+ - !ruby/object:Gem::Version
244
+ version: '1.0'
245
+ type: :development
246
+ prerelease: false
247
+ version_requirements: !ruby/object:Gem::Requirement
248
+ requirements:
249
+ - - ">="
250
+ - !ruby/object:Gem::Version
251
+ version: '1.0'
252
+ - !ruby/object:Gem::Dependency
253
+ name: sqlite3
254
+ requirement: !ruby/object:Gem::Requirement
255
+ requirements:
256
+ - - ">="
257
+ - !ruby/object:Gem::Version
258
+ version: '0'
259
+ type: :development
260
+ prerelease: false
261
+ version_requirements: !ruby/object:Gem::Requirement
262
+ requirements:
263
+ - - ">="
264
+ - !ruby/object:Gem::Version
265
+ version: '0'
266
+ - !ruby/object:Gem::Dependency
267
+ name: timecop
268
+ requirement: !ruby/object:Gem::Requirement
269
+ requirements:
270
+ - - ">="
271
+ - !ruby/object:Gem::Version
272
+ version: '0'
273
+ type: :development
274
+ prerelease: false
275
+ version_requirements: !ruby/object:Gem::Requirement
276
+ requirements:
277
+ - - ">="
278
+ - !ruby/object:Gem::Version
279
+ version: '0'
280
+ - !ruby/object:Gem::Dependency
281
+ name: webmock
282
+ requirement: !ruby/object:Gem::Requirement
283
+ requirements:
284
+ - - ">="
285
+ - !ruby/object:Gem::Version
286
+ version: '0'
287
+ type: :development
288
+ prerelease: false
289
+ version_requirements: !ruby/object:Gem::Requirement
290
+ requirements:
291
+ - - ">="
292
+ - !ruby/object:Gem::Version
293
+ version: '0'
84
294
  description:
85
295
  email: github.60k5k@simplelogin.co
86
296
  executables: []
@@ -147,6 +357,7 @@ files:
147
357
  - lib/caoutsearch/search/query/setters.rb
148
358
  - lib/caoutsearch/search/query_builder.rb
149
359
  - lib/caoutsearch/search/query_builder/aggregations.rb
360
+ - lib/caoutsearch/search/query_builder/contexts.rb
150
361
  - lib/caoutsearch/search/query_methods.rb
151
362
  - lib/caoutsearch/search/records.rb
152
363
  - lib/caoutsearch/search/resettable.rb
@@ -179,7 +390,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
179
390
  - !ruby/object:Gem::Version
180
391
  version: '0'
181
392
  requirements: []
182
- rubygems_version: 3.3.7
393
+ rubygems_version: 3.4.8
183
394
  signing_key:
184
395
  specification_version: 4
185
396
  summary: An alternative approach to index & search with Elasticsearch & Ruby on Rails