Module: ActiveVersion

Extended by:
ActiveSupport::Autoload
Defined in:
lib/active_version.rb,
lib/active_version/query.rb,
lib/active_version/audits.rb,
lib/active_version/railtie.rb,
lib/active_version/runtime.rb,
lib/active_version/version.rb,
lib/active_version/adapters.rb,
lib/active_version/database.rb,
lib/active_version/sharding.rb,
lib/active_version/migrators.rb,
lib/active_version/revisions.rb,
lib/active_version/translations.rb,
lib/active_version/column_mapper.rb,
lib/active_version/configuration.rb,
lib/active_version/migrators/base.rb,
lib/active_version/adapters/sequel.rb,
lib/active_version/instrumentation.rb,
lib/active_version/version_registry.rb,
lib/active_version/audits/has_audits.rb,
lib/active_version/migrators/audited.rb,
lib/active_version/audits/sql_builder.rb,
lib/active_version/audits/audit_record.rb,
lib/active_version/revisions/sql_builder.rb,
lib/active_version/adapters/active_record.rb,
lib/active_version/revisions/has_revisions.rb,
lib/active_version/unique_version_collision.rb,
lib/active_version/revisions/revision_record.rb,
lib/active_version/adapters/sequel/versioning.rb,
lib/active_version/sharding/connection_router.rb,
lib/active_version/adapters/active_record/base.rb,
lib/active_version/database/triggers/postgresql.rb,
lib/active_version/adapters/active_record/audits.rb,
lib/active_version/audits/audit_record/callbacks.rb,
lib/active_version/translations/has_translations.rb,
lib/active_version/audits/has_audits/audit_writer.rb,
lib/active_version/audits/audit_record/serializers.rb,
lib/active_version/translations/translation_record.rb,
lib/active_version/adapters/active_record/revisions.rb,
lib/active_version/audits/has_audits/audit_combiner.rb,
lib/active_version/audits/has_audits/change_filters.rb,
lib/active_version/audits/has_audits/audit_callbacks.rb,
lib/generators/active_version/audits/audits_generator.rb,
lib/active_version/adapters/active_record/translations.rb,
lib/generators/active_version/install/install_generator.rb,
lib/generators/active_version/triggers/triggers_generator.rb,
lib/active_version/revisions/has_revisions/revision_queries.rb,
lib/generators/active_version/revisions/revisions_generator.rb,
lib/active_version/audits/has_audits/database_adapter_helper.rb,
lib/active_version/revisions/has_revisions/revision_manipulation.rb,
lib/generators/active_version/translations/translations_generator.rb,
sig/active_version.rbs,
sig/active_version/core.rbs,
sig/active_version/audits.rbs,
sig/active_version/runtime.rbs,
sig/active_version/advanced.rbs,
sig/active_version/revisions.rbs,
sig/active_version/translations.rbs,
sig/active_version/configuration.rbs,
sig/active_version/instrumentation.rbs,
sig/active_version/registry_and_mapping.rbs

Overview

Main entry point for ActiveVersion

Defined Under Namespace

Modules: Adapters, Audits, Database, Generators, Instrumentation, Migrators, Query, Revisions, Runtime, SchemaGuards, Sharding, Translations, UniqueVersionCollision Classes: ColumnMapper, Configuration, ConfigurationError, DeletedColumnError, Error, FutureTimeError, Railtie, ReadonlyVersionError, RequestStore, VersionNotFoundError, VersionRegistry

Constant Summary collapse

VERSION =

Returns:

  • (String)
"2.0.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.loggerObject



336
337
338
339
340
# File 'lib/active_version.rb', line 336

def logger
  return @logger if defined?(@logger)

  @logger = default_logger
end

Class Method Details

.adapter_for(model_class, version_type) ⇒ Object

Parameters:

  • model_class (Class)
  • version_type (Symbol, String)

Returns:

  • (Object)


324
325
326
# File 'lib/active_version.rb', line 324

def self.adapter_for(model_class, version_type)
  Runtime.adapter.connection_for(model_class, version_type)
end

.auditing_enabledBoolean

Convenience methods for accessing configuration

Returns:

  • (Boolean)


73
74
75
# File 'lib/active_version.rb', line 73

def self.auditing_enabled
  config.auditing_enabled
end

.auditing_enabled=(value) ⇒ Boolean

Parameters:

  • value (Boolean)

Returns:

  • (Boolean)


77
78
79
# File 'lib/active_version.rb', line 77

def self.auditing_enabled=(value)
  config.auditing_enabled = value
end

.clear_context!nil

Clear persistent context

Returns:

  • (nil)


154
155
156
157
158
159
# File 'lib/active_version.rb', line 154

def self.clear_context!
  store_delete(:active_version_persistent_context)
  store_set(:active_version_context_depth, 0)
  store_set(:active_version_in_block, false)
  nil
end

.clear_scoped_keys!(pattern) ⇒ void

This method returns an undefined value.

Parameters:

  • pattern (Regexp)


200
201
202
# File 'lib/active_version.rb', line 200

def self.clear_scoped_keys!(pattern)
  store_keys.grep(pattern).each { |key| store_delete(key) }
end

.column_mapperColumnMapper

Column mapper access

Returns:



293
294
295
# File 'lib/active_version.rb', line 293

def self.column_mapper
  @column_mapper ||= ColumnMapper.new
end

.configConfiguration

Global configuration

Returns:



63
64
65
# File 'lib/active_version.rb', line 63

def self.config
  @config ||= Configuration.new
end

.configure {|config| ... } ⇒ Configuration

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:



67
68
69
70
# File 'lib/active_version.rb', line 67

def self.configure
  yield config if block_given?
  config
end

.connection_for(model_class, version_type) ⇒ Object

Connection access is intentionally application-owned. ActiveVersion does not route between shards/connections. These methods remain as pass-through helpers.

Parameters:

  • model_class (Class)
  • version_type (Symbol, String)

Returns:

  • (Object)


320
321
322
# File 'lib/active_version.rb', line 320

def self.connection_for(model_class, version_type)
  :default
end

.contextHash[untyped, untyped]

Returns:

  • (Hash[untyped, untyped])


102
103
104
105
106
107
# File 'lib/active_version.rb', line 102

def self.context
  # Merge persistent context with request-scoped context
  persistent = store_get(:active_version_persistent_context) || {}
  request_scoped = RequestStore.version_context || {}
  persistent.merge(request_scoped)
end

.context=(value) ⇒ Hash[untyped, untyped]

Parameters:

  • value (Hash[untyped, untyped])

Returns:

  • (Hash[untyped, untyped])

Raises:



109
110
111
112
# File 'lib/active_version.rb', line 109

def self.context=(value)
  raise ConfigurationError, "context must be a hash" unless value.is_a?(Hash)
  RequestStore.version_context = value
end

.default_loggerObject



349
350
351
352
353
354
# File 'lib/active_version.rb', line 349

def default_logger
  l = Logger.new($stderr)
  l.level = Logger::WARN
  l.formatter = proc { |_, _, _, msg| "#{msg}\n" }
  l
end

.disable_auditingBoolean

Returns:

  • (Boolean)


279
280
281
# File 'lib/active_version.rb', line 279

def self.disable_auditing
  self.auditing_enabled = false
end

.enable_auditingBoolean

Returns:

  • (Boolean)


283
284
285
# File 'lib/active_version.rb', line 283

def self.enable_auditing
  self.auditing_enabled = true
end

.enter_context_block!Object



205
206
207
208
209
# File 'lib/active_version.rb', line 205

def self.enter_context_block!
  depth = store_get(:active_version_context_depth).to_i + 1
  store_set(:active_version_context_depth, depth)
  store_set(:active_version_in_block, depth.positive?)
end

.leave_context_block!Object



211
212
213
214
215
216
# File 'lib/active_version.rb', line 211

def self.leave_context_block!
  depth = store_get(:active_version_context_depth).to_i - 1
  depth = 0 if depth.negative?
  store_set(:active_version_context_depth, depth)
  store_set(:active_version_in_block, depth.positive?)
end

.log_debug(message) ⇒ Object



342
343
344
345
346
347
# File 'lib/active_version.rb', line 342

def log_debug(message)
  logger = self.logger
  return if logger.nil?

  logger.debug(message) if logger.respond_to?(:debug)
end

.parse_time(time) ⇒ Time

Parse time from various formats Converts Numeric (Unix timestamp), String, Date, Time, or other objects to Time

Parameters:

  • time (Numeric, String, Date, Time, Object)

    Time value in various formats

Returns:

  • (Time)

    Time object



301
302
303
304
305
306
307
308
309
310
# File 'lib/active_version.rb', line 301

def self.parse_time(time)
  parser = time_parser
  case time
  when Numeric then parser.at(time)
  when String then parser.parse(time)
  when Date then time.to_time
  when Time then time
  else parser.parse(time.to_s)
  end
end

.parse_time_to_time(time) ⇒ Time

Parse time and return Time object (alias for clarity)

Parameters:

  • time (Numeric, String, Date, Time, untyped)

Returns:

  • (Time)


313
314
315
# File 'lib/active_version.rb', line 313

def self.parse_time_to_time(time)
  parse_time(time)
end

.registryVersionRegistry

Version registry access

Returns:



288
289
290
# File 'lib/active_version.rb', line 288

def self.registry
  @registry ||= VersionRegistry.new
end

.reset_runtime_adapter!Object

Returns:

  • (Object)


90
91
92
# File 'lib/active_version.rb', line 90

def self.reset_runtime_adapter!
  Runtime.reset_adapter!
end

.runtime_adapterObject

Runtime adapter access (ActiveRecord by default).

Returns:

  • (Object)


82
83
84
# File 'lib/active_version.rb', line 82

def self.runtime_adapter
  Runtime.adapter
end

.runtime_adapter=(adapter) ⇒ Object

Parameters:

  • adapter (Object)

Returns:

  • (Object)


86
87
88
# File 'lib/active_version.rb', line 86

def self.runtime_adapter=(adapter)
  Runtime.adapter = adapter
end

.store_delete(key) ⇒ Object

Parameters:

  • key (Symbol)

Returns:

  • (Object)


181
182
183
# File 'lib/active_version.rb', line 181

def self.store_delete(key)
  store_set(key, nil)
end

.store_get(key) ⇒ Object

Parameters:

  • key (Symbol)

Returns:

  • (Object)


161
162
163
164
165
166
167
168
169
# File 'lib/active_version.rb', line 161

def self.store_get(key)
  if config.execution_scope == :thread
    Thread.current.thread_variable_get(key)
  elsif Fiber.current.respond_to?(:[])
    Fiber.current[key]
  else
    thread_current_for_fallback_store[key]
  end
end

.store_keysObject



185
186
187
188
189
190
191
192
193
# File 'lib/active_version.rb', line 185

def self.store_keys
  if config.execution_scope == :thread
    Thread.current.thread_variables
  elsif thread_current_for_fallback_store.respond_to?(:keys)
    thread_current_for_fallback_store.keys
  else
    []
  end
end

.store_set(key, value) ⇒ Object

Parameters:

  • key (Symbol)
  • value (Object)

Returns:

  • (Object)


171
172
173
174
175
176
177
178
179
# File 'lib/active_version.rb', line 171

def self.store_set(key, value)
  if config.execution_scope == :thread
    Thread.current.thread_variable_set(key, value)
  elsif Fiber.current.respond_to?(:[]=)
    Fiber.current[key] = value
  else
    thread_current_for_fallback_store[key] = value
  end
end

.time_parserObject



252
253
254
255
# File 'lib/active_version.rb', line 252

def self.time_parser
  zone = Time.zone if Time.respond_to?(:zone)
  zone || Time
end

.transactional_context_supported?Boolean

Returns:

  • (Boolean)


243
244
245
246
247
248
249
250
# File 'lib/active_version.rb', line 243

def self.transactional_context_supported?
  connection = Runtime.adapter.base_connection
  Runtime.supports_transactional_context?(connection)
rescue *Runtime.active_record_connection_errors
  false
rescue
  false
end

.with_connection(model_class, version_type) {|Runtime.adapter.connection_for(model_class, version_type)| ... } ⇒ Object

Parameters:

  • model_class (Class)
  • version_type (Symbol, String)

Yields:

Yield Parameters:

  • arg0 (Object)

Yield Returns:

  • (Object)

Returns:

  • (Object)


328
329
330
# File 'lib/active_version.rb', line 328

def self.with_connection(model_class, version_type, &block)
  yield(Runtime.adapter.connection_for(model_class, version_type))
end

.with_context(context = nil, transactional: true, **kwargs) { ... } ⇒ Object

Transaction-aware context (uses PostgreSQL session variables) Accepts either a hash as first argument or keyword arguments

Parameters:

  • context (Hash[untyped, untyped]) (defaults to: nil)
  • transactional: (Boolean) (defaults to: true)
  • kwargs (Object)

Yields:

Yield Returns:

  • (Object)

Returns:

  • (Object)

Raises:

  • (ArgumentError)


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/active_version.rb', line 116

def self.with_context(context = nil, transactional: true, **kwargs, &block)
  raise ArgumentError, "with_context requires a block" unless block_given?

  # If context is nil but kwargs are provided, use kwargs as context
  # If context is provided, use it (and ignore kwargs)
  # If both are nil/empty, use empty hash
  context_hash = if context.nil? && kwargs.any?
    kwargs
  elsif context.is_a?(Hash)
    context
  elsif context.nil?
    {}
  else
    raise ArgumentError, "context must be a hash or keyword arguments"
  end

  if transactional_context_supported?
    # Use PostgreSQL session variables for transaction-aware context
    with_transactional_context(context_hash, &block)
  else
    # Fallback to thread-local context
    with_thread_local_context(context_hash, &block)
  end
end

.with_context!(context) ⇒ nil

Persistent context (connection-level, persists across operations)

Parameters:

  • context (Hash[untyped, untyped])

Returns:

  • (nil)

Raises:

  • (ArgumentError)


142
143
144
145
146
147
148
149
150
151
# File 'lib/active_version.rb', line 142

def self.with_context!(context)
  raise ArgumentError, "context must be a hash" unless context.is_a?(Hash)

  if store_get(:active_version_in_block)
    raise Error, "with_context! cannot be called from within a with_context block"
  end

  store_set(:active_version_persistent_context, context)
  nil
end

.with_thread_local_context(context, &block) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/active_version.rb', line 257

def self.with_thread_local_context(context, &block)
  old_context = self.context.dup
  old_block_context = store_get(:active_version_block_context)
  enter_context_block!
  self.context = old_context.merge(context)
  store_set(:active_version_block_context, context)
  yield
ensure
  self.context = old_context
  store_set(:active_version_block_context, old_block_context)
  leave_context_block!
end

.with_transactional_context(context, &block) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/active_version.rb', line 218

def self.with_transactional_context(context, &block)
  connection = Runtime.adapter.base_connection
  old_context = self.context.dup
  old_block_context = store_get(:active_version_block_context)
  enter_context_block!

  # Set PostgreSQL session variable
  if connection.open_transactions.positive?
    encoded_context = connection.quote(ActiveSupport::JSON.encode(context))
    connection.execute("SET LOCAL active_version.context = #{encoded_context}")
  end

  # Also update thread-local for immediate access
  self.context = old_context.merge(context)
  store_set(:active_version_block_context, context)

  yield
ensure
  # Context is automatically cleared on transaction rollback
  # But we still restore thread-local context
  self.context = old_context
  store_set(:active_version_block_context, old_block_context)
  leave_context_block!
end

.without_auditing { ... } ⇒ Object

Disable versioning globally

Yields:

Yield Returns:

  • (Object)

Returns:

  • (Object)


271
272
273
274
275
276
277
# File 'lib/active_version.rb', line 271

def self.without_auditing
  auditing_was_enabled = auditing_enabled
  disable_auditing
  yield
ensure
  enable_auditing if auditing_was_enabled
end