pg_sql_triggers 1.0.0 → 1.1.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.
- checksums.yaml +4 -4
- data/.erb_lint.yml +47 -0
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +112 -1
- data/COVERAGE.md +58 -0
- data/Goal.md +450 -123
- data/README.md +53 -215
- data/app/controllers/pg_sql_triggers/application_controller.rb +46 -0
- data/app/controllers/pg_sql_triggers/dashboard_controller.rb +4 -1
- data/app/controllers/pg_sql_triggers/generator_controller.rb +76 -8
- data/app/controllers/pg_sql_triggers/migrations_controller.rb +18 -0
- data/app/models/pg_sql_triggers/trigger_registry.rb +93 -12
- 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 +22 -4
- data/app/views/pg_sql_triggers/generator/preview.html.erb +244 -16
- data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +221 -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/config/initializers/pg_sql_triggers.rb +69 -0
- data/db/migrate/20251222000001_create_pg_sql_triggers_tables.rb +3 -1
- data/db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb +8 -0
- data/docs/README.md +66 -0
- data/docs/api-reference.md +681 -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 +493 -0
- data/docs/web-ui.md +353 -0
- data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +3 -1
- data/lib/generators/pg_sql_triggers/templates/initializer.rb +44 -2
- data/lib/pg_sql_triggers/drift/db_queries.rb +116 -0
- data/lib/pg_sql_triggers/drift/detector.rb +187 -0
- data/lib/pg_sql_triggers/drift/reporter.rb +179 -0
- data/lib/pg_sql_triggers/drift.rb +14 -11
- data/lib/pg_sql_triggers/dsl/trigger_definition.rb +15 -1
- data/lib/pg_sql_triggers/generator/form.rb +3 -1
- data/lib/pg_sql_triggers/generator/service.rb +82 -26
- data/lib/pg_sql_triggers/migration.rb +1 -1
- data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +344 -0
- data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +143 -0
- data/lib/pg_sql_triggers/migrator/safety_validator.rb +258 -0
- data/lib/pg_sql_triggers/migrator.rb +85 -3
- data/lib/pg_sql_triggers/registry/manager.rb +100 -13
- 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/function_tester.rb +66 -24
- data/lib/pg_sql_triggers/testing/safe_executor.rb +23 -11
- data/lib/pg_sql_triggers/testing/syntax_validator.rb +24 -1
- data/lib/pg_sql_triggers/version.rb +1 -1
- data/lib/pg_sql_triggers.rb +24 -0
- data/lib/tasks/trigger_migrations.rake +33 -0
- data/scripts/generate_coverage_report.rb +129 -0
- metadata +45 -5
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
# Configuration Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for configuring PgSqlTriggers in your Rails application.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Configuration File](#configuration-file)
|
|
9
|
+
- [Core Settings](#core-settings)
|
|
10
|
+
- [Kill Switch Configuration](#kill-switch-configuration)
|
|
11
|
+
- [Permission System](#permission-system)
|
|
12
|
+
- [Environment Detection](#environment-detection)
|
|
13
|
+
- [Advanced Configuration](#advanced-configuration)
|
|
14
|
+
- [Examples](#examples)
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
PgSqlTriggers is configured through an initializer file. All configuration is done within the `PgSqlTriggers.configure` block.
|
|
19
|
+
|
|
20
|
+
## Configuration File
|
|
21
|
+
|
|
22
|
+
The default configuration file is created during installation:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# config/initializers/pg_sql_triggers.rb
|
|
26
|
+
PgSqlTriggers.configure do |config|
|
|
27
|
+
# Your configuration here
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Core Settings
|
|
32
|
+
|
|
33
|
+
### `default_environment`
|
|
34
|
+
|
|
35
|
+
Specifies how to detect the current environment.
|
|
36
|
+
|
|
37
|
+
- **Type**: Lambda/Proc
|
|
38
|
+
- **Default**: `-> { Rails.env }`
|
|
39
|
+
- **Returns**: String or Symbol
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
config.default_environment = -> { Rails.env }
|
|
43
|
+
|
|
44
|
+
# Custom environment detection
|
|
45
|
+
config.default_environment = -> {
|
|
46
|
+
ENV['APP_ENV'] || Rails.env
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Static environment
|
|
50
|
+
config.default_environment = -> { 'production' }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `mount_path`
|
|
54
|
+
|
|
55
|
+
Customize where the web UI is mounted (configured in routes, not initializer).
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# config/routes.rb
|
|
59
|
+
Rails.application.routes.draw do
|
|
60
|
+
mount PgSqlTriggers::Engine, at: "/admin/triggers"
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Kill Switch Configuration
|
|
65
|
+
|
|
66
|
+
### `kill_switch_enabled`
|
|
67
|
+
|
|
68
|
+
Master toggle for the kill switch system.
|
|
69
|
+
|
|
70
|
+
- **Type**: Boolean
|
|
71
|
+
- **Default**: `true`
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
# Enable kill switch (recommended)
|
|
75
|
+
config.kill_switch_enabled = true
|
|
76
|
+
|
|
77
|
+
# Disable kill switch (not recommended for production)
|
|
78
|
+
config.kill_switch_enabled = false
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `kill_switch_environments`
|
|
82
|
+
|
|
83
|
+
List of environments where the kill switch is active.
|
|
84
|
+
|
|
85
|
+
- **Type**: Array of Symbols
|
|
86
|
+
- **Default**: `[:production, :staging]`
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# Default: protect production and staging
|
|
90
|
+
config.kill_switch_environments = %i[production staging]
|
|
91
|
+
|
|
92
|
+
# Protect additional environments
|
|
93
|
+
config.kill_switch_environments = %i[production staging qa demo]
|
|
94
|
+
|
|
95
|
+
# Only protect production
|
|
96
|
+
config.kill_switch_environments = [:production]
|
|
97
|
+
|
|
98
|
+
# Protect all except development
|
|
99
|
+
config.kill_switch_environments = %i[production staging test qa uat]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `kill_switch_confirmation_required`
|
|
103
|
+
|
|
104
|
+
Whether confirmation text is required for overrides.
|
|
105
|
+
|
|
106
|
+
- **Type**: Boolean
|
|
107
|
+
- **Default**: `true`
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# Require confirmation text (recommended)
|
|
111
|
+
config.kill_switch_confirmation_required = true
|
|
112
|
+
|
|
113
|
+
# Only require ENV override (less safe)
|
|
114
|
+
config.kill_switch_confirmation_required = false
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `kill_switch_confirmation_pattern`
|
|
118
|
+
|
|
119
|
+
Defines the format of required confirmation text.
|
|
120
|
+
|
|
121
|
+
- **Type**: Lambda/Proc
|
|
122
|
+
- **Parameter**: `operation` (Symbol)
|
|
123
|
+
- **Returns**: String
|
|
124
|
+
- **Default**: `->(operation) { "EXECUTE #{operation.to_s.upcase}" }`
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# Default pattern
|
|
128
|
+
config.kill_switch_confirmation_pattern = ->(operation) {
|
|
129
|
+
"EXECUTE #{operation.to_s.upcase}"
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Include date
|
|
133
|
+
config.kill_switch_confirmation_pattern = ->(operation) {
|
|
134
|
+
date = Date.today.strftime('%Y%m%d')
|
|
135
|
+
"EXECUTE-#{operation.to_s.upcase}-#{date}"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Include environment
|
|
139
|
+
config.kill_switch_confirmation_pattern = ->(operation) {
|
|
140
|
+
env = Rails.env.upcase
|
|
141
|
+
"#{env}-#{operation.to_s.upcase}"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Custom prefix
|
|
145
|
+
config.kill_switch_confirmation_pattern = ->(operation) {
|
|
146
|
+
"CONFIRM-PRODUCTION-#{operation.to_s.upcase}"
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### `kill_switch_logger`
|
|
151
|
+
|
|
152
|
+
Logger for kill switch events.
|
|
153
|
+
|
|
154
|
+
- **Type**: Logger instance
|
|
155
|
+
- **Default**: `Rails.logger`
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
# Use Rails logger (default)
|
|
159
|
+
config.kill_switch_logger = Rails.logger
|
|
160
|
+
|
|
161
|
+
# Separate log file
|
|
162
|
+
config.kill_switch_logger = Logger.new(
|
|
163
|
+
Rails.root.join('log', 'kill_switch.log')
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Custom formatter
|
|
167
|
+
kill_switch_logger = Logger.new(Rails.root.join('log', 'kill_switch.log'))
|
|
168
|
+
kill_switch_logger.formatter = proc do |severity, datetime, progname, msg|
|
|
169
|
+
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{msg}\n"
|
|
170
|
+
end
|
|
171
|
+
config.kill_switch_logger = kill_switch_logger
|
|
172
|
+
|
|
173
|
+
# Multiple destinations
|
|
174
|
+
require 'logger'
|
|
175
|
+
config.kill_switch_logger = Logger.new(STDOUT)
|
|
176
|
+
config.kill_switch_logger.extend(ActiveSupport::Logger.broadcast(
|
|
177
|
+
Logger.new(Rails.root.join('log', 'kill_switch.log'))
|
|
178
|
+
))
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Permission System
|
|
182
|
+
|
|
183
|
+
### `permission_checker`
|
|
184
|
+
|
|
185
|
+
Custom authorization logic for the web UI and API.
|
|
186
|
+
|
|
187
|
+
- **Type**: Lambda/Proc
|
|
188
|
+
- **Parameters**:
|
|
189
|
+
- `actor` (Hash): Information about who is performing the action
|
|
190
|
+
- `action` (Symbol): The action being performed
|
|
191
|
+
- `environment` (String): Current environment
|
|
192
|
+
- **Returns**: Boolean
|
|
193
|
+
- **Default**: `->(_actor, _action, _environment) { true }`
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
# Default: allow all (development only!)
|
|
197
|
+
config.permission_checker = ->(_actor, _action, _environment) { true }
|
|
198
|
+
|
|
199
|
+
# Basic user check
|
|
200
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
201
|
+
user = User.find_by(id: actor[:id])
|
|
202
|
+
user.present?
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Role-based permissions
|
|
206
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
207
|
+
user = User.find_by(id: actor[:id])
|
|
208
|
+
return false unless user
|
|
209
|
+
|
|
210
|
+
case action
|
|
211
|
+
when :view
|
|
212
|
+
user.present?
|
|
213
|
+
when :operate
|
|
214
|
+
user.operator? || user.admin?
|
|
215
|
+
when :admin
|
|
216
|
+
user.admin?
|
|
217
|
+
else
|
|
218
|
+
false
|
|
219
|
+
end
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# Environment-specific permissions
|
|
223
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
224
|
+
user = User.find_by(id: actor[:id])
|
|
225
|
+
return false unless user
|
|
226
|
+
|
|
227
|
+
if environment.to_s == 'production'
|
|
228
|
+
# Stricter in production
|
|
229
|
+
user.admin?
|
|
230
|
+
else
|
|
231
|
+
# More permissive in other environments
|
|
232
|
+
user.developer? || user.admin?
|
|
233
|
+
end
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# Integration with Pundit
|
|
237
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
238
|
+
user = User.find_by(id: actor[:id])
|
|
239
|
+
policy = PgSqlTriggersPolicy.new(user, :pg_sql_triggers)
|
|
240
|
+
|
|
241
|
+
case action
|
|
242
|
+
when :view
|
|
243
|
+
policy.read?
|
|
244
|
+
when :operate
|
|
245
|
+
policy.operate?
|
|
246
|
+
when :admin
|
|
247
|
+
policy.admin?
|
|
248
|
+
else
|
|
249
|
+
false
|
|
250
|
+
end
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Integration with CanCanCan
|
|
254
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
255
|
+
user = User.find_by(id: actor[:id])
|
|
256
|
+
ability = Ability.new(user)
|
|
257
|
+
|
|
258
|
+
case action
|
|
259
|
+
when :view
|
|
260
|
+
ability.can?(:read, :pg_sql_triggers)
|
|
261
|
+
when :operate
|
|
262
|
+
ability.can?(:operate, :pg_sql_triggers)
|
|
263
|
+
when :admin
|
|
264
|
+
ability.can?(:admin, :pg_sql_triggers)
|
|
265
|
+
else
|
|
266
|
+
false
|
|
267
|
+
end
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Permission Levels
|
|
272
|
+
|
|
273
|
+
The permission checker should handle three levels:
|
|
274
|
+
|
|
275
|
+
#### `:view` (Read-Only)
|
|
276
|
+
- View triggers and status
|
|
277
|
+
- View migrations
|
|
278
|
+
- View drift information
|
|
279
|
+
- Access console read-only methods
|
|
280
|
+
|
|
281
|
+
#### `:operate`
|
|
282
|
+
- All `:view` permissions
|
|
283
|
+
- Enable/disable triggers
|
|
284
|
+
- Run migrations
|
|
285
|
+
- Apply generated triggers
|
|
286
|
+
|
|
287
|
+
#### `:admin`
|
|
288
|
+
- All `:operate` permissions
|
|
289
|
+
- Drop triggers
|
|
290
|
+
- Execute SQL capsules
|
|
291
|
+
- Modify registry directly
|
|
292
|
+
|
|
293
|
+
## Environment Detection
|
|
294
|
+
|
|
295
|
+
### Custom Environment Logic
|
|
296
|
+
|
|
297
|
+
```ruby
|
|
298
|
+
# Use environment variable
|
|
299
|
+
config.default_environment = -> {
|
|
300
|
+
ENV['DEPLOYMENT_ENV'] || Rails.env
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# Detect from hostname
|
|
304
|
+
config.default_environment = -> {
|
|
305
|
+
hostname = Socket.gethostname
|
|
306
|
+
case hostname
|
|
307
|
+
when /prod/
|
|
308
|
+
'production'
|
|
309
|
+
when /staging/
|
|
310
|
+
'staging'
|
|
311
|
+
else
|
|
312
|
+
Rails.env
|
|
313
|
+
end
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
# Use Rails credentials
|
|
317
|
+
config.default_environment = -> {
|
|
318
|
+
Rails.application.credentials.environment || Rails.env
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Advanced Configuration
|
|
323
|
+
|
|
324
|
+
### Complete Example
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
# config/initializers/pg_sql_triggers.rb
|
|
328
|
+
PgSqlTriggers.configure do |config|
|
|
329
|
+
# Environment Detection
|
|
330
|
+
config.default_environment = -> { Rails.env }
|
|
331
|
+
|
|
332
|
+
# Kill Switch Settings
|
|
333
|
+
config.kill_switch_enabled = true
|
|
334
|
+
config.kill_switch_environments = %i[production staging]
|
|
335
|
+
config.kill_switch_confirmation_required = true
|
|
336
|
+
|
|
337
|
+
# Custom confirmation pattern with date
|
|
338
|
+
config.kill_switch_confirmation_pattern = ->(operation) {
|
|
339
|
+
date = Date.today.strftime('%Y%m%d')
|
|
340
|
+
"EXECUTE-#{operation.to_s.upcase}-#{date}"
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
# Dedicated kill switch logger
|
|
344
|
+
kill_switch_logger = Logger.new(
|
|
345
|
+
Rails.root.join('log', 'kill_switch.log'),
|
|
346
|
+
10, # Keep 10 old log files
|
|
347
|
+
1024 * 1024 * 10 # 10 MB per file
|
|
348
|
+
)
|
|
349
|
+
kill_switch_logger.formatter = proc do |severity, datetime, progname, msg|
|
|
350
|
+
"[#{datetime.iso8601}] [#{severity}] #{msg}\n"
|
|
351
|
+
end
|
|
352
|
+
config.kill_switch_logger = kill_switch_logger
|
|
353
|
+
|
|
354
|
+
# Role-based permission system
|
|
355
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
356
|
+
user = User.find_by(id: actor[:id])
|
|
357
|
+
return false unless user
|
|
358
|
+
|
|
359
|
+
case action
|
|
360
|
+
when :view
|
|
361
|
+
user.has_role?(:viewer, :operator, :admin)
|
|
362
|
+
when :operate
|
|
363
|
+
user.has_role?(:operator, :admin)
|
|
364
|
+
when :admin
|
|
365
|
+
user.has_role?(:admin)
|
|
366
|
+
else
|
|
367
|
+
false
|
|
368
|
+
end
|
|
369
|
+
}
|
|
370
|
+
end
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Environment-Specific Configuration
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
# config/initializers/pg_sql_triggers.rb
|
|
377
|
+
PgSqlTriggers.configure do |config|
|
|
378
|
+
# Common settings
|
|
379
|
+
config.default_environment = -> { Rails.env }
|
|
380
|
+
config.kill_switch_enabled = true
|
|
381
|
+
|
|
382
|
+
# Environment-specific settings
|
|
383
|
+
case Rails.env.to_sym
|
|
384
|
+
when :production
|
|
385
|
+
config.kill_switch_environments = [:production]
|
|
386
|
+
config.kill_switch_confirmation_required = true
|
|
387
|
+
config.kill_switch_confirmation_pattern = ->(op) {
|
|
388
|
+
"PRODUCTION-EXECUTE-#{op.to_s.upcase}-#{SecureRandom.hex(2)}"
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
392
|
+
user = User.find_by(id: actor[:id])
|
|
393
|
+
user&.admin? # Only admins in production
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
when :staging
|
|
397
|
+
config.kill_switch_environments = [:staging]
|
|
398
|
+
config.kill_switch_confirmation_required = true
|
|
399
|
+
config.kill_switch_confirmation_pattern = ->(op) {
|
|
400
|
+
"STAGING-#{op.to_s.upcase}"
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
404
|
+
user = User.find_by(id: actor[:id])
|
|
405
|
+
user&.developer? || user&.admin?
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
when :development
|
|
409
|
+
config.kill_switch_environments = []
|
|
410
|
+
config.kill_switch_confirmation_required = false
|
|
411
|
+
|
|
412
|
+
config.permission_checker = ->(_actor, _action, _environment) { true }
|
|
413
|
+
|
|
414
|
+
when :test
|
|
415
|
+
config.kill_switch_enabled = false
|
|
416
|
+
config.permission_checker = ->(_actor, _action, _environment) { true }
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Examples
|
|
422
|
+
|
|
423
|
+
### Production-Grade Configuration
|
|
424
|
+
|
|
425
|
+
```ruby
|
|
426
|
+
PgSqlTriggers.configure do |config|
|
|
427
|
+
# Use Rails environment
|
|
428
|
+
config.default_environment = -> { Rails.env }
|
|
429
|
+
|
|
430
|
+
# Enable kill switch
|
|
431
|
+
config.kill_switch_enabled = true
|
|
432
|
+
config.kill_switch_environments = %i[production staging]
|
|
433
|
+
config.kill_switch_confirmation_required = true
|
|
434
|
+
|
|
435
|
+
# Date-based confirmation
|
|
436
|
+
config.kill_switch_confirmation_pattern = ->(operation) {
|
|
437
|
+
"EXECUTE-#{operation.to_s.upcase}-#{Date.today.strftime('%Y%m%d')}"
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
# Structured logging
|
|
441
|
+
config.kill_switch_logger = ActiveSupport::TaggedLogging.new(
|
|
442
|
+
Logger.new(Rails.root.join('log', 'kill_switch.log'))
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Integration with existing authorization
|
|
446
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
447
|
+
user = User.find_by(id: actor[:id])
|
|
448
|
+
return false unless user
|
|
449
|
+
|
|
450
|
+
policy = PgSqlTriggersPolicy.new(user, :triggers)
|
|
451
|
+
|
|
452
|
+
case action
|
|
453
|
+
when :view
|
|
454
|
+
policy.read?
|
|
455
|
+
when :operate
|
|
456
|
+
policy.write?
|
|
457
|
+
when :admin
|
|
458
|
+
policy.admin?
|
|
459
|
+
else
|
|
460
|
+
false
|
|
461
|
+
end
|
|
462
|
+
}
|
|
463
|
+
end
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Development-Friendly Configuration
|
|
467
|
+
|
|
468
|
+
```ruby
|
|
469
|
+
PgSqlTriggers.configure do |config|
|
|
470
|
+
config.default_environment = -> { Rails.env }
|
|
471
|
+
|
|
472
|
+
if Rails.env.development?
|
|
473
|
+
# Disabled kill switch for development
|
|
474
|
+
config.kill_switch_enabled = false
|
|
475
|
+
config.permission_checker = ->(_actor, _action, _environment) { true }
|
|
476
|
+
else
|
|
477
|
+
# Standard production settings
|
|
478
|
+
config.kill_switch_enabled = true
|
|
479
|
+
config.kill_switch_environments = %i[production staging]
|
|
480
|
+
config.kill_switch_confirmation_required = true
|
|
481
|
+
|
|
482
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
483
|
+
user = User.find_by(id: actor[:id])
|
|
484
|
+
user&.admin?
|
|
485
|
+
}
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Multi-Tenant Configuration
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
PgSqlTriggers.configure do |config|
|
|
494
|
+
# Detect environment from tenant
|
|
495
|
+
config.default_environment = -> {
|
|
496
|
+
tenant = Apartment::Tenant.current
|
|
497
|
+
tenant == 'production_tenant' ? 'production' : Rails.env
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
config.kill_switch_enabled = true
|
|
501
|
+
config.kill_switch_environments = [:production]
|
|
502
|
+
|
|
503
|
+
# Tenant-aware permissions
|
|
504
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
505
|
+
user = User.find_by(id: actor[:id])
|
|
506
|
+
return false unless user
|
|
507
|
+
|
|
508
|
+
tenant = Apartment::Tenant.current
|
|
509
|
+
|
|
510
|
+
# Check user has permission for current tenant
|
|
511
|
+
user.can_access_tenant?(tenant) && user.has_permission?(action)
|
|
512
|
+
}
|
|
513
|
+
end
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Configuration Validation
|
|
517
|
+
|
|
518
|
+
Verify your configuration is correct:
|
|
519
|
+
|
|
520
|
+
```ruby
|
|
521
|
+
# In Rails console
|
|
522
|
+
PgSqlTriggers.configuration.kill_switch_enabled
|
|
523
|
+
# => true
|
|
524
|
+
|
|
525
|
+
PgSqlTriggers.configuration.kill_switch_environments
|
|
526
|
+
# => [:production, :staging]
|
|
527
|
+
|
|
528
|
+
PgSqlTriggers.configuration.default_environment.call
|
|
529
|
+
# => "development"
|
|
530
|
+
|
|
531
|
+
# Test permission checker
|
|
532
|
+
actor = { id: 1, type: 'console' }
|
|
533
|
+
PgSqlTriggers.configuration.permission_checker.call(actor, :view, 'production')
|
|
534
|
+
# => true or false
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## Next Steps
|
|
538
|
+
|
|
539
|
+
- [Kill Switch](kill-switch.md) - Understand production safety features
|
|
540
|
+
- [Web UI](web-ui.md) - Configure the web interface
|
|
541
|
+
- [Getting Started](getting-started.md) - Initial setup guide
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Getting Started with PgSqlTriggers
|
|
2
|
+
|
|
3
|
+
This guide will help you install and set up PgSqlTriggers in your Rails application.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### 1. Add the Gem
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'pg_sql_triggers'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. Install Dependencies
|
|
16
|
+
|
|
17
|
+
Execute the bundle command:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 3. Run the Installer
|
|
24
|
+
|
|
25
|
+
Run the installation generator:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
rails generate pg_sql_triggers:install
|
|
29
|
+
rails db:migrate
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This will:
|
|
33
|
+
1. Create an initializer at `config/initializers/pg_sql_triggers.rb`
|
|
34
|
+
2. Create migrations for the registry table
|
|
35
|
+
3. Mount the engine at `/pg_sql_triggers`
|
|
36
|
+
|
|
37
|
+
## Verify Installation
|
|
38
|
+
|
|
39
|
+
After installation, you should have:
|
|
40
|
+
|
|
41
|
+
- **Initializer**: `config/initializers/pg_sql_triggers.rb` with default configuration
|
|
42
|
+
- **Registry Table**: `pg_sql_triggers_registry` table in your database
|
|
43
|
+
- **Web UI**: Accessible at `http://localhost:3000/pg_sql_triggers`
|
|
44
|
+
- **Trigger Directory**: `db/triggers/` for storing trigger migrations
|
|
45
|
+
|
|
46
|
+
## Quick Start Example
|
|
47
|
+
|
|
48
|
+
### 1. Create Your First Trigger Definition
|
|
49
|
+
|
|
50
|
+
Create a trigger definition file:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# app/triggers/users_email_validation.rb
|
|
54
|
+
PgSqlTriggers::DSL.pg_sql_trigger "users_email_validation" do
|
|
55
|
+
table :users
|
|
56
|
+
on :insert, :update
|
|
57
|
+
function :validate_user_email
|
|
58
|
+
|
|
59
|
+
version 1
|
|
60
|
+
enabled false
|
|
61
|
+
|
|
62
|
+
when_env :production
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
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
|
+
Edit the generated migration in `db/triggers/`:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
# db/triggers/YYYYMMDDHHMMSS_add_email_validation.rb
|
|
78
|
+
class AddEmailValidation < PgSqlTriggers::Migration
|
|
79
|
+
def up
|
|
80
|
+
execute <<-SQL
|
|
81
|
+
CREATE OR REPLACE FUNCTION validate_user_email()
|
|
82
|
+
RETURNS TRIGGER AS $$
|
|
83
|
+
BEGIN
|
|
84
|
+
IF NEW.email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
|
|
85
|
+
RAISE EXCEPTION 'Invalid email format';
|
|
86
|
+
END IF;
|
|
87
|
+
RETURN NEW;
|
|
88
|
+
END;
|
|
89
|
+
$$ LANGUAGE plpgsql;
|
|
90
|
+
|
|
91
|
+
CREATE TRIGGER user_email_validation
|
|
92
|
+
BEFORE INSERT OR UPDATE ON users
|
|
93
|
+
FOR EACH ROW
|
|
94
|
+
EXECUTE FUNCTION validate_user_email();
|
|
95
|
+
SQL
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def down
|
|
99
|
+
execute <<-SQL
|
|
100
|
+
DROP TRIGGER IF EXISTS user_email_validation ON users;
|
|
101
|
+
DROP FUNCTION IF EXISTS validate_user_email();
|
|
102
|
+
SQL
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3. Run the Migration
|
|
108
|
+
|
|
109
|
+
Apply the trigger migration:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
rake trigger:migrate
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 4. Access the Web UI
|
|
116
|
+
|
|
117
|
+
Open your browser and navigate to:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
http://localhost:3000/pg_sql_triggers
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
You should see your trigger listed in the dashboard.
|
|
124
|
+
|
|
125
|
+
## Next Steps
|
|
126
|
+
|
|
127
|
+
- [Usage Guide](usage-guide.md) - Learn about the DSL and migration system
|
|
128
|
+
- [Web UI Documentation](web-ui.md) - Explore the web dashboard features
|
|
129
|
+
- [Kill Switch](kill-switch.md) - Understand production safety features
|
|
130
|
+
- [Configuration](configuration.md) - Configure advanced settings
|
|
131
|
+
- [API Reference](api-reference.md) - Console commands and programmatic access
|
|
132
|
+
|
|
133
|
+
## Examples Repository
|
|
134
|
+
|
|
135
|
+
For working examples and a complete demonstration of PgSqlTriggers in action, check out the [example repository](https://github.com/samaswin87/pg_triggers_example).
|