active_version 1.0.0 → 1.0.1

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: ec3713fc6fd0f073d9bb77d1e44c674a2023cf15f263fa05180457648052e8a1
4
- data.tar.gz: 65ba5308255d161420e028564050e75044b975c9f8b520605d1caedab3d7f05b
3
+ metadata.gz: 4636e51affa996a3f7c8cd98f32e05d6d1892ffe2dc3a79b58edb17bde8619f4
4
+ data.tar.gz: 42b4f546aeec443faf25d9d3b039ed515499f6b9284b520792afd4af711a721e
5
5
  SHA512:
6
- metadata.gz: eaf25bb79e12b3a85144c6c191a31ab97dea2a94c91dca682fa3cc9a729e648c29fd2e593605452945bfce0da7e2b391e290998aa9e69113ab00434b7627adca
7
- data.tar.gz: fba81088323b53f4ad45acf8e04e82c63085854fcc407c7b4321dd687d06a12f336c1e4fe5f9224ce99cb3125f5e4735ac5237622f39008dbb9da74361562682
6
+ metadata.gz: b78b064c4a79a1d1ce1780de50e009116e2e0864c25323745f6596385d197d058cc7c4a08bc58f07a7ff7addc678415482d7011698f1114da920b3f282498cc0
7
+ data.tar.gz: 3b35459213700a318435ea787706e6565dfa6c95e311fa7e0f3ef7ad39206f9330b465791cc074be818f1b045e60c154d6d188d1385a4aa54bb81cd0b9daef3f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.0.1 (2026-03-11)
4
+
5
+ - Fixed Rails 7.2 thread-local audited options handling in `with_audited_options`
6
+ - Added Rails 6 appraisal and CI matrix coverage
7
+ - Improved test DB connection setup to honor `DATABASE_URL` and fail fast when PostgreSQL is explicitly requested
8
+ - Added compatibility fix for ActiveSupport on older Rails with newer Ruby by loading `logger` early
9
+ - Updated generator integration spec behavior on TruffleRuby to avoid known native extension incompatibility
10
+
3
11
  ## 1.0.0 (2026-03-08)
4
12
 
5
13
  - Initial release of ActiveVersion library
@@ -33,4 +41,3 @@
33
41
  - Added instrumentation hooks via ActiveSupport::Notifications
34
42
  - Added configurable column naming and per-model and global configuration options
35
43
  - Added comprehensive test suite with unit tests, integration tests, and test helpers
36
-
data/README.md CHANGED
@@ -59,6 +59,8 @@ For a step-by-step guide (prerequisites, generators, migrations, model setup, an
59
59
 
60
60
  Quick checklist: add the gem → `bundle install` → `rails g active_version:install` → run the feature generators for your models (e.g. `rails g active_version:audits Post --storage=json_column`) → `rails db:migrate` → include the concerns and `has_translations` / `has_revisions` / `has_audits` in each model.
61
61
 
62
+ For production usage, also configure destination models (`PostAudit`, `PostRevision`, `PostTranslation`) with `configure_audit`, `configure_revision`, and `configure_translation`.
63
+
62
64
  ## Quick Start
63
65
 
64
66
  ### Setup
@@ -221,20 +223,44 @@ end
221
223
  class PostRevision < ApplicationRecord
222
224
  include ActiveVersion::Revisions::RevisionRecord
223
225
 
224
- configure_revision(version_column: :version,
225
- foreign_key: :post_id
226
- )
226
+ configure_revision do
227
+ version_column :version
228
+ foreign_key :post_id
229
+ end
227
230
  end
228
231
 
229
232
  class PostTranslation < ApplicationRecord
230
233
  include ActiveVersion::Translations::TranslationRecord
231
234
 
232
- configure_translation(locale_column: :locale,
233
- foreign_key: :post_id
234
- )
235
+ configure_translation do
236
+ locale_column :locale
237
+ foreign_key :post_id
238
+ end
235
239
  end
236
240
  ```
237
241
 
242
+ Keyword-argument style is also supported:
243
+
244
+ ```ruby
245
+ configure_revision(version_column: :version, foreign_key: :post_id)
246
+ configure_translation(locale_column: :locale, foreign_key: :post_id)
247
+ ```
248
+
249
+ ### Schema Evolution and Sync Strategy
250
+
251
+ When using `:mirror_columns` audits, revisions, and translations, destination tables must stay schema-compatible with their source model payload.
252
+
253
+ - Keep source and destination columns in sync for attributes you persist.
254
+ - Avoid destructive one-shot renames/drops across source and destination.
255
+ - Prefer gradual schema rollout:
256
+ 1. add new column(s) to source and destination
257
+ 2. deploy write path that can populate both shapes
258
+ 3. backfill historical rows as needed
259
+ 4. migrate readers to the new shape
260
+ 5. deprecate and later remove old columns in a separate rollout
261
+
262
+ This phased approach avoids runtime mismatches and preserves audit/revision/translation continuity during deploys.
263
+
238
264
  ### Per-Model Configuration
239
265
 
240
266
  ```ruby
@@ -151,6 +151,10 @@ module ActiveVersion
151
151
  end
152
152
  rescue NameError
153
153
  # Source class not yet defined, will be set up later
154
+ rescue *ActiveVersion::Runtime.active_record_connection_errors => e
155
+ if defined?(Rails) && Rails.respond_to?(:logger)
156
+ Rails.logger&.debug("[ActiveVersion] Deferred audit association setup for #{name}: #{e.class}: #{e.message}")
157
+ end
154
158
  end
155
159
  end
156
160
 
@@ -1,3 +1,5 @@
1
+ require "json"
2
+
1
3
  module ActiveVersion
2
4
  module Audits
3
5
  module HasAudits
@@ -27,28 +29,31 @@ module ActiveVersion
27
29
  reload
28
30
  end
29
31
 
30
- # Clear association cache to ensure we get fresh data from database
31
- if respond_to?(:audits)
32
- audits.reset
33
- if respond_to?(:association)
34
- assoc = association(:audits)
35
- assoc.reset if assoc.respond_to?(:loaded?) && assoc.loaded?
36
- end
32
+ # Clear association cache to ensure we get fresh data from database.
33
+ # Avoid calling audits reader directly here to prevent AR 6.1
34
+ # delegation edge cases on dynamic models.
35
+ if respond_to?(:association) && association_cached?(:audits)
36
+ association(:audits).reset
37
37
  end
38
38
 
39
39
  # Get all audits fresh from database (not from cache)
40
40
  # Query directly to ensure we get updated values after SQL updates
41
41
  auditable_type = audited_options[:class_name] || self.class.name
42
- if auditable_type != self.class.name
43
- auditable_column = ActiveVersion.column_mapper.column_for(self.class, :audits, :auditable)
44
- version_column = ActiveVersion.column_mapper.column_for(self.class, :audits, :version)
45
- all_audits = self.class.audit_class.where({"#{auditable_column}_type" => auditable_type}.merge(active_version_audit_identity_map))
46
- .order(version_column => :asc)
47
- .to_a
48
- else
49
- # Use association but ensure it's not cached
50
- all_audits = audits.reload.to_a
42
+ auditable_column = ActiveVersion.column_mapper.column_for(self.class, :audits, :auditable)
43
+ version_column = ActiveVersion.column_mapper.column_for(self.class, :audits, :version)
44
+ audit_klass =
45
+ if self.class.reflect_on_association(:audits)
46
+ association(:audits).klass
47
+ else
48
+ self.class.audit_class
49
+ end
50
+ if audit_klass.nil? && self.class.respond_to?(:resolve_audit_class_option, true)
51
+ audit_klass = self.class.send(:resolve_audit_class_option, audited_options[:as])
51
52
  end
53
+ break unless audit_klass
54
+ all_audits = audit_klass.where({"#{auditable_column}_type" => auditable_type}.merge(active_version_audit_identity_map))
55
+ .order(version_column => :asc)
56
+ .to_a
52
57
 
53
58
  # Filter out combined audits (those with empty changes)
54
59
  # Check raw column value first (before JSON parsing) for "{}" string
@@ -160,9 +165,9 @@ module ActiveVersion
160
165
  conn = audit_class.connection
161
166
  table_name = audit_class.table_name
162
167
  updates = []
163
- updates << "#{conn.quote_column_name(changes_column)} = #{conn.quote(combined_changes.to_json)}"
168
+ updates << "#{conn.quote_column_name(changes_column)} = #{conn.quote(JSON.generate(combined_changes))}"
164
169
  if combined_context.any?
165
- updates << "#{conn.quote_column_name(context_column)} = #{conn.quote(combined_context.to_json)}"
170
+ updates << "#{conn.quote_column_name(context_column)} = #{conn.quote(JSON.generate(combined_context))}"
166
171
  end
167
172
  target_id = conn.quote(combine_target.read_attribute(:id))
168
173
  sql = "UPDATE #{conn.quote_table_name(table_name)} SET #{updates.join(", ")} WHERE id = #{target_id}"
@@ -180,7 +185,7 @@ module ActiveVersion
180
185
  # Use raw SQL with proper escaping to bypass ActiveRecord's readonly checks
181
186
  conn = audit_class.connection
182
187
  table_name = audit_class.table_name
183
- empty_json_str = {}.to_json # This is "{}"
188
+ empty_json_str = JSON.generate({})
184
189
  empty_json = conn.quote(empty_json_str)
185
190
  quoted_comment = conn.quote(combined_comment)
186
191
 
@@ -194,16 +199,13 @@ module ActiveVersion
194
199
  end
195
200
 
196
201
  # Clear association cache to ensure fresh data is loaded after updates
197
- if respond_to?(:audits)
198
- # Clear the association cache
202
+ if respond_to?(:association) && association_cached?(:audits)
203
+ association(:audits).reset
204
+ end
205
+ begin
199
206
  audits.reset
200
- # Also clear any loaded association state
201
- if respond_to?(:association)
202
- assoc = association(:audits)
203
- if assoc.respond_to?(:loaded?) && assoc.loaded?
204
- assoc.reset
205
- end
206
- end
207
+ rescue NoMethodError
208
+ nil
207
209
  end
208
210
  end
209
211
  end
@@ -190,7 +190,7 @@ module ActiveVersion
190
190
  begin
191
191
  audit_class.create!(insert_attrs)
192
192
  combine_audits_if_needed if attrs[:action] != "create"
193
- audits.reset
193
+ association(:audits).reset if association_cached?(:audits)
194
194
  nil
195
195
  rescue ActiveRecord::RecordNotUnique => e
196
196
  # Handle unique constraint violation (likely version conflict)
@@ -208,7 +208,7 @@ module ActiveVersion
208
208
  begin
209
209
  audit_class.create!(insert_attrs)
210
210
  combine_audits_if_needed if attrs[:action] != "create"
211
- audits.reset
211
+ association(:audits).reset if association_cached?(:audits)
212
212
  nil
213
213
  rescue => retry_error
214
214
  handle_audit_errors(retry_error, attrs[:action])
@@ -1,3 +1,5 @@
1
+ require "json"
2
+
1
3
  module ActiveVersion
2
4
  module Audits
3
5
  module HasAudits
@@ -105,7 +107,7 @@ module ActiveVersion
105
107
  changes.each_with_object({}) do |(k, v), h|
106
108
  h[k] = v.last if v.is_a?(Array)
107
109
  h[k] = v unless v.is_a?(Array)
108
- h[k] = h[k].to_json if h[k].is_a?(Hash)
110
+ h[k] = JSON.generate(h[k]) if h[k].is_a?(Hash) || h[k].is_a?(Array)
109
111
  end
110
112
  end
111
113
  end
@@ -24,14 +24,8 @@ module ActiveVersion
24
24
 
25
25
  # Get audit class name
26
26
  def self.audit_class
27
- # Check class attribute first (set by set_audit)
28
- return superclass.audit_class if respond_to?(:superclass) && superclass.respond_to?(:audit_class) && superclass.audit_class
29
27
  return @audit_class if @audit_class
30
28
 
31
- # Check if class attribute was set via class_attribute
32
- attr_value = read_inheritable_attribute(:audit_class) if respond_to?(:read_inheritable_attribute)
33
- return attr_value if attr_value
34
-
35
29
  if audited_options && audited_options[:as]
36
30
  klass = case audited_options[:as]
37
31
  when String, Symbol
@@ -168,35 +162,7 @@ module ActiveVersion
168
162
  # This ensures class_audited_options can find it
169
163
  @audited_options_base = normalized.dup
170
164
  self.audited_options = normalized
171
-
172
- # Override audited_options to merge thread-local config
173
- # class_attribute methods can't be easily overridden, so we need to use alias_method
174
- unless respond_to?(:audited_options_without_thread_local, true)
175
- alias_method :audited_options_without_thread_local, :audited_options
176
- define_singleton_method :audited_options do
177
- # Get base class-level options (without thread-local)
178
- # Use send to call private method in correct context
179
- class_level = send(:class_audited_options)
180
- key = send(:audited_current_options_key)
181
- thread_local = ActiveVersion.store_get(key)
182
-
183
- # Start with class-level options (deep copy to avoid reference issues)
184
- result = if class_level.is_a?(Hash)
185
- class_level.deep_dup
186
- else
187
- {}
188
- end
189
-
190
- # Merge thread-local over class-level (thread-local takes precedence)
191
- if thread_local.is_a?(Hash) && !thread_local.empty?
192
- thread_local.each do |k, v|
193
- result[k] = v
194
- end
195
- end
196
-
197
- result
198
- end
199
- end
165
+ install_thread_local_audited_options_reader!
200
166
 
201
167
  self.audit_associated_with = audited_options[:associated_with]
202
168
 
@@ -261,6 +227,7 @@ module ActiveVersion
261
227
  @audited_options_base = normalized.dup
262
228
  self.audit_class = resolved_audit_class
263
229
  @audit_class = resolved_audit_class # Also set instance variable for the custom method
230
+ install_thread_local_audited_options_reader!
264
231
 
265
232
  # Ensure audit class associations are set up
266
233
  resolved_audit_class.setup_associations if resolved_audit_class.respond_to?(:setup_associations)
@@ -324,15 +291,35 @@ module ActiveVersion
324
291
  # This overrides the class_attribute reader to merge thread-local overrides
325
292
  def update_audited_options(new_options)
326
293
  normalized = normalize_audited_options(new_options)
327
- resolved_audit_class = audit_class
294
+ resolved_audit_class = resolve_audit_class_option(normalized[:as]) || audit_class
295
+ if resolved_audit_class
296
+ self.audit_class = resolved_audit_class
297
+ @audit_class = resolved_audit_class
298
+ if resolved_audit_class.name.present?
299
+ has_many :audits,
300
+ as: :auditable,
301
+ class_name: resolved_audit_class.name.to_s,
302
+ inverse_of: false
303
+ end
304
+ end
328
305
  register_audit_column_mappings_from_destination(resolved_audit_class) if resolved_audit_class
329
306
  normalized = infer_audit_storage_and_columns(resolved_audit_class, normalized) if resolved_audit_class
330
307
  self.audited_options = normalized
331
308
  # Store base value in instance variable for class_audited_options to access
332
309
  @audited_options_base = normalized.dup
310
+ install_thread_local_audited_options_reader!
333
311
  self.audit_associated_with = audited_options[:associated_with]
334
312
  end
335
313
 
314
+ def resolve_audit_class_option(value)
315
+ case value
316
+ when String, Symbol
317
+ value.to_s.safe_constantize
318
+ when Class
319
+ value
320
+ end
321
+ end
322
+
336
323
  def normalize_audited_options(options)
337
324
  {
338
325
  on: Array.wrap(options[:on] || [:create, :update, :destroy]),
@@ -366,6 +353,7 @@ module ActiveVersion
366
353
  public
367
354
 
368
355
  def with_audited_options(options = {})
356
+ install_thread_local_audited_options_reader!
369
357
  thread_key = audited_current_options_key
370
358
  current = ActiveVersion.store_get(thread_key)
371
359
  # Store only the thread-local overrides (merge with existing if any)
@@ -375,14 +363,24 @@ module ActiveVersion
375
363
  normalized = {}
376
364
  # Convert options to hash (paper_trail pattern: simple to_h call)
377
365
  # Handle both Hash and objects that respond to to_h
378
- opts_hash = if options.respond_to?(:to_h)
379
- options.to_h
380
- elsif options.is_a?(Hash)
366
+ opts_hash = if options.is_a?(Hash)
381
367
  options
368
+ elsif options.respond_to?(:to_h)
369
+ options.to_h
382
370
  else
383
371
  {}
384
372
  end
385
373
 
374
+ # Some objects (e.g. Struct.new(:to_h).new({...})) stringify into
375
+ # { to_h: {...} } instead of returning the intended options hash.
376
+ if opts_hash.is_a?(Hash)
377
+ if opts_hash.key?(:to_h) && opts_hash[:to_h].is_a?(Hash)
378
+ opts_hash = opts_hash[:to_h]
379
+ elsif opts_hash.key?("to_h") && opts_hash["to_h"].is_a?(Hash)
380
+ opts_hash = opts_hash["to_h"]
381
+ end
382
+ end
383
+
386
384
  opts_hash.each do |k, v|
387
385
  next if v.nil?
388
386
  key = k.is_a?(Symbol) ? k : k.to_sym
@@ -422,6 +420,46 @@ module ActiveVersion
422
420
 
423
421
  private
424
422
 
423
+ def install_thread_local_audited_options_reader!
424
+ unless singleton_class.instance_methods(false).include?(:audited_options_without_thread_local)
425
+ singleton_class.alias_method :audited_options_without_thread_local, :audited_options
426
+ end
427
+
428
+ define_singleton_method :audited_options do
429
+ class_level = send(:class_audited_options)
430
+ key = send(:audited_current_options_key)
431
+ thread_local = ActiveVersion.store_get(key)
432
+
433
+ result = if class_level.is_a?(Hash)
434
+ class_level.each_with_object({}) do |(k, v), hash|
435
+ hash[k] = safe_dup_audited_option_value(v)
436
+ end
437
+ else
438
+ {}
439
+ end
440
+
441
+ if thread_local.is_a?(Hash) && !thread_local.empty?
442
+ thread_local.each do |k, v|
443
+ result[k] = v
444
+ end
445
+ end
446
+
447
+ result
448
+ end
449
+ @active_version_audited_options_wrapped = true
450
+ end
451
+
452
+ def safe_dup_audited_option_value(value)
453
+ case value
454
+ when Hash
455
+ value.each_with_object({}) { |(k, v), hash| hash[k] = safe_dup_audited_option_value(v) }
456
+ when Array
457
+ value.map { |item| safe_dup_audited_option_value(item) }
458
+ else
459
+ value
460
+ end
461
+ end
462
+
425
463
  # Get the base class_attribute value without thread-local merging
426
464
  def class_audited_options
427
465
  # Try to get from instance variable first (most direct)
@@ -516,7 +554,7 @@ module ActiveVersion
516
554
  else
517
555
  inferred[:only] ||= []
518
556
  end
519
- rescue ActiveRecord::ConnectionNotDefined, ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
557
+ rescue *ActiveVersion::Runtime.active_record_connection_errors
520
558
  inferred[:storage] ||= ActiveVersion.config.audit_storage
521
559
  inferred[:only] ||= []
522
560
  end
@@ -763,7 +801,7 @@ module ActiveVersion
763
801
  end
764
802
 
765
803
  def clear_rolled_back_audits
766
- audits.reset
804
+ association(:audits).reset if association_cached?(:audits)
767
805
  end
768
806
 
769
807
  # Override audits method to handle dynamically created classes
@@ -777,18 +815,24 @@ module ActiveVersion
777
815
  raise ConfigurationError, "Cannot determine class name for dynamically created class. Please specify class_name option in has_audits (e.g., has_audits as: PostAudit, class_name: 'Post')"
778
816
  end
779
817
 
780
- # If class_name is different from actual class name, query directly
781
818
  uses_custom_auditable_id = audited_options[:identity_resolver].present? ||
782
819
  Array(active_version_audit_identity_columns).length > 1
783
- if auditable_type != self.class.name || uses_custom_auditable_id
784
- auditable_column = ActiveVersion.column_mapper.column_for(self.class, :audits, :auditable)
785
- version_column = ActiveVersion.column_mapper.column_for(self.class, :audits, :version)
786
- self.class.audit_class.where({"#{auditable_column}_type" => auditable_type}.merge(active_version_audit_identity_map))
787
- .order(version_column => :asc)
788
- else
789
- # Use normal association for classes with proper names
790
- super
820
+
821
+ audit_klass =
822
+ if !uses_custom_auditable_id && self.class.reflect_on_association(:audits)
823
+ association(:audits).klass
824
+ else
825
+ self.class.audit_class
826
+ end
827
+ audit_klass ||= self.class.send(:resolve_audit_class_option, audited_options[:as]) if self.class.respond_to?(:resolve_audit_class_option, true)
828
+ raise ConfigurationError, "No audit class configured for #{self.class.name}" unless audit_klass
829
+
830
+ if !uses_custom_auditable_id && auditable_type == self.class.name && self.class.reflect_on_association(:audits)
831
+ return super
791
832
  end
833
+
834
+ auditable_column = ActiveVersion.column_mapper.column_for(self.class, :audits, :auditable)
835
+ audit_klass.where({"#{auditable_column}_type" => auditable_type}.merge(active_version_audit_identity_map))
792
836
  end
793
837
 
794
838
  def active_version_auditable_id_value
@@ -1,3 +1,5 @@
1
+ require "json"
2
+
1
3
  module ActiveVersion
2
4
  module Audits
3
5
  # SQL builder for batch audit operations
@@ -248,7 +250,7 @@ module ActiveVersion
248
250
  def prepare_sql_value(value)
249
251
  case value
250
252
  when Hash, Array
251
- value.to_json
253
+ JSON.generate(value)
252
254
  when Time, DateTime
253
255
  value.utc
254
256
  when Date
@@ -1,5 +1,6 @@
1
1
  require "active_version/revisions/has_revisions/revision_queries"
2
2
  require "active_version/revisions/has_revisions/revision_manipulation"
3
+ require "json"
3
4
 
4
5
  module ActiveVersion
5
6
  module Revisions
@@ -329,7 +330,7 @@ module ActiveVersion
329
330
  def revision_sql_value(value)
330
331
  case value
331
332
  when Hash, Array
332
- value.to_json
333
+ JSON.generate(value)
333
334
  when Time, DateTime
334
335
  value.utc
335
336
  when Date
@@ -394,16 +395,18 @@ module ActiveVersion
394
395
 
395
396
  def create_revision_before_update
396
397
  pointer = instance_variable_get(:@active_version_pointer)
398
+ truncated_forward_history = false
397
399
  if pointer
398
400
  version_column = revision_version_column
399
401
  # Classic linear undo/redo behavior: editing after undo drops forward history.
400
402
  revisions_scope.where("#{version_column} > ?", pointer).delete_all
401
403
  revisions.reset
402
404
  remove_instance_variable(:@active_version_pointer)
405
+ truncated_forward_history = true
403
406
  end
404
407
  # Check if we should create revision
405
408
  return unless should_create_revision?
406
- return if latest_revision_matches_current_state?
409
+ return if truncated_forward_history && latest_revision_matches_current_state?
407
410
 
408
411
  result = create_snapshot!(use_old_values: true)
409
412
 
@@ -103,7 +103,7 @@ module ActiveVersion
103
103
  version_column = fallback_column.to_sym if fallback_column
104
104
  end
105
105
  validates version_column, presence: true, uniqueness: {scope: Array(source_foreign_key)} if version_column
106
- rescue NameError, ActiveRecord::ConnectionNotDefined
106
+ rescue NameError, *ActiveVersion::Runtime.active_record_connection_errors
107
107
  # Source class not yet defined, will be set up later
108
108
  end
109
109
  end
@@ -1,3 +1,5 @@
1
+ require "json"
2
+
1
3
  module ActiveVersion
2
4
  module Revisions
3
5
  # SQL builder for batch revision operations
@@ -251,7 +253,7 @@ module ActiveVersion
251
253
  def prepare_sql_value(value)
252
254
  case value
253
255
  when Hash, Array
254
- value.to_json
256
+ JSON.generate(value)
255
257
  when Time, DateTime
256
258
  value.utc
257
259
  when Date
@@ -91,12 +91,13 @@ module ActiveVersion
91
91
  def active_record_connection_errors
92
92
  return [] unless defined?(::ActiveRecord)
93
93
 
94
- [
94
+ errors = [
95
95
  ::ActiveRecord::ConnectionNotEstablished,
96
96
  ::ActiveRecord::NoDatabaseError,
97
- ::ActiveRecord::StatementInvalid,
98
- ::ActiveRecord::ConnectionNotDefined
97
+ ::ActiveRecord::StatementInvalid
99
98
  ]
99
+ errors << ::ActiveRecord::ConnectionNotDefined if defined?(::ActiveRecord::ConnectionNotDefined)
100
+ errors
100
101
  end
101
102
 
102
103
  def supports_transactional_context?(connection)
@@ -76,7 +76,7 @@ module ActiveVersion
76
76
  return locale_column if column_names.include?(locale_column.to_s)
77
77
 
78
78
  ActiveVersion.config.translation_locale_column
79
- rescue NameError, ActiveRecord::ConnectionNotDefined
79
+ rescue NameError, *ActiveVersion::Runtime.active_record_connection_errors
80
80
  ActiveVersion.config.translation_locale_column
81
81
  end
82
82
 
@@ -99,7 +99,7 @@ module ActiveVersion
99
99
  begin
100
100
  locale_column = locale_column_name
101
101
  validates locale_column, presence: true, uniqueness: {scope: Array(source_foreign_key)}
102
- rescue NameError, ActiveRecord::ConnectionNotDefined
102
+ rescue NameError, *ActiveVersion::Runtime.active_record_connection_errors
103
103
  # Source class not yet defined, will be set up later
104
104
  end
105
105
  end
@@ -116,7 +116,7 @@ module ActiveVersion
116
116
  column = columns_hash[locale_column.to_s]
117
117
  return unless column&.type == :integer
118
118
  enum locale_column, I18n.available_locales.index_by(&:to_s)
119
- rescue NameError, ActiveRecord::ConnectionNotDefined
119
+ rescue NameError, *ActiveVersion::Runtime.active_record_connection_errors
120
120
  # Source class not yet defined
121
121
  end
122
122
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveVersion
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -1,3 +1,4 @@
1
+ require "logger"
1
2
  require "active_support"
2
3
  begin
3
4
  require "active_record"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_version
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -85,14 +85,14 @@ dependencies:
85
85
  requirements:
86
86
  - - ">="
87
87
  - !ruby/object:Gem::Version
88
- version: '2.1'
88
+ version: '1.4'
89
89
  type: :development
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
- version: '2.1'
95
+ version: '1.4'
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: pg
98
98
  requirement: !ruby/object:Gem::Requirement