pg_sql_triggers 1.0.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/.erb_lint.yml +47 -0
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +29 -1
- data/Goal.md +408 -123
- data/README.md +47 -215
- data/app/controllers/pg_sql_triggers/application_controller.rb +46 -0
- data/app/controllers/pg_sql_triggers/generator_controller.rb +10 -4
- data/app/controllers/pg_sql_triggers/migrations_controller.rb +18 -0
- data/app/models/pg_sql_triggers/trigger_registry.rb +20 -2
- data/app/views/layouts/pg_sql_triggers/application.html.erb +34 -1
- data/app/views/pg_sql_triggers/dashboard/index.html.erb +70 -30
- data/app/views/pg_sql_triggers/generator/new.html.erb +4 -4
- data/app/views/pg_sql_triggers/generator/preview.html.erb +14 -6
- data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +189 -0
- data/app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb +40 -0
- data/app/views/pg_sql_triggers/tables/index.html.erb +0 -2
- data/app/views/pg_sql_triggers/tables/show.html.erb +3 -4
- data/db/migrate/20251222000001_create_pg_sql_triggers_tables.rb +1 -1
- data/docs/README.md +66 -0
- data/docs/api-reference.md +663 -0
- data/docs/configuration.md +541 -0
- data/docs/getting-started.md +135 -0
- data/docs/kill-switch.md +586 -0
- data/docs/screenshots/.gitkeep +1 -0
- data/docs/screenshots/Generate Trigger.png +0 -0
- data/docs/screenshots/Triggers Page.png +0 -0
- data/docs/screenshots/kill error.png +0 -0
- data/docs/screenshots/kill modal for migration down.png +0 -0
- data/docs/usage-guide.md +420 -0
- data/docs/web-ui.md +339 -0
- data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +1 -1
- data/lib/generators/pg_sql_triggers/templates/initializer.rb +36 -2
- data/lib/pg_sql_triggers/generator/service.rb +1 -1
- data/lib/pg_sql_triggers/migration.rb +1 -1
- data/lib/pg_sql_triggers/migrator.rb +27 -3
- data/lib/pg_sql_triggers/registry/manager.rb +6 -6
- data/lib/pg_sql_triggers/sql/kill_switch.rb +300 -0
- data/lib/pg_sql_triggers/testing/dry_run.rb +5 -7
- data/lib/pg_sql_triggers/testing/safe_executor.rb +23 -11
- data/lib/pg_sql_triggers/version.rb +1 -1
- data/lib/pg_sql_triggers.rb +12 -0
- data/lib/tasks/trigger_migrations.rake +33 -0
- metadata +35 -5
data/docs/usage-guide.md
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
# Usage Guide
|
|
2
|
+
|
|
3
|
+
This guide covers the core features of PgSqlTriggers: trigger definitions using the DSL, migration management, and drift detection.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Declaring Triggers](#declaring-triggers)
|
|
8
|
+
- [Trigger Migrations](#trigger-migrations)
|
|
9
|
+
- [Combined Schema and Trigger Migrations](#combined-schema-and-trigger-migrations)
|
|
10
|
+
- [Drift Detection](#drift-detection)
|
|
11
|
+
|
|
12
|
+
## Declaring Triggers
|
|
13
|
+
|
|
14
|
+
PgSqlTriggers provides a Ruby DSL for defining triggers. Trigger definitions are declarative and separate from their implementation.
|
|
15
|
+
|
|
16
|
+
### Basic Trigger Definition
|
|
17
|
+
|
|
18
|
+
Create trigger definition files in `app/triggers/`:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# app/triggers/users_email_validation.rb
|
|
22
|
+
PgSqlTriggers::DSL.pg_sql_trigger "users_email_validation" do
|
|
23
|
+
table :users
|
|
24
|
+
on :insert, :update
|
|
25
|
+
function :validate_user_email
|
|
26
|
+
|
|
27
|
+
version 1
|
|
28
|
+
enabled false
|
|
29
|
+
|
|
30
|
+
when_env :production
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### DSL Reference
|
|
35
|
+
|
|
36
|
+
#### `table`
|
|
37
|
+
Specifies which table the trigger is attached to:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
table :users
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### `on`
|
|
44
|
+
Defines when the trigger fires (one or more events):
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
on :insert # Single event
|
|
48
|
+
on :insert, :update # Multiple events
|
|
49
|
+
on :delete # Delete operations
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### `function`
|
|
53
|
+
The PostgreSQL function that the trigger executes:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
function :validate_user_email
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### `version`
|
|
60
|
+
Version number for tracking changes:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
version 1 # Increment when trigger logic changes
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### `enabled`
|
|
67
|
+
Initial state of the trigger:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
enabled true # Trigger is active
|
|
71
|
+
enabled false # Trigger is inactive
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### `when_env`
|
|
75
|
+
Environment-specific activation:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
when_env :production # Only in production
|
|
79
|
+
when_env :staging, :production # Multiple environments
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Complete Example
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
# app/triggers/orders_billing_trigger.rb
|
|
86
|
+
PgSqlTriggers::DSL.pg_sql_trigger "orders_billing_trigger" do
|
|
87
|
+
table :orders
|
|
88
|
+
on :insert, :update
|
|
89
|
+
function :calculate_order_total
|
|
90
|
+
|
|
91
|
+
version 2
|
|
92
|
+
enabled true
|
|
93
|
+
|
|
94
|
+
when_env :production, :staging
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Trigger Migrations
|
|
99
|
+
|
|
100
|
+
Trigger migrations work similarly to Rails schema migrations but are specifically for PostgreSQL triggers and functions.
|
|
101
|
+
|
|
102
|
+
### Generating Migrations
|
|
103
|
+
|
|
104
|
+
Create a new trigger migration:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
rails generate trigger:migration add_validation_trigger
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This creates a timestamped file in `db/triggers/`:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
db/triggers/20231215120000_add_validation_trigger.rb
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Migration Structure
|
|
117
|
+
|
|
118
|
+
Migrations have `up` and `down` methods:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
# db/triggers/20231215120000_add_validation_trigger.rb
|
|
122
|
+
class AddValidationTrigger < PgSqlTriggers::Migration
|
|
123
|
+
def up
|
|
124
|
+
execute <<-SQL
|
|
125
|
+
CREATE OR REPLACE FUNCTION validate_user_email()
|
|
126
|
+
RETURNS TRIGGER AS $$
|
|
127
|
+
BEGIN
|
|
128
|
+
IF NEW.email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
|
|
129
|
+
RAISE EXCEPTION 'Invalid email format';
|
|
130
|
+
END IF;
|
|
131
|
+
RETURN NEW;
|
|
132
|
+
END;
|
|
133
|
+
$$ LANGUAGE plpgsql;
|
|
134
|
+
|
|
135
|
+
CREATE TRIGGER user_email_validation
|
|
136
|
+
BEFORE INSERT OR UPDATE ON users
|
|
137
|
+
FOR EACH ROW
|
|
138
|
+
EXECUTE FUNCTION validate_user_email();
|
|
139
|
+
SQL
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def down
|
|
143
|
+
execute <<-SQL
|
|
144
|
+
DROP TRIGGER IF EXISTS user_email_validation ON users;
|
|
145
|
+
DROP FUNCTION IF EXISTS validate_user_email();
|
|
146
|
+
SQL
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Running Migrations
|
|
152
|
+
|
|
153
|
+
#### Apply All Pending Migrations
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
rake trigger:migrate
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Rollback Last Migration
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
rake trigger:rollback
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Rollback Multiple Steps
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
rake trigger:rollback STEP=3
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### Check Migration Status
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
rake trigger:migrate:status
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Output example:
|
|
178
|
+
```
|
|
179
|
+
Status Migration ID Migration Name
|
|
180
|
+
--------------------------------------------------
|
|
181
|
+
up 20231215120000 Add validation trigger
|
|
182
|
+
up 20231216130000 Add billing trigger
|
|
183
|
+
down 20231217140000 Add audit trigger
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### Run Specific Migration Up
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
rake trigger:migrate:up VERSION=20231215120000
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Run Specific Migration Down
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
rake trigger:migrate:down VERSION=20231215120000
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Redo Last Migration
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
rake trigger:migrate:redo
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This rolls back and re-applies the last migration.
|
|
205
|
+
|
|
206
|
+
### Migration Best Practices
|
|
207
|
+
|
|
208
|
+
1. **Always Provide Down Method**: Ensure migrations are reversible
|
|
209
|
+
2. **Use Idempotent SQL**: Use `CREATE OR REPLACE FUNCTION` and `DROP ... IF EXISTS`
|
|
210
|
+
3. **Test in Development**: Verify migrations work before applying to production
|
|
211
|
+
4. **Version Control**: Commit migration files to git
|
|
212
|
+
5. **Incremental Changes**: Keep migrations small and focused
|
|
213
|
+
|
|
214
|
+
### Complex Migration Example
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
# db/triggers/20231218150000_add_order_audit.rb
|
|
218
|
+
class AddOrderAudit < PgSqlTriggers::Migration
|
|
219
|
+
def up
|
|
220
|
+
execute <<-SQL
|
|
221
|
+
-- Create audit table
|
|
222
|
+
CREATE TABLE IF NOT EXISTS order_audits (
|
|
223
|
+
id SERIAL PRIMARY KEY,
|
|
224
|
+
order_id INTEGER NOT NULL,
|
|
225
|
+
operation VARCHAR(10) NOT NULL,
|
|
226
|
+
old_data JSONB,
|
|
227
|
+
new_data JSONB,
|
|
228
|
+
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
-- Create audit function
|
|
232
|
+
CREATE OR REPLACE FUNCTION audit_order_changes()
|
|
233
|
+
RETURNS TRIGGER AS $$
|
|
234
|
+
BEGIN
|
|
235
|
+
IF TG_OP = 'DELETE' THEN
|
|
236
|
+
INSERT INTO order_audits (order_id, operation, old_data)
|
|
237
|
+
VALUES (OLD.id, 'DELETE', row_to_json(OLD));
|
|
238
|
+
RETURN OLD;
|
|
239
|
+
ELSIF TG_OP = 'UPDATE' THEN
|
|
240
|
+
INSERT INTO order_audits (order_id, operation, old_data, new_data)
|
|
241
|
+
VALUES (NEW.id, 'UPDATE', row_to_json(OLD), row_to_json(NEW));
|
|
242
|
+
RETURN NEW;
|
|
243
|
+
ELSIF TG_OP = 'INSERT' THEN
|
|
244
|
+
INSERT INTO order_audits (order_id, operation, new_data)
|
|
245
|
+
VALUES (NEW.id, 'INSERT', row_to_json(NEW));
|
|
246
|
+
RETURN NEW;
|
|
247
|
+
END IF;
|
|
248
|
+
END;
|
|
249
|
+
$$ LANGUAGE plpgsql;
|
|
250
|
+
|
|
251
|
+
-- Create trigger
|
|
252
|
+
CREATE TRIGGER order_audit_trigger
|
|
253
|
+
AFTER INSERT OR UPDATE OR DELETE ON orders
|
|
254
|
+
FOR EACH ROW
|
|
255
|
+
EXECUTE FUNCTION audit_order_changes();
|
|
256
|
+
SQL
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def down
|
|
260
|
+
execute <<-SQL
|
|
261
|
+
DROP TRIGGER IF EXISTS order_audit_trigger ON orders;
|
|
262
|
+
DROP FUNCTION IF EXISTS audit_order_changes();
|
|
263
|
+
DROP TABLE IF EXISTS order_audits;
|
|
264
|
+
SQL
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Combined Schema and Trigger Migrations
|
|
270
|
+
|
|
271
|
+
For convenience, PgSqlTriggers provides rake tasks that run both schema and trigger migrations together.
|
|
272
|
+
|
|
273
|
+
### Run Both Migrations
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
rake db:migrate:with_triggers
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This runs:
|
|
280
|
+
1. `rake db:migrate` (Rails schema migrations)
|
|
281
|
+
2. `rake trigger:migrate` (Trigger migrations)
|
|
282
|
+
|
|
283
|
+
### Rollback Both
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
rake db:rollback:with_triggers
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
This rolls back:
|
|
290
|
+
1. The most recent trigger migration
|
|
291
|
+
2. The most recent schema migration
|
|
292
|
+
|
|
293
|
+
### Check Status of Both
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
rake db:migrate:status:with_triggers
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Shows status of both schema and trigger migrations.
|
|
300
|
+
|
|
301
|
+
### Get Versions of Both
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
rake db:version:with_triggers
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Displays current versions of both migration types.
|
|
308
|
+
|
|
309
|
+
## Drift Detection
|
|
310
|
+
|
|
311
|
+
PgSqlTriggers automatically detects when the actual database state differs from your DSL definitions.
|
|
312
|
+
|
|
313
|
+
### Drift States
|
|
314
|
+
|
|
315
|
+
#### Managed & In Sync
|
|
316
|
+
The trigger exists in the database and matches the DSL definition exactly.
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
Status: ✓ Managed & In Sync
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Managed & Drifted
|
|
323
|
+
The trigger exists but its definition doesn't match the DSL (e.g., function modified outside PgSqlTriggers).
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
Status: ⚠ Managed & Drifted
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Actions:**
|
|
330
|
+
- Review the differences
|
|
331
|
+
- Update the DSL to match the database
|
|
332
|
+
- Re-apply the migration to restore the DSL definition
|
|
333
|
+
|
|
334
|
+
#### Manual Override
|
|
335
|
+
The trigger was modified outside of PgSqlTriggers (e.g., via direct SQL).
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
Status: ⚠ Manual Override
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Actions:**
|
|
342
|
+
- Document the manual changes
|
|
343
|
+
- Update the DSL to reflect the changes
|
|
344
|
+
- Create a new migration if needed
|
|
345
|
+
|
|
346
|
+
#### Disabled
|
|
347
|
+
The trigger is disabled via PgSqlTriggers.
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
Status: ○ Disabled
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Actions:**
|
|
354
|
+
- Enable via console or Web UI
|
|
355
|
+
- Verify the trigger is needed
|
|
356
|
+
|
|
357
|
+
#### Dropped
|
|
358
|
+
The trigger was dropped but still exists in the registry.
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
Status: ✗ Dropped
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Actions:**
|
|
365
|
+
- Re-apply the migration
|
|
366
|
+
- Remove from registry if no longer needed
|
|
367
|
+
|
|
368
|
+
#### Unknown
|
|
369
|
+
The trigger exists in the database but not in the PgSqlTriggers registry.
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
Status: ? Unknown
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Actions:**
|
|
376
|
+
- Add a DSL definition for the trigger
|
|
377
|
+
- Create a migration to bring it under management
|
|
378
|
+
- Drop it if it's no longer needed
|
|
379
|
+
|
|
380
|
+
### Checking for Drift
|
|
381
|
+
|
|
382
|
+
#### Via Console
|
|
383
|
+
|
|
384
|
+
```ruby
|
|
385
|
+
# Get drift information for all triggers
|
|
386
|
+
PgSqlTriggers::Registry.diff
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### Via Web UI
|
|
390
|
+
|
|
391
|
+
Navigate to the dashboard at `/pg_sql_triggers` to see drift status visually.
|
|
392
|
+
|
|
393
|
+
### Resolving Drift
|
|
394
|
+
|
|
395
|
+
1. **Review the Drift**: Understand what changed and why
|
|
396
|
+
2. **Choose Resolution**:
|
|
397
|
+
- Update DSL to match database
|
|
398
|
+
- Re-apply migration to match DSL
|
|
399
|
+
- Create new migration for intentional changes
|
|
400
|
+
3. **Verify**: Check that drift is resolved
|
|
401
|
+
|
|
402
|
+
Example:
|
|
403
|
+
|
|
404
|
+
```ruby
|
|
405
|
+
# Check current state
|
|
406
|
+
drift = PgSqlTriggers::Registry.diff
|
|
407
|
+
|
|
408
|
+
# Review specific trigger
|
|
409
|
+
trigger = PgSqlTriggers::Registry.find_by(trigger_name: "users_email_validation")
|
|
410
|
+
trigger.drift_status # => "drifted"
|
|
411
|
+
|
|
412
|
+
# Re-apply migration to fix drift
|
|
413
|
+
rake trigger:migrate:redo
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Next Steps
|
|
417
|
+
|
|
418
|
+
- [Web UI Documentation](web-ui.md) - Manage triggers through the web interface
|
|
419
|
+
- [Kill Switch](kill-switch.md) - Production safety features
|
|
420
|
+
- [API Reference](api-reference.md) - Console commands and programmatic access
|