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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.erb_lint.yml +47 -0
  3. data/.rubocop.yml +4 -1
  4. data/CHANGELOG.md +112 -1
  5. data/COVERAGE.md +58 -0
  6. data/Goal.md +450 -123
  7. data/README.md +53 -215
  8. data/app/controllers/pg_sql_triggers/application_controller.rb +46 -0
  9. data/app/controllers/pg_sql_triggers/dashboard_controller.rb +4 -1
  10. data/app/controllers/pg_sql_triggers/generator_controller.rb +76 -8
  11. data/app/controllers/pg_sql_triggers/migrations_controller.rb +18 -0
  12. data/app/models/pg_sql_triggers/trigger_registry.rb +93 -12
  13. data/app/views/layouts/pg_sql_triggers/application.html.erb +34 -1
  14. data/app/views/pg_sql_triggers/dashboard/index.html.erb +70 -30
  15. data/app/views/pg_sql_triggers/generator/new.html.erb +22 -4
  16. data/app/views/pg_sql_triggers/generator/preview.html.erb +244 -16
  17. data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +221 -0
  18. data/app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb +40 -0
  19. data/app/views/pg_sql_triggers/tables/index.html.erb +0 -2
  20. data/app/views/pg_sql_triggers/tables/show.html.erb +3 -4
  21. data/config/initializers/pg_sql_triggers.rb +69 -0
  22. data/db/migrate/20251222000001_create_pg_sql_triggers_tables.rb +3 -1
  23. data/db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb +8 -0
  24. data/docs/README.md +66 -0
  25. data/docs/api-reference.md +681 -0
  26. data/docs/configuration.md +541 -0
  27. data/docs/getting-started.md +135 -0
  28. data/docs/kill-switch.md +586 -0
  29. data/docs/screenshots/.gitkeep +1 -0
  30. data/docs/screenshots/Generate Trigger.png +0 -0
  31. data/docs/screenshots/Triggers Page.png +0 -0
  32. data/docs/screenshots/kill error.png +0 -0
  33. data/docs/screenshots/kill modal for migration down.png +0 -0
  34. data/docs/usage-guide.md +493 -0
  35. data/docs/web-ui.md +353 -0
  36. data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +3 -1
  37. data/lib/generators/pg_sql_triggers/templates/initializer.rb +44 -2
  38. data/lib/pg_sql_triggers/drift/db_queries.rb +116 -0
  39. data/lib/pg_sql_triggers/drift/detector.rb +187 -0
  40. data/lib/pg_sql_triggers/drift/reporter.rb +179 -0
  41. data/lib/pg_sql_triggers/drift.rb +14 -11
  42. data/lib/pg_sql_triggers/dsl/trigger_definition.rb +15 -1
  43. data/lib/pg_sql_triggers/generator/form.rb +3 -1
  44. data/lib/pg_sql_triggers/generator/service.rb +82 -26
  45. data/lib/pg_sql_triggers/migration.rb +1 -1
  46. data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +344 -0
  47. data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +143 -0
  48. data/lib/pg_sql_triggers/migrator/safety_validator.rb +258 -0
  49. data/lib/pg_sql_triggers/migrator.rb +85 -3
  50. data/lib/pg_sql_triggers/registry/manager.rb +100 -13
  51. data/lib/pg_sql_triggers/sql/kill_switch.rb +300 -0
  52. data/lib/pg_sql_triggers/testing/dry_run.rb +5 -7
  53. data/lib/pg_sql_triggers/testing/function_tester.rb +66 -24
  54. data/lib/pg_sql_triggers/testing/safe_executor.rb +23 -11
  55. data/lib/pg_sql_triggers/testing/syntax_validator.rb +24 -1
  56. data/lib/pg_sql_triggers/version.rb +1 -1
  57. data/lib/pg_sql_triggers.rb +24 -0
  58. data/lib/tasks/trigger_migrations.rake +33 -0
  59. data/scripts/generate_coverage_report.rb +129 -0
  60. 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
- ## Installation
24
+ ## Requirements
25
25
 
26
- Add this line to your application's Gemfile:
26
+ - **Ruby 3.0+**
27
+ - **Rails 6.1+**
28
+ - **PostgreSQL** (any supported version)
27
29
 
28
- ```ruby
29
- gem 'pg_sql_triggers'
30
- ```
30
+ ## Quick Start
31
31
 
32
- And then execute:
32
+ ### Installation
33
33
 
34
- ```bash
35
- $ bundle install
34
+ ```ruby
35
+ # Gemfile
36
+ gem 'pg_sql_triggers'
36
37
  ```
37
38
 
38
- Run the installer:
39
-
40
39
  ```bash
41
- $ rails generate pg_sql_triggers:install
42
- $ rails db:migrate
40
+ bundle install
41
+ rails generate pg_sql_triggers:install
42
+ rails db:migrate
43
43
  ```
44
44
 
45
- This will:
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
- ### 2. Trigger Migrations
71
-
72
- Generate and run trigger migrations similar to Rails schema migrations:
59
+ ### Create and Run Migration
73
60
 
74
61
  ```bash
75
- # Generate a new trigger migration
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
- ### 3. Combined Schema and Trigger Migrations
66
+ ### Access the Web UI
147
67
 
148
- Run both schema and trigger migrations together:
68
+ Navigate to `http://localhost:3000/pg_sql_triggers` to manage triggers visually.
149
69
 
150
- ```bash
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
- # Rollback both (rolls back the most recent migration)
155
- rake db:rollback:with_triggers
72
+ ## Documentation
156
73
 
157
- # Check status of both
158
- rake db:migrate:status:with_triggers
74
+ Comprehensive documentation is available in the [docs](docs/) directory:
159
75
 
160
- # Get versions of both
161
- rake db:version:with_triggers
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
- ### 4. Console Introspection
83
+ ## Key Features
165
84
 
166
- Access trigger information from the Rails console:
85
+ ### Trigger DSL
86
+ Define triggers using a Rails-native Ruby DSL with versioning and environment control.
167
87
 
168
- ```ruby
169
- # List all triggers
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
- # List disabled triggers
176
- PgSqlTriggers::Registry.disabled
91
+ ### Drift Detection
92
+ Automatically detect when database triggers drift from your DSL definitions.
177
93
 
178
- # Get triggers for a specific table
179
- PgSqlTriggers::Registry.for_table(:users)
94
+ ### Production Kill Switch
95
+ Multi-layered safety mechanism preventing accidental destructive operations in production environments.
180
96
 
181
- # Check for drift
182
- PgSqlTriggers::Registry.diff
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
- ### 5. Web UI
100
+ ### Permissions
101
+ Three-tier permission system (Viewer, Operator, Admin) with customizable authorization.
189
102
 
190
- Access the web UI at `http://localhost:3000/pg_sql_triggers` to:
103
+ ## Examples
191
104
 
192
- - View all triggers and their status
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. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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: 0 # Will be calculated by Drift::Detector
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
- @form = PgSqlTriggers::Generator::Form.new
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.expect(
100
- pg_sql_triggers_generator_form: [:trigger_name, :table_name, :function_name, :version,
101
- :enabled, :condition, :generate_function_stub, :function_body,
102
- { events: [], environments: [] }]
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
- temp_registry = PgSqlTriggers::TriggerRegistry.new(
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
- validator.validate_function_syntax
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}"