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/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

Overview

Main entry point for ActiveVersion

Defined Under Namespace

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

Constant Summary collapse

VERSION =
"1.3.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.loggerObject



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

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

  @logger = default_logger
end

Class Method Details

.adapter_for(model_class, version_type) ⇒ Object



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

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

.auditing_enabledObject

Convenience methods for accessing configuration



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

def self.auditing_enabled
  config.auditing_enabled
end

.auditing_enabled=(value) ⇒ Object



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

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

.clear_context!Object

Clear persistent context



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

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) ⇒ Object



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

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

.column_mapperObject

Column mapper access



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

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

.configObject

Global configuration



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

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

.configure {|config| ... } ⇒ Object

Yields:



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

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.



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

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

.contextObject



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

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) ⇒ Object

Raises:



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

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

.default_loggerObject



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

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

.disable_auditingObject



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

def self.disable_auditing
  self.auditing_enabled = false
end

.enable_auditingObject



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

def self.enable_auditing
  self.auditing_enabled = true
end

.enter_context_block!Object



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

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



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

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

.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



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

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) ⇒ Object

Parse time and return Time object (alias for clarity)



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

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

.registryObject

Version registry access



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

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

.reset_runtime_adapter!Object



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

def self.reset_runtime_adapter!
  Runtime.reset_adapter!
end

.runtime_adapterObject

Runtime adapter access (ActiveRecord by default).



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

def self.runtime_adapter
  Runtime.adapter
end

.runtime_adapter=(adapter) ⇒ Object



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

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

.store_delete(key) ⇒ Object



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

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

.store_get(key) ⇒ Object



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

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



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

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



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

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



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

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

.transactional_context_supported?Boolean

Returns:

  • (Boolean)


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

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

Yields:



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

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, &block) ⇒ Object

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

Raises:

  • (ArgumentError)


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

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) ⇒ Object

Persistent context (connection-level, persists across operations)

Raises:

  • (ArgumentError)


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

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



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

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



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

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_auditingObject

Disable versioning globally



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

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