elasticsearch_record 1.4.0 → 1.5.1

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: 8a386dd571b2e88a62e26f815c16783d3fda97ed911b4edffa4db884230dfc18
4
- data.tar.gz: a50697c2043a3160c31716d07d9afbe05bab691267773b92f424bead64193ecb
3
+ metadata.gz: b6e4ca9acc6ee9abda4a3dacec4a75f9f1af77c361dbff3b53aa30648dfa5a5f
4
+ data.tar.gz: f6cf2edda611efb7daa6585764c09c2320040c8970555d64dab96ced763d2f19
5
5
  SHA512:
6
- metadata.gz: 4c3172895b50805a17964e0db2406b90fd5797c40d91d17ee9c0cea2a26d0b52d328663f5b44ff882d32bf490662146ef5064040361ae5a677d97868078332b5
7
- data.tar.gz: 3d86fb89924f99c2ad64e44c225f78c434cabcbadc4660dfa8032c5038394b05b5ab949abd93d9895b3a7148c07f12ac99374c8cee1c2ce805f2188eab6be143
6
+ metadata.gz: 1d9327c0b6500d47ec5459ebd5e6053d8178d59b33cd690d5f9a2752ec22cc52d2728b295dd4644c5f7fa6b794c6a8d69305b372c6c138e2ae8c717500669453
7
+ data.tar.gz: 050e345874250113d23c69d4de6d3c8f35c7c6b4538fe8d1ee3dbeaf86a64dc7c745175a8301c5d78121481927bff34033a321e390fbaddf3356110951afb3c4
data/README.md CHANGED
@@ -14,10 +14,8 @@ _ElasticsearchRecord is a ActiveRecord adapter and provides similar functionalit
14
14
 
15
15
  **PLEASE NOTE:**
16
16
 
17
- - This is still in **development**!
18
- - Specs & documentation will follow.
19
- - You might experience BUGs and Exceptions...
20
- - Currently supports only ActiveRecord 7.0 + Elasticsearch 8.4 _(downgrade for rails 6.x is planned in future versions)_
17
+ - Specs & documentation are still missing, but will follow.
18
+ - Currently supports ActiveRecord ~> 7.0 + Elasticsearch >= 7.17
21
19
 
22
20
  -----
23
21
 
@@ -58,12 +56,13 @@ Or install it yourself as:
58
56
  ## Setup
59
57
 
60
58
  ### a) Update your **database.yml** and add a elasticsearch connection:
59
+
61
60
  ```yml
62
61
  # config/database.yml
63
62
 
64
63
  development:
65
64
  primary:
66
- # <...>
65
+ # <...>
67
66
 
68
67
  # elasticsearch
69
68
  elasticsearch:
@@ -71,10 +70,24 @@ Or install it yourself as:
71
70
  host: localhost:9200
72
71
  user: elastic
73
72
  password: '****'
74
- log: true
73
+
74
+ # enable ES verbose logging
75
+ # log: true
76
+
77
+ # add table (index) prefix & suffix to all 'tables'
78
+ # table_name_prefix: 'app-'
79
+ # table_name_suffix: '-development'
75
80
 
76
81
  production:
77
- ...
82
+ # <...>
83
+
84
+ # elasticsearch
85
+ elasticsearch:
86
+ # <...>
87
+
88
+ # add table (index) prefix & suffix to all 'tables'
89
+ # table_name_prefix: 'app-'
90
+ # table_name_suffix: '-production'
78
91
 
79
92
  test:
80
93
  ...
@@ -82,15 +95,19 @@ Or install it yourself as:
82
95
 
83
96
  ```
84
97
 
85
- ### b) Require ```elasticsearch_record/instrumentation``` in your application.rb (if you want to...):
98
+ ### b) Require `elasticsearch_record/instrumentation` in your application.rb (if you want to...):
99
+
86
100
  ```ruby
87
101
  # config/application.rb
102
+
88
103
  require_relative "boot"
89
104
 
90
105
  require "rails"
91
106
  # Pick the frameworks you want:
92
107
 
93
- # ...
108
+ # <...>
109
+
110
+ # add instrumentation
94
111
  require 'elasticsearch_record/instrumentation'
95
112
 
96
113
  module Application
@@ -98,17 +115,29 @@ module Application
98
115
  end
99
116
  ```
100
117
 
101
- ### c) Create a model that inherits from ```ElasticsearchRecord::Base``` model.
118
+ ### c) Create a model that inherits from `ElasticsearchRecord::Base` model.
119
+
102
120
  ```ruby
103
121
  # app/models/application_elasticsearch_record.rb
104
-
105
- class Search < ElasticsearchRecord::Base
106
-
122
+
123
+ class ApplicationElasticsearchRecord < ElasticsearchRecord::Base
124
+ # needs to be abstract
125
+ self.abstract_class = true
107
126
  end
127
+ ```
128
+
129
+ Example class, that inherits from **ApplicationElasticsearchRecord**
108
130
 
131
+ ```ruby
132
+ # app/models/search.rb
133
+
134
+ class Search < ApplicationElasticsearchRecord
135
+
136
+ end
109
137
  ```
110
138
 
111
139
  ### d) have FUN with your model:
140
+
112
141
  ```ruby
113
142
  scope = Search
114
143
  .where(name: 'Custom Object Name')
@@ -129,12 +158,13 @@ scope.where(kind: :undefined).offset(10).update_all(name: "New Name")
129
158
 
130
159
  ## Active Record Query Interface
131
160
 
132
- ### Refactored ```where``` method:
161
+ ### Refactored `where` method:
133
162
  Different to the default where-method you can now use it in different ways.
134
163
 
135
164
  Using it by default with a Hash, the method decides itself to either add a filter, or must_not clause.
136
165
 
137
- _Hint: If not provided through ```#kind```-method a default kind **:bool** will be used._
166
+ _Hint: If not provided through `#kind`-method a default kind **:bool** will be used._
167
+
138
168
  ```ruby
139
169
  # use it by default
140
170
  Search.where(name: 'A nice object')
@@ -187,7 +217,6 @@ results_count = scope.count
187
217
  # > 5
188
218
  total = scope.total
189
219
  # > 3335
190
-
191
220
  ```
192
221
 
193
222
  ### Available query/relation chain methods
@@ -239,11 +268,79 @@ _see simple documentation about these methods @ [rubydoc](https://rubydoc.info/g
239
268
 
240
269
  -----
241
270
 
242
- ### Useful model class attributes
243
- - index_base_name
244
- - relay_id_attribute
271
+ ## Useful model class attributes
272
+
273
+ ### index_base_name
274
+ Rails resolves a pluralized underscore table_name from the class name by default - which will not work for some models.
275
+
276
+ To support a generic +table_name_prefix+ & +table_name_suffix+ from the _database.yml_,
277
+ the 'index_base_name' provides a possibility to chain prefix, **base** and suffix.
278
+
279
+ ```ruby
280
+ class UnusalStat < ApplicationElasticsearchRecord
281
+ self.index_base_name = 'unusal-stats'
282
+ end
283
+
284
+ UnusalStat.where(year: 2023).to_query
285
+ # => {:index=>"app-unusal-stats-development", :body ...
286
+ ```
287
+
288
+ ### delegate_id_attribute
289
+ Rails resolves the primary_key's value by accessing the **#id** method.
290
+
291
+ Since Elasticsearch also supports an additional, independent **id** attribute,
292
+ it would only be able to access this through `_read_attribute(:id)`.
293
+
294
+ To also have the ability of accessing this attribute through the default, this flag can be enabled.
295
+
296
+ ```ruby
297
+ class SearchUser < ApplicationElasticsearchRecord
298
+ # attributes: id, name
299
+ end
300
+
301
+ # create new user within the index
302
+ user = SearchUser.create(id: 8, name: 'Parker')
303
+
304
+ # accessing the id, does NOT return the stored id by default - this will be delegated to the primary_key '_id'.
305
+ user.id
306
+ # => 'b2e34xa2'
307
+
308
+ # -- ENABLE delegation -------------------------------------------------------------------
309
+ SearchUser.delegate_id_attribute = true
310
+
311
+ # create new user within the index
312
+ user = SearchUser.create(id: 9, name: 'Pam')
313
+
314
+ # accessing the id accesses the stored attribute now
315
+ user.id
316
+ # => 9
317
+
318
+ # accessing the ES index id
319
+ user._id
320
+ # => 'xtf31bh8x'
321
+ ```
322
+
323
+ ## delegate_query_nil_limit
324
+ Elasticsearch's default value for queries without a **size** is forced to **10**.
325
+ To provide a similar behaviour as the (my)SQL interface,
326
+ this can be automatically set to the `max_result_window` value by calling `.limit(nil)` on the models' relation.
327
+
328
+ ```ruby
329
+ SearchUser.where(name: 'Peter').limit(nil)
330
+ # returns a maximum of 10 items ...
331
+ # => [...]
332
+
333
+ # -- ENABLE delegation -------------------------------------------------------------------
334
+ SearchUser.delegate_query_nil_limit = true
245
335
 
246
- ### Useful model class methods
336
+ SearchUser.where(name: 'Peter').limit(nil)
337
+ # returns up to 10_000 items ...
338
+ # => [...]
339
+
340
+ # hint: if you want more than 10_000 use the +#pit_results+ method!
341
+ ```
342
+
343
+ ## Useful model class methods
247
344
  - auto_increment?
248
345
  - max_result_window
249
346
  - source_column_names
@@ -251,26 +348,29 @@ _see simple documentation about these methods @ [rubydoc](https://rubydoc.info/g
251
348
  - find_by_query
252
349
  - msearch
253
350
 
254
- ### Useful model API methods
255
- Fast access to model-related methods for easier access without creating a overcomplicated method call.
351
+ ## Useful model API methods
352
+ Quick access to model-related methods for easier access without creating a overcomplicated method call on the models connection...
256
353
 
257
354
  Access these methods through the model class method `.api`.
355
+
258
356
  ```ruby
259
- # returns mapping of model class
260
- klass.api.mappings
357
+ # returns mapping of model class
358
+ klass.api.mappings
261
359
 
262
- # e.g. for ElasticUser model
263
- ElasticUser.api.mappings
360
+ # e.g. for ElasticUser model
361
+ SearchUser.api.mappings
264
362
 
265
- # insert new raw data
266
- ElasticUser.api.insert([{name: 'Hans', age: 34}, {name: 'Peter', age: 22}])
363
+ # insert new raw data
364
+ SearchUser.api.insert([{name: 'Hans', age: 34}, {name: 'Peter', age: 22}])
267
365
  ```
268
366
 
269
- * open
270
- * close
271
- * refresh
272
- * block
273
- * unblock
367
+ * open!
368
+ * close!
369
+ * refresh!
370
+ * block!
371
+ * unblock!
372
+ * drop!(confirm: true)
373
+ * truncate!(confirm: true)
274
374
  * mappings
275
375
  * metas
276
376
  * settings
@@ -290,12 +390,14 @@ Fast insert, update, delete raw data
290
390
  * delete
291
391
  * bulk
292
392
 
393
+ -----
394
+
293
395
  ## ActiveRecord ConnectionAdapters table-methods
294
396
  Access these methods through the model class method `.connection`.
295
397
 
296
398
  ```ruby
297
- # returns mapping of provided table (index)
298
- klass.connection.table_mappings('table-name')
399
+ # returns mapping of provided table (index)
400
+ klass.connection.table_mappings('table-name')
299
401
  ```
300
402
 
301
403
  - table_mappings
@@ -316,7 +418,7 @@ Access these methods through the model class method `.connection`.
316
418
  ## Active Record Schema migration methods
317
419
  Access these methods through the model's connection or within any `Migration`.
318
420
 
319
- **cluster actions:**
421
+ ### cluster actions:
320
422
  - open_table
321
423
  - open_tables
322
424
  - close_table
@@ -333,7 +435,7 @@ Access these methods through the model's connection or within any `Migration`.
333
435
  - change_table
334
436
  - rename_table
335
437
 
336
- **table actions:**
438
+ ### table actions:
337
439
  - change_meta
338
440
  - remove_meta
339
441
  - add_mapping
@@ -348,8 +450,10 @@ Access these methods through the model's connection or within any `Migration`.
348
450
  - change_alias
349
451
  - remove_alias
350
452
 
453
+
454
+ **Example migration:**
455
+
351
456
  ```ruby
352
- # Example migration
353
457
  class AddTests < ActiveRecord::Migration[7.0]
354
458
  def up
355
459
  create_table "assignments", if_not_exists: true do |t|
@@ -422,6 +526,49 @@ class AddTests < ActiveRecord::Migration[7.0]
422
526
  end
423
527
  ```
424
528
 
529
+ ## environment-related-table-name:
530
+ Using the `_env_table_name`-method will resolve the table (index) name within the current environment,
531
+ even if the environments shares the same cluster ...
532
+
533
+ This can be provided through the `database.yml` by using the `table_name_prefix/suffix` configuration keys.
534
+ Within the migration the `_env_table_name`-method must be used in combination with the table (index) base name.
535
+
536
+ **Example:**
537
+ Production uses a index suffix with '-pro', development uses '-dev' - they share the same cluster, but different indexes.
538
+
539
+ For the **settings** table:
540
+
541
+ * settings-pro
542
+ * settings-dev
543
+
544
+ A single migration can be created to be used within each environment:
545
+
546
+ ```ruby
547
+ # Example migration
548
+ class AddSettings < ActiveRecord::Migration[7.0]
549
+ def up
550
+ create_table _env_table_name("settings"), force: true do |t|
551
+ t.mapping :created_at, :date
552
+ t.mapping :key, :integer do |m|
553
+ m.primary_key = true
554
+ m.auto_increment = 10
555
+ end
556
+ t.mapping :status, :keyword
557
+ t.mapping :updated_at, :date
558
+ t.mapping :value, :text
559
+
560
+ t.setting "index.number_of_replicas", "0"
561
+ t.setting "index.number_of_shards", "1"
562
+ t.setting "index.routing.allocation.include._tier_preference", "data_content"
563
+ end
564
+ end
565
+
566
+ def down
567
+ drop_table _env_table_name("settings")
568
+ end
569
+ end
570
+ ```
571
+
425
572
  ## Docs
426
573
 
427
574
  [CHANGELOG](docs/CHANGELOG.md)
data/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # ElasticsearchRecord - CHANGELOG
2
2
 
3
+ ## [1.5.1] - 2023-07-11
4
+ * [fix] `ElasticsearchRecord::ModelApi` 'drop!' & 'truncate!' methods to support correct parameter 'confirm'
5
+ * [ref] improved yard documentation
6
+
7
+ ## [1.5.0] - 2023-07-10
8
+ * [add] additional `ElasticsearchRecord::ModelApi` methods **drop!** & **truncate!**, which have to be called with a `confirm:true` parameter
9
+ * [add] `.ElasticsearchRecord::Base.delegate_query_nil_limit` to automatically delegate a relations `limit(nil)`-call to the **max_result_window** _(set to 10.000 as default)_
10
+ * [add] `ActiveRecord::ConnectionAdapters::Elasticsearch::SchemaStatements#access_shard_doc?` which checks, if the **PIT**-shard_doc order is available
11
+ * [add] support for **_shard_doc** as a default order for `ElasticsearchRecord::Relation#pit_results`
12
+ * [ref] `.ElasticsearchRecord::Base.relay_id_attribute` to a more coherent name: `delegate_id_attribute`
13
+ * [ref] `ElasticsearchRecord::Relation#ordered_relation` to optimize already ordered relations
14
+ * [ref] gemspecs to support different versions of Elasticsearch
15
+ * [ref] improved README
16
+ * [fix] `ElasticsearchRecord::Relation#pit_results` infinite loop _(caused by missing order)_
17
+ * [fix] `ElasticsearchRecord::Relation#pit_results` results generation without 'uniq' check of the array
18
+
3
19
  ## [1.4.0] - 2023-01-27
4
20
  * [add] `ElasticsearchRecord::ModelApi` for fast & easy access the elasticsearch index - callable through `.api` (e.g. ElasticUser.api.mappings)
5
21
  * [ref] `ElasticsearchRecord::Instrumentation::LogSubscriber` to truncate the query-string (default: 1000)
@@ -32,12 +32,13 @@ DESC
32
32
 
33
33
  spec.require_paths = ["lib"]
34
34
 
35
- spec.add_dependency 'activerecord', '~> 7.0.0'
36
- spec.add_dependency 'elasticsearch', '~> 8.4'
35
+ spec.add_dependency 'activerecord', '~> 7.0'
36
+ spec.add_dependency 'elasticsearch', '>= 7.17'
37
37
 
38
38
  #spec.add_development_dependency 'coveralls_reborn', '~> 0.25'
39
39
  spec.add_development_dependency 'rspec', '~> 3.0'
40
40
  spec.add_development_dependency 'rake', "~> 13.0"
41
41
  spec.add_development_dependency 'yard', '~> 0.9'
42
42
  spec.add_development_dependency 'yard-activesupport-concern', '~> 0.0.1'
43
+ spec.add_development_dependency 'yard-relative_markdown_links', '>= 0.4'
43
44
  end
@@ -100,7 +100,7 @@ module ActiveRecord
100
100
  end
101
101
 
102
102
  # returns the meta hash
103
- # @param [Hash]
103
+ # @return [Hash]
104
104
  def meta
105
105
  __get_attribute(:meta) || {}
106
106
  end
@@ -376,6 +376,14 @@ module ActiveRecord
376
376
  @access_id_fielddata
377
377
  end
378
378
 
379
+ # returns true if +_shard_doc+ field can be accessed through PIT-search.
380
+ # @return [Boolean]
381
+ def access_shard_doc?
382
+ @access_shard_doc = cluster_info[:version] >= "7.12" if @access_shard_doc.nil?
383
+
384
+ @access_shard_doc
385
+ end
386
+
379
387
  # Returns basic information about the cluster.
380
388
  # @return [Hash{Symbol->Unknown}]
381
389
  def cluster_info
@@ -5,20 +5,25 @@ module ElasticsearchRecord
5
5
  included do
6
6
  # Rails resolves the primary_key's value by accessing the +#id+ method.
7
7
  # Since Elasticsearch also supports an additional, independent +id+ attribute, it would only be able to access
8
- # this through +read_attribute(:id)+.
8
+ # this through +_read_attribute(:id)+.
9
9
  # To also have the ability of accessing this attribute through the default, this flag can be enabled.
10
10
  # @attribute! Boolean
11
- class_attribute :relay_id_attribute, instance_writer: false, default: false
11
+ class_attribute :delegate_id_attribute, instance_writer: false, default: false
12
+
13
+ # Elasticsearch's default value for queries without a +size+ is forced to +10+.
14
+ # To provide a similar behaviour as SQL, this can be automatically set to the +max_result_window+ value.
15
+ # @attribute! Boolean
16
+ class_attribute :delegate_query_nil_limit, instance_writer: false, default: false
12
17
  end
13
18
 
14
19
  # overwrite to provide a Elasticsearch version of returning a 'primary_key' attribute.
15
20
  # Elasticsearch uses the static +_id+ column as primary_key, but also supports an additional +id+ column.
16
21
  # To provide functionality of returning the +id+ attribute, this method must also support it
17
- # with enabled +relay_id_attribute+.
22
+ # with enabled +delegate_id_attribute+.
18
23
  # @return [Object]
19
24
  def id
20
25
  # check, if the model has a +id+ attribute
21
- return _read_attribute('id') if relay_id_attribute? && has_attribute?('id')
26
+ return _read_attribute('id') if delegate_id_attribute? && has_attribute?('id')
22
27
 
23
28
  super
24
29
  end
@@ -26,11 +31,11 @@ module ElasticsearchRecord
26
31
  # overwrite to provide a Elasticsearch version of setting a 'primary_key' attribute.
27
32
  # Elasticsearch uses the static +_id+ column as primary_key, but also supports an additional +id+ column.
28
33
  # To provide functionality of setting the +id+ attribute, this method must also support it
29
- # with enabled +relay_id_attribute+.
34
+ # with enabled +delegate_id_attribute+.
30
35
  # @param [Object] value
31
36
  def id=(value)
32
37
  # check, if the model has a +id+ attribute
33
- return _write_attribute('id', value) if relay_id_attribute? && has_attribute?('id')
38
+ return _write_attribute('id', value) if delegate_id_attribute? && has_attribute?('id')
34
39
 
35
40
  # auxiliary update the +_id+ virtual column if we have a different primary_key
36
41
  _write_attribute('_id', value) if @primary_key != '_id'
@@ -41,13 +46,13 @@ module ElasticsearchRecord
41
46
  # overwrite to provide a Elasticsearch version of returning a 'primary_key' was attribute.
42
47
  # Elasticsearch uses the static +_id+ column as primary_key, but also supports an additional +id+ column.
43
48
  # To provide functionality of returning the +id_Was+ attribute, this method must also support it
44
- # with enabled +relay_id_attribute+.
49
+ # with enabled +delegate_id_attribute+.
45
50
  def id_was
46
- relay_id_attribute? && has_attribute?('id') ? attribute_was('id') : super
51
+ delegate_id_attribute? && has_attribute?('id') ? attribute_was('id') : super
47
52
  end
48
53
 
49
54
  # overwrite the write_attribute method to always write to the 'id'-attribute, if present.
50
- # This methods does not check for +relay_id_attribute+ flag!
55
+ # This methods does not check for +delegate_id_attribute+ flag!
51
56
  # see @ ActiveRecord::AttributeMethods::Write#write_attribute
52
57
  def write_attribute(attr_name, value)
53
58
  return _write_attribute('id', value) if attr_name.to_s == 'id' && has_attribute?('id')
@@ -56,7 +61,7 @@ module ElasticsearchRecord
56
61
  end
57
62
 
58
63
  # overwrite read_attribute method to read from the 'id'-attribute, if present.
59
- # This methods does not check for +relay_id_attribute+ flag!
64
+ # This methods does not check for +delegate_id_attribute+ flag!
60
65
  # see @ ActiveRecord::AttributeMethods::Read#read_attribute
61
66
  def read_attribute(attr_name, &block)
62
67
  return _read_attribute('id', &block) if attr_name.to_s == 'id' && has_attribute?('id')
@@ -8,8 +8,8 @@ module ElasticsearchRecord
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 1
11
- MINOR = 4
12
- TINY = 0
11
+ MINOR = 5
12
+ TINY = 1
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -8,18 +8,47 @@ module ElasticsearchRecord
8
8
  @klass = klass
9
9
  end
10
10
 
11
- # undelegated schema methods: clone rename create drop
11
+ # undelegated schema methods: clone rename create
12
12
  # those should not be quick-accessible, since they might end in heavily broken index
13
13
 
14
14
  # delegated dangerous methods (created with exclamation mark)
15
- # not able to provide individual arguments - always the defaults will be used
15
+ # not able to provide individual arguments - always the defaults will be used!
16
+ #
17
+ # @example
18
+ # open!
19
+ # close!
20
+ # refresh!
21
+ # block!
22
+ # unblock!
16
23
  %w(open close refresh block unblock).each do |method|
17
24
  define_method("#{method}!") do
18
25
  _connection.send("#{method}_table", _index_name)
19
26
  end
20
27
  end
21
28
 
29
+ # delegated dangerous methods with confirm parameter (created with exclamation mark)
30
+ # a exception will be raised, if +confirm:true+ is missing.
31
+ #
32
+ # @example
33
+ # drop!(confirm: true)
34
+ # truncate!(confirm: true)
35
+ %w(drop truncate).each do |method|
36
+ define_method("#{method}!") do |confirm: false|
37
+ raise "#{method} of table '#{_index_name}' aborted!\nexecution not confirmed!\ncall with: #{klass}.api.#{method}!(confirm: true)" unless confirm
38
+ _connection.send("#{method}_table", _index_name)
39
+ end
40
+ end
41
+
22
42
  # delegated table methods
43
+ #
44
+ # @example
45
+ # mappings
46
+ # metas
47
+ # settings
48
+ # aliases
49
+ # state
50
+ # schema
51
+ # exists?
23
52
  %w(mappings metas settings aliases state schema exists?).each do |method|
24
53
  define_method(method) do |*args|
25
54
  _connection.send("table_#{method}", _index_name, *args)
@@ -27,12 +56,96 @@ module ElasticsearchRecord
27
56
  end
28
57
 
29
58
  # delegated plain methods
59
+ #
60
+ # @example
61
+ # alias_exists?
62
+ # setting_exists?
63
+ # mapping_exists?
64
+ # meta_exists?
30
65
  %w(alias_exists? setting_exists? mapping_exists? meta_exists?).each do |method|
31
66
  define_method(method) do |*args|
32
67
  _connection.send(method, _index_name, *args)
33
68
  end
34
69
  end
35
70
 
71
+ # -- DYNAMIC METHOD DOCUMENTATION FOR YARD -------------------------------------------------------------------------
72
+
73
+ # @!method open!
74
+ # Shortcut to open the closed index.
75
+ # @return [Boolean] acknowledged status
76
+
77
+ # @!method close!
78
+ # Shortcut to close the opened index.
79
+ # @return [Boolean] acknowledged status
80
+
81
+ # @!method refresh!
82
+ # Shortcut to refresh the index.
83
+ # @return [Boolean] result state (returns false if refreshing failed)
84
+
85
+ # @!method block!
86
+ # Shortcut to block write access on the index
87
+ # @return [Boolean] acknowledged status
88
+
89
+ # @!method unblock!
90
+ # Shortcut to unblock all blocked accesses on the index
91
+ # @return [Boolean] acknowledged status
92
+
93
+ # @!method drop!(confirm: false)
94
+ # Shortcut to drop the index
95
+ # @param confirm
96
+ # @return [Boolean] acknowledged status
97
+
98
+ # @!method truncate!(confirm: false)
99
+ # Shortcut to truncate the index
100
+ # @param confirm
101
+ # @return [Boolean] acknowledged status
102
+
103
+ # @!method mappings
104
+ # Shortcut for mappings
105
+ # @return [Hash]
106
+
107
+ # @!method metas
108
+ # Shortcut for metas
109
+ # @return [Hash]
110
+
111
+ # @!method settings(flat_settings=true)
112
+ # Shortcut for settings
113
+ # @param [Boolean] flat_settings (default: true)
114
+ # @return [Hash]
115
+
116
+ # @!method aliases
117
+ # Shortcut for aliases
118
+ # @return [Hash]
119
+
120
+ # @!method state
121
+ # Shortcut for state
122
+ # @return [Hash]
123
+
124
+ # @!method schema(features=[])
125
+ # Shortcut for schema
126
+ # @param [Array, Symbol] features
127
+ # @return [Hash]
128
+
129
+ # @!method exists?
130
+ # Shortcut for exists
131
+ # @return [Boolean]
132
+
133
+ # @!method alias_exists?
134
+ # Shortcut for alias_exists
135
+ # @return [Boolean]
136
+
137
+ # @!method setting_exists?
138
+ # Shortcut for setting_exists
139
+ # @return [Boolean]
140
+
141
+ # @!method mapping_exists?
142
+ # Shortcut for mapping_exists
143
+ # @return [Boolean]
144
+
145
+ # @!method meta_exists?
146
+ # Shortcut for meta_exists
147
+ # @return [Boolean]
148
+
36
149
  # fast insert/update data.
37
150
  #
38
151
  # @example
@@ -89,7 +202,7 @@ module ElasticsearchRecord
89
202
  if data[0].is_a?(Hash)
90
203
  bulk(data, :delete, **options)
91
204
  else
92
- bulk(data.map{|id| {id: id}}, :delete, **options)
205
+ bulk(data.map { |id| { id: id } }, :delete, **options)
93
206
  end
94
207
  end
95
208
 
@@ -102,7 +215,7 @@ module ElasticsearchRecord
102
215
 
103
216
  _connection.api(:core, :bulk, {
104
217
  index: _index_name,
105
- body: data.map{|item| {operation => {_id: item[:id], data: item.except(:id)}}},
218
+ body: data.map { |item| { operation => { _id: item[:id], data: item.except(:id) } } },
106
219
  refresh: refresh
107
220
  }, "BULK #{operation.to_s.upcase}", **options)
108
221
  end
@@ -99,21 +99,29 @@ module ElasticsearchRecord
99
99
  # overwrite original methods to provide a elasticsearch version:
100
100
  # checks against the +#access_id_fielddata?+ to ensure the Elasticsearch Cluster allows access on the +_id+ field.
101
101
  def ordered_relation
102
- # resolve valid primary_key (either not the '_id' or +access_id_fielddata?+ is enabled)
102
+ # order values already exist
103
+ return self unless order_values.empty?
104
+
105
+ # resolve valid primary_key
106
+ # - either it is NOT the '_id' column
107
+ # OR
108
+ # - it is the '_id'-column, but +access_id_fielddata?+ is also enabled!
103
109
  valid_primary_key = if primary_key != '_id' || klass.connection.access_id_fielddata?
104
- primary_key
105
- else
106
- nil
107
- end
110
+ primary_key
111
+ else
112
+ nil
113
+ end
108
114
 
109
115
  # slightly changed original methods content
110
- if order_values.empty? && (implicit_order_column || valid_primary_key)
116
+ if implicit_order_column || valid_primary_key
117
+ # order by +implicit_order_column+ AND +primary_key+
111
118
  if implicit_order_column && valid_primary_key && implicit_order_column != valid_primary_key
112
119
  order(table[implicit_order_column].asc, table[valid_primary_key].asc)
113
120
  else
114
121
  order(table[implicit_order_column || valid_primary_key].asc)
115
122
  end
116
123
  else
124
+ # order is not possible due restricted settings
117
125
  self
118
126
  end
119
127
  end
@@ -91,15 +91,20 @@ module ElasticsearchRecord
91
91
  # @param [String] keep_alive - how long to keep alive (for each single request) - default: '1m'
92
92
  # @param [Integer] batch_size - how many results per query (default: 1000 - this means at least 10 queries before reaching the +max_result_window+)
93
93
  def pit_results(keep_alive: '1m', batch_size: 1000)
94
- raise ArgumentError, "Batch size cannot be above the 'max_result_window' (#{klass.max_result_window}) !" if batch_size > klass.max_result_window
94
+ raise(ArgumentError, "Batch size cannot be above the 'max_result_window' (#{klass.max_result_window}) !") if batch_size > klass.max_result_window
95
95
 
96
- # check if a limit or offset values was provided
96
+ # check if limit or offset values where provided
97
97
  results_limit = limit_value ? limit_value : Float::INFINITY
98
98
  results_offset = offset_value ? offset_value : 0
99
99
 
100
100
  # search_after requires a order - we resolve a order either from provided value or by default ...
101
101
  relation = ordered_relation
102
102
 
103
+ # FALLBACK (without any order) for restricted access to the '_id' field.
104
+ # with PIT a order by '_shard_doc' can also be used
105
+ # see @ https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html
106
+ relation.order!(_shard_doc: :asc) if relation.order_values.empty? && klass.connection.access_shard_doc?
107
+
103
108
  # clear limit & offset
104
109
  relation.offset!(nil).limit!(nil)
105
110
 
@@ -135,7 +140,7 @@ module ElasticsearchRecord
135
140
  if block_given?
136
141
  yield ranged_results
137
142
  else
138
- results |= ranged_results
143
+ results += ranged_results
139
144
  end
140
145
 
141
146
  # add to total
@@ -150,8 +155,8 @@ module ElasticsearchRecord
150
155
  # we ran out of data
151
156
  break if current_results_length < batch_size
152
157
 
153
- # additional security - required?
154
- # break if current_pit_hash[:search_after] == current_response['hits']['hits'][-1]['sort']
158
+ # additional security - prevents infinite loops
159
+ raise(::ActiveRecord::StatementInvalid, "'pit_results' aborted due an infinite loop error (invalid or missing order)") if current_pit_hash[:search_after] == current_response['hits']['hits'][-1]['sort'] && current_pit_hash[:pit][:id] == current_response['pit_id']
155
160
 
156
161
  # -------- NEXT LOOP changes --------
157
162
 
@@ -43,6 +43,15 @@ module ElasticsearchRecord
43
43
  @values[:aggs] = value
44
44
  end
45
45
 
46
+ # overwrite the limit_value setter, to provide a special behaviour of auto-setting the +max_result_window+.
47
+ def limit=(limit)
48
+ if limit == '__max__' || (limit.nil? && delegate_query_nil_limit?)
49
+ super(max_result_window)
50
+ else
51
+ super
52
+ end
53
+ end
54
+
46
55
  private
47
56
 
48
57
  # alternative method to avoid redefining the const +VALID_UNSCOPING_VALUES+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticsearch_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Gonsior
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-27 00:00:00.000000000 Z
11
+ date: 2023-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 7.0.0
19
+ version: '7.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 7.0.0
26
+ version: '7.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: elasticsearch
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '8.4'
33
+ version: '7.17'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '8.4'
40
+ version: '7.17'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -94,10 +94,24 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 0.0.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard-relative_markdown_links
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0.4'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0.4'
97
111
  description: 'ElasticsearchRecord is a ActiveRecord adapter and provides similar functionality
98
112
  for Elasticsearch.
99
113
 
100
- '
114
+ '
101
115
  email:
102
116
  - info@ruby-smart.org
103
117
  executables: []
@@ -191,7 +205,7 @@ metadata:
191
205
  source_code_uri: https://github.com/ruby-smart/elasticsearch_record
192
206
  documentation_uri: https://rubydoc.info/gems/elasticsearch_record
193
207
  changelog_uri: https://github.com/ruby-smart/elasticsearch_record/blob/main/docs/CHANGELOG.md
194
- post_install_message:
208
+ post_install_message:
195
209
  rdoc_options: []
196
210
  require_paths:
197
211
  - lib
@@ -206,8 +220,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
220
  - !ruby/object:Gem::Version
207
221
  version: '0'
208
222
  requirements: []
209
- rubygems_version: 3.1.6
210
- signing_key:
223
+ rubygems_version: 3.3.26
224
+ signing_key:
211
225
  specification_version: 4
212
226
  summary: ActiveRecord adapter for Elasticsearch
213
227
  test_files: []