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
data/README.md
CHANGED
|
@@ -21,37 +21,28 @@ Rails teams use PostgreSQL triggers for data integrity, performance, and billing
|
|
|
21
21
|
- UI control
|
|
22
22
|
- Emergency SQL escape hatches
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Requirements
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
- **Ruby 3.0+**
|
|
27
|
+
- **Rails 6.1+**
|
|
28
|
+
- **PostgreSQL** (any supported version)
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
gem 'pg_sql_triggers'
|
|
30
|
-
```
|
|
30
|
+
## Quick Start
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
### Installation
|
|
33
33
|
|
|
34
|
-
```
|
|
35
|
-
|
|
34
|
+
```ruby
|
|
35
|
+
# Gemfile
|
|
36
|
+
gem 'pg_sql_triggers'
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
Run the installer:
|
|
39
|
-
|
|
40
39
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
bundle install
|
|
41
|
+
rails generate pg_sql_triggers:install
|
|
42
|
+
rails db:migrate
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
1. Create an initializer at `config/initializers/pg_sql_triggers.rb`
|
|
47
|
-
2. Create migrations for registry table
|
|
48
|
-
3. Mount the engine at `/pg_sql_triggers`
|
|
49
|
-
|
|
50
|
-
## Usage
|
|
51
|
-
|
|
52
|
-
### 1. Declaring Triggers
|
|
53
|
-
|
|
54
|
-
Create trigger definitions using the Ruby DSL:
|
|
45
|
+
### Define a Trigger
|
|
55
46
|
|
|
56
47
|
```ruby
|
|
57
48
|
# app/triggers/users_email_validation.rb
|
|
@@ -59,222 +50,59 @@ PgSqlTriggers::DSL.pg_sql_trigger "users_email_validation" do
|
|
|
59
50
|
table :users
|
|
60
51
|
on :insert, :update
|
|
61
52
|
function :validate_user_email
|
|
62
|
-
|
|
63
53
|
version 1
|
|
64
54
|
enabled false
|
|
65
|
-
|
|
66
55
|
when_env :production
|
|
67
56
|
end
|
|
68
57
|
```
|
|
69
58
|
|
|
70
|
-
###
|
|
71
|
-
|
|
72
|
-
Generate and run trigger migrations similar to Rails schema migrations:
|
|
59
|
+
### Create and Run Migration
|
|
73
60
|
|
|
74
61
|
```bash
|
|
75
|
-
|
|
76
|
-
rails generate trigger:migration add_validation_trigger
|
|
77
|
-
|
|
78
|
-
# Run pending trigger migrations
|
|
62
|
+
rails generate trigger:migration add_email_validation
|
|
79
63
|
rake trigger:migrate
|
|
80
|
-
|
|
81
|
-
# Rollback last trigger migration
|
|
82
|
-
rake trigger:rollback
|
|
83
|
-
|
|
84
|
-
# Rollback multiple steps
|
|
85
|
-
rake trigger:rollback STEP=3
|
|
86
|
-
|
|
87
|
-
# Check migration status
|
|
88
|
-
rake trigger:migrate:status
|
|
89
|
-
|
|
90
|
-
# Run a specific migration up
|
|
91
|
-
rake trigger:migrate:up VERSION=20231215120000
|
|
92
|
-
|
|
93
|
-
# Run a specific migration down
|
|
94
|
-
rake trigger:migrate:down VERSION=20231215120000
|
|
95
|
-
|
|
96
|
-
# Redo last migration
|
|
97
|
-
rake trigger:migrate:redo
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
**Web UI Migration Management:**
|
|
101
|
-
|
|
102
|
-
You can also manage migrations directly from the web dashboard:
|
|
103
|
-
|
|
104
|
-
- **Apply All Pending Migrations**: Click the "Apply All Pending Migrations" button to run all pending migrations at once
|
|
105
|
-
- **Rollback Last Migration**: Use the "Rollback Last Migration" button to undo the most recent migration
|
|
106
|
-
- **Redo Last Migration**: Click "Redo Last Migration" to rollback and re-apply the last migration
|
|
107
|
-
- **Individual Migration Actions**: Each migration in the status table has individual "Up", "Down", or "Redo" buttons for granular control
|
|
108
|
-
|
|
109
|
-
All migration actions include confirmation dialogs and provide feedback via flash messages.
|
|
110
|
-
|
|
111
|
-
Trigger migrations are stored in `db/triggers/` and follow the same naming convention as Rails migrations (`YYYYMMDDHHMMSS_name.rb`).
|
|
112
|
-
|
|
113
|
-
Example trigger migration:
|
|
114
|
-
|
|
115
|
-
```ruby
|
|
116
|
-
# db/triggers/20231215120000_add_validation_trigger.rb
|
|
117
|
-
class AddValidationTrigger < PgSqlTriggers::Migration
|
|
118
|
-
def up
|
|
119
|
-
execute <<-SQL
|
|
120
|
-
CREATE OR REPLACE FUNCTION validate_user_email()
|
|
121
|
-
RETURNS TRIGGER AS $$
|
|
122
|
-
BEGIN
|
|
123
|
-
IF NEW.email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
|
|
124
|
-
RAISE EXCEPTION 'Invalid email format';
|
|
125
|
-
END IF;
|
|
126
|
-
RETURN NEW;
|
|
127
|
-
END;
|
|
128
|
-
$$ LANGUAGE plpgsql;
|
|
129
|
-
|
|
130
|
-
CREATE TRIGGER user_email_validation
|
|
131
|
-
BEFORE INSERT OR UPDATE ON users
|
|
132
|
-
FOR EACH ROW
|
|
133
|
-
EXECUTE FUNCTION validate_user_email();
|
|
134
|
-
SQL
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def down
|
|
138
|
-
execute <<-SQL
|
|
139
|
-
DROP TRIGGER IF EXISTS user_email_validation ON users;
|
|
140
|
-
DROP FUNCTION IF EXISTS validate_user_email();
|
|
141
|
-
SQL
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
64
|
```
|
|
145
65
|
|
|
146
|
-
###
|
|
66
|
+
### Access the Web UI
|
|
147
67
|
|
|
148
|
-
|
|
68
|
+
Navigate to `http://localhost:3000/pg_sql_triggers` to manage triggers visually.
|
|
149
69
|
|
|
150
|
-
|
|
151
|
-
# Run both schema and trigger migrations
|
|
152
|
-
rake db:migrate:with_triggers
|
|
70
|
+
Screenshots are available in the [docs/screenshots](docs/screenshots/) directory.
|
|
153
71
|
|
|
154
|
-
|
|
155
|
-
rake db:rollback:with_triggers
|
|
72
|
+
## Documentation
|
|
156
73
|
|
|
157
|
-
|
|
158
|
-
rake db:migrate:status:with_triggers
|
|
74
|
+
Comprehensive documentation is available in the [docs](docs/) directory:
|
|
159
75
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
76
|
+
- **[Getting Started](docs/getting-started.md)** - Installation and basic setup
|
|
77
|
+
- **[Usage Guide](docs/usage-guide.md)** - DSL syntax, migrations, and drift detection
|
|
78
|
+
- **[Web UI](docs/web-ui.md)** - Using the web dashboard
|
|
79
|
+
- **[Kill Switch](docs/kill-switch.md)** - Production safety features
|
|
80
|
+
- **[Configuration](docs/configuration.md)** - Complete configuration reference
|
|
81
|
+
- **[API Reference](docs/api-reference.md)** - Console API and programmatic access
|
|
163
82
|
|
|
164
|
-
|
|
83
|
+
## Key Features
|
|
165
84
|
|
|
166
|
-
|
|
85
|
+
### Trigger DSL
|
|
86
|
+
Define triggers using a Rails-native Ruby DSL with versioning and environment control.
|
|
167
87
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
PgSqlTriggers::Registry.list
|
|
171
|
-
|
|
172
|
-
# List enabled triggers
|
|
173
|
-
PgSqlTriggers::Registry.enabled
|
|
88
|
+
### Migration System
|
|
89
|
+
Manage trigger functions and definitions with a migration system similar to Rails schema migrations.
|
|
174
90
|
|
|
175
|
-
|
|
176
|
-
|
|
91
|
+
### Drift Detection
|
|
92
|
+
Automatically detect when database triggers drift from your DSL definitions.
|
|
177
93
|
|
|
178
|
-
|
|
179
|
-
|
|
94
|
+
### Production Kill Switch
|
|
95
|
+
Multi-layered safety mechanism preventing accidental destructive operations in production environments.
|
|
180
96
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# Validate all triggers
|
|
185
|
-
PgSqlTriggers::Registry.validate!
|
|
186
|
-
```
|
|
97
|
+
### Web Dashboard
|
|
98
|
+
Visual interface for managing triggers, running migrations, and executing SQL capsules.
|
|
187
99
|
|
|
188
|
-
###
|
|
100
|
+
### Permissions
|
|
101
|
+
Three-tier permission system (Viewer, Operator, Admin) with customizable authorization.
|
|
189
102
|
|
|
190
|
-
|
|
103
|
+
## Examples
|
|
191
104
|
|
|
192
|
-
|
|
193
|
-
- Enable/disable triggers
|
|
194
|
-
- View drift states
|
|
195
|
-
- Execute SQL capsules
|
|
196
|
-
- Manage trigger lifecycle
|
|
197
|
-
- **Run trigger migrations** (up/down/redo) directly from the dashboard
|
|
198
|
-
- Apply all pending migrations with a single click
|
|
199
|
-
- Rollback the last migration
|
|
200
|
-
- Redo the last migration
|
|
201
|
-
- Individual migration controls for each migration in the status table
|
|
202
|
-
|
|
203
|
-
<img width="3360" height="2506" alt="screencapture-localhost-3000-pg-triggers-2025-12-27-17_04_29" src="https://github.com/user-attachments/assets/a7f5904b-1172-41fc-ba3f-c05587cb1fe8" />
|
|
204
|
-
|
|
205
|
-
<img width="3360" height="3420" alt="screencapture-localhost-3000-pg-triggers-generator-new-2025-12-27-17_04_49" src="https://github.com/user-attachments/assets/fc9e53f2-f540-489d-8e41-6075dab8d731" />
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
### 6. Permissions
|
|
209
|
-
|
|
210
|
-
PgSqlTriggers supports three permission levels:
|
|
211
|
-
|
|
212
|
-
- **Viewer**: Read-only access (view triggers, diffs)
|
|
213
|
-
- **Operator**: Can enable/disable triggers, apply generated triggers
|
|
214
|
-
- **Admin**: Full access including dropping triggers and executing SQL
|
|
215
|
-
|
|
216
|
-
Configure custom permission checking:
|
|
217
|
-
|
|
218
|
-
```ruby
|
|
219
|
-
# config/initializers/pg_sql_triggers.rb
|
|
220
|
-
PgSqlTriggers.configure do |config|
|
|
221
|
-
config.permission_checker = ->(actor, action, environment) {
|
|
222
|
-
# Your custom permission logic
|
|
223
|
-
user = User.find(actor[:id])
|
|
224
|
-
user.has_permission?(action)
|
|
225
|
-
}
|
|
226
|
-
end
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### 7. Drift Detection
|
|
230
|
-
|
|
231
|
-
PgSqlTriggers automatically detects drift between your DSL definitions and the actual database state:
|
|
232
|
-
|
|
233
|
-
- **Managed & In Sync**: Trigger matches DSL definition
|
|
234
|
-
- **Managed & Drifted**: Trigger exists but doesn't match DSL
|
|
235
|
-
- **Manual Override**: Trigger was modified outside of PgSqlTriggers
|
|
236
|
-
- **Disabled**: Trigger is disabled
|
|
237
|
-
- **Dropped**: Trigger was dropped but still in registry
|
|
238
|
-
- **Unknown**: Trigger exists in DB but not in registry
|
|
239
|
-
|
|
240
|
-
### 8. Production Kill Switch
|
|
241
|
-
|
|
242
|
-
By default, PgSqlTriggers blocks destructive operations in production:
|
|
243
|
-
|
|
244
|
-
```ruby
|
|
245
|
-
# config/initializers/pg_sql_triggers.rb
|
|
246
|
-
PgSqlTriggers.configure do |config|
|
|
247
|
-
# Enable production kill switch (default: true)
|
|
248
|
-
config.kill_switch_enabled = true
|
|
249
|
-
end
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Override for specific operations:
|
|
253
|
-
|
|
254
|
-
```ruby
|
|
255
|
-
PgSqlTriggers::SQL.override_kill_switch do
|
|
256
|
-
# Dangerous operation here
|
|
257
|
-
end
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
## Configuration
|
|
261
|
-
|
|
262
|
-
```ruby
|
|
263
|
-
# config/initializers/pg_sql_triggers.rb
|
|
264
|
-
PgSqlTriggers.configure do |config|
|
|
265
|
-
# Kill switch for production (default: true)
|
|
266
|
-
config.kill_switch_enabled = true
|
|
267
|
-
|
|
268
|
-
# Environment detection (default: -> { Rails.env })
|
|
269
|
-
config.default_environment = -> { Rails.env }
|
|
270
|
-
|
|
271
|
-
# Custom permission checker
|
|
272
|
-
config.permission_checker = ->(actor, action, environment) {
|
|
273
|
-
# Return true/false based on your authorization logic
|
|
274
|
-
true
|
|
275
|
-
}
|
|
276
|
-
end
|
|
277
|
-
```
|
|
105
|
+
For working examples and complete demonstrations, check out the [example repository](https://github.com/samaswin87/pg_triggers_example).
|
|
278
106
|
|
|
279
107
|
## Core Principles
|
|
280
108
|
|
|
@@ -285,10 +113,20 @@ end
|
|
|
285
113
|
|
|
286
114
|
## Development
|
|
287
115
|
|
|
288
|
-
After checking out the repo, run `bin/setup` to install dependencies.
|
|
116
|
+
After checking out the repo, run `bin/setup` to install dependencies. Run `rake spec` to run tests. Run `bin/console` for an interactive prompt.
|
|
117
|
+
|
|
118
|
+
To install this gem locally, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and run `bundle exec rake release`.
|
|
289
119
|
|
|
290
|
-
|
|
120
|
+
## Test Coverage
|
|
121
|
+
|
|
122
|
+
See [COVERAGE.md](COVERAGE.md) for detailed coverage information.
|
|
123
|
+
|
|
124
|
+
**Total Coverage: 84.97%**
|
|
291
125
|
|
|
292
126
|
## Contributing
|
|
293
127
|
|
|
294
128
|
Bug reports and pull requests are welcome on GitHub at https://github.com/samaswin87/pg_sql_triggers.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
See [LICENSE](LICENSE) file for details.
|
|
@@ -9,6 +9,9 @@ module PgSqlTriggers
|
|
|
9
9
|
|
|
10
10
|
before_action :check_permissions?
|
|
11
11
|
|
|
12
|
+
# Helper methods available in views
|
|
13
|
+
helper_method :current_environment, :kill_switch_active?, :expected_confirmation_text
|
|
14
|
+
|
|
12
15
|
private
|
|
13
16
|
|
|
14
17
|
def check_permissions?
|
|
@@ -31,5 +34,48 @@ module PgSqlTriggers
|
|
|
31
34
|
def current_user_id
|
|
32
35
|
"unknown"
|
|
33
36
|
end
|
|
37
|
+
|
|
38
|
+
# ========== Kill Switch Helpers ==========
|
|
39
|
+
|
|
40
|
+
# Returns the current environment
|
|
41
|
+
def current_environment
|
|
42
|
+
Rails.env
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Checks if kill switch is active for the current environment
|
|
46
|
+
def kill_switch_active?
|
|
47
|
+
PgSqlTriggers::SQL::KillSwitch.active?(environment: current_environment)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Checks kill switch before executing a dangerous operation
|
|
51
|
+
# Raises KillSwitchError if the operation is blocked
|
|
52
|
+
#
|
|
53
|
+
# @param operation [Symbol] The operation being performed
|
|
54
|
+
# @param confirmation [String, nil] Optional confirmation text from params
|
|
55
|
+
def check_kill_switch(operation:, confirmation: nil)
|
|
56
|
+
PgSqlTriggers::SQL::KillSwitch.check!(
|
|
57
|
+
operation: operation,
|
|
58
|
+
environment: current_environment,
|
|
59
|
+
confirmation: confirmation,
|
|
60
|
+
actor: current_actor
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Before action to require kill switch override for an action
|
|
65
|
+
# Add to specific controller actions that need protection:
|
|
66
|
+
# before_action -> { require_kill_switch_override(:operation_name) }, only: [:dangerous_action]
|
|
67
|
+
def require_kill_switch_override(operation, confirmation: nil)
|
|
68
|
+
check_kill_switch(operation: operation, confirmation: confirmation)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns the expected confirmation text for an operation (for use in views)
|
|
72
|
+
def expected_confirmation_text(operation)
|
|
73
|
+
if PgSqlTriggers.respond_to?(:kill_switch_confirmation_pattern) &&
|
|
74
|
+
PgSqlTriggers.kill_switch_confirmation_pattern.respond_to?(:call)
|
|
75
|
+
PgSqlTriggers.kill_switch_confirmation_pattern.call(operation)
|
|
76
|
+
else
|
|
77
|
+
"EXECUTE #{operation.to_s.upcase}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
34
80
|
end
|
|
35
81
|
end
|
|
@@ -4,11 +4,14 @@ module PgSqlTriggers
|
|
|
4
4
|
class DashboardController < ApplicationController
|
|
5
5
|
def index
|
|
6
6
|
@triggers = PgSqlTriggers::TriggerRegistry.order(created_at: :desc)
|
|
7
|
+
|
|
8
|
+
# Get drift summary
|
|
9
|
+
drift_summary = PgSqlTriggers::Drift::Reporter.summary
|
|
7
10
|
@stats = {
|
|
8
11
|
total: @triggers.count,
|
|
9
12
|
enabled: @triggers.enabled.count,
|
|
10
13
|
disabled: @triggers.disabled.count,
|
|
11
|
-
drifted:
|
|
14
|
+
drifted: drift_summary[:drifted]
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
# Migration status with pagination
|
|
@@ -8,7 +8,16 @@ module PgSqlTriggers
|
|
|
8
8
|
# GET /generator/new
|
|
9
9
|
# Display the multi-step form wizard
|
|
10
10
|
def new
|
|
11
|
-
|
|
11
|
+
# Restore form data from session if available (when user clicks "Back to Edit")
|
|
12
|
+
session_data = session[:generator_form_data]
|
|
13
|
+
if session_data
|
|
14
|
+
# Convert to hash and symbolize keys for Form initialization
|
|
15
|
+
form_data = session_data.is_a?(Hash) ? session_data.symbolize_keys : session_data.to_h.symbolize_keys
|
|
16
|
+
@form = PgSqlTriggers::Generator::Form.new(form_data)
|
|
17
|
+
session.delete(:generator_form_data)
|
|
18
|
+
else
|
|
19
|
+
@form = PgSqlTriggers::Generator::Form.new
|
|
20
|
+
end
|
|
12
21
|
@available_tables = fetch_available_tables
|
|
13
22
|
end
|
|
14
23
|
|
|
@@ -17,7 +26,19 @@ module PgSqlTriggers
|
|
|
17
26
|
def preview
|
|
18
27
|
@form = PgSqlTriggers::Generator::Form.new(generator_params)
|
|
19
28
|
|
|
29
|
+
# If user clicked "Back to Edit", store form data in session and redirect
|
|
30
|
+
if params[:back_to_edit].present?
|
|
31
|
+
# Store form data as a hash (convert ActionController::Parameters to hash)
|
|
32
|
+
session[:generator_form_data] = generator_params.to_h
|
|
33
|
+
redirect_to new_generator_path
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
20
37
|
if @form.valid?
|
|
38
|
+
# Store form data in session so it can be restored if user clicks "Back to Edit"
|
|
39
|
+
# Convert ActionController::Parameters to hash
|
|
40
|
+
session[:generator_form_data] = generator_params.to_h
|
|
41
|
+
|
|
21
42
|
# Validate SQL function body (required field)
|
|
22
43
|
@sql_validation = validate_function_sql(@form)
|
|
23
44
|
|
|
@@ -36,6 +57,9 @@ module PgSqlTriggers
|
|
|
36
57
|
# POST /generator/create
|
|
37
58
|
# Actually create the files and register in TriggerRegistry
|
|
38
59
|
def create
|
|
60
|
+
# Check kill switch before generating trigger
|
|
61
|
+
check_kill_switch(operation: :ui_trigger_generate, confirmation: params[:confirmation_text])
|
|
62
|
+
|
|
39
63
|
@form = PgSqlTriggers::Generator::Form.new(generator_params)
|
|
40
64
|
|
|
41
65
|
if @form.valid?
|
|
@@ -55,6 +79,8 @@ module PgSqlTriggers
|
|
|
55
79
|
result = PgSqlTriggers::Generator::Service.create_trigger(@form, actor: current_actor)
|
|
56
80
|
|
|
57
81
|
if result[:success]
|
|
82
|
+
# Clear session data after successful creation
|
|
83
|
+
session.delete(:generator_form_data)
|
|
58
84
|
files_msg = "Migration: #{result[:migration_path]}, DSL: #{result[:dsl_path]}"
|
|
59
85
|
redirect_to root_path,
|
|
60
86
|
notice: "Trigger generated successfully. Files created: #{files_msg}"
|
|
@@ -67,6 +93,9 @@ module PgSqlTriggers
|
|
|
67
93
|
@available_tables = fetch_available_tables
|
|
68
94
|
render :new
|
|
69
95
|
end
|
|
96
|
+
rescue PgSqlTriggers::KillSwitchError => e
|
|
97
|
+
flash[:error] = e.message
|
|
98
|
+
redirect_to root_path
|
|
70
99
|
end
|
|
71
100
|
|
|
72
101
|
# POST /generator/validate_table (AJAX)
|
|
@@ -96,10 +125,10 @@ module PgSqlTriggers
|
|
|
96
125
|
private
|
|
97
126
|
|
|
98
127
|
def generator_params
|
|
99
|
-
params.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
128
|
+
params.require(:pg_sql_triggers_generator_form).permit(
|
|
129
|
+
:trigger_name, :table_name, :function_name, :version,
|
|
130
|
+
:enabled, :condition, :timing, :generate_function_stub, :function_body,
|
|
131
|
+
events: [], environments: []
|
|
103
132
|
)
|
|
104
133
|
end
|
|
105
134
|
|
|
@@ -131,13 +160,52 @@ module PgSqlTriggers
|
|
|
131
160
|
return nil if form.function_body.blank?
|
|
132
161
|
|
|
133
162
|
# Create a temporary trigger registry object for validation
|
|
134
|
-
|
|
163
|
+
# Only include condition if the column exists
|
|
164
|
+
registry_attributes = {
|
|
135
165
|
trigger_name: form.trigger_name,
|
|
166
|
+
table_name: form.table_name,
|
|
136
167
|
function_body: form.function_body
|
|
137
|
-
|
|
168
|
+
}
|
|
169
|
+
# Only set condition if the column exists in the database
|
|
170
|
+
if PgSqlTriggers::TriggerRegistry.column_names.include?("condition")
|
|
171
|
+
registry_attributes[:condition] = form.condition
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
temp_registry = PgSqlTriggers::TriggerRegistry.new(registry_attributes)
|
|
175
|
+
|
|
176
|
+
# Build definition JSON for condition validation
|
|
177
|
+
definition = {
|
|
178
|
+
name: form.trigger_name,
|
|
179
|
+
table_name: form.table_name,
|
|
180
|
+
function_name: form.function_name,
|
|
181
|
+
events: form.events.compact_blank,
|
|
182
|
+
version: form.version,
|
|
183
|
+
enabled: form.enabled,
|
|
184
|
+
environments: form.environments.compact_blank,
|
|
185
|
+
condition: form.condition,
|
|
186
|
+
timing: form.timing || "before",
|
|
187
|
+
function_body: form.function_body
|
|
188
|
+
}
|
|
189
|
+
temp_registry.definition = definition.to_json
|
|
138
190
|
|
|
139
191
|
validator = PgSqlTriggers::Testing::SyntaxValidator.new(temp_registry)
|
|
140
|
-
|
|
192
|
+
|
|
193
|
+
# Validate function syntax
|
|
194
|
+
function_result = validator.validate_function_syntax
|
|
195
|
+
return function_result unless function_result[:valid]
|
|
196
|
+
|
|
197
|
+
# Validate condition if present
|
|
198
|
+
if form.condition.present?
|
|
199
|
+
condition_result = validator.validate_condition
|
|
200
|
+
unless condition_result[:valid]
|
|
201
|
+
return {
|
|
202
|
+
valid: false,
|
|
203
|
+
error: "WHEN condition validation failed: #{condition_result[:error]}"
|
|
204
|
+
}
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
function_result
|
|
141
209
|
rescue StandardError => e
|
|
142
210
|
{ valid: false, error: "Validation error: #{e.message}" }
|
|
143
211
|
end
|
|
@@ -5,6 +5,9 @@ module PgSqlTriggers
|
|
|
5
5
|
# Provides actions to run migrations up, down, and redo
|
|
6
6
|
class MigrationsController < ApplicationController
|
|
7
7
|
def up
|
|
8
|
+
# Check kill switch before running migration
|
|
9
|
+
check_kill_switch(operation: :ui_migration_up, confirmation: params[:confirmation_text])
|
|
10
|
+
|
|
8
11
|
target_version = params[:version]&.to_i
|
|
9
12
|
PgSqlTriggers::Migrator.ensure_migrations_table!
|
|
10
13
|
|
|
@@ -22,6 +25,9 @@ module PgSqlTriggers
|
|
|
22
25
|
end
|
|
23
26
|
end
|
|
24
27
|
redirect_to root_path
|
|
28
|
+
rescue PgSqlTriggers::KillSwitchError => e
|
|
29
|
+
flash[:error] = e.message
|
|
30
|
+
redirect_to root_path
|
|
25
31
|
rescue StandardError => e
|
|
26
32
|
Rails.logger.error("Migration up failed: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
27
33
|
flash[:error] = "Failed to apply migration: #{e.message}"
|
|
@@ -29,6 +35,9 @@ module PgSqlTriggers
|
|
|
29
35
|
end
|
|
30
36
|
|
|
31
37
|
def down
|
|
38
|
+
# Check kill switch before rolling back migration
|
|
39
|
+
check_kill_switch(operation: :ui_migration_down, confirmation: params[:confirmation_text])
|
|
40
|
+
|
|
32
41
|
target_version = params[:version]&.to_i
|
|
33
42
|
PgSqlTriggers::Migrator.ensure_migrations_table!
|
|
34
43
|
|
|
@@ -48,6 +57,9 @@ module PgSqlTriggers
|
|
|
48
57
|
flash[:success] = "Rolled back last migration successfully."
|
|
49
58
|
end
|
|
50
59
|
redirect_to root_path
|
|
60
|
+
rescue PgSqlTriggers::KillSwitchError => e
|
|
61
|
+
flash[:error] = e.message
|
|
62
|
+
redirect_to root_path
|
|
51
63
|
rescue StandardError => e
|
|
52
64
|
Rails.logger.error("Migration down failed: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
53
65
|
flash[:error] = "Failed to rollback migration: #{e.message}"
|
|
@@ -55,6 +67,9 @@ module PgSqlTriggers
|
|
|
55
67
|
end
|
|
56
68
|
|
|
57
69
|
def redo
|
|
70
|
+
# Check kill switch before redoing migration
|
|
71
|
+
check_kill_switch(operation: :ui_migration_redo, confirmation: params[:confirmation_text])
|
|
72
|
+
|
|
58
73
|
target_version = params[:version]&.to_i
|
|
59
74
|
PgSqlTriggers::Migrator.ensure_migrations_table!
|
|
60
75
|
|
|
@@ -75,6 +90,9 @@ module PgSqlTriggers
|
|
|
75
90
|
flash[:success] = "Last migration redone successfully."
|
|
76
91
|
end
|
|
77
92
|
redirect_to root_path
|
|
93
|
+
rescue PgSqlTriggers::KillSwitchError => e
|
|
94
|
+
flash[:error] = e.message
|
|
95
|
+
redirect_to root_path
|
|
78
96
|
rescue StandardError => e
|
|
79
97
|
Rails.logger.error("Migration redo failed: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
80
98
|
flash[:error] = "Failed to redo migration: #{e.message}"
|