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
@@ -0,0 +1,65 @@
1
+ require "rails/generators"
2
+ require "rails/generators/active_record"
3
+
4
+ module ActiveVersion
5
+ module Generators
6
+ class AuditsGenerator < Rails::Generators::NamedBase
7
+ include ActiveRecord::Generators::Migration
8
+
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ class_option :storage,
12
+ type: :string,
13
+ default: "json_column",
14
+ desc: "Storage type: json_column, yaml_column, or mirror_columns"
15
+
16
+ def create_audit_model
17
+ template "audit_model.rb.erb",
18
+ File.join("app/models", class_path, "#{file_name}_audit.rb")
19
+ end
20
+
21
+ def generate_migration_file
22
+ if options[:storage] == "mirror_columns"
23
+ migration_template "migration_table.rb.erb",
24
+ File.join("db/migrate", "create_#{table_name}.rb"),
25
+ migration_version: migration_version
26
+ else
27
+ migration_template "migration_jsonb.rb.erb",
28
+ File.join("db/migrate", "create_#{table_name}.rb"),
29
+ migration_version: migration_version
30
+ end
31
+ end
32
+
33
+ def inject_has_audits
34
+ model_path = File.join("app/models", class_path, "#{file_name}.rb")
35
+ return unless File.exist?(model_path)
36
+
37
+ inject_into_class(model_path, class_name) do
38
+ " has_audits\n"
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def migration_version
45
+ "[#{ActiveRecord::Migration.current_version}]"
46
+ end
47
+
48
+ def table_name
49
+ "#{file_name}_audits"
50
+ end
51
+
52
+ def audit_class_name
53
+ "#{class_name}Audit"
54
+ end
55
+
56
+ def foreign_key
57
+ "#{file_name}_id"
58
+ end
59
+
60
+ def storage_type
61
+ options[:storage] || "json_column"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,16 @@
1
+
2
+ class <%= audit_class_name %> < ApplicationRecord
3
+ include ActiveVersion::Audits::AuditRecord
4
+
5
+ self.table_name = "<%= table_name %>"
6
+
7
+ configure_audit do
8
+ storage :<%= storage_type %>
9
+ action_column :action
10
+ changes_column :audited_changes
11
+ context_column :audited_context
12
+ comment_column :comment
13
+ version_column :version
14
+ user_column :user_id
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ class Create<%= audit_class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :<%= table_name %> do |t|
4
+ t.references :auditable, polymorphic: true, null: false, index: false
5
+ t.string :action, null: false
6
+ t.public_send(payload_column_type, :audited_changes, null: false)
7
+ t.integer :version, null: false
8
+ t.references :user, polymorphic: true
9
+ t.references :associated, polymorphic: true
10
+ t.text :comment
11
+ t.public_send(payload_column_type, :audited_context)
12
+ t.string :remote_address
13
+ t.string :request_uuid
14
+
15
+ t.timestamps
16
+ end
17
+
18
+ add_index :<%= table_name %>, [:auditable_type, :auditable_id, :version], unique: true, name: "index_<%= table_name %>_on_auditable_and_version"
19
+ end
20
+
21
+ private
22
+
23
+ def postgresql?
24
+ connection.adapter_name.to_s.casecmp("postgresql").zero?
25
+ end
26
+
27
+ def payload_column_type
28
+ return :jsonb if postgresql?
29
+ return :json if connection.native_database_types.is_a?(Hash) && connection.native_database_types.key?(:json)
30
+
31
+ :text
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ class Create<%= audit_class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :<%= table_name %> do |t|
4
+ t.references :auditable, polymorphic: true, null: false, index: false
5
+ t.string :action, null: false
6
+ t.integer :version, null: false
7
+ t.references :user, polymorphic: true
8
+ t.references :associated, polymorphic: true
9
+ t.text :comment
10
+ t.public_send(payload_column_type, :audited_context)
11
+ t.string :remote_address
12
+ t.string :request_uuid
13
+
14
+ # Store changes as structured columns
15
+ # Note: Add columns based on your model's attributes
16
+ # Example:
17
+ # t.string :title
18
+ # t.text :body
19
+
20
+ t.timestamps
21
+ end
22
+
23
+ add_index :<%= table_name %>, [:auditable_type, :auditable_id, :version], unique: true, name: "index_<%= table_name %>_on_auditable_and_version"
24
+ end
25
+
26
+ private
27
+
28
+ def payload_column_type
29
+ return :jsonb if connection.adapter_name.to_s.casecmp("postgresql").zero?
30
+ return :json if connection.native_database_types.is_a?(Hash) && connection.native_database_types.key?(:json)
31
+
32
+ :text
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ require "rails/generators"
2
+
3
+ module ActiveVersion
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Creates ActiveVersion initializer and configuration file"
9
+
10
+ def create_initializer
11
+ template "initializer.rb.erb", "config/initializers/active_version.rb"
12
+ end
13
+
14
+ def show_readme
15
+ readme "README" if behavior == :invoke
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+
2
+ # ActiveVersion Configuration
3
+ # See https://github.com/amkisko/active_version.rb for documentation
4
+
5
+ ActiveVersion.configure do |config|
6
+ # Global auditing control
7
+ config.auditing_enabled = true
8
+
9
+ # Current user method (for audits)
10
+ config.current_user_method = :current_user
11
+
12
+ # Ignored attributes (won't be tracked in audits/revisions)
13
+ config.ignored_attributes = %w[lock_version created_at updated_at created_on updated_on]
14
+
15
+ # Translation defaults
16
+ config.translation_locale_column = :locale
17
+ config.translation_default_locale = :en
18
+
19
+ # Revision defaults
20
+ config.revision_version_column = :version
21
+
22
+ # Audit defaults
23
+ # Prefer explicit destination model config via:
24
+ # configure_audit { storage :json_column; action_column :action; ... }
25
+ # configure_revision(version_column:, foreign_key:, ...)
26
+ # configure_translation(locale_column:, foreign_key:, ...)
27
+ # These global values are fallbacks for audit models that do not declare schema.
28
+ config.audit_storage = :json_column
29
+ config.audit_auditable_column = :auditable
30
+ config.audit_associated_column = :associated
31
+ config.audit_remote_address_column = :remote_address
32
+ config.audit_request_uuid_column = :request_uuid
33
+
34
+ # Infrastructure note:
35
+ # Connection routing / connection topology / partition lifecycle are application-owned.
36
+ # ActiveVersion reads/writes using the active ActiveRecord connection.
37
+
38
+ end
@@ -0,0 +1,71 @@
1
+ require "rails/generators"
2
+ require "rails/generators/active_record"
3
+
4
+ module ActiveVersion
5
+ module Generators
6
+ class RevisionsGenerator < Rails::Generators::NamedBase
7
+ include ActiveRecord::Generators::Migration
8
+
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ class_option :backfill,
12
+ type: :boolean,
13
+ default: false,
14
+ desc: "Create initial snapshots for existing records"
15
+
16
+ def create_revision_model
17
+ template "revision_model.rb.erb",
18
+ File.join("app/models", class_path, "#{file_name}_revision.rb")
19
+ end
20
+
21
+ def generate_migration_file
22
+ migration_template "migration.rb.erb",
23
+ File.join("db/migrate", "create_#{table_name}.rb"),
24
+ migration_version: migration_version
25
+ end
26
+
27
+ def inject_has_revisions
28
+ model_path = File.join("app/models", class_path, "#{file_name}.rb")
29
+ return unless File.exist?(model_path)
30
+
31
+ inject_into_class(model_path, class_name) do
32
+ " has_revisions\n"
33
+ end
34
+ end
35
+
36
+ def create_backfill_migration
37
+ return unless options[:backfill]
38
+
39
+ migration_template "backfill_migration.rb.erb",
40
+ File.join("db/migrate", "backfill_#{table_name}.rb"),
41
+ migration_version: migration_version
42
+ end
43
+
44
+ private
45
+
46
+ def migration_version
47
+ "[#{ActiveRecord::Migration.current_version}]"
48
+ end
49
+
50
+ def table_name
51
+ "#{file_name}_revisions"
52
+ end
53
+
54
+ def revision_class_name
55
+ "#{class_name}Revision"
56
+ end
57
+
58
+ def foreign_key
59
+ "#{file_name}_id"
60
+ end
61
+
62
+ def version_column
63
+ ActiveVersion.config.revision_version_column
64
+ end
65
+
66
+ def source_table_name
67
+ file_name.pluralize
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,19 @@
1
+ class Backfill<%= revision_class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ # Create initial snapshots for existing records
4
+ # Note: Adjust column list based on your actual source table structure
5
+ execute <<-SQL
6
+ INSERT INTO <%= table_name %> (<%= foreign_key %>, <%= version_column %>, title, body, created_at, updated_at)
7
+ SELECT id, 1, title, body, created_at, updated_at
8
+ FROM <%= source_table_name %>
9
+ WHERE id NOT IN (SELECT DISTINCT <%= foreign_key %> FROM <%= table_name %>)
10
+ SQL
11
+ end
12
+
13
+ def down
14
+ # Remove initial snapshots
15
+ execute <<-SQL
16
+ DELETE FROM <%= table_name %> WHERE <%= version_column %> = 1
17
+ SQL
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ class Create<%= revision_class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :<%= table_name %> do |t|
4
+ t.references :<%= file_name %>, null: false, foreign_key: true, index: false
5
+ t.integer :<%= version_column %>, null: false
6
+
7
+ # Copy all columns from source table
8
+ # Note: This is a simplified version - in production, you'd want to
9
+ # inspect the actual source table columns dynamically
10
+ # For now, we'll add common columns that can be customized
11
+ t.text :title
12
+ t.text :body
13
+ # Add other columns as needed
14
+
15
+ t.timestamps
16
+ end
17
+
18
+ add_index :<%= table_name %>, [:<%= foreign_key %>, :<%= version_column %>], unique: true, name: "index_<%= table_name %>_on_<%= foreign_key %>_and_<%= version_column %>"
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+
2
+ class <%= revision_class_name %> < ApplicationRecord
3
+ include ActiveVersion::Revisions::RevisionRecord
4
+
5
+ configure_revision(version_column: :<%= version_column %>,
6
+ foreign_key: :<%= foreign_key %>
7
+ )
8
+ end
@@ -0,0 +1,16 @@
1
+ class Create<%= translation_class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :<%= table_name %> do |t|
4
+ t.references :<%= file_name %>, null: false, foreign_key: true, index: false
5
+ t.string :<%= locale_column %>, null: false
6
+
7
+ <% migration_attributes.each do |attr| -%>
8
+ <%= attr %>
9
+ <% end -%>
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :<%= table_name %>, [:<%= foreign_key %>, :<%= locale_column %>], unique: true, name: "index_<%= table_name %>_on_<%= foreign_key %>_and_<%= locale_column %>"
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+
2
+ class <%= translation_class_name %> < ApplicationRecord
3
+ include ActiveVersion::Translations::TranslationRecord
4
+
5
+ configure_translation(locale_column: :<%= locale_column %>,
6
+ foreign_key: :<%= foreign_key %>
7
+ )
8
+
9
+ <% if translated_attributes.any? -%>
10
+ # Translated attributes:
11
+ <% translated_attributes.each do |attr| -%>
12
+ # <%= attr.split(':').first %>
13
+ <% end -%>
14
+ <% end -%>
15
+ end
@@ -0,0 +1,73 @@
1
+ require "rails/generators"
2
+ require "rails/generators/active_record"
3
+
4
+ module ActiveVersion
5
+ module Generators
6
+ class TranslationsGenerator < Rails::Generators::NamedBase
7
+ include ActiveRecord::Generators::Migration
8
+
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
12
+
13
+ class_option :translated_attributes,
14
+ type: :array,
15
+ default: [],
16
+ desc: "Attributes to translate (e.g., title:string body:text)"
17
+
18
+ def create_translation_model
19
+ template "translation_model.rb.erb",
20
+ File.join("app/models", class_path, "#{file_name}_translation.rb")
21
+ end
22
+
23
+ def generate_migration_file
24
+ migration_template "migration.rb.erb",
25
+ File.join("db/migrate", "create_#{table_name}.rb"),
26
+ migration_version: migration_version
27
+ end
28
+
29
+ def inject_has_translations
30
+ model_path = File.join("app/models", class_path, "#{file_name}.rb")
31
+ return unless File.exist?(model_path)
32
+
33
+ inject_into_class(model_path, class_name) do
34
+ " has_translations\n"
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def migration_version
41
+ "[#{ActiveRecord::Migration.current_version}]"
42
+ end
43
+
44
+ def table_name
45
+ "#{file_name}_translations"
46
+ end
47
+
48
+ def translation_class_name
49
+ "#{class_name}Translation"
50
+ end
51
+
52
+ def foreign_key
53
+ "#{file_name}_id"
54
+ end
55
+
56
+ def locale_column
57
+ ActiveVersion.config.translation_locale_column
58
+ end
59
+
60
+ def translated_attributes
61
+ options[:translated_attributes] || []
62
+ end
63
+
64
+ def migration_attributes
65
+ translated_attributes.map do |attr|
66
+ name, type = attr.split(":")
67
+ type ||= "string"
68
+ "t.#{type} :#{name}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,100 @@
1
+ class Add<%= type.camelize %>TriggerTo<%= class_name.pluralize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ <%- if type == "audit" -%>
4
+ # Create audit trigger function
5
+ execute <<-SQL
6
+ <%= ActiveVersion::Database::Triggers::PostgreSQL.generate_audit_trigger_function(
7
+ table_name,
8
+ audit_table_name,
9
+ auditable_type: auditable_type,
10
+ version_column: ActiveVersion.config.audit_version_column,
11
+ changes_column: ActiveVersion.config.audit_changes_column,
12
+ context_column: ActiveVersion.config.audit_context_column,
13
+ action_column: ActiveVersion.config.audit_action_column
14
+ ).indent(4) %>
15
+ SQL
16
+
17
+ # Create audit trigger
18
+ execute <<-SQL
19
+ <%= ActiveVersion::Database::Triggers::PostgreSQL.generate_audit_trigger(
20
+ table_name,
21
+ trigger_name: trigger_name,
22
+ events: events.map(&:to_sym)
23
+ ).indent(4) %>
24
+ SQL
25
+ <%- elsif type == "revision" -%>
26
+ # Create revision trigger function
27
+ execute <<-SQL
28
+ <%= ActiveVersion::Database::Triggers::PostgreSQL.generate_revision_trigger_function(
29
+ table_name,
30
+ revision_table_name,
31
+ foreign_key: "#{file_name}_id",
32
+ version_column: ActiveVersion.config.revision_version_column,
33
+ columns: tracked_columns
34
+ ).indent(4) %>
35
+ SQL
36
+
37
+ # Create revision trigger
38
+ execute <<-SQL
39
+ <%= ActiveVersion::Database::Triggers::PostgreSQL.generate_revision_trigger(
40
+ table_name,
41
+ trigger_name: trigger_name
42
+ ).indent(4) %>
43
+ SQL
44
+ <%- end -%>
45
+ end
46
+
47
+ def down
48
+ <%- if type == "audit" -%>
49
+ # Drop audit trigger
50
+ execute <<-SQL
51
+ <%= ActiveVersion::Database::Triggers::PostgreSQL.drop_trigger(trigger_name, table_name).indent(4) %>
52
+ SQL
53
+
54
+ # Drop audit trigger function
55
+ execute <<-SQL
56
+ <%= ActiveVersion::Database::Triggers::PostgreSQL.drop_trigger_function(trigger_function_name).indent(4) %>
57
+ SQL
58
+ <%- elsif type == "revision" -%>
59
+ # Drop revision trigger
60
+ execute <<-SQL
61
+ <%= ActiveVersion::Database::Triggers::PostgreSQL.drop_trigger(trigger_name, table_name).indent(4) %>
62
+ SQL
63
+
64
+ # Drop revision trigger function
65
+ execute <<-SQL
66
+ <%= ActiveVersion::Database::Triggers::PostgreSQL.drop_trigger_function(trigger_function_name).indent(4) %>
67
+ SQL
68
+ <%- end -%>
69
+ end
70
+
71
+ private
72
+
73
+ def table_name
74
+ "<%= table_name %>"
75
+ end
76
+
77
+ def audit_table_name
78
+ "<%= audit_table_name %>"
79
+ end
80
+
81
+ def revision_table_name
82
+ "<%= revision_table_name %>"
83
+ end
84
+
85
+ def auditable_type
86
+ "<%= auditable_type %>"
87
+ end
88
+
89
+ def trigger_name
90
+ "<%= trigger_name %>"
91
+ end
92
+
93
+ def trigger_function_name
94
+ "<%= trigger_function_name %>"
95
+ end
96
+
97
+ def tracked_columns
98
+ <%= tracked_columns.inspect %>
99
+ end
100
+ end
@@ -0,0 +1,74 @@
1
+ require "rails/generators"
2
+ require "rails/generators/active_record"
3
+
4
+ module ActiveVersion
5
+ module Generators
6
+ class TriggersGenerator < Rails::Generators::NamedBase
7
+ include ActiveRecord::Generators::Migration
8
+
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ class_option :type,
12
+ type: :string,
13
+ default: "audit",
14
+ desc: "Trigger type: audit or revision"
15
+
16
+ class_option :events,
17
+ type: :array,
18
+ default: ["insert", "update", "delete"],
19
+ desc: "Events to trigger on (for audits)"
20
+
21
+ def generate_trigger_migration_file
22
+ migration_template "migration.rb.erb",
23
+ File.join("db/migrate", "add_#{type}_trigger_to_#{table_name}.rb"),
24
+ migration_version: migration_version
25
+ end
26
+
27
+ private
28
+
29
+ def migration_version
30
+ "[#{ActiveRecord::Migration.current_version}]"
31
+ end
32
+
33
+ def table_name
34
+ file_name.pluralize
35
+ end
36
+
37
+ def type
38
+ options[:type] || "audit"
39
+ end
40
+
41
+ def events
42
+ options[:events] || ["insert", "update", "delete"]
43
+ end
44
+
45
+ def trigger_function_name
46
+ "active_version_#{type}_#{table_name}"
47
+ end
48
+
49
+ def trigger_name
50
+ "active_version_#{type}_on_#{table_name}"
51
+ end
52
+
53
+ def audit_table_name
54
+ "#{table_name}_audits"
55
+ end
56
+
57
+ def revision_table_name
58
+ "#{table_name}_revisions"
59
+ end
60
+
61
+ def auditable_type
62
+ file_name.classify
63
+ end
64
+
65
+ def tracked_columns
66
+ return [] unless ActiveRecord::Base.connected?
67
+
68
+ ActiveRecord::Base.connection.columns(table_name).map(&:name)
69
+ rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished, StandardError
70
+ []
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,51 @@
1
+ module ActiveVersion
2
+ module SchemaGuards
3
+ def self.validate_partitioned_keys!: (Class record_class, Symbol version_type) -> nil
4
+ def self.expected_unique_columns: (Class record_class, Symbol version_type) -> Array[String]
5
+ def self.postgres?: (untyped connection) -> bool
6
+ def self.partitioned_table?: (untyped connection, String table_name) -> bool
7
+ def self.partition_key_columns: (untyped connection, String table_name) -> Array[String]
8
+ def self.partition_primary_key_columns: (untyped connection, String table_name) -> Array[String]
9
+ end
10
+
11
+ module Query
12
+ def self.audits: (untyped record, ?Hash[Symbol, untyped] opts) -> untyped
13
+ def self.translations: (untyped record, ?Hash[Symbol, untyped] opts) -> untyped
14
+ def self.revisions: (untyped record, ?Hash[Symbol, untyped] opts) -> untyped
15
+ def self.current_transaction: (?Hash[Symbol, untyped] opts) -> untyped
16
+ end
17
+
18
+ module Sharding
19
+ class ConnectionRouter
20
+ def self.connection_for: (Class model_class, Symbol version_type) -> Symbol | String | untyped
21
+ def self.adapter_for: (Class model_class, Symbol version_type) -> untyped
22
+ def self.with_connection: (Class model_class, Symbol version_type) { (untyped) -> untyped } -> untyped
23
+ end
24
+ end
25
+
26
+ module Database
27
+ module Triggers
28
+ module PostgreSQL
29
+ def self.generate_audit_trigger_function: (String table_name, String audit_table_name, ?Hash[Symbol, untyped] options) -> String
30
+ def self.generate_audit_trigger: (String table_name, ?Hash[Symbol, untyped] options) -> String
31
+ def self.generate_revision_trigger_function: (String table_name, String revision_table_name, ?Hash[Symbol, untyped] options) -> String
32
+ def self.generate_revision_trigger: (String table_name, ?Hash[Symbol, untyped] options) -> String
33
+ def self.drop_trigger_function: (String function_name) -> String
34
+ def self.drop_trigger: (String trigger_name, String table_name) -> String
35
+ end
36
+ end
37
+ end
38
+
39
+ module Migrators
40
+ class Base
41
+ def self.migrate: (Class model_class, ?Hash[Symbol, untyped] options) -> Integer
42
+ def self.create_audit_table: (String table_name, ?Hash[Symbol, untyped] options) -> untyped
43
+ def self.create_revision_table: (String table_name, ?Hash[Symbol, untyped] options) -> untyped
44
+ def self.create_translation_table: (String table_name, ?Hash[Symbol, untyped] options) -> untyped
45
+ end
46
+
47
+ class Audited < Base
48
+ def self.migrate: (Class model_class, ?Hash[Symbol, untyped] options) -> Integer
49
+ end
50
+ end
51
+ end