pg_sql_triggers 1.4.0 → 1.5.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/.erb_lint.yml +0 -0
  3. data/.rspec +0 -0
  4. data/.rubocop.yml +6 -16
  5. data/AGENTS.md +8 -0
  6. data/CHANGELOG.md +104 -2
  7. data/COVERAGE.md +39 -41
  8. data/LICENSE +0 -0
  9. data/README.md +24 -3
  10. data/RELEASE.md +0 -0
  11. data/Rakefile +5 -0
  12. data/app/assets/javascripts/pg_sql_triggers/application.js +0 -0
  13. data/app/assets/javascripts/pg_sql_triggers/trigger_actions.js +0 -0
  14. data/app/assets/stylesheets/pg_sql_triggers/application.css +0 -0
  15. data/app/controllers/concerns/pg_sql_triggers/error_handling.rb +0 -0
  16. data/app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb +0 -0
  17. data/app/controllers/concerns/pg_sql_triggers/permission_checking.rb +6 -5
  18. data/app/controllers/pg_sql_triggers/application_controller.rb +0 -0
  19. data/app/controllers/pg_sql_triggers/audit_logs_controller.rb +81 -64
  20. data/app/controllers/pg_sql_triggers/dashboard_controller.rb +111 -34
  21. data/app/controllers/pg_sql_triggers/migrations_controller.rb +13 -14
  22. data/app/controllers/pg_sql_triggers/tables_controller.rb +8 -0
  23. data/app/controllers/pg_sql_triggers/triggers_controller.rb +1 -0
  24. data/app/helpers/pg_sql_triggers/dashboard_helper.rb +19 -0
  25. data/app/helpers/pg_sql_triggers/permissions_helper.rb +3 -2
  26. data/app/models/pg_sql_triggers/application_record.rb +0 -0
  27. data/app/models/pg_sql_triggers/audit_log.rb +29 -47
  28. data/app/models/pg_sql_triggers/trigger_registry.rb +105 -78
  29. data/app/views/layouts/pg_sql_triggers/application.html.erb +0 -0
  30. data/app/views/pg_sql_triggers/audit_logs/index.html.erb +9 -5
  31. data/app/views/pg_sql_triggers/dashboard/index.html.erb +107 -24
  32. data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +0 -0
  33. data/app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb +0 -0
  34. data/app/views/pg_sql_triggers/tables/index.html.erb +26 -14
  35. data/app/views/pg_sql_triggers/tables/show.html.erb +0 -0
  36. data/app/views/pg_sql_triggers/triggers/_drop_modal.html.erb +0 -0
  37. data/app/views/pg_sql_triggers/triggers/_re_execute_modal.html.erb +0 -0
  38. data/app/views/pg_sql_triggers/triggers/show.html.erb +33 -0
  39. data/config/initializers/pg_sql_triggers.rb +0 -0
  40. data/config/routes.rb +0 -0
  41. data/db/migrate/{20251222000001_create_pg_sql_triggers_tables.rb → 20251222104327_create_pg_sql_triggers_tables.rb} +0 -0
  42. data/db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb +0 -0
  43. data/db/migrate/{20260103000001_create_pg_sql_triggers_audit_log.rb → 20260103114508_create_pg_sql_triggers_audit_log.rb} +0 -0
  44. data/db/migrate/{20260228000001_add_for_each_to_pg_sql_triggers_registry.rb → 20260228162233_add_for_each_to_pg_sql_triggers_registry.rb} +0 -0
  45. data/db/migrate/20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb +9 -0
  46. data/docs/README.md +3 -0
  47. data/docs/api-reference.md +133 -0
  48. data/docs/audit-trail.md +1 -1
  49. data/docs/configuration.md +172 -0
  50. data/docs/getting-started.md +14 -0
  51. data/docs/kill-switch.md +0 -0
  52. data/docs/permissions.md +6 -9
  53. data/docs/troubleshooting.md +0 -0
  54. data/docs/ui-guide.md +0 -0
  55. data/docs/usage-guide.md +74 -0
  56. data/docs/web-ui.md +0 -0
  57. data/lib/generators/pg_sql_triggers/install_generator.rb +0 -0
  58. data/lib/generators/pg_sql_triggers/templates/README +0 -0
  59. data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +0 -0
  60. data/lib/generators/pg_sql_triggers/templates/initializer.rb +14 -0
  61. data/lib/generators/pg_sql_triggers/templates/trigger_dsl.rb.tt +0 -0
  62. data/lib/generators/pg_sql_triggers/templates/trigger_migration.rb.erb +0 -0
  63. data/lib/generators/pg_sql_triggers/templates/trigger_migration_full.rb.tt +0 -0
  64. data/lib/generators/pg_sql_triggers/trigger_generator.rb +0 -0
  65. data/lib/generators/pg_sql_triggers/trigger_migration_generator.rb +0 -0
  66. data/lib/pg_sql_triggers/alerting.rb +77 -0
  67. data/lib/pg_sql_triggers/database_introspection.rb +0 -0
  68. data/lib/pg_sql_triggers/deferral_checksum.rb +54 -0
  69. data/lib/pg_sql_triggers/drift/db_queries.rb +14 -5
  70. data/lib/pg_sql_triggers/drift/detector.rb +9 -1
  71. data/lib/pg_sql_triggers/drift/reporter.rb +0 -0
  72. data/lib/pg_sql_triggers/drift.rb +5 -0
  73. data/lib/pg_sql_triggers/dsl/trigger_definition.rb +56 -2
  74. data/lib/pg_sql_triggers/dsl.rb +0 -0
  75. data/lib/pg_sql_triggers/engine.rb +35 -0
  76. data/lib/pg_sql_triggers/errors.rb +0 -0
  77. data/lib/pg_sql_triggers/events_checksum.rb +114 -0
  78. data/lib/pg_sql_triggers/migration.rb +5 -6
  79. data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +77 -73
  80. data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +0 -0
  81. data/lib/pg_sql_triggers/migrator/safety_validator.rb +3 -1
  82. data/lib/pg_sql_triggers/migrator.rb +90 -94
  83. data/lib/pg_sql_triggers/permissions/checker.rb +12 -15
  84. data/lib/pg_sql_triggers/permissions.rb +1 -0
  85. data/lib/pg_sql_triggers/rake_development_boot.rb +65 -0
  86. data/lib/pg_sql_triggers/registry/manager.rb +27 -13
  87. data/lib/pg_sql_triggers/registry/validator.rb +226 -2
  88. data/lib/pg_sql_triggers/registry.rb +0 -0
  89. data/lib/pg_sql_triggers/schema_dumper_extension.rb +32 -0
  90. data/lib/pg_sql_triggers/sql/kill_switch.rb +2 -1
  91. data/lib/pg_sql_triggers/sql.rb +0 -0
  92. data/lib/pg_sql_triggers/testing/dry_run.rb +0 -0
  93. data/lib/pg_sql_triggers/testing/function_tester.rb +97 -107
  94. data/lib/pg_sql_triggers/testing/safe_executor.rb +0 -0
  95. data/lib/pg_sql_triggers/testing/syntax_validator.rb +0 -0
  96. data/lib/pg_sql_triggers/testing.rb +0 -0
  97. data/lib/pg_sql_triggers/trigger_structure_dumper.rb +111 -0
  98. data/lib/pg_sql_triggers/version.rb +1 -1
  99. data/lib/pg_sql_triggers.rb +17 -0
  100. data/lib/tasks/trigger_migrations.rake +235 -152
  101. data/rakelib/pg_sql_triggers_environment.rake +9 -0
  102. data/scripts/generate_coverage_report.rb +4 -1
  103. data/sig/pg_sql_triggers.rbs +0 -0
  104. metadata +65 -13
  105. data/GEM_ANALYSIS.md +0 -368
  106. data/Goal.md +0 -742
  107. data/pg_sql_triggers.gemspec +0 -53
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3009d08e9604d3cad9af352b595fe76fb2688c7f1bf80cfbb92bbb9bbadeb996
4
- data.tar.gz: 0155742bcf1ad97690f42c8193da6ff408f09dab26f8684c84a6954c8438f28f
3
+ metadata.gz: 7adcec137e32ecf9e1013a7dbb98a16844a5f8082b0d910a7380903cd2151678
4
+ data.tar.gz: b285ecac25cf7806a56f0205fca527a158b48c7f26b99adb4c1c304b3b2d17d7
5
5
  SHA512:
6
- metadata.gz: f76fc2c48a00922e7265acce7d8c87e0477686540b4749aef94bbd1d4b914f7cdf76a0c28cc3f3dd816489c24606ee6b2fe424d309dbc03e70cf160fba6daad8
7
- data.tar.gz: 4988c497e39a54d517e1f96a4328609de0bb0451658d228b46a4bd2d448267b7866ede607eb702430510a88b16fb328d801fc81a00e0d93c231ad7d94caff216
6
+ metadata.gz: 0aeb0cb7beee89da7f9c8993fd166b569c4cacb684e1b82222c104d083c970b9878da4ae368449aeeda63839c991522148e6dc4996c0ffce79ca04e49063b1e0
7
+ data.tar.gz: 0d0034cf4c0ee07dc9f868fa44924a9fd737861ac1d067c67b189ae5f08172af8f9596fc625b029f5dce8d36fe00ec287e0bbb2ace5a4725ce43eabdfe66907a
data/.erb_lint.yml CHANGED
File without changes
data/.rspec CHANGED
File without changes
data/.rubocop.yml CHANGED
@@ -1,9 +1,7 @@
1
- require:
2
- - rubocop-rspec
3
- - rubocop-rspec_rails
4
-
5
1
  plugins:
6
2
  - rubocop-rails
3
+ - rubocop-rspec
4
+ - rubocop-rspec_rails
7
5
  - rubocop-capybara
8
6
  - rubocop-factory_bot
9
7
 
@@ -24,18 +22,14 @@ Metrics/AbcSize:
24
22
  Max: 65
25
23
  Exclude:
26
24
  - 'spec/**/*'
27
- - 'app/controllers/**/*'
28
- - 'lib/generators/**/*'
29
25
  - 'lib/pg_sql_triggers/testing/**/*'
30
26
 
31
27
  Metrics/BlockLength:
32
28
  Max: 50
33
29
  Exclude:
34
30
  - 'spec/**/*'
35
- - 'config/routes.rb'
36
31
  - '*.gemspec'
37
- - 'lib/tasks/**/*'
38
-
32
+
39
33
  Metrics/ClassLength:
40
34
  Max: 200
41
35
  Exclude:
@@ -44,21 +38,17 @@ Metrics/ClassLength:
44
38
  Metrics/CyclomaticComplexity:
45
39
  Max: 18
46
40
  Exclude:
47
- - 'app/controllers/**/*'
48
41
  - 'lib/pg_sql_triggers/testing/**/*'
49
42
 
50
43
  Metrics/MethodLength:
51
44
  Max: 55
52
45
  Exclude:
53
46
  - 'spec/**/*'
54
- - 'app/controllers/**/*'
55
- - 'lib/generators/**/*'
56
47
  - 'lib/pg_sql_triggers/testing/**/*'
57
48
 
58
49
  Metrics/PerceivedComplexity:
59
50
  Max: 15
60
51
  Exclude:
61
- - 'app/controllers/**/*'
62
52
  - 'lib/pg_sql_triggers/testing/**/*'
63
53
 
64
54
  # Style
@@ -97,9 +87,6 @@ RSpec/ExampleLength:
97
87
  Exclude:
98
88
  - 'spec/**/*_spec.rb'
99
89
 
100
- RSpec/FilePath:
101
- Enabled: false
102
-
103
90
  RSpec/InstanceVariable:
104
91
  Enabled: false
105
92
 
@@ -115,6 +102,9 @@ RSpec/MultipleDescribes:
115
102
  RSpec/MultipleExpectations:
116
103
  Max: 10
117
104
 
105
+ RSpec/MultipleMemoizedHelpers:
106
+ Max: 8
107
+
118
108
  RSpec/SpecFilePathFormat:
119
109
  Enabled: false
120
110
 
data/AGENTS.md ADDED
@@ -0,0 +1,8 @@
1
+ # Agent / contributor notes
2
+
3
+ ## This repository
4
+
5
+ - Run Ruby tooling (**bundle**, **rubocop**, **rspec**) via **WSL** with **asdf** (use a login shell, e.g. `wsl bash -lic '…'`).
6
+ - **PostgreSQL** is expected in **Docker**; for local tests use **`aswin` / `aswin`** (`TEST_DB_USER`, `TEST_DB_PASSWORD`, or `DATABASE_URL` as appropriate).
7
+
8
+ More detail and example commands: `.cursor/rules/dev-environment.mdc`.
data/CHANGELOG.md CHANGED
@@ -5,6 +5,108 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ No changes yet.
11
+
12
+ ## [1.5.0] - 2026-04-15
13
+
14
+ These notes follow the phased gap analysis in [`IMPLEMENTATION_PLAN.md`](IMPLEMENTATION_PLAN.md)
15
+ (baseline **v1.4.0**). This release ships Phase 0 cleanup, Phase 1 routing-test hardening,
16
+ Phase 2 production readiness (drift alerting, dashboard search/filter/pagination), Phase 3
17
+ PostgreSQL feature parity (column-level `UPDATE OF`, constraint triggers with deferral,
18
+ `schema.rb` / `structure.sql` integration), and Phase 4 trigger ordering hints.
19
+
20
+ ### Added
21
+
22
+ - **Drift alerting** — Configurable `PgSqlTriggers.drift_notifier` for external notification when
23
+ drift detection finds drifted, dropped, or unknown triggers; `PgSqlTriggers::Alerting` module;
24
+ `PgSqlTriggers::Drift.check_and_notify`; Rake task `trigger:check_drift` with optional
25
+ `FAIL_ON_DRIFT=1`; `ActiveSupport::Notifications` event `pg_sql_triggers.drift_check`.
26
+ The gem’s root `Rakefile` loads `rakelib/pg_sql_triggers_environment.rake` and
27
+ `lib/tasks/trigger_migrations.rake` so `bundle exec rake trigger:*` works when developing the
28
+ gem (not only from a host app).
29
+ ([lib/pg_sql_triggers/alerting.rb](lib/pg_sql_triggers/alerting.rb),
30
+ [lib/tasks/trigger_migrations.rake](lib/tasks/trigger_migrations.rake),
31
+ [lib/pg_sql_triggers/rake_development_boot.rb](lib/pg_sql_triggers/rake_development_boot.rb),
32
+ [docs/configuration.md](docs/configuration.md))
33
+
34
+ - **Column-level `UPDATE OF` triggers** — DSL method `on_update_of(*columns)` sets the event to
35
+ `update` and records column names; SQL generation and checksums use `EventsChecksum` so
36
+ `UPDATE OF "col1", "col2"` is represented consistently in drift detection. Registry validation
37
+ rejects column lists unless an update event is present.
38
+ ([lib/pg_sql_triggers/dsl/trigger_definition.rb](lib/pg_sql_triggers/dsl/trigger_definition.rb),
39
+ [lib/pg_sql_triggers/events_checksum.rb](lib/pg_sql_triggers/events_checksum.rb),
40
+ [lib/pg_sql_triggers/registry/validator.rb](lib/pg_sql_triggers/registry/validator.rb))
41
+
42
+ - **Constraint triggers and deferral** — DSL supports `constraint_trigger!` with `deferrable` /
43
+ `initially` (constraint triggers only). Registry migration
44
+ `20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb` adds `constraint_trigger`,
45
+ `deferrable`, and `initially` columns. Validator and drift checksum paths normalize deferral using
46
+ catalog fields such as `tgdeferrable` / `tginitdeferred` where applicable.
47
+ ([db/migrate/20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb](db/migrate/20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb),
48
+ [lib/pg_sql_triggers/dsl/trigger_definition.rb](lib/pg_sql_triggers/dsl/trigger_definition.rb),
49
+ [lib/pg_sql_triggers/registry/validator.rb](lib/pg_sql_triggers/registry/validator.rb))
50
+
51
+ - **Trigger ordering hints (`depends_on`)** — Optional `depends_on(*names)` on the DSL records
52
+ intended ordering relative to PostgreSQL’s alphabetical firing order. `Registry::Validator` checks
53
+ same-table scope, timing/`FOR EACH` consistency, absence of cycles, and name ordering; Rake task
54
+ `trigger:validate_order` runs the same rules from the CLI.
55
+ ([lib/pg_sql_triggers/dsl/trigger_definition.rb](lib/pg_sql_triggers/dsl/trigger_definition.rb),
56
+ [lib/pg_sql_triggers/registry/validator.rb](lib/pg_sql_triggers/registry/validator.rb),
57
+ [lib/tasks/trigger_migrations.rake](lib/tasks/trigger_migrations.rake))
58
+
59
+ - **Dashboard search, filters, and pagination** — Trigger index supports query parameters
60
+ (`table`, `state`, `source`, `q`) with offset/limit pagination for large registries.
61
+ ([app/controllers/pg_sql_triggers/dashboard_controller.rb](app/controllers/pg_sql_triggers/dashboard_controller.rb))
62
+
63
+ - **`schema.rb` awareness and trigger SQL snapshots** — `ActiveRecord::SchemaDumper` is prepended
64
+ with `SchemaDumperExtension` to append optional comments pointing at trigger workflows when
65
+ `PgSqlTriggers.append_trigger_notes_to_schema_dump` is true (default). Rake tasks `trigger:dump`
66
+ and `trigger:load` round-trip `db/trigger_structure.sql` via `TriggerStructureDumper`; path is
67
+ configurable with `PgSqlTriggers.trigger_structure_sql_path`. When
68
+ `PgSqlTriggers.migrate_triggers_after_schema_load` is true (default), `db:schema:load` can chain
69
+ to `trigger:migrate` (skippable with `SKIP_TRIGGER_MIGRATE_AFTER_SCHEMA_LOAD`).
70
+ ([lib/pg_sql_triggers/schema_dumper_extension.rb](lib/pg_sql_triggers/schema_dumper_extension.rb),
71
+ [lib/pg_sql_triggers/trigger_structure_dumper.rb](lib/pg_sql_triggers/trigger_structure_dumper.rb),
72
+ [lib/pg_sql_triggers/engine.rb](lib/pg_sql_triggers/engine.rb),
73
+ [lib/tasks/trigger_migrations.rake](lib/tasks/trigger_migrations.rake),
74
+ [lib/pg_sql_triggers.rb](lib/pg_sql_triggers.rb))
75
+
76
+ - **Engine routing coverage** — Declarative routing specs for the mountable engine.
77
+ ([spec/routing/pg_sql_triggers_routes_spec.rb](spec/routing/pg_sql_triggers_routes_spec.rb))
78
+
79
+ ### Changed
80
+
81
+ - **SQL permission helper naming** — View and controller helpers use `can_execute_sql_operations?`
82
+ instead of capsule-era `can_execute_sql?`, with comments aligned to migration-style SQL execution.
83
+ ([app/helpers/pg_sql_triggers/permissions_helper.rb](app/helpers/pg_sql_triggers/permissions_helper.rb),
84
+ [app/controllers/concerns/pg_sql_triggers/permission_checking.rb](app/controllers/concerns/pg_sql_triggers/permission_checking.rb))
85
+
86
+ - **`TriggerRegistry#recreate_trigger`** — Uses an explicit `source == "dsl"` branch before the
87
+ `function_body` path so DSL re-execution is obvious to readers.
88
+ ([app/models/pg_sql_triggers/trigger_registry.rb](app/models/pg_sql_triggers/trigger_registry.rb))
89
+
90
+ ### Fixed
91
+
92
+ - **`can_generate_triggers?` permission alignment** — Helper and concern both use the
93
+ `:generate_trigger` action so UI and controller authorization stay consistent.
94
+ ([app/helpers/pg_sql_triggers/permissions_helper.rb](app/helpers/pg_sql_triggers/permissions_helper.rb),
95
+ [app/controllers/concerns/pg_sql_triggers/permission_checking.rb](app/controllers/concerns/pg_sql_triggers/permission_checking.rb))
96
+
97
+ ### Planned
98
+
99
+ - **Documentation accuracy** — Regenerate [`COVERAGE.md`](COVERAGE.md) from the current tree with
100
+ SimpleCov (plan §1.1); keep user-facing docs in sync with removed v1.4.0 features where files
101
+ still exist.
102
+
103
+ - **Test hardening** — Additional request/controller specs for migration error paths and
104
+ permission edge cases (plan §4.2–4.3); extend coverage for notifier and dashboard filter
105
+ behaviour where gaps remain (plan Phase 1–2).
106
+
107
+ - **Optional (Phase 4)** — Kill-switch time-window auto-lock; trigger definition export/import
108
+ (JSON/YAML) and related Rake tasks.
109
+
8
110
  ## [1.4.0] - 2026-03-01
9
111
 
10
112
  ### Added
@@ -15,7 +117,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
117
  `for_each_statement`, let trigger definitions declare the desired granularity explicitly. The
16
118
  value defaults to `"row"` so all existing definitions continue to produce `FOR EACH ROW` triggers
17
119
  without modification. The field is stored in a new `for_each` column on the registry table
18
- (migration `20260228000001_add_for_each_to_pg_sql_triggers_registry.rb`), included in all three
120
+ (migration `20260228162233_add_for_each_to_pg_sql_triggers_registry.rb`), included in all three
19
121
  checksum computations (`TriggerRegistry#calculate_checksum`, `Registry::Manager#calculate_checksum`,
20
122
  and `Drift::Detector#calculate_db_checksum`), extracted from live trigger definitions via a new
21
123
  `extract_trigger_for_each` helper, and validated by `Registry::Validator` (only `"row"` and
@@ -26,7 +128,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
26
128
  [lib/pg_sql_triggers/registry/validator.rb](lib/pg_sql_triggers/registry/validator.rb),
27
129
  [lib/pg_sql_triggers/drift/detector.rb](lib/pg_sql_triggers/drift/detector.rb),
28
130
  [app/models/pg_sql_triggers/trigger_registry.rb](app/models/pg_sql_triggers/trigger_registry.rb),
29
- [db/migrate/20260228000001_add_for_each_to_pg_sql_triggers_registry.rb](db/migrate/20260228000001_add_for_each_to_pg_sql_triggers_registry.rb))
131
+ [db/migrate/20260228162233_add_for_each_to_pg_sql_triggers_registry.rb](db/migrate/20260228162233_add_for_each_to_pg_sql_triggers_registry.rb))
30
132
 
31
133
  ### Changed
32
134
 
data/COVERAGE.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Code Coverage Report
2
2
 
3
- **Total Coverage: 95.99%**
3
+ **Total Coverage: 96.04%**
4
4
 
5
- Covered: 2319 / 2416 lines
5
+ Covered: 2499 / 2602 lines
6
6
 
7
7
  ---
8
8
 
@@ -10,58 +10,56 @@ Covered: 2319 / 2416 lines
10
10
 
11
11
  | File | Coverage | Covered Lines | Missed Lines | Total Lines |
12
12
  |------|----------|---------------|--------------|-------------|
13
- | `lib/pg_sql_triggers/drift.rb` | 100.0% ✅ | 13 | 0 | 13 |
14
- | `lib/pg_sql_triggers/drift/db_queries.rb` | 100.0% ✅ | 24 | 0 | 24 |
15
- | `lib/pg_sql_triggers/dsl.rb` | 100.0% ✅ | 9 | 0 | 9 |
16
- | `lib/pg_sql_triggers/dsl/trigger_definition.rb` | 100.0% ✅ | 37 | 0 | 37 |
17
- | `lib/pg_sql_triggers/generator.rb` | 100.0% ✅ | 4 | 0 | 4 |
18
- | `lib/pg_sql_triggers/generator/form.rb` | 100.0% ✅ | 36 | 0 | 36 |
19
- | `lib/pg_sql_triggers/generator/service.rb` | 100.0% ✅ | 101 | 0 | 101 |
20
- | `lib/generators/pg_sql_triggers/install_generator.rb` | 100.0% ✅ | 18 | 0 | 18 |
21
- | `lib/generators/trigger/migration_generator.rb` | 100.0% ✅ | 27 | 0 | 27 |
22
13
  | `lib/pg_sql_triggers/migration.rb` | 100.0% ✅ | 4 | 0 | 4 |
14
+ | `lib/pg_sql_triggers/migrator/pre_apply_comparator.rb` | 100.0% ✅ | 125 | 0 | 125 |
15
+ | `lib/generators/pg_sql_triggers/install_generator.rb` | 100.0% ✅ | 18 | 0 | 18 |
16
+ | `lib/pg_sql_triggers/dsl/trigger_definition.rb` | 100.0% ✅ | 60 | 0 | 60 |
17
+ | `lib/pg_sql_triggers/dsl.rb` | 100.0% ✅ | 9 | 0 | 9 |
23
18
  | `lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb` | 100.0% ✅ | 75 | 0 | 75 |
24
- | `lib/pg_sql_triggers/migrator/safety_validator.rb` | 100.0% ✅ | 110 | 0 | 110 |
19
+ | `lib/pg_sql_triggers/sql.rb` | 100.0% ✅ | 7 | 0 | 7 |
20
+ | `lib/pg_sql_triggers/migrator/safety_validator.rb` | 100.0% ✅ | 120 | 0 | 120 |
21
+ | `lib/pg_sql_triggers/drift.rb` | 100.0% ✅ | 15 | 0 | 15 |
25
22
  | `lib/pg_sql_triggers/permissions.rb` | 100.0% ✅ | 11 | 0 | 11 |
26
- | `lib/pg_sql_triggers/permissions/checker.rb` | 100.0% ✅ | 17 | 0 | 17 |
27
- | `lib/pg_sql_triggers/registry/validator.rb` | 100.0% ✅ | 5 | 0 | 5 |
28
- | `lib/pg_sql_triggers/sql/capsule.rb` | 100.0% ✅ | 28 | 0 | 28 |
29
- | `lib/pg_sql_triggers/sql/executor.rb` | 100.0% ✅ | 63 | 0 | 63 |
30
- | `lib/pg_sql_triggers/testing.rb` | 100.0% ✅ | 6 | 0 | 6 |
31
- | `lib/pg_sql_triggers/testing/syntax_validator.rb` | 100.0% ✅ | 58 | 0 | 58 |
23
+ | `lib/pg_sql_triggers/permissions/checker.rb` | 100.0% ✅ | 16 | 0 | 16 |
24
+ | `app/controllers/pg_sql_triggers/triggers_controller.rb` | 100.0% ✅ | 76 | 0 | 76 |
25
+ | `app/helpers/pg_sql_triggers/dashboard_helper.rb` | 100.0% ✅ | 7 | 0 | 7 |
26
+ | `app/controllers/pg_sql_triggers/dashboard_controller.rb` | 100.0% ✅ | 72 | 0 | 72 |
27
+ | `lib/pg_sql_triggers.rb` | 100.0% ✅ | 51 | 0 | 51 |
28
+ | `lib/pg_sql_triggers/errors.rb` | 100.0% ✅ | 83 | 0 | 83 |
32
29
  | `lib/pg_sql_triggers/testing/dry_run.rb` | 100.0% ✅ | 24 | 0 | 24 |
33
- | `app/models/pg_sql_triggers/audit_log.rb` | 100.0% ✅ | 28 | 0 | 28 |
34
- | `app/models/pg_sql_triggers/trigger_registry.rb` | 100.0% ✅ | 176 | 0 | 176 |
30
+ | `lib/pg_sql_triggers/testing/syntax_validator.rb` | 100.0% ✅ | 58 | 0 | 58 |
31
+ | `lib/pg_sql_triggers/testing.rb` | 100.0% ✅ | 6 | 0 | 6 |
32
+ | `config/initializers/pg_sql_triggers.rb` | 100.0% ✅ | 10 | 0 | 10 |
33
+ | `app/models/pg_sql_triggers/application_record.rb` | 100.0% ✅ | 3 | 0 | 3 |
34
+ | `app/models/pg_sql_triggers/audit_log.rb` | 100.0% ✅ | 32 | 0 | 32 |
35
35
  | `app/controllers/concerns/pg_sql_triggers/error_handling.rb` | 100.0% ✅ | 19 | 0 | 19 |
36
36
  | `app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb` | 100.0% ✅ | 17 | 0 | 17 |
37
- | `app/models/pg_sql_triggers/application_record.rb` | 100.0% ✅ | 3 | 0 | 3 |
37
+ | `app/controllers/concerns/pg_sql_triggers/permission_checking.rb` | 100.0% ✅ | 41 | 0 | 41 |
38
38
  | `app/controllers/pg_sql_triggers/application_controller.rb` | 100.0% ✅ | 13 | 0 | 13 |
39
39
  | `app/helpers/pg_sql_triggers/permissions_helper.rb` | 100.0% ✅ | 16 | 0 | 16 |
40
- | `app/controllers/pg_sql_triggers/dashboard_controller.rb` | 100.0% ✅ | 26 | 0 | 26 |
41
- | `config/initializers/pg_sql_triggers.rb` | 100.0% ✅ | 10 | 0 | 10 |
42
- | `lib/pg_sql_triggers/errors.rb` | 100.0% ✅ | 83 | 0 | 83 |
43
- | `app/controllers/pg_sql_triggers/triggers_controller.rb` | 100.0% ✅ | 75 | 0 | 75 |
44
- | `lib/pg_sql_triggers.rb` | 100.0% ✅ | 40 | 0 | 40 |
45
- | `lib/pg_sql_triggers/migrator/pre_apply_comparator.rb` | 99.19% ✅ | 122 | 1 | 123 |
46
- | `lib/pg_sql_triggers/drift/detector.rb` | 98.48% ✅ | 65 | 1 | 66 |
47
- | `app/controllers/pg_sql_triggers/audit_logs_controller.rb` | 97.73% ✅ | 43 | 1 | 44 |
48
- | `app/controllers/pg_sql_triggers/sql_capsules_controller.rb` | 97.14% ✅ | 68 | 2 | 70 |
40
+ | `app/controllers/pg_sql_triggers/audit_logs_controller.rb` | 100.0% ✅ | 55 | 0 | 55 |
41
+ | `app/controllers/pg_sql_triggers/migrations_controller.rb` | 98.82% ✅ | 84 | 1 | 85 |
42
+ | `lib/pg_sql_triggers/registry/manager.rb` | 98.81% ✅ | 83 | 1 | 84 |
43
+ | `lib/pg_sql_triggers/events_checksum.rb` | 98.33% ✅ | 59 | 1 | 60 |
44
+ | `app/models/pg_sql_triggers/trigger_registry.rb` | 97.69% ✅ | 211 | 5 | 216 |
45
+ | `lib/pg_sql_triggers/sql/kill_switch.rb` | 96.51% ✅ | 83 | 3 | 86 |
49
46
  | `lib/generators/pg_sql_triggers/trigger_migration_generator.rb` | 96.3% ✅ | 26 | 1 | 27 |
50
- | `lib/pg_sql_triggers/sql/kill_switch.rb` | 96.04% ✅ | 97 | 4 | 101 |
51
- | `lib/pg_sql_triggers/migrator.rb` | 95.42% ✅ | 125 | 6 | 131 |
52
- | `lib/pg_sql_triggers/registry/manager.rb` | 95.08% ✅ | 58 | 3 | 61 |
53
- | `app/controllers/pg_sql_triggers/tables_controller.rb` | 94.74% ✅ | 18 | 1 | 19 |
47
+ | `lib/pg_sql_triggers/alerting.rb` | 96.3% ✅ | 26 | 1 | 27 |
48
+ | `lib/pg_sql_triggers/drift/db_queries.rb` | 96.15% ✅ | 25 | 1 | 26 |
49
+ | `lib/pg_sql_triggers/deferral_checksum.rb` | 96.0% ✅ | 24 | 1 | 25 |
50
+ | `lib/pg_sql_triggers/migrator.rb` | 95.78% ✅ | 159 | 7 | 166 |
51
+ | `lib/pg_sql_triggers/registry/validator.rb` | 94.83% ✅ | 165 | 9 | 174 |
54
52
  | `lib/pg_sql_triggers/database_introspection.rb` | 94.29% ✅ | 66 | 4 | 70 |
55
53
  | `lib/pg_sql_triggers/drift/reporter.rb` | 94.12% ✅ | 96 | 6 | 102 |
56
- | `lib/pg_sql_triggers/engine.rb` | 92.86% ✅ | 13 | 1 | 14 |
54
+ | `lib/pg_sql_triggers/drift/detector.rb` | 92.5% ✅ | 74 | 6 | 80 |
57
55
  | `lib/pg_sql_triggers/testing/safe_executor.rb` | 91.89% ✅ | 34 | 3 | 37 |
58
56
  | `lib/pg_sql_triggers/registry.rb` | 91.84% ✅ | 45 | 4 | 49 |
59
- | `app/controllers/pg_sql_triggers/generator_controller.rb` | 91.49% ✅ | 86 | 8 | 94 |
60
- | `lib/pg_sql_triggers/sql.rb` | 90.91% | 10 | 1 | 11 |
61
- | `lib/pg_sql_triggers/testing/function_tester.rb` | 89.71% ⚠️ | 61 | 7 | 68 |
62
- | `app/controllers/concerns/pg_sql_triggers/permission_checking.rb` | 85.37% ⚠️ | 35 | 6 | 41 |
63
- | `app/controllers/pg_sql_triggers/migrations_controller.rb` | 82.76% ⚠️ | 72 | 15 | 87 |
64
- | `config/routes.rb` | 12.0% ❌ | 3 | 22 | 25 |
57
+ | `lib/pg_sql_triggers/trigger_structure_dumper.rb` | 90.32% ✅ | 56 | 6 | 62 |
58
+ | `lib/pg_sql_triggers/testing/function_tester.rb` | 88.31% ⚠️ | 68 | 9 | 77 |
59
+ | `app/controllers/pg_sql_triggers/tables_controller.rb` | 83.78% ⚠️ | 31 | 6 | 37 |
60
+ | `lib/pg_sql_triggers/schema_dumper_extension.rb` | 73.33% ⚠️ | 11 | 4 | 15 |
61
+ | `lib/pg_sql_triggers/engine.rb` | 72.97% ⚠️ | 27 | 10 | 37 |
62
+ | `config/routes.rb` | 17.65% ❌ | 3 | 14 | 17 |
65
63
 
66
64
  ---
67
65
 
data/LICENSE CHANGED
File without changes
data/README.md CHANGED
@@ -42,6 +42,8 @@ rails generate pg_sql_triggers:install
42
42
  rails db:migrate
43
43
  ```
44
44
 
45
+ Schema migrations bundled with the gem are listed in [Getting Started — Gem schema migrations](docs/getting-started.md#gem-schema-migrations) (ordered `db/migrate/*.rb` filenames).
46
+
45
47
  ### Define a Trigger
46
48
 
47
49
  ```ruby
@@ -97,9 +99,27 @@ Files land in `app/triggers/` and `db/triggers/` for code review like any other
97
99
  ### Migration System
98
100
  Manage trigger functions and definitions with a migration system similar to Rails schema migrations.
99
101
 
102
+ ### schema.rb, structure.sql, and trigger snapshots
103
+
104
+ `db:schema:dump` does not capture PostgreSQL triggers. This gem addresses that in three ways:
105
+
106
+ 1. **Comments in `schema.rb`** — When using the default Ruby schema format, `rails db:schema:dump` appends a short note listing managed triggers and pointing to `trigger:migrate` / `trigger:load`. Disable with `PgSqlTriggers.append_trigger_notes_to_schema_dump = false`.
107
+ 2. **`db/trigger_structure.sql`** — Run `rails trigger:dump` to write `CREATE FUNCTION` / `CREATE TRIGGER` statements for registered triggers (or all non-internal triggers in `public` if the registry table is absent). Apply on a fresh DB with `rails trigger:load` (runs arbitrary SQL; kill switch applies in protected environments). Override the path with `FILE=...` or `TRIGGER_STRUCTURE_SQL=...`, or set `PgSqlTriggers.trigger_structure_sql_path`.
108
+ 3. **`db:schema:load`** — After loading `schema.rb`, `trigger:migrate` runs automatically so pending trigger migrations apply. Opt out with `SKIP_TRIGGER_MIGRATE_AFTER_SCHEMA_LOAD=1` or `PgSqlTriggers.migrate_triggers_after_schema_load = false`.
109
+
110
+ For a single SQL artifact that includes tables and triggers, set `config.active_record.schema_format = :sql` and use Rails’ `structure.sql` workflow; keep `db/triggers` migrations as the source of truth and refresh `db/trigger_structure.sql` when you want a portable trigger-only snapshot.
111
+
100
112
  ### Drift Detection
101
113
  Automatically detect when database triggers drift from your DSL definitions. N+1-free bulk detection across all triggers.
102
114
 
115
+ ### Drift Alerting
116
+ Configure `PgSqlTriggers.drift_notifier` to push alerts to Slack, PagerDuty, email, or any external system when drift detection finds drifted, dropped, or unknown triggers. Schedule `rake trigger:check_drift` (with optional `FAIL_ON_DRIFT=1`) for CI or cron-based monitoring. An `ActiveSupport::Notifications` event `pg_sql_triggers.drift_check` is emitted on every run for APM instrumentation.
117
+
118
+ ### Advanced PostgreSQL Trigger Features
119
+ - **Column-level `UPDATE OF` triggers** — `on_update_of(:email, :status)` fires only when specific columns change, a common audit/performance optimisation.
120
+ - **Constraint triggers with deferral** — `constraint_trigger!` plus `deferrable` / `initially` options emit `CREATE CONSTRAINT TRIGGER ... DEFERRABLE INITIALLY DEFERRED` for referential-integrity patterns that need to fire at transaction commit.
121
+ - **Ordering hints** — `depends_on "other_trigger"` captures intended firing order among same-table triggers. PostgreSQL fires same-kind triggers alphabetically; `rake trigger:validate_order` (and `Registry.validate!`) verifies declared dependencies, flags cycles, and enforces name-ordering so declared order matches execution order.
122
+
103
123
  ### Production Kill Switch
104
124
  Multi-layered safety mechanism preventing accidental destructive operations in production environments.
105
125
 
@@ -109,6 +129,7 @@ Visual interface for managing triggers and running migrations. Includes:
109
129
  - **Last Applied Tracking**: See when triggers were last applied with human-readable timestamps
110
130
  - **Breadcrumb Navigation**: Easy navigation between dashboard, tables, and triggers
111
131
  - **Permission-Aware UI**: Buttons show/hide based on user role
132
+ - **Search, Filter, and Pagination**: Filter triggers by table, drift state, or source; full-text search on name/table; offset/limit pagination (`?table=users&state=drifted&source=dsl&q=email&trigger_page=2&trigger_per_page=20`)
112
133
 
113
134
  ### Audit Logging
114
135
  Comprehensive audit trail for all trigger operations:
@@ -171,9 +192,9 @@ To install this gem locally, run `bundle exec rake install`. To release a new ve
171
192
 
172
193
  ## Test Coverage
173
194
 
174
- See [COVERAGE.md](COVERAGE.md) for detailed coverage information.
175
-
176
- **Total Coverage: 84.97%**
195
+ See [COVERAGE.md](COVERAGE.md) for detailed coverage information. The bundled report was
196
+ generated against an earlier tree and will be regenerated as part of the next release cycle
197
+ (run `bundle exec rspec` with SimpleCov, then `ruby scripts/generate_coverage_report.rb`).
177
198
 
178
199
  ## Contributing
179
200
 
data/RELEASE.md CHANGED
File without changes
data/Rakefile CHANGED
@@ -6,6 +6,11 @@
6
6
  # - rake release: Build, tag, push to git, and publish to RubyGems.org
7
7
  require "bundler/gem_tasks"
8
8
 
9
+ # Minimal Rails + engine task load so `bundle exec rake trigger:*` works from this repo
10
+ # (host apps load these via Rails::Engine#rake_tasks instead).
11
+ Dir[File.expand_path("rakelib/**/*.rake", __dir__)].each { |f| load f }
12
+ load File.expand_path("lib/tasks/trigger_migrations.rake", __dir__)
13
+
9
14
  # RSpec tasks:
10
15
  # - rake spec: Run the test suite
11
16
  require "rspec/core/rake_task"
@@ -7,7 +7,7 @@ module PgSqlTriggers
7
7
  included do
8
8
  # Helper methods available in views
9
9
  helper_method :current_actor, :can_view_triggers?, :can_enable_disable_triggers?,
10
- :can_drop_triggers?, :can_execute_sql?, :can_generate_triggers?, :can_apply_triggers?
10
+ :can_drop_triggers?, :can_execute_sql_operations?, :can_generate_triggers?, :can_apply_triggers?
11
11
  end
12
12
 
13
13
  # Returns the current actor (user) performing the action.
@@ -67,7 +67,7 @@ module PgSqlTriggers
67
67
  redirect_to root_path, alert: "Insufficient permissions. Operator role required."
68
68
  end
69
69
 
70
- # Checks if current actor has admin permissions (drop/re-execute/execute SQL).
70
+ # Checks if current actor has admin permissions (drop/re-execute).
71
71
  #
72
72
  # @raise [ActionController::RedirectError] Redirects if permission denied
73
73
  def check_admin_permission
@@ -99,14 +99,15 @@ module PgSqlTriggers
99
99
  PgSqlTriggers::Permissions.can?(current_actor, :drop_trigger, environment: current_environment)
100
100
  end
101
101
 
102
- # @return [Boolean] true if current actor can execute SQL capsules
103
- def can_execute_sql?
102
+ # @return [Boolean] true if the +:execute_sql+ action is allowed (privileged SQL for host apps;
103
+ # not used by built-in UI)
104
+ def can_execute_sql_operations?
104
105
  PgSqlTriggers::Permissions.can?(current_actor, :execute_sql, environment: current_environment)
105
106
  end
106
107
 
107
108
  # @return [Boolean] true if current actor can generate triggers
108
109
  def can_generate_triggers?
109
- PgSqlTriggers::Permissions.can?(current_actor, :apply_trigger, environment: current_environment)
110
+ PgSqlTriggers::Permissions.can?(current_actor, :generate_trigger, environment: current_environment)
110
111
  end
111
112
 
112
113
  # @return [Boolean] true if current actor can apply triggers