elasticsearch_record 2.0.0 → 3.0.0

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: 27f0d74b39e373493b56511cb08e07ed2fc447da5f33cf5a23a8a080072f3d4e
4
- data.tar.gz: 52f5843b03ea19f3f236910f2268aadb1eba5b7eae48c59734d162fdd2bc6000
3
+ metadata.gz: df21aa38516f4e56830e16ffcff4164b65541a869e3964771771eacdf4e8ad16
4
+ data.tar.gz: 7ba579e28c1a85e7f890654c99c675eb60dd2fddf1b1eb877b0967e17f1d7097
5
5
  SHA512:
6
- metadata.gz: '07733791c4f29ed331631d6ede8ba83b23ebad2af68afffda6e998eca7d09f4aad4a57a83c87e10dbd866a98bb8f77a6d4fffc905b1dd214d13f48313d603955'
7
- data.tar.gz: 4f3e9ffb52a06c4db77fb698b7e715eac3ad77aa259df5f66905890f69646b35e4f6ee809c52feae3180544de8e47943f6cff95faeff12c43bfaecc9d0e01cb4
6
+ metadata.gz: cb521dc265ec3a517ebda7b8b6e60b5471b1411bd8f04d6be72098e95177e32af06267bc9a2e2881699d0f7f317c3bf1ef77b6613d9657df6ac7347affa80cf9
7
+ data.tar.gz: 981b0955f21d2260d794cc1946ed560fa70d0e429b5c0bc97d2c9c5b29b396af471cd8d6460929fa451913ad44f7d9992c9b8c0d8a6cd6a7035c338147161138
data/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
  [![Gem Version](https://badge.fury.io/rb/elasticsearch_record.svg)](https://badge.fury.io/rb/elasticsearch_record)
7
7
  [![License](https://img.shields.io/github/license/ruby-smart/elasticsearch_record)](docs/LICENSE)
8
8
 
9
+ [![Coverage Status](https://coveralls.io/repos/github/ruby-smart/elasticsearch_record/badge.svg?branch=main&kill_cache=1)](https://coveralls.io/github/ruby-smart/elasticsearch_record?branch=main)
10
+
9
11
  ActiveRecord adapter for Elasticsearch
10
12
 
11
13
  _ElasticsearchRecord is a ActiveRecord adapter and provides similar functionality for Elasticsearch._
@@ -14,9 +16,9 @@ _ElasticsearchRecord is a ActiveRecord adapter and provides similar functionalit
14
16
 
15
17
  **PLEASE NOTE:**
16
18
 
17
- - This is the `main`-branch, which currently supports rails **7.1** _(see section 'Rails_Versions' for supported versions)_
18
- - supports ActiveRecord ~> 7.1 + Elasticsearch >= 7.17
19
- - added features up to Elasticsearch `8.17.1`
19
+ - This is the `main`-branch, which supports rails **7.2** _(see section 'Rails_Versions' for supported versions)_
20
+ - supports ActiveRecord ~> 7.2 + Elasticsearch >= 7.17
21
+ - added features up to Elasticsearch `8.17.1` _(tested against `8.19.14`)_
20
22
  - _ES|QL_ queries _(`TYPE_ESQL` / the `esql.query` gate)_ require **Elasticsearch >= 8.11**, where the feature became
21
23
  generally available. All other features remain available from Elasticsearch `7.17`.
22
24
 
@@ -26,6 +28,13 @@ _ElasticsearchRecord is a ActiveRecord adapter and provides similar functionalit
26
28
 
27
29
  Supported rails versions:
28
30
 
31
+ ### Rails 7.2:
32
+ _(since gem version 2.1)_
33
+
34
+ https://github.com/ruby-smart/elasticsearch_record/tree/rails-7-2-stable
35
+
36
+ [![rails-7-1-stable](https://img.shields.io/badge/rails-7.2.stable-orange.svg)](https://github.com/ruby-smart/elasticsearch_record/tree/rails-7-2-stable)
37
+
29
38
  ### Rails 7.1:
30
39
  _(since gem version 1.8)_
31
40
 
@@ -47,7 +56,7 @@ https://github.com/ruby-smart/elasticsearch_record/tree/rails-7-0-stable
47
56
  Add this line to your application's Gemfile:
48
57
 
49
58
  ```ruby
50
- gem 'elasticsearch_record', '~> 1.8'
59
+ gem 'elasticsearch_record'
51
60
 
52
61
  # alternative
53
62
  gem 'elasticsearch_record', git: 'https://github.com/ruby-smart/elasticsearch_record', branch: 'rails-7-1-stable'
@@ -63,6 +72,78 @@ Or install it yourself as:
63
72
 
64
73
  $ gem install elasticsearch_record
65
74
 
75
+ -----
76
+
77
+ ## Upgrading to 2.0
78
+
79
+ Version **2.0** contains breaking changes. Coming from **1.8.x**, check the following:
80
+
81
+ ### 1. Table (index) names are resolved by default
82
+
83
+ Every table statement now resolves its name through `#_env_table_name`, so the
84
+ `table_name_prefix` / `table_name_suffix` no longer have to be applied by hand in migrations or database statements
85
+ _(which silently wrote into another environment's index)_.
86
+
87
+ ```ruby
88
+ # 1.8.x - the prefix / suffix had to be applied manually
89
+ drop_table _env_table_name("settings")
90
+
91
+ # 2.0 - resolved on its own
92
+ drop_table "settings"
93
+ ```
94
+
95
+ Existing migrations keep working - `_env_table_name` is idempotent and still public, so a
96
+ hand-resolved name resolves to the very same index. A new `decorate:`-argument opts out per
97
+ call, and `ElasticsearchRecord.decorate_table_names` acts as a global kill-switch.
98
+
99
+ _see @ [environment-related-table-name](#environment-related-table-name), [opting out with `decorate: false`](#opting-out-with-decorate-false) & [global kill-switch](#global-kill-switch)_
100
+
101
+ ### 2. `select` raises on metadata fields
102
+
103
+ Metadata fields _(`_id`, `_score`, `_index`, ...)_ are not part of the `_source` node, so they
104
+ could never be resolved through the `_source`-filter this method builds - providing them
105
+ silently created a filter that never matched.
106
+
107
+ ```ruby
108
+ Search.select(:_id)
109
+ # => ActiveRecord::UnknownAttributeReference
110
+ ```
111
+
112
+ They are returned anyway and accessible on each record _(`Search.first._id`)_. To resolve the
113
+ metadata **without** transferring the `_source`, use the new `#meta_only!` method.
114
+
115
+ ### 3. `restore_table` replaces its `open:`-argument with `unblock:`
116
+
117
+ A restore runs through a `clone`, so the restored table **inherits** the 'write'-block of its
118
+ source - the table was open, but read-only. `unblock:` _(default: `true`)_ releases it again.
119
+ `ModelApi#restore!` follows along.
120
+
121
+ ```ruby
122
+ # 1.8.x
123
+ restore_table 'settings', from: 'settings-snapshot-2024', open: true
124
+
125
+ # 2.0
126
+ restore_table 'settings', from: 'settings-snapshot-2024', unblock: true
127
+ ```
128
+
129
+ ### 4. The plural table statements no longer skip AR-internal indices
130
+
131
+ `#open_tables`, `#close_tables`, `#refresh_tables` & `#truncate_tables` no longer subtract the
132
+ ActiveRecord-internal indices _(`schema_migrations` & `ar_internal_metadata`)_ - an explicitly
133
+ named index was silently dropped from the list. They now return an `Array` with an entry for
134
+ **every** provided name.
135
+
136
+ ### 5. `truncate_table` raises for AR-internal indices
137
+
138
+ The statement runs a `drop` & `create` and would wipe the migration state.
139
+ `#drop_table` stays unguarded by design _(ActiveRecord resets both tables through it)_.
140
+
141
+ ### 6. `esql` & `msearch` dropped their `async:`-argument
142
+
143
+ Both dispatch through the public `exec_query` now, since rails 7.1 made `internal_exec_query`
144
+ private - the `async:`-argument was dropped along with it.
145
+
146
+ -----
66
147
 
67
148
  ## Features
68
149
  * ActiveRecord's `create, read, update & delete` behaviours
@@ -72,6 +153,8 @@ Or install it yourself as:
72
153
  * additional relation methods to find records with `filter, must, must_not, should`
73
154
  * aggregated queries with Elasticsearch `aggregation` methods
74
155
  * resolve search response `hits`, `aggregations`, `buckets`, ... instead of ActiveRecord objects
156
+ * `SQL` & `ES|QL` queries resolve their **tabular** response into records _(`find_by_sql` with a String query, `find_by_esql`)_
157
+ * table (index) names are resolved within the current environment _(`table_name_prefix` / `table_name_suffix`)_
75
158
  * Third-party gem support
76
159
  * access `elasticsearch-dsl` query builder through `model.search{ ... }`
77
160
  * Schema
@@ -255,6 +338,11 @@ buckets = Search.where(name: 'A nice object').aggregate(:total, {sum: {field: :a
255
338
  results = Search.where(name: 'A nice object').pit_results
256
339
  # > [{ "_id": "abc123", "name": "A nice object", ...
257
340
 
341
+ # resolves ONLY the metadata nodes of each hit - the '_source' is not transferred at all.
342
+ # (this is the replacement for a - no longer supported - 'select(:_id)')
343
+ metas = Search.where(name: 'A nice object').meta_only!.results
344
+ # > [{ "_id": "abc123", "_index": "search", "_score": 1.0 }, ...
345
+
258
346
  # returns the total value of the query without querying again (it uses the total value from the response)
259
347
  scope = Search.where(name: 'A nice object').limit(5)
260
348
  results_count = scope.count
@@ -288,10 +376,7 @@ _(also see @ [github](https://github.com/ruby-smart/elasticsearch_record/blob/ma
288
376
  - must
289
377
  - should
290
378
  - aggregate
291
- - restrict
292
- - hits_only!
293
- - aggs_only!
294
- - total_only!
379
+ - select _(raises on metadata fields - see @ [Upgrading to 2.0](#2-select-raises-on-metadata-fields))_
295
380
 
296
381
  _see simple documentation about these methods @ {ElasticsearchRecord::Relation::QueryMethods rubydoc}_
297
382
 
@@ -308,7 +393,7 @@ _(also see @ [github](https://github.com/ruby-smart/elasticsearch_record/blob/ma
308
393
  - boxplot
309
394
  - stats
310
395
  - string_stats
311
- - matrix_stats
396
+ - matrix_stats _(requires at least two columns)_
312
397
  - median_absolute_deviation
313
398
  - calculate
314
399
 
@@ -316,6 +401,13 @@ _see simple documentation about these methods @ {ElasticsearchRecord::Relation::
316
401
 
317
402
  _(also see @ [github](https://github.com/ruby-smart/elasticsearch_record/blob/main/lib/elasticsearch_record/relation/calculation_methods.rb) )_
318
403
 
404
+ ### Available query configuration methods
405
+
406
+ - hits_only! _(prevents to resolve aggs)_
407
+ - aggs_only! _(prevents to resolve hits / source data)_
408
+ - total_only! _(prevents to resolve aggs, hits / source data)_
409
+ - meta_only! _(resolves the metadata nodes (`_id`, `_score`, ...) of each hit without transferring the `_source`)_
410
+
319
411
  ### Available result methods
320
412
  - aggregations
321
413
  - buckets
@@ -544,7 +636,7 @@ Access these methods through the model's connection or within any `Migration`.
544
636
  **Example migration:**
545
637
 
546
638
  ```ruby
547
- class AddTests < ActiveRecord::Migration[7.0]
639
+ class AddTests < ActiveRecord::Migration[7.1]
548
640
  def up
549
641
  create_table "assignments", if_not_exists: true do |t|
550
642
  t.string :key, primary_key: true
@@ -647,7 +739,7 @@ A single migration can be created to be used within each environment:
647
739
 
648
740
  ```ruby
649
741
  # Example migration
650
- class AddSettings < ActiveRecord::Migration[7.0]
742
+ class AddSettings < ActiveRecord::Migration[7.1]
651
743
  def up
652
744
  # creates 'settings-pro' on production & 'settings-dev' on development
653
745
  create_table "settings", force: true do |t|
data/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # ElasticsearchRecord - CHANGELOG
2
2
 
3
+ ## [3.0.0] - 2026-09-08
4
+ * [add] **BREAKING**: requires `activerecord ~> 7.2.0`
5
+ * [add] `Quoting::ClassMethods#quote_column_name` & `#quote_table_name` - identifier quoting is class-level & mandatory now
6
+ * [add] `ElasticsearchRecord::InternalMetadata` - always disabled _(the flag moved from the adapter to the database config)_
7
+ * [add] `patches/active_record/connection_pool_patch` - routes `#migrations_paths`, `#schema_migration` & `#internal_metadata` back to the adapter
8
+ * [add] specs for `ElasticsearchAdapter#translate_exception`
9
+ * [add] specs for `Relation::ValueMethods#limit_value=`
10
+ * [add] specs for `Core#id=`, `#id_was`, `#write_attribute` & `#read_attribute`
11
+ * [add] specs for `TableSettingDefinition`
12
+ * [add] specs for `InternalMetadata#enabled?`
13
+ * [ref] the adapter registers itself through `ActiveRecord::ConnectionAdapters.register` - `ConnectionHandling#elasticsearch_connection` removed
14
+ * [ref] the adapter provides `#internal_metadata` & `#migration_context` again; `#schema_migration` builds from the **pool**
15
+ * [ref] `SchemaMigration#integer_versions` resolves through `pool.with_connection`
16
+ * [ref] `#find_by_sql`, `#find_by_query` & `#find_by_esql` resolve through `with_connection`; `allow_retry:` forwarded down to `#internal_exec_query`
17
+ * [ref] `#_insert_record`, `#_insert_with_auto_increment` & `#cached_find_by_statement` take the connection as first argument
18
+ * [ref] `Arel::Collectors::ElasticsearchQuery#retryable` accessor added _(rails marks idempotent queries as retryable)_
19
+ * [ref] `assert_mutability!` -> `assert_modifiable!` & `ImmutableRelation` -> `UnmodifiableRelation`
20
+ * [ref] `Arel::Nodes::Or` is a nary node now - takes its children as a single `Array`
21
+ * [fix] `Querying::ClassMethods#search` to nest the `Elasticsearch::DSL` block result into the `body` and to only enter the DSL branch **if a block was provided**
22
+
3
23
  ## [2.0.0] - 2026-08-10
4
24
  * [add] `ElasticsearchRecord.decorate_table_names` _(default: `true`)_ as global kill-switch for the table name decoration - only provides the default for an omitted `decorate:`-argument, `#_env_table_name` stays unaffected
5
25
  * [add] `TableStatements#truncate_table` raises an `ArgumentError` for AR-internal indices - the statement runs a `drop` & `create` and would wipe the migration state. `#drop_table` stays unguarded by design _(AR resets both tables through it)_
@@ -14,7 +14,7 @@ DESC
14
14
 
15
15
  spec.homepage = "https://github.com/ruby-smart/elasticsearch_record"
16
16
  spec.license = "MIT"
17
- spec.required_ruby_version = ">= 2.7.0"
17
+ spec.required_ruby_version = ">= 3.1.0"
18
18
 
19
19
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
20
20
  spec.metadata["homepage_uri"] = spec.homepage
@@ -32,7 +32,7 @@ DESC
32
32
 
33
33
  spec.require_paths = ["lib"]
34
34
 
35
- spec.add_dependency 'activerecord', '~> 7.1.0'
35
+ spec.add_dependency 'activerecord', '~> 7.2.0'
36
36
  spec.add_dependency 'elasticsearch', '>= 7.17'
37
37
 
38
38
  spec.add_development_dependency 'coveralls_reborn', '~> 0.25'
@@ -137,10 +137,11 @@ module ActiveRecord
137
137
  # @param [Array] binds - not supported on the top-level and therefore ignored!
138
138
  # @param [Boolean] prepare - used by the default AbstractAdapter - but not supported and therefore never ignored!
139
139
  # @param [Boolean] async
140
+ # @param [Boolean] allow_retry
140
141
  # @return [ElasticsearchRecord::Result]
141
- def internal_exec_query(query, name = "QUERY", binds = [], prepare: false, async: false)
142
+ def internal_exec_query(query, name = "QUERY", binds = [], prepare: false, async: false, allow_retry: false)
142
143
  build_result(
143
- internal_execute(query, name, async: async),
144
+ internal_execute(query, name, async: async, allow_retry: allow_retry),
144
145
  columns: query.columns
145
146
  )
146
147
  end
@@ -4,6 +4,22 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module Elasticsearch
6
6
  module Quoting # :nodoc:
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods # :nodoc:
10
+ # since rails 7.2 identifier quoting is class-level & mandatory (the abstract
11
+ # implementation raises a +NotImplementedError+). Elasticsearch fields & indices are
12
+ # plain JSON keys - so they pass through unquoted.
13
+ def quote_column_name(column_name)
14
+ column_name.to_s
15
+ end
16
+
17
+ # Quotes the table (index) name. Defaults to column name quoting.
18
+ def quote_table_name(table_name)
19
+ quote_column_name(table_name)
20
+ end
21
+ end
22
+
7
23
  def quoted_true
8
24
  'true'
9
25
  end
@@ -22,16 +22,6 @@ gem 'elasticsearch'
22
22
  require 'elasticsearch'
23
23
 
24
24
  module ActiveRecord # :nodoc:
25
- module ConnectionHandling # :nodoc:
26
- def elasticsearch_adapter_class
27
- ConnectionAdapters::ElasticsearchAdapter
28
- end
29
-
30
- def elasticsearch_connection(config)
31
- elasticsearch_adapter_class.new(config)
32
- end
33
- end
34
-
35
25
  module ConnectionAdapters # :nodoc:
36
26
  class ElasticsearchAdapter < AbstractAdapter
37
27
  include Elasticsearch::UnsupportedImplementation
@@ -168,7 +158,17 @@ module ActiveRecord # :nodoc:
168
158
  end
169
159
 
170
160
  def schema_migration # :nodoc:
171
- ElasticsearchRecord::SchemaMigration.new(self)
161
+ ElasticsearchRecord::SchemaMigration.new(pool)
162
+ end
163
+
164
+ def internal_metadata # :nodoc:
165
+ ElasticsearchRecord::InternalMetadata.new(pool)
166
+ end
167
+
168
+ # since rails 7.2 the migration context is resolved through the connection pool -
169
+ # provided here as connection method again (used by +#assume_migrated_upto_version+)
170
+ def migration_context # :nodoc:
171
+ pool.migration_context
172
172
  end
173
173
 
174
174
  # provide a table_name_prefix from the configuration to create & restrict schema creation
@@ -429,4 +429,12 @@ module ActiveRecord # :nodoc:
429
429
  ::ActiveRecord::Type.register(:range, ::ActiveRecord::ConnectionAdapters::Elasticsearch::Type::Range, adapter: :elasticsearch)
430
430
  end
431
431
  end
432
- end
432
+ end
433
+
434
+ # since rails 7.2 an adapter has to register itself - the former load-by-convention
435
+ # through +ConnectionHandling#elasticsearch_connection+ is gone
436
+ ActiveRecord::ConnectionAdapters.register(
437
+ 'elasticsearch',
438
+ 'ActiveRecord::ConnectionAdapters::ElasticsearchAdapter',
439
+ 'active_record/connection_adapters/elasticsearch_adapter'
440
+ )
@@ -9,6 +9,9 @@ module Arel # :nodoc: all
9
9
  # required for ActiveRecord
10
10
  attr_accessor :preparable
11
11
 
12
+ # required for ActiveRecord (rails 7.2 marks idempotent queries as retryable)
13
+ attr_accessor :retryable
14
+
12
15
  # returns the current bind index (default: 1)
13
16
  attr_reader :bind_index
14
17
 
@@ -96,7 +96,7 @@ module ElasticsearchRecord
96
96
  # used to create a cacheable statement.
97
97
  # This is a 1:1 copy, except that we use our own class +ElasticsearchRecord::StatementCache+
98
98
  # see @ ActiveRecord::Core::ClassMethods#cached_find_by_statement
99
- def cached_find_by_statement(key, &block)
99
+ def cached_find_by_statement(connection, key, &block)
100
100
  cache = @find_by_statement_cache[connection.prepared_statements]
101
101
  cache.compute_if_absent(key) { ElasticsearchRecord::StatementCache.create(connection, &block) }
102
102
  end
@@ -7,7 +7,7 @@ module ElasticsearchRecord
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 2
10
+ MAJOR = 3
11
11
  MINOR = 0
12
12
  TINY = 0
13
13
  PRE = nil
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/internal_metadata"
4
+
5
+ module ElasticsearchRecord
6
+ class InternalMetadata < ::ActiveRecord::InternalMetadata
7
+
8
+ # overwrite method to always disable the internal metadata index.
9
+ #
10
+ # Elasticsearch cannot serve the internal metadata table
11
+ # (see +ElasticsearchAdapter#use_metadata_table?+) - but since rails 7.2 the flag is
12
+ # resolved through the database config (default: enabled) instead of the adapter,
13
+ # so a default configuration would suddenly create & write a 'ar_internal_metadata'
14
+ # index on the cluster.
15
+ def enabled?
16
+ false
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/connection_adapters/abstract/connection_pool'
4
+
5
+ module ElasticsearchRecord
6
+ module Patches
7
+ module ActiveRecord
8
+ # Since rails 7.2 the migration plumbing is resolved through the connection POOL
9
+ # (+ConnectionPool#migration_context+ builds from +#migrations_paths+, +#schema_migration+ &
10
+ # +#internal_metadata+) instead of the connection - so the adapters own implementations are
11
+ # no longer consulted. Without this patch a Elasticsearch pool would migrate with the generic
12
+ # +ActiveRecord::SchemaMigration+ (which resolves the index name through +ActiveRecord::Base+
13
+ # and only ever sees the first ten migrations - Elasticsearch default search size), enable the
14
+ # internal metadata index (the adapter cannot serve it) and fall back to the default
15
+ # 'db/migrate' path.
16
+ #
17
+ # The patch simply routes those factories back to the adapter class, which keeps the
18
+ # Elasticsearch-specific implementations in one place.
19
+ module ConnectionPoolPatch
20
+ def self.included(base)
21
+ base.send(:prepend, PrependMethods)
22
+ end
23
+
24
+ module PrependMethods
25
+ def migrations_paths
26
+ return super unless elasticsearch_pool?
27
+
28
+ db_config.migrations_paths || ['db/migrate_elasticsearch']
29
+ end
30
+
31
+ def schema_migration
32
+ return super unless elasticsearch_pool?
33
+
34
+ ElasticsearchRecord::SchemaMigration.new(self)
35
+ end
36
+
37
+ def internal_metadata
38
+ return super unless elasticsearch_pool?
39
+
40
+ ElasticsearchRecord::InternalMetadata.new(self)
41
+ end
42
+
43
+ private
44
+
45
+ # returns true, if the pools config resolves to the Elasticsearch adapter
46
+ # @return [Boolean]
47
+ def elasticsearch_pool?
48
+ db_config.adapter_class <= ::ActiveRecord::ConnectionAdapters::ElasticsearchAdapter
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ # include once only!
57
+ ::ActiveRecord::ConnectionAdapters::ConnectionPool.include(ElasticsearchRecord::Patches::ActiveRecord::ConnectionPoolPatch) unless ::ActiveRecord::ConnectionAdapters::ConnectionPool.included_modules.include?(ElasticsearchRecord::Patches::ActiveRecord::ConnectionPoolPatch)
@@ -5,14 +5,15 @@ module ElasticsearchRecord
5
5
  module ClassMethods
6
6
  # insert a new record into the Elasticsearch index
7
7
  # NOTICE: We don't want to mess up with the Arel-builder - so we send new data directly to the API
8
+ # @param [ActiveRecord::ConnectionAdapters::ElasticsearchAdapter] connection
8
9
  # @param [ActiveModel::Attribute] values
9
10
  # @return [Object] id
10
- def _insert_record(values, returning)
11
+ def _insert_record(connection, values, returning)
11
12
  # values is not a "key=>values"-Hash, but a +ActiveModel::Attribute+ - so the casted values gets resolved here
12
13
  values = values.transform_values(&:value)
13
14
 
14
15
  # resolve & update a auto_increment value, if configured
15
- _insert_with_auto_increment(values) do |arguments|
16
+ _insert_with_auto_increment(connection, values) do |arguments|
16
17
  # build new query
17
18
  query = ElasticsearchRecord::Query.new(
18
19
  index: table_name,
@@ -62,7 +63,7 @@ module ElasticsearchRecord
62
63
  private
63
64
 
64
65
  # Resolves the +auto_increment+ status from the tables +_meta+ attributes.
65
- def _insert_with_auto_increment(values)
66
+ def _insert_with_auto_increment(connection, values)
66
67
  # check, if the primary_key's value is provided.
67
68
  # so, no need to resolve a +auto_increment+ value, but provide the id directly
68
69
  if (id = values[self.primary_key]).present?
@@ -42,8 +42,9 @@ module ElasticsearchRecord
42
42
  # @param [String, Hash, ElasticsearchRecord::Query] sql
43
43
  # @param [Array] binds
44
44
  # @param [nil] preparable
45
+ # @param [Boolean] allow_retry
45
46
  # @param [Proc] block
46
- def find_by_sql(sql, binds = [], preparable: nil, &block)
47
+ def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
47
48
  query = case sql
48
49
  when String # really find by SQL
49
50
  ElasticsearchRecord::Query.new(
@@ -61,7 +62,11 @@ module ElasticsearchRecord
61
62
  sql
62
63
  end
63
64
 
64
- _load_from_sql(_query_by_sql(query, binds), &block)
65
+ result = with_connection do |c|
66
+ _query_by_sql(c, query, binds, preparable: preparable, allow_retry: allow_retry)
67
+ end
68
+
69
+ _load_from_sql(result, &block)
65
70
  end
66
71
 
67
72
  # finds records by query arguments
@@ -74,7 +79,11 @@ module ElasticsearchRecord
74
79
  # IMPORTANT: Always provide all columns to prevent unknown attributes that should be nil ...
75
80
  columns: source_column_names)
76
81
 
77
- _load_from_sql(_query_by_sql(query), &block)
82
+ result = with_connection do |c|
83
+ _query_by_sql(c, query)
84
+ end
85
+
86
+ _load_from_sql(result, &block)
78
87
  end
79
88
 
80
89
  # ES|QL query API
@@ -90,7 +99,11 @@ module ElasticsearchRecord
90
99
  # IMPORTANT: Always provide all columns
91
100
  columns: source_column_names)
92
101
 
93
- _load_from_sql(_query_by_sql(query), &block)
102
+ result = with_connection do |c|
103
+ _query_by_sql(c, query)
104
+ end
105
+
106
+ _load_from_sql(result, &block)
94
107
  end
95
108
 
96
109
  # executes a +esql+ by provided *ES|SL* query
@@ -123,17 +136,28 @@ module ElasticsearchRecord
123
136
  connection.exec_query(query, "#{name} Msearch")
124
137
  end
125
138
 
126
- # executes a search by provided +RAW+ query - supports +Elasticsearch::DSL+ gem if loaded
139
+ # executes a search by provided +RAW+ query - supports +Elasticsearch::DSL+ gem if loaded.
140
+ #
141
+ # Without a block the trailing options-hash is used as query arguments.
142
+ # With a block the +Elasticsearch::DSL+ gem builds the query - if the gem is not
143
+ # available (or the block fails), the block is SILENTLY ignored and the
144
+ # options-hash is used instead.
127
145
  def search(*args, &block)
128
- begin
129
- # require the Elasticsearch::DSL gem, if loaded
130
- require 'elasticsearch/dsl'
131
- query = ::Elasticsearch::DSL::Search::Search.new(*args, &block).to_hash
132
- rescue LoadError
133
- query = args.extract_options!
134
- rescue
135
- query = args.extract_options!
136
- end
146
+ query = if block_given?
147
+ begin
148
+ # require the Elasticsearch::DSL gem, if loaded
149
+ require 'elasticsearch/dsl'
150
+ # PLEASE NOTE: +Search#to_hash+ returns the request BODY - it must be nested
151
+ # into the query arguments, otherwise it is sent as (invalid) URL parameters.
152
+ { body: ::Elasticsearch::DSL::Search::Search.new(*args, &block).to_hash }
153
+ rescue LoadError
154
+ args.extract_options!
155
+ rescue
156
+ args.extract_options!
157
+ end
158
+ else
159
+ args.extract_options!
160
+ end
137
161
 
138
162
  find_by_query(query)
139
163
  end
@@ -10,7 +10,7 @@ module ElasticsearchRecord
10
10
  # transforms the current relation into arel, compiles it to query and executes the query.
11
11
  # returns the result object.
12
12
  #
13
- # PLEASE NOTE: This makes the query +immutable+ and raises a +ActiveRecord::ImmutableRelation+
13
+ # PLEASE NOTE: This makes the query +immutable+ and raises a +ActiveRecord::UnmodifiableRelation+
14
14
  # if you try to change it's values.
15
15
  #
16
16
  # PLEASE NOTE: resolving records _(instantiate)_ is never possible after calling this method!
@@ -109,7 +109,8 @@ module ElasticsearchRecord # :nodoc:
109
109
  left = left[key]
110
110
  right = right[key]
111
111
 
112
- or_clause = Arel::Nodes::Or.new(left, right)
112
+ # since rails 7.2 +Arel::Nodes::Or+ is a nary node and takes its children as a single Array
113
+ or_clause = Arel::Nodes::Or.new([left, right])
113
114
 
114
115
  common.predicates[key] = ElasticsearchRecord::Relation::QueryClause.new(key, [Arel::Nodes::Grouping.new(or_clause)])
115
116
  common
@@ -260,7 +260,7 @@ module ElasticsearchRecord
260
260
  unless _valid_unscoping_values.include?(scope)
261
261
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{_valid_unscoping_values.to_a.join(", :")}."
262
262
  end
263
- assert_mutability!
263
+ assert_modifiable!
264
264
  @values.delete(scope)
265
265
  when Hash
266
266
  scope.each do |key, target_value|
@@ -8,7 +8,7 @@ module ElasticsearchRecord
8
8
 
9
9
  def kind_value=(value)
10
10
  # checks if records are already loaded - in this case we cannot mutate the query anymore
11
- assert_mutability!
11
+ assert_modifiable!
12
12
 
13
13
  @values[:kind] = value.to_sym
14
14
  end
@@ -18,7 +18,7 @@ module ElasticsearchRecord
18
18
  end
19
19
 
20
20
  def configure_value=(value)
21
- assert_mutability!
21
+ assert_modifiable!
22
22
 
23
23
  @values[:configure] = value
24
24
  end
@@ -28,7 +28,7 @@ module ElasticsearchRecord
28
28
  end
29
29
 
30
30
  def query_clause=(value)
31
- assert_mutability!
31
+ assert_modifiable!
32
32
 
33
33
  @values[:query] = value
34
34
  end
@@ -38,7 +38,7 @@ module ElasticsearchRecord
38
38
  end
39
39
 
40
40
  def aggs_clause=(value)
41
- assert_mutability!
41
+ assert_modifiable!
42
42
 
43
43
  @values[:aggs] = value
44
44
  end
@@ -14,9 +14,12 @@ module ElasticsearchRecord
14
14
  sm = Arel::SelectManager.new(arel_table)
15
15
  sm.project(arel_table[primary_key])
16
16
  sm.order(arel_table[primary_key].asc)
17
- sm.take(connection.max_result_window(table_name))
18
17
 
19
- connection.select_values(sm, "#{self.class} Load")
18
+ @pool.with_connection do |connection|
19
+ sm.take(connection.max_result_window(table_name))
20
+
21
+ connection.select_values(sm, "#{self.class} Load")
22
+ end
20
23
  end
21
24
  end
22
25
  end
@@ -22,6 +22,7 @@ module ElasticsearchRecord
22
22
  eager_autoload do
23
23
  autoload :Base
24
24
  autoload :Core
25
+ autoload :InternalMetadata
25
26
  autoload :ModelSchema
26
27
  autoload :ModelApi
27
28
  autoload :Persistence
@@ -84,6 +85,7 @@ end
84
85
 
85
86
  ActiveSupport.on_load(:active_record) do
86
87
  # load patches
88
+ require 'elasticsearch_record/patches/active_record/connection_pool_patch'
87
89
  require 'elasticsearch_record/patches/active_record/relation_merger_patch'
88
90
  require 'elasticsearch_record/patches/arel/select_core_patch'
89
91
  require 'elasticsearch_record/patches/arel/select_manager_patch'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticsearch_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Gonsior
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 7.1.0
18
+ version: 7.2.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 7.1.0
25
+ version: 7.2.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: elasticsearch
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -186,8 +186,10 @@ files:
186
186
  - lib/elasticsearch_record/instrumentation/controller_runtime.rb
187
187
  - lib/elasticsearch_record/instrumentation/log_subscriber.rb
188
188
  - lib/elasticsearch_record/instrumentation/railtie.rb
189
+ - lib/elasticsearch_record/internal_metadata.rb
189
190
  - lib/elasticsearch_record/model_api.rb
190
191
  - lib/elasticsearch_record/model_schema.rb
192
+ - lib/elasticsearch_record/patches/active_record/connection_pool_patch.rb
191
193
  - lib/elasticsearch_record/patches/active_record/relation_merger_patch.rb
192
194
  - lib/elasticsearch_record/patches/arel/select_core_patch.rb
193
195
  - lib/elasticsearch_record/patches/arel/select_manager_patch.rb
@@ -226,7 +228,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
226
228
  requirements:
227
229
  - - ">="
228
230
  - !ruby/object:Gem::Version
229
- version: 2.7.0
231
+ version: 3.1.0
230
232
  required_rubygems_version: !ruby/object:Gem::Requirement
231
233
  requirements:
232
234
  - - ">="