caoutsearch 0.0.5 → 0.0.7

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