active_version 1.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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +36 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +492 -0
  5. data/SECURITY.md +29 -0
  6. data/lib/active_version/adapters/active_record/audits.rb +36 -0
  7. data/lib/active_version/adapters/active_record/base.rb +37 -0
  8. data/lib/active_version/adapters/active_record/revisions.rb +49 -0
  9. data/lib/active_version/adapters/active_record/translations.rb +45 -0
  10. data/lib/active_version/adapters/active_record.rb +10 -0
  11. data/lib/active_version/adapters/sequel/versioning.rb +282 -0
  12. data/lib/active_version/adapters/sequel.rb +9 -0
  13. data/lib/active_version/adapters.rb +5 -0
  14. data/lib/active_version/audits/audit_record/callbacks.rb +180 -0
  15. data/lib/active_version/audits/audit_record/serializers.rb +49 -0
  16. data/lib/active_version/audits/audit_record.rb +522 -0
  17. data/lib/active_version/audits/has_audits/audit_callbacks.rb +46 -0
  18. data/lib/active_version/audits/has_audits/audit_combiner.rb +212 -0
  19. data/lib/active_version/audits/has_audits/audit_writer.rb +282 -0
  20. data/lib/active_version/audits/has_audits/change_filters.rb +114 -0
  21. data/lib/active_version/audits/has_audits/database_adapter_helper.rb +86 -0
  22. data/lib/active_version/audits/has_audits.rb +891 -0
  23. data/lib/active_version/audits/sql_builder.rb +263 -0
  24. data/lib/active_version/audits.rb +10 -0
  25. data/lib/active_version/column_mapper.rb +92 -0
  26. data/lib/active_version/configuration.rb +124 -0
  27. data/lib/active_version/database/triggers/postgresql.rb +243 -0
  28. data/lib/active_version/database.rb +7 -0
  29. data/lib/active_version/instrumentation.rb +226 -0
  30. data/lib/active_version/migrators/audited.rb +84 -0
  31. data/lib/active_version/migrators/base.rb +191 -0
  32. data/lib/active_version/migrators.rb +8 -0
  33. data/lib/active_version/query.rb +105 -0
  34. data/lib/active_version/railtie.rb +17 -0
  35. data/lib/active_version/revisions/has_revisions/revision_manipulation.rb +499 -0
  36. data/lib/active_version/revisions/has_revisions/revision_queries.rb +182 -0
  37. data/lib/active_version/revisions/has_revisions.rb +443 -0
  38. data/lib/active_version/revisions/revision_record.rb +287 -0
  39. data/lib/active_version/revisions/sql_builder.rb +266 -0
  40. data/lib/active_version/revisions.rb +10 -0
  41. data/lib/active_version/runtime.rb +148 -0
  42. data/lib/active_version/sharding/connection_router.rb +20 -0
  43. data/lib/active_version/sharding.rb +7 -0
  44. data/lib/active_version/tasks/active_version.rake +29 -0
  45. data/lib/active_version/translations/has_translations.rb +350 -0
  46. data/lib/active_version/translations/translation_record.rb +258 -0
  47. data/lib/active_version/translations.rb +9 -0
  48. data/lib/active_version/version.rb +3 -0
  49. data/lib/active_version/version_registry.rb +87 -0
  50. data/lib/active_version.rb +329 -0
  51. data/lib/generators/active_version/audits/audits_generator.rb +65 -0
  52. data/lib/generators/active_version/audits/templates/audit_model.rb.erb +16 -0
  53. data/lib/generators/active_version/audits/templates/migration_jsonb.rb.erb +33 -0
  54. data/lib/generators/active_version/audits/templates/migration_table.rb.erb +34 -0
  55. data/lib/generators/active_version/install/install_generator.rb +19 -0
  56. data/lib/generators/active_version/install/templates/initializer.rb.erb +38 -0
  57. data/lib/generators/active_version/revisions/revisions_generator.rb +71 -0
  58. data/lib/generators/active_version/revisions/templates/backfill_migration.rb.erb +19 -0
  59. data/lib/generators/active_version/revisions/templates/migration.rb.erb +20 -0
  60. data/lib/generators/active_version/revisions/templates/revision_model.rb.erb +8 -0
  61. data/lib/generators/active_version/translations/templates/migration.rb.erb +16 -0
  62. data/lib/generators/active_version/translations/templates/translation_model.rb.erb +15 -0
  63. data/lib/generators/active_version/translations/translations_generator.rb +73 -0
  64. data/lib/generators/active_version/triggers/templates/migration.rb.erb +100 -0
  65. data/lib/generators/active_version/triggers/triggers_generator.rb +74 -0
  66. data/sig/active_version/advanced.rbs +51 -0
  67. data/sig/active_version/audits.rbs +128 -0
  68. data/sig/active_version/configuration.rbs +38 -0
  69. data/sig/active_version/core.rbs +53 -0
  70. data/sig/active_version/instrumentation.rbs +17 -0
  71. data/sig/active_version/registry_and_mapping.rbs +18 -0
  72. data/sig/active_version/revisions.rbs +70 -0
  73. data/sig/active_version/runtime.rbs +29 -0
  74. data/sig/active_version/translations.rbs +43 -0
  75. data/sig/active_version.rbs +3 -0
  76. metadata +443 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ec3713fc6fd0f073d9bb77d1e44c674a2023cf15f263fa05180457648052e8a1
4
+ data.tar.gz: 65ba5308255d161420e028564050e75044b975c9f8b520605d1caedab3d7f05b
5
+ SHA512:
6
+ metadata.gz: eaf25bb79e12b3a85144c6c191a31ab97dea2a94c91dca682fa3cc9a729e648c29fd2e593605452945bfce0da7e2b391e290998aa9e69113ab00434b7627adca
7
+ data.tar.gz: fba81088323b53f4ad45acf8e04e82c63085854fcc407c7b4321dd687d06a12f336c1e4fe5f9224ce99cb3125f5e4735ac5237622f39008dbb9da74361562682
data/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ # CHANGELOG
2
+
3
+ ## 1.0.0 (2026-03-08)
4
+
5
+ - Initial release of ActiveVersion library
6
+ - Added translations module with locale-based versioning and `has_translations` declaration
7
+ - Added `translate(attr, locale:)` and `translation(locale:)` methods for accessing translations
8
+ - Added `translated_scopes` for dynamic scopes and `translated_copies` for value copying
9
+ - Added automatic default translation creation and locale-based queries
10
+ - Added revisions module with schema-aligned snapshots and `has_revisions` declaration
11
+ - Added automatic revision creation on update with version numbering
12
+ - Added `at_version(version)`, `at(time:, version:)`, and `at!(time:, version:)` methods for version access
13
+ - Added `undo!`, `redo!`, and `switch_to!(version)` methods for version control
14
+ - Added `diff_from(time:, version:)` for diff generation and `create_snapshot!` for manual snapshots
15
+ - Added `without_revisions` for temporarily disabling revision tracking
16
+ - Added audits module with JSONB and table storage options and `has_audits` declaration
17
+ - Added automatic audit creation on create/update/destroy operations
18
+ - Added `audit_sql` for single record SQL generation and `batch_insert_sql` for batch operations
19
+ - Added context tracking (global and per-model) for audits
20
+ - Added comment support, user tracking, request UUID and remote address tracking
21
+ - Added revision reconstruction from audits
22
+ - Added conditional auditing with `if:` and `unless:` options
23
+ - Added audit combining for storage limits and redaction support for sensitive data
24
+ - Added encrypted attributes filtering
25
+ - Added PostgreSQL trigger functions for audits and revisions with trigger generators
26
+ - Added ability to disable triggers via session variables and context support via session variables
27
+ - Added sharding support with connection routing per model, global and per-model shard configuration
28
+ - Added `connection_for`, `adapter_for`, and `with_connection` methods for shard management
29
+ - Added query builder with `ActiveVersion::Query.audits(record, opts)`, `ActiveVersion::Query.translations(record, opts)`, and `ActiveVersion::Query.revisions(record, opts)` methods
30
+ - Added shard-aware queries
31
+ - Added migration helpers with `ActiveVersion::Migrators::Base` base class and `ActiveVersion::Migrators::Audited` for audited gem migration
32
+ - Added Rails generators: `rails g active_version:install`, `rails g active_version:translations Model`, `rails g active_version:revisions Model`, `rails g active_version:audits Model --storage=json_column`, `rails g active_version:triggers Model --type=audit`
33
+ - Added instrumentation hooks via ActiveSupport::Notifications
34
+ - Added configurable column naming and per-model and global configuration options
35
+ - Added comprehensive test suite with unit tests, integration tests, and test helpers
36
+
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 amkisko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,492 @@
1
+ # ActiveVersion
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/active_version.svg)](https://badge.fury.io/rb/active_version) [![Test Status](https://github.com/amkisko/active_version.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/active_version.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/active_version.rb/graph/badge.svg?token=2U6NXJOVVM)](https://codecov.io/gh/amkisko/active_version.rb)
4
+
5
+ A unified versioning library for ActiveRecord that handles translations, revisions, and audits in a single, extensible architecture.
6
+
7
+ ## Features
8
+
9
+ - Translations: Locale-based versioning with automatic value copying
10
+ - Revisions: Schema-aligned snapshots for workflow management
11
+ - Audits: `:json_column`, `:yaml_column`, or `:mirror_columns` change tracking
12
+ - Database Triggers: Optional PostgreSQL triggers for zero-overhead versioning
13
+ - Sharding Support: Route version tables to separate databases
14
+ - SQL Generation: Batch operations via SQL generation
15
+ - Configurable: Flexible column naming and per-model configuration
16
+
17
+ ## Deliberate Differences from PaperTrail, Audited, and Similar Gems
18
+
19
+ ActiveVersion intentionally chooses explicitness and operational readability over implicit defaults.
20
+
21
+ - Do not enable ActiveVersion together with `audited` or `paper_trail` on the same model.
22
+ These libraries will start conflicting with each other on method names occupation.
23
+ - Manual provisioning is required per model and feature.
24
+ You explicitly generate and wire audits, revisions, and translations tables/models for each domain model. We do not auto-provision global defaults behind the scenes.
25
+ Example: unlike gems that create and rely on a single default audits table automatically, ActiveVersion expects deliberate setup per model.
26
+ - Audit schema is configured explicitly on destination audit models.
27
+ Storage mode and audit column mapping belong to the audit model/table contract (`configure_audit`), not hidden global magic.
28
+ - Revision/translation schema is also destination-model owned.
29
+ Version/locale and identity key semantics are declared on revision/translation models.
30
+ - No incremental/delta-chain storage for revisions or audits.
31
+ ActiveVersion avoids patch-chain persistence and reconstruction complexity as a core design choice.
32
+ - No diff/patch-based persistence layer.
33
+ We prefer straightforward record payloads and predictable query/debug behavior.
34
+ Rationale: diff/patch persistence is costly to implement and maintain at scale, while storage footprint is usually manageable with retention policies, archival, partitioning, or cold storage.
35
+
36
+ ## Installation
37
+
38
+ Add this line to your application's Gemfile:
39
+
40
+ ```ruby
41
+ gem 'active_version'
42
+ ```
43
+
44
+ And then execute:
45
+
46
+ ```bash
47
+ $ bundle install
48
+ ```
49
+
50
+ Or install it yourself as:
51
+
52
+ ```bash
53
+ $ gem install active_version
54
+ ```
55
+
56
+ ### Setting up in an existing project
57
+
58
+ For a step-by-step guide (prerequisites, generators, migrations, model setup, and optional triggers), see [docs/SETUP_IN_EXISTING_PROJECT.md](docs/SETUP_IN_EXISTING_PROJECT.md).
59
+
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
+
62
+ ## Quick Start
63
+
64
+ ### Setup
65
+
66
+ ```bash
67
+ # Generate initializer
68
+ rails g active_version:install
69
+ ```
70
+
71
+ ### Translations
72
+
73
+ ```ruby
74
+ # Generate translation support
75
+ rails g active_version:translations Post
76
+
77
+ # In your model
78
+ class Post < ApplicationRecord
79
+ has_translations
80
+ end
81
+
82
+ # Usage
83
+ post = Post.create!(title: "Hello", body: "World")
84
+ post.translations.create!(locale: "fi", title: "Hei", body: "Maailma")
85
+
86
+ post.translate(:title, locale: "fi") # => "Hei"
87
+ post.translation(locale: "fi") # => PostTranslation instance
88
+ ```
89
+
90
+ ### Revisions
91
+
92
+ ```ruby
93
+ # Generate revision support
94
+ rails g active_version:revisions Post
95
+
96
+ # In your model
97
+ class Post < ApplicationRecord
98
+ has_revisions
99
+ end
100
+
101
+ # Usage
102
+ post = Post.create!(title: "v1")
103
+ post.update!(title: "v2") # Creates revision automatically
104
+
105
+ post.current_version # => 1
106
+ post.revision(version: 1) # => PostRevision instance
107
+ post.at_version(1) # => Post instance at version 1
108
+ post.undo! # => Revert to previous version
109
+ post.diff_from(version: 1) # => { "changes" => { "title" => {...} } }
110
+ ```
111
+
112
+ ### Audits
113
+
114
+ ```ruby
115
+ # Generate audit support
116
+ rails g active_version:audits Post --storage=json_column
117
+
118
+ # In your model
119
+ class Post < ApplicationRecord
120
+ has_audits
121
+ end
122
+
123
+ # Usage
124
+ post = Post.create!(title: "Hello")
125
+ post.update!(title: "World")
126
+
127
+ post.audits.count # => 2
128
+ post.audits.last.audited_changes # => {"title" => ["Hello", "World"]}
129
+ post.revision(version: 1) # => Post instance at version 1
130
+
131
+ # Generate SQL for batch operations
132
+ sql = post.audit_sql
133
+ PostAudit.batch_insert_sql([post1, post2], force: true)
134
+ PostAudit.batch_insert_sql(force: true) { [post1, post2] }
135
+ PostRevision.batch_insert_sql(version: 1) do |batch|
136
+ post1 = Post.create!(title: "A")
137
+ post2 = Post.create!(title: "B")
138
+ post3 = Post.create!(title: "C")
139
+ post1.update!(title: "A1")
140
+ post2.update!(title: "B1")
141
+ post3.destroy!
142
+ end
143
+ PostAudit.batch_insert(force: true) do |batch|
144
+ batch << post1
145
+ batch << post2
146
+ end
147
+ ```
148
+
149
+ ### Batch Semantics and Feature Interaction
150
+
151
+ - `batch_insert` always executes SQL generated by `batch_insert_sql`.
152
+ - By default, clean records are skipped. Use `force: true` or `allow_saved: true` to include them.
153
+ - Block mode supports:
154
+ - returning records array
155
+ - collector style (`|batch| batch << record`)
156
+ - callback-capture mode for side-effect blocks (no explicit returned records)
157
+ - In batch mode, merge-style features are intentionally not applied:
158
+ - audit compaction (`max_audits` / combine flow) is not executed
159
+ - revision debounce merge window is not executed
160
+ - Other behavior still applies from model configuration:
161
+ - identity resolution and foreign key mapping
162
+ - destination schema configuration
163
+ - context merging (`options[:context]` for audit batch helpers)
164
+
165
+ ## Configuration
166
+
167
+ ### Global Configuration
168
+
169
+ ```ruby
170
+ # config/initializers/active_version.rb
171
+ ActiveVersion.configure do |config|
172
+ config.auditing_enabled = true
173
+ config.current_user_method = :current_user
174
+
175
+ # Global fallback naming (prefer destination audit model config instead)
176
+ config.translation_locale_column = :locale
177
+ config.revision_version_column = :version
178
+ end
179
+ ```
180
+
181
+ ### Destination Audit Model Configuration (Preferred)
182
+
183
+ ```ruby
184
+ class PostAudit < ApplicationRecord
185
+ include ActiveVersion::Audits::AuditRecord
186
+
187
+ configure_audit do
188
+ storage :json_column # :json_column | :yaml_column | :mirror_columns
189
+ action_column :action
190
+ changes_column :audited_changes
191
+ context_column :audited_context
192
+ comment_column :comment
193
+ version_column :version
194
+ user_column :user_id
195
+ end
196
+ end
197
+ ```
198
+
199
+ Custom storage providers can be registered per audit model:
200
+
201
+ ```ruby
202
+ class PostAudit < ApplicationRecord
203
+ include ActiveVersion::Audits::AuditRecord
204
+
205
+ register_storage_provider(:msgpack) do |_audit_class, _column_name|
206
+ MyMsgpackCodec.new # must respond to #load and #dump
207
+ end
208
+
209
+ configure_audit do
210
+ storage :msgpack
211
+ action_column :action
212
+ changes_column :audited_changes
213
+ context_column :audited_context
214
+ end
215
+ end
216
+ ```
217
+
218
+ ### Destination Revision/Translation Configuration (Preferred)
219
+
220
+ ```ruby
221
+ class PostRevision < ApplicationRecord
222
+ include ActiveVersion::Revisions::RevisionRecord
223
+
224
+ configure_revision(version_column: :version,
225
+ foreign_key: :post_id
226
+ )
227
+ end
228
+
229
+ class PostTranslation < ApplicationRecord
230
+ include ActiveVersion::Translations::TranslationRecord
231
+
232
+ configure_translation(locale_column: :locale,
233
+ foreign_key: :post_id
234
+ )
235
+ end
236
+ ```
237
+
238
+ ### Per-Model Configuration
239
+
240
+ ```ruby
241
+ class Post < ApplicationRecord
242
+ has_audits(
243
+ table_name: "custom_post_audits",
244
+ identity_resolver: :external_id, # optional: use custom identity value for auditable_id
245
+ only: [:title, :body], # Only track these fields
246
+ except: [:internal_notes], # Don't track these
247
+ max_audits: 100, # Limit storage
248
+ associated_with: :company, # Track associated model
249
+ if: :should_audit?, # Conditional auditing
250
+ comment_required: true # Require comments
251
+ )
252
+
253
+ has_revisions(
254
+ # Revisions are always table-based
255
+ table_name: "custom_post_revisions",
256
+ foreign_key: :record_uuid, # optional: custom FK column in revision table
257
+ identity_resolver: :external_id # optional: source value used for FK writes/queries
258
+ )
259
+
260
+ has_translations(
261
+ # Translations are always table-based
262
+ table_name: "custom_post_translations",
263
+ foreign_key: :record_uuid # optional: custom FK column in translation table
264
+ )
265
+ end
266
+ ```
267
+
268
+ ## Advanced Features
269
+
270
+ ### Database Triggers
271
+
272
+ ```bash
273
+ # Generate trigger for audits
274
+ rails g active_version:triggers Post --type=audit
275
+
276
+ # Generate trigger for revisions
277
+ rails g active_version:triggers Post --type=revision
278
+ ```
279
+
280
+ ### Infrastructure Ownership
281
+
282
+ ```ruby
283
+ # Keep infrastructure concerns in application code:
284
+ # - connection topology routing / connected_to blocks
285
+ # - partition management
286
+ # - replication / topology policy
287
+ #
288
+ # ActiveVersion intentionally does not route between connections/topologies.
289
+ # It follows your current ActiveRecord connection and declared model schema.
290
+ ```
291
+
292
+ ### Runtime Adapter (Advanced)
293
+
294
+ ```ruby
295
+ # Default runtime is ActiveRecord-backed.
296
+ ActiveVersion.runtime_adapter
297
+
298
+ # Advanced/non-AR integrations can provide their own adapter object:
299
+ # - base_connection
300
+ # - connection_for(model_class, version_type)
301
+ # Optional capability hooks:
302
+ # - supports_transactional_context?(connection)
303
+ # - supports_current_transaction_id?(connection)
304
+ #
305
+ # Example:
306
+ # ActiveVersion.runtime_adapter = MyCustomAdapter.new
307
+
308
+ # Optional boot-time contract check:
309
+ # ActiveVersion::Runtime.valid_adapter?(ActiveVersion.runtime_adapter) # => true/false
310
+ ```
311
+
312
+ Required adapter contract:
313
+
314
+ ```ruby
315
+ class MyCustomAdapter
316
+ def base_connection
317
+ # returns a connection-like object for global/runtime operations
318
+ end
319
+
320
+ def connection_for(model_class, version_type)
321
+ # returns the connection-like object used for this source model/type
322
+ end
323
+
324
+ # Optional capability hooks. If omitted, ActiveVersion falls back to
325
+ # adapter_name-based PostgreSQL detection.
326
+ def supports_transactional_context?(connection)
327
+ connection.adapter_name.to_s.casecmp("postgresql").zero?
328
+ end
329
+
330
+ def supports_current_transaction_id?(connection)
331
+ connection.adapter_name.to_s.casecmp("postgresql").zero?
332
+ end
333
+
334
+ def supports_partition_catalog_checks?(connection)
335
+ connection.adapter_name.to_s.casecmp("postgresql").zero?
336
+ end
337
+ end
338
+ ```
339
+
340
+ See runnable samples:
341
+ - [`examples/sinatra_demo/runtime_adapter_example.rb`](examples/sinatra_demo/runtime_adapter_example.rb)
342
+ - [`examples/sinatra_demo/sequel_like_runtime_adapter_example.rb`](examples/sinatra_demo/sequel_like_runtime_adapter_example.rb)
343
+
344
+ For partitioning and connection-topology suggestions, see [docs/PARTITIONING_AND_SHARDING.md](docs/PARTITIONING_AND_SHARDING.md).
345
+ For non-ActiveRecord runtime adapter guidance, see [docs/NON_ACTIVE_RECORD.md](docs/NON_ACTIVE_RECORD.md).
346
+ For dependency/license disclosure, see [docs/THIRD_PARTY_NOTICES.md](docs/THIRD_PARTY_NOTICES.md).
347
+
348
+ ### Query Builder
349
+
350
+ ```ruby
351
+ # Unified query interface
352
+ ActiveVersion::Query.audits(post, preload: :user, order_by: { desc: :created_at })
353
+ ActiveVersion::Query.translations(post, locale: "en")
354
+ ActiveVersion::Query.revisions(post, version: 2)
355
+ ```
356
+
357
+ ### Context Tracking
358
+
359
+ ```ruby
360
+ # Set global context
361
+ ActiveVersion.with_context(ip: request.ip, user_agent: request.user_agent) do
362
+ post.update!(title: "New")
363
+ end
364
+
365
+ # Set per-model context
366
+ post.audit_context = { request_id: "123" }
367
+ post.save!
368
+ ```
369
+
370
+ ### Disabling Versioning
371
+
372
+ ```ruby
373
+ # Global
374
+ ActiveVersion.without_auditing do
375
+ # ...
376
+ end
377
+
378
+ # Per-model
379
+ Post.without_auditing do
380
+ # ...
381
+ end
382
+
383
+ # Per-instance
384
+ post.without_auditing do
385
+ # ...
386
+ end
387
+ ```
388
+
389
+ ## Migration from Other Gems
390
+
391
+ ### From audited
392
+
393
+ ```ruby
394
+ # 1. Generate audit tables
395
+ rails g active_version:audits Post --storage=json_column
396
+
397
+ # 2. Migrate data
398
+ ActiveVersion::Migrators::Audited.migrate(Post)
399
+
400
+ # 3. Update code
401
+ # Replace audited with has_audits
402
+ ```
403
+
404
+ ### From paper_trail
405
+
406
+ ```ruby
407
+ # 1. Generate revision tables
408
+ rails g active_version:revisions Post
409
+
410
+ # 2. Migrate data (manual or via migrator)
411
+ # 3. Update code
412
+ ```
413
+
414
+ ## Benchmarking
415
+
416
+ Benchmarks are available under RSpec with `:benchmark` tag and are excluded from normal runs by default.
417
+ Latest published results and analysis are in [BENCHMARK.md](BENCHMARK.md).
418
+ The report includes per-record overhead vs ActiveRecord baseline (`p5`, `mean`, `p95`).
419
+ The report also separates ActiveRecord and Sequel benchmark groups to avoid cross-ORM baseline mixing.
420
+
421
+ ```bash
422
+ # Normal test run (benchmarks excluded)
423
+ bundle exec rspec
424
+
425
+ # Explicit benchmark run
426
+ usr/bin/benchmark.rb
427
+ ```
428
+
429
+ `usr/bin/benchmark.rb` runs two sections by default: `sqlite` and `postgresql`.
430
+
431
+ Environment knobs:
432
+
433
+ - `ACTIVE_VERSION_BENCH_ITERATIONS` (default: `5000`)
434
+ - `ACTIVE_VERSION_BENCH_WARMUP` (default: `200`)
435
+ - `ACTIVE_VERSION_BENCH_ROUNDS` (default: `5`)
436
+ - `BENCHMARK=1` (set automatically by `usr/bin/benchmark.rb`)
437
+
438
+ ## API Reference
439
+
440
+ ### Translations
441
+
442
+ - `has_translations` - Declare model has translations
443
+ - `translate(attr, locale:)` - Get translated attribute
444
+ - `translation(locale:)` - Get translation record
445
+ - `translated_scopes(*attrs)` - Generate scopes for translated attributes
446
+ - `translated_copies(*attrs)` - Generate copy methods
447
+
448
+ ### Revisions
449
+
450
+ - `has_revisions` - Declare model has revisions
451
+ - `current_version` - Get current version number
452
+ - `revision(version:)` - Get revision record
453
+ - `at_version(version)` - Get copy at version
454
+ - `at(time:, version:)` - Get copy at time/version
455
+ - `at!(time:, version:)` - Revert to time/version
456
+ - `undo!` - Revert to previous version
457
+ - `redo!` - Restore to future version
458
+ - `switch_to!(version)` - Switch to version
459
+ - `diff_from(time:, version:)` - Get diff from time/version
460
+ - `create_snapshot!` - Manually create snapshot
461
+
462
+ ### Audits
463
+
464
+ - `has_audits` - Declare model has audits
465
+ - `audit_sql` - Generate SQL for single audit
466
+ - `batch_insert_sql(records, **options)` - Generate SQL for batch inserts (also supports block)
467
+ - `batch_insert(records, **options)` - Execute batch inserts (also supports block)
468
+ - `revision(version:)` - Reconstruct from audits
469
+ - `revision_at(time:)` - Reconstruct at time
470
+ - `own_and_associated_audits` - Get own and associated audits
471
+
472
+ ## Requirements
473
+
474
+ - Ruby >= 3.0.0
475
+ - ActiveRecord >= 6.0.0 (default runtime)
476
+ - SQLite or MySQL or PostgreSQL (optional, for triggers and JSONB support)
477
+
478
+ ## Contributing
479
+
480
+ Bug reports and pull requests are welcome on GitHub at https://github.com/amkisko/active_version.rb.
481
+
482
+ ## License
483
+
484
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
485
+
486
+ ## Sponsors
487
+
488
+ Sponsored by [Kisko Labs](https://www.kiskolabs.com).
489
+
490
+ <a href="https://www.kiskolabs.com">
491
+ <img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
492
+ </a>
data/SECURITY.md ADDED
@@ -0,0 +1,29 @@
1
+ # SECURITY
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ Do NOT open a public GitHub issue for security vulnerabilities.
6
+
7
+ Email security details to: security@kiskolabs.com
8
+
9
+ Include: description, steps to reproduce, potential impact, and suggested fix (if available).
10
+
11
+ ### Response Timeline
12
+
13
+ - We will acknowledge receipt of your report
14
+ - We will provide an initial assessment
15
+ - We will keep you informed of our progress and resolution timeline
16
+
17
+ ### Disclosure Policy
18
+
19
+ - We will work with you to understand and resolve the issue
20
+ - We will credit you for the discovery (unless you prefer to remain anonymous)
21
+ - We will publish a security advisory after the vulnerability is patched
22
+ - We will coordinate public disclosure with you
23
+
24
+ ## Automation Security
25
+
26
+ * Context Isolation: It is strictly forbidden to include production credentials, API keys, or Personally Identifiable Information (PII) in prompts sent to third-party LLMs or automation services.
27
+
28
+ * Supply Chain: All automated dependencies must be verified.
29
+
@@ -0,0 +1,36 @@
1
+ module ActiveVersion
2
+ module Adapters
3
+ module ActiveRecord
4
+ module Audits
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Add has_audits method to ActiveRecord::Base
9
+ end
10
+
11
+ module ClassMethods
12
+ # Declare that a model has audits
13
+ def has_audits(options = {})
14
+ include ActiveVersion::Audits::HasAudits unless included_modules.include?(ActiveVersion::Audits::HasAudits)
15
+
16
+ # Call the HasAudits implementation once included
17
+ ActiveVersion::Audits::HasAudits::ClassMethods.instance_method(:has_audits)
18
+ .bind_call(self, options)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ # Include audits adapter in ActiveRecord::Base
27
+ ActiveSupport.on_load(:active_record) do
28
+ include ActiveVersion::Adapters::ActiveRecord::Audits
29
+ end
30
+
31
+ # If ActiveRecord::Base is already loaded, include immediately
32
+ if defined?(ActiveRecord::Base) && ActiveRecord::Base.respond_to?(:include)
33
+ unless ActiveRecord::Base.included_modules.include?(ActiveVersion::Adapters::ActiveRecord::Audits)
34
+ ActiveSupport.on_load(:active_record) { include ActiveVersion::Adapters::ActiveRecord::Audits }
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveVersion
2
+ module Adapters
3
+ module ActiveRecord
4
+ # Base adapter for ActiveRecord integration
5
+ module Base
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # This will be extended by specific versioning modules
10
+ end
11
+
12
+ # Instance method to get column names (delegates to class method)
13
+ # This provides compatibility with ActiveRecord's class method as an instance method
14
+ def column_names
15
+ self.class.column_names
16
+ end
17
+
18
+ module ClassMethods
19
+ # Check if model has versioning enabled
20
+ def has_versioning?(version_type)
21
+ ActiveVersion.registry.registered?(self, version_type)
22
+ end
23
+
24
+ # Get version class for this model
25
+ def version_class_for(version_type)
26
+ ActiveVersion.registry.version_class_for(self, version_type)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # Include base adapter in ActiveRecord::Base
35
+ ActiveSupport.on_load(:active_record) do
36
+ include ActiveVersion::Adapters::ActiveRecord::Base
37
+ end