pg_sql_triggers 1.3.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 (117) 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 +354 -0
  7. data/COVERAGE.md +39 -41
  8. data/LICENSE +0 -0
  9. data/README.md +44 -26
  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 +137 -74
  29. data/app/views/layouts/pg_sql_triggers/application.html.erb +0 -1
  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 -27
  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 +27 -18
  35. data/app/views/pg_sql_triggers/tables/show.html.erb +0 -2
  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 -14
  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/20260228162233_add_for_each_to_pg_sql_triggers_registry.rb +8 -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 +176 -152
  48. data/docs/audit-trail.md +1 -1
  49. data/docs/configuration.md +196 -3
  50. data/docs/getting-started.md +31 -16
  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 +112 -67
  56. data/docs/web-ui.md +3 -103
  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 +11 -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 +29 -0
  64. data/lib/generators/pg_sql_triggers/trigger_generator.rb +83 -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 +26 -13
  70. data/lib/pg_sql_triggers/drift/detector.rb +59 -38
  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 +68 -20
  74. data/lib/pg_sql_triggers/dsl.rb +0 -0
  75. data/lib/pg_sql_triggers/engine.rb +49 -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 +85 -82
  80. data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +0 -0
  81. data/lib/pg_sql_triggers/migrator/safety_validator.rb +34 -12
  82. data/lib/pg_sql_triggers/migrator.rb +137 -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 +60 -21
  87. data/lib/pg_sql_triggers/registry/validator.rb +287 -6
  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 +154 -275
  91. data/lib/pg_sql_triggers/sql.rb +0 -6
  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 +21 -1
  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 +68 -22
  105. data/Goal.md +0 -742
  106. data/app/controllers/pg_sql_triggers/generator_controller.rb +0 -213
  107. data/app/controllers/pg_sql_triggers/sql_capsules_controller.rb +0 -161
  108. data/app/views/pg_sql_triggers/generator/new.html.erb +0 -388
  109. data/app/views/pg_sql_triggers/generator/preview.html.erb +0 -305
  110. data/app/views/pg_sql_triggers/sql_capsules/new.html.erb +0 -81
  111. data/app/views/pg_sql_triggers/sql_capsules/show.html.erb +0 -85
  112. data/lib/generators/trigger/migration_generator.rb +0 -60
  113. data/lib/pg_sql_triggers/generator/form.rb +0 -80
  114. data/lib/pg_sql_triggers/generator/service.rb +0 -339
  115. data/lib/pg_sql_triggers/generator.rb +0 -8
  116. data/lib/pg_sql_triggers/sql/capsule.rb +0 -79
  117. data/lib/pg_sql_triggers/sql/executor.rb +0 -200
@@ -34,6 +34,20 @@ This will:
34
34
  2. Create migrations for the registry table
35
35
  3. Mount the engine at `/pg_sql_triggers`
36
36
 
37
+ ## Gem schema migrations
38
+
39
+ The gem’s Rails schema migrations live under `db/migrate/` in this repository. Versions use the standard **`YYYYMMDDHHMMSS`** prefix (14 digits: date and clock time). Rails runs them in version order.
40
+
41
+ Run order (oldest first):
42
+
43
+ 1. `20251222104327_create_pg_sql_triggers_tables.rb` — creates `pg_sql_triggers_registry`
44
+ 2. `20251229071916_add_timing_to_pg_sql_triggers_registry.rb` — adds `timing` on the registry
45
+ 3. `20260103114508_create_pg_sql_triggers_audit_log.rb` — creates `pg_sql_triggers_audit_log`
46
+ 4. `20260228162233_add_for_each_to_pg_sql_triggers_registry.rb` — adds `for_each` on the registry
47
+ 5. `20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb` — adds `constraint_trigger`, `deferrable`, `initially` on the registry
48
+
49
+ If you upgrade the gem and rename migration files locally, update the corresponding rows in `schema_migrations` so the version strings match these filenames.
50
+
37
51
  ## Verify Installation
38
52
 
39
53
  After installation, you should have:
@@ -45,9 +59,19 @@ After installation, you should have:
45
59
 
46
60
  ## Quick Start Example
47
61
 
48
- ### 1. Create Your First Trigger Definition
62
+ ### 1. Scaffold a Trigger (DSL + Migration)
63
+
64
+ Use the CLI generator to create both files at once:
65
+
66
+ ```bash
67
+ rails generate pg_sql_triggers:trigger users_email_validation users insert update --timing before --function validate_user_email
68
+ ```
69
+
70
+ This creates:
71
+ - `app/triggers/users_email_validation.rb` — DSL stub
72
+ - `db/triggers/TIMESTAMP_users_email_validation.rb` — migration with function + trigger SQL
49
73
 
50
- Create a trigger definition file:
74
+ Edit the DSL stub to match your requirements:
51
75
 
52
76
  ```ruby
53
77
  # app/triggers/users_email_validation.rb
@@ -56,21 +80,12 @@ PgSqlTriggers::DSL.pg_sql_trigger "users_email_validation" do
56
80
  on :insert, :update
57
81
  function :validate_user_email
58
82
 
59
- version 1
60
- enabled false
61
-
62
- when_env :production
83
+ self.version = 1
84
+ self.enabled = true
85
+ timing :before
63
86
  end
64
87
  ```
65
88
 
66
- ### 2. Generate a Migration
67
-
68
- Create a trigger migration to implement the function:
69
-
70
- ```bash
71
- rails generate trigger:migration add_email_validation
72
- ```
73
-
74
89
  Edit the generated migration in `db/triggers/`:
75
90
 
76
91
  ```ruby
@@ -104,7 +119,7 @@ class AddEmailValidation < PgSqlTriggers::Migration
104
119
  end
105
120
  ```
106
121
 
107
- ### 3. Run the Migration
122
+ ### 2. Run the Migration
108
123
 
109
124
  Apply the trigger migration:
110
125
 
@@ -112,7 +127,7 @@ Apply the trigger migration:
112
127
  rake trigger:migrate
113
128
  ```
114
129
 
115
- ### 4. Access the Web UI
130
+ ### 3. Access the Web UI
116
131
 
117
132
  Open your browser and navigate to:
118
133
 
data/docs/kill-switch.md CHANGED
File without changes
data/docs/permissions.md CHANGED
@@ -18,7 +18,7 @@ The permission system provides three levels of access:
18
18
 
19
19
  - **Viewer**: Read-only access to view triggers and their status
20
20
  - **Operator**: Can enable/disable triggers, apply migrations, generate triggers
21
- - **Admin**: Full access including drop, re-execute, and SQL capsule execution
21
+ - **Admin**: Full access including drop, re-execute, and the reserved `execute_sql` permission action for host-defined privileged SQL
22
22
 
23
23
  By default, all permissions are allowed (permissive mode). **You must configure permissions in production** to enforce access controls.
24
24
 
@@ -49,7 +49,7 @@ Admins have full access:
49
49
  - All Operator permissions
50
50
  - Drop triggers
51
51
  - Re-execute triggers
52
- - Execute SQL capsules
52
+ - `execute_sql` (privileged SQL; for custom integrations — not used by built-in UI)
53
53
  - Override drift detection
54
54
 
55
55
  ## Actions and Required Roles
@@ -67,7 +67,7 @@ The following actions are mapped to permission levels:
67
67
  | `generate_trigger` | Operator | Generate triggers via UI |
68
68
  | `test_trigger` | Operator | Test trigger functions |
69
69
  | `drop_trigger` | Admin | Drop a trigger from database |
70
- | `execute_sql` | Admin | Execute SQL capsules |
70
+ | `execute_sql` | Admin | Privileged SQL (`permission_checker` / custom tooling; not used by built-in UI) |
71
71
  | `override_drift` | Admin | Override drift detection warnings |
72
72
 
73
73
  ## Configuration
@@ -272,12 +272,9 @@ PgSqlTriggers::Registry.drop(
272
272
  confirmation: "EXECUTE TRIGGER_DROP"
273
273
  )
274
274
 
275
- # Execute SQL capsule (requires Admin)
276
- PgSqlTriggers::SQL::Executor.execute(
277
- capsule,
278
- actor: current_user,
279
- confirmation: "EXECUTE SQL"
280
- )
275
+ # Custom tooling: gate raw SQL with the Admin-level :execute_sql action (requires Admin)
276
+ PgSqlTriggers::Permissions.check!(current_user, :execute_sql)
277
+ # ... your application's SQL execution ...
281
278
  ```
282
279
 
283
280
  ### Permission Errors in Console
File without changes
data/docs/ui-guide.md CHANGED
File without changes
data/docs/usage-guide.md CHANGED
@@ -15,7 +15,7 @@ PgSqlTriggers provides a Ruby DSL for defining triggers. Trigger definitions are
15
15
 
16
16
  ### Basic Trigger Definition
17
17
 
18
- Create trigger definition files in `app/triggers/`:
18
+ Create trigger definition files in `app/triggers/` (or scaffold them with the CLI generator — see below):
19
19
 
20
20
  ```ruby
21
21
  # app/triggers/users_email_validation.rb
@@ -24,11 +24,9 @@ PgSqlTriggers::DSL.pg_sql_trigger "users_email_validation" do
24
24
  on :insert, :update
25
25
  function :validate_user_email
26
26
 
27
- version 1
28
- enabled false
27
+ self.version = 1
28
+ self.enabled = true
29
29
  timing :before
30
-
31
- when_env :production
32
30
  end
33
31
  ```
34
32
 
@@ -61,25 +59,30 @@ function :validate_user_email
61
59
  Version number for tracking changes:
62
60
 
63
61
  ```ruby
64
- version 1 # Increment when trigger logic changes
62
+ self.version = 1 # Increment when trigger logic changes
65
63
  ```
66
64
 
67
65
  #### `enabled`
68
- Initial state of the trigger:
66
+ Initial state of the trigger (defaults to `true`):
69
67
 
70
68
  ```ruby
71
- enabled true # Trigger is active
72
- enabled false # Trigger is inactive
69
+ self.enabled = true # Trigger is active (default)
70
+ self.enabled = false # Trigger is inactive
73
71
  ```
74
72
 
75
- #### `when_env`
76
- Environment-specific activation:
73
+ #### `for_each_row` / `for_each_statement`
74
+ PostgreSQL execution granularity (defaults to row-level):
77
75
 
78
76
  ```ruby
79
- when_env :production # Only in production
80
- when_env :staging, :production # Multiple environments
77
+ for_each_row # FOR EACH ROW (default)
78
+ for_each_statement # FOR EACH STATEMENT
81
79
  ```
82
80
 
81
+ #### `when_env`
82
+ **Deprecated.** Environment-specific trigger declarations cause schema drift between environments.
83
+ Use application-level configuration to gate trigger behaviour by environment instead.
84
+ Calling `when_env` emits a deprecation warning and will be removed in a future major version.
85
+
83
86
  #### `timing`
84
87
  Specifies when the trigger fires relative to the event (BEFORE or AFTER):
85
88
 
@@ -88,85 +91,127 @@ timing :before # Trigger fires before constraint checks (default)
88
91
  timing :after # Trigger fires after constraint checks
89
92
  ```
90
93
 
91
- ### Complete Example
94
+ #### `on_update_of`
95
+ Declares a **column-level** trigger that fires only when specific columns change
96
+ (PostgreSQL `UPDATE OF col1, col2`). This is a common performance optimisation for audit
97
+ triggers: they run only when the columns you care about are modified.
92
98
 
93
99
  ```ruby
94
- # app/triggers/orders_billing_trigger.rb
95
- PgSqlTriggers::DSL.pg_sql_trigger "orders_billing_trigger" do
100
+ on_update_of :email, :status # Sets event to UPDATE and records columns
101
+ ```
102
+
103
+ Notes:
104
+ - `on_update_of` sets the event to `update` and stores the column list; calling `on(...)` again
105
+ clears the column list.
106
+ - Column names must be simple SQL identifiers (`[a-zA-Z_][a-zA-Z0-9_]*`). The gem quotes them
107
+ in generated SQL, so pass them unquoted.
108
+ - The column list is included in the checksum, so changing the list is detected as drift.
109
+
110
+ #### `constraint_trigger!` / `deferrable` / `initially`
111
+ Declares a **constraint trigger** (`CREATE CONSTRAINT TRIGGER`) with optional deferral. Constraint
112
+ triggers must be `AFTER` triggers, cannot use `TRUNCATE`, and are the only triggers that can be
113
+ deferred.
114
+
115
+ ```ruby
116
+ PgSqlTriggers::DSL.pg_sql_trigger "orders_integrity_check" do
96
117
  table :orders
97
118
  on :insert, :update
98
- function :calculate_order_total
119
+ function :check_orders_referential_integrity
120
+
121
+ constraint_trigger! # Emits CREATE CONSTRAINT TRIGGER, forces timing :after
122
+ self.deferrable = :deferrable
123
+ self.initially = :deferred # Evaluate at transaction commit
124
+ end
125
+ ```
126
+
127
+ Valid combinations:
128
+
129
+ | `deferrable` | `initially` | SQL clause |
130
+ |---------------------|----------------------------|-------------------------------------|
131
+ | `:deferrable` | `:deferred` | `DEFERRABLE INITIALLY DEFERRED` |
132
+ | `:deferrable` | `:immediate` (or `nil`) | `DEFERRABLE INITIALLY IMMEDIATE` |
133
+ | `:not_deferrable` | (must be `nil`) | `NOT DEFERRABLE` |
134
+ | `nil` | `nil` | (no deferral clause) |
99
135
 
100
- version 2
101
- enabled true
136
+ `Registry::Validator` rejects `deferrable`/`initially` unless `constraint_trigger!` is set,
137
+ and rejects constraint triggers that use `:before` timing or `TRUNCATE` events. Drift detection
138
+ reads deferral state from `pg_trigger.tgdeferrable` / `tginitdeferred` so changes made outside
139
+ the gem are surfaced as drift.
140
+
141
+ #### `depends_on`
142
+ Declares an **ordering hint** relative to another trigger on the same table. PostgreSQL fires
143
+ same-kind triggers in alphabetical order by name; `depends_on` captures the intended order so the
144
+ registry validator can verify that naming matches dependency declarations.
145
+
146
+ ```ruby
147
+ PgSqlTriggers::DSL.pg_sql_trigger "log_user_change" do
148
+ table :users
149
+ on :insert, :update
150
+ function :log_user_change
102
151
  timing :after
103
152
 
104
- when_env :production, :staging
153
+ depends_on "validate_user_email" # Must exist on same table, same timing/FOR EACH, overlapping events
105
154
  end
106
155
  ```
107
156
 
108
- ## Trigger Generator
157
+ Run `rake trigger:validate_order` (or `PgSqlTriggers::Registry.validate!`) to enforce:
109
158
 
110
- PgSqlTriggers provides a web-based generator and Rails generators for creating trigger definitions and migrations quickly.
159
+ - Referenced prerequisite triggers exist and are on the same table.
160
+ - Prerequisites share timing (`before`/`after`), `FOR EACH` granularity, and have at least one
161
+ overlapping event with the dependent.
162
+ - There are no circular dependencies.
163
+ - Names sort alphabetically in the required order (the prerequisite's name must sort before
164
+ the dependent's).
111
165
 
112
- ### Web UI Generator
166
+ The trigger detail page in the web UI displays prerequisites and dependents for DSL triggers.
113
167
 
114
- The web UI generator provides a user-friendly interface for creating triggers:
168
+ ### Complete Example
115
169
 
116
- 1. Navigate to `/pg_sql_triggers/generator/new` in your browser
117
- 2. Fill in the trigger details:
118
- - **Trigger Name**: Lowercase letters, numbers, and underscores only
119
- - **Table Name**: The PostgreSQL table to attach the trigger to
120
- - **Function Name**: The PostgreSQL function name (must match the function body)
121
- - **Timing**: When the trigger fires - BEFORE (before constraint checks) or AFTER (after constraint checks)
122
- - **Events**: Select one or more events (INSERT, UPDATE, DELETE, TRUNCATE)
123
- - **Function Body**: The complete PostgreSQL function definition
124
- - **Version**: Starting version number (default: 1)
125
- - **Enabled**: Whether the trigger should be enabled initially
126
- - **Environments**: Optional environment restrictions
127
- - **Condition**: Optional WHEN condition for the trigger
128
- 3. Preview the generated DSL and migration code (includes timing and condition display)
129
- 4. Create the trigger files
170
+ ```ruby
171
+ # app/triggers/orders_billing_trigger.rb
172
+ PgSqlTriggers::DSL.pg_sql_trigger "orders_billing_trigger" do
173
+ table :orders
174
+ on :insert, :update
175
+ function :calculate_order_total
176
+
177
+ self.version = 2
178
+ self.enabled = true
179
+ timing :after
180
+ for_each_row
181
+ end
182
+ ```
130
183
 
131
- The generator creates:
132
- - A DSL definition file in `app/triggers/`
133
- - A migration file in `db/triggers/`
134
- - A registry entry in the database
184
+ ## Trigger Generator
135
185
 
136
- ### Rails Generators
186
+ PgSqlTriggers provides a CLI generator that scaffolds a DSL definition and migration together from the command line.
137
187
 
138
- You can also use Rails generators to create trigger migrations:
188
+ ### CLI Generator
139
189
 
140
190
  ```bash
141
- # Generate a trigger migration
142
- rails generate trigger:migration add_user_validation
191
+ rails generate pg_sql_triggers:trigger TRIGGER_NAME TABLE_NAME [EVENTS...] [--timing before|after] [--function fn_name]
192
+ ```
143
193
 
144
- # Or using the full namespace
145
- rails generate pg_sql_triggers:trigger_migration add_user_validation
194
+ Example:
195
+
196
+ ```bash
197
+ rails generate pg_sql_triggers:trigger users_email_validation users insert update --timing before --function validate_user_email
146
198
  ```
147
199
 
148
- This creates a migration file in `db/triggers/` that you can edit to add your trigger logic.
200
+ This creates:
201
+ - `app/triggers/users_email_validation.rb` — DSL stub
202
+ - `db/triggers/TIMESTAMP_users_email_validation.rb` — migration with function + trigger SQL
203
+
204
+ Files are written directly into the working tree and go through version control and code review like any other source file.
149
205
 
150
- ### Generator Features
206
+ ### Migration-Only Generator
151
207
 
152
- The generator handles:
153
- - **Function Name Formatting**: Automatically quotes function names with special characters
154
- - **Multiple Environments**: Supports multiple environment restrictions
155
- - **Condition Escaping**: Properly escapes quotes in WHEN conditions
156
- - **Event Combinations**: Handles single or multiple events (INSERT, UPDATE, DELETE, TRUNCATE)
157
- - **Migration Numbering**: Automatically generates sequential migration numbers
158
- - **Error Handling**: Graceful error handling with detailed error messages
208
+ To generate a standalone trigger migration without a DSL stub:
159
209
 
160
- ### Generator Edge Cases
210
+ ```bash
211
+ rails generate pg_sql_triggers:trigger_migration add_user_validation
212
+ ```
161
213
 
162
- The generator properly handles:
163
- - Function names with special characters (quoted vs unquoted)
164
- - Multiple environments in a single trigger
165
- - Complex WHEN conditions with quotes
166
- - All event type combinations
167
- - Standalone gem usage (without Rails context)
168
- - Migration number collisions
169
- - Blank events and environments (filtered automatically)
214
+ This creates a timestamped file in `db/triggers/` that you can edit to add your trigger logic.
170
215
 
171
216
  ## Trigger Migrations
172
217
 
@@ -177,7 +222,7 @@ Trigger migrations work similarly to Rails schema migrations but are specificall
177
222
  Create a new trigger migration:
178
223
 
179
224
  ```bash
180
- rails generate trigger:migration add_validation_trigger
225
+ rails generate pg_sql_triggers:trigger_migration add_validation_trigger
181
226
  ```
182
227
 
183
228
  This creates a timestamped file in `db/triggers/`:
data/docs/web-ui.md CHANGED
@@ -8,7 +8,6 @@ The PgSqlTriggers web interface provides a visual dashboard for managing trigger
8
8
  - [Dashboard Overview](#dashboard-overview)
9
9
  - [Managing Triggers](#managing-triggers)
10
10
  - [Migration Management](#migration-management)
11
- - [SQL Capsules](#sql-capsules)
12
11
  - [Audit Log](#audit-log)
13
12
  - [Permissions and Safety](#permissions-and-safety)
14
13
 
@@ -96,7 +95,7 @@ Each table row displays:
96
95
  - **Status**: Summary of enabled/disabled triggers
97
96
  - **Actions**:
98
97
  - "View Details" - Navigate to the table detail page
99
- - "Create Trigger" - Generate a new trigger for this table
98
+ - "Create Trigger" - Scaffold a new trigger for this table using the CLI generator (see [Trigger Generator](usage-guide.md#trigger-generator))
100
99
 
101
100
  ### Table Detail Page
102
101
 
@@ -303,83 +302,6 @@ After each migration action:
303
302
  - **Error**: Red flash message with error details
304
303
  - **Warnings**: Yellow flash message if issues occurred
305
304
 
306
- ## SQL Capsules
307
-
308
- SQL Capsules provide emergency escape hatches for executing SQL directly with comprehensive safety checks and audit logging.
309
-
310
- ### When to Use SQL Capsules
311
-
312
- Use SQL Capsules for:
313
- - Emergency fixes in production
314
- - Critical data corrections
315
- - Testing SQL functions
316
- - Debugging trigger behavior
317
- - One-off database operations
318
-
319
- ### Creating and Executing SQL Capsules
320
-
321
- 1. Navigate to "SQL Capsules" → "New SQL Capsule"
322
- 2. Fill in the capsule form:
323
- - **Name**: Unique identifier (alphanumeric, underscores, hyphens only)
324
- - **Environment**: Target environment (e.g., production, staging)
325
- - **Purpose**: Detailed explanation of what the SQL does and why (required for audit trail)
326
- - **SQL**: The SQL statement(s) to execute
327
- 3. Click "Create and Execute" or "Save for Later"
328
- 4. Review the capsule details on the confirmation page
329
- 5. In protected environments, enter confirmation text when prompted
330
- 6. Click "Execute" to run the SQL
331
- 7. Review the execution results
332
-
333
- ### Viewing Capsule History
334
-
335
- 1. Navigate to "SQL Capsules" → "History"
336
- 2. View list of previously executed capsules with:
337
- - Name and purpose
338
- - Environment and timestamp
339
- - SQL checksum
340
- - Execution status
341
- 3. Click on a capsule to view details
342
- 4. Re-execute historical capsules if needed
343
-
344
- ### Safety Features
345
-
346
- - **Admin Permission Required**: Only Admin users can create and execute SQL capsules
347
- - **Production Protection**: Requires typed confirmation in protected environments
348
- - **Kill Switch Integration**: All executions are protected by kill switch
349
- - **Comprehensive Logging**: All operations logged with actor, timestamp, and checksum
350
- - **Transactional Execution**: SQL runs in a transaction and rolls back on error
351
- - **Registry Storage**: All capsules are stored in the registry with checksums
352
- - **Purpose Tracking**: Required purpose field ensures all executions are documented
353
-
354
- ### Example SQL Capsules
355
-
356
- #### View All Triggers
357
- ```sql
358
- SELECT
359
- trigger_name,
360
- event_object_table,
361
- action_timing,
362
- event_manipulation
363
- FROM information_schema.triggers
364
- WHERE trigger_schema = 'public';
365
- ```
366
-
367
- #### Check Function Definitions
368
- ```sql
369
- SELECT
370
- routine_name,
371
- routine_type
372
- FROM information_schema.routines
373
- WHERE routine_schema = 'public'
374
- AND routine_name LIKE '%trigger%';
375
- ```
376
-
377
- #### Verify Trigger State
378
- ```sql
379
- SELECT * FROM pg_sql_triggers_registry
380
- WHERE trigger_name = 'users_email_validation';
381
- ```
382
-
383
305
  ## Audit Log
384
306
 
385
307
  The Audit Log provides a comprehensive view of all trigger operations performed through the web UI, console APIs, and CLI. This feature is essential for compliance, debugging, and tracking changes to your trigger ecosystem.
@@ -446,7 +368,6 @@ All of the following operations are logged to the audit log:
446
368
  - **Disable Trigger**: Success/failure, before/after state
447
369
  - **Drop Trigger**: Success/failure, reason, state changes
448
370
  - **Re-execute Trigger**: Success/failure, reason, drift diff information
449
- - **SQL Capsule Execution**: Success/failure, capsule details
450
371
  - **Migration Operations**: Up, down, and redo operations (infrastructure ready)
451
372
 
452
373
  Each log entry includes:
@@ -497,7 +418,6 @@ Cannot:
497
418
  #### Admin (Full Access)
498
419
  - All Operator permissions
499
420
  - Drop triggers
500
- - Execute SQL via capsules
501
421
  - Modify registry directly
502
422
 
503
423
  ### Kill Switch Protection
@@ -529,9 +449,9 @@ PgSqlTriggers.configure do |config|
529
449
  case action
530
450
  when :view_triggers, :view_diffs
531
451
  user.present? # Viewer level
532
- when :enable_trigger, :disable_trigger, :apply_trigger, :generate_trigger, :test_trigger, :dry_run_sql
452
+ when :enable_trigger, :disable_trigger, :apply_trigger, :test_trigger
533
453
  user.operator? || user.admin? # Operator level
534
- when :drop_trigger, :execute_sql, :override_drift
454
+ when :drop_trigger, :override_drift
535
455
  user.admin? # Admin level
536
456
  else
537
457
  false
@@ -550,32 +470,12 @@ end
550
470
  ### Main Dashboard
551
471
  ![Main Dashboard](screenshots/dashboard.png)
552
472
 
553
- ### Trigger Generator
554
-
555
- The trigger generator provides a comprehensive form for creating triggers:
556
-
557
- 1. **Basic Information**: Trigger name, table name, function name, and function body
558
- 2. **Trigger Events**: Select timing (BEFORE/AFTER) and events (INSERT, UPDATE, DELETE, TRUNCATE)
559
- 3. **Configuration**: Version, environments, WHEN condition, and enabled state
560
- 4. **Preview**: Review generated DSL and migration code with timing and condition information
561
-
562
- The preview page displays:
563
- - Generated DSL code with timing
564
- - Trigger configuration summary (timing, events, table, function, condition)
565
- - PL/pgSQL function body (editable)
566
- - SQL validation results
567
-
568
- ![Trigger Generator](screenshots/generator.png)
569
-
570
473
  ### Migration Management
571
474
  ![Migration Management](screenshots/migrations.png)
572
475
 
573
476
  ### Kill Switch Protection
574
477
  ![Kill Switch](screenshots/kill-switch.png)
575
478
 
576
- ### SQL Capsules
577
- ![SQL Capsules](screenshots/sql-capsules.png)
578
-
579
479
  ## Dashboard Enhancements (v1.3.0+)
580
480
 
581
481
  ### Last Applied Column
File without changes
File without changes
@@ -66,4 +66,18 @@ PgSqlTriggers.configure do |config|
66
66
  # You can also override per-migration with ALLOW_UNSAFE_MIGRATIONS=true environment variable
67
67
  # Default: false (recommended for safety)
68
68
  config.allow_unsafe_migrations = false
69
+
70
+ # ========== Schema / structure.sql integration ==========
71
+ # Triggers are not included in db/schema.rb. After db:schema:load, the engine runs
72
+ # trigger:migrate by default. Set SKIP_TRIGGER_MIGRATE_AFTER_SCHEMA_LOAD=1 to skip.
73
+ # config.migrate_triggers_after_schema_load = true
74
+ #
75
+ # Optional: append comments to schema.rb pointing at db/trigger_structure.sql.
76
+ # config.append_trigger_notes_to_schema_dump = true
77
+ #
78
+ # Snapshot live trigger SQL: bin/rails trigger:dump (apply with trigger:load).
79
+ # config.trigger_structure_sql_path = Rails.root.join("db/trigger_structure.sql")
80
+ #
81
+ # For databases where triggers must appear in the main SQL dump, prefer:
82
+ # config.active_record.schema_format = :sql
69
83
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ PgSqlTriggers::DSL.pg_sql_trigger "<%= trigger_name %>" do
4
+ table :<%= table_name %>
5
+ on <%= events_list %>
6
+ function :<%= function_name %>
7
+
8
+ version 1
9
+ enabled true
10
+ timing :<%= timing %>
11
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= trigger_class_name %> < PgSqlTriggers::Migration
4
+ def up
5
+ execute <<-SQL
6
+ CREATE OR REPLACE FUNCTION <%= function_name %>()
7
+ RETURNS TRIGGER AS $$
8
+ BEGIN
9
+ -- TODO: implement trigger logic
10
+ RETURN NEW;
11
+ END;
12
+ $$ LANGUAGE plpgsql;
13
+ SQL
14
+
15
+ execute <<-SQL
16
+ CREATE TRIGGER <%= trigger_name %>
17
+ <%= timing.upcase %> <%= events_sql %> ON <%= table_name %>
18
+ FOR EACH ROW
19
+ EXECUTE FUNCTION <%= function_name %>();
20
+ SQL
21
+ end
22
+
23
+ def down
24
+ execute <<-SQL
25
+ DROP TRIGGER IF EXISTS <%= trigger_name %> ON <%= table_name %>;
26
+ DROP FUNCTION IF EXISTS <%= function_name %>();
27
+ SQL
28
+ end
29
+ end