pg_sql_triggers 1.2.0 → 1.4.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +397 -1
  3. data/COVERAGE.md +26 -19
  4. data/GEM_ANALYSIS.md +368 -0
  5. data/Goal.md +276 -155
  6. data/README.md +45 -22
  7. data/app/assets/javascripts/pg_sql_triggers/trigger_actions.js +50 -0
  8. data/app/controllers/concerns/pg_sql_triggers/error_handling.rb +56 -0
  9. data/app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb +66 -0
  10. data/app/controllers/concerns/pg_sql_triggers/permission_checking.rb +117 -0
  11. data/app/controllers/pg_sql_triggers/application_controller.rb +10 -62
  12. data/app/controllers/pg_sql_triggers/audit_logs_controller.rb +102 -0
  13. data/app/controllers/pg_sql_triggers/dashboard_controller.rb +4 -9
  14. data/app/controllers/pg_sql_triggers/tables_controller.rb +30 -4
  15. data/app/controllers/pg_sql_triggers/triggers_controller.rb +3 -21
  16. data/app/helpers/pg_sql_triggers/permissions_helper.rb +43 -0
  17. data/app/models/pg_sql_triggers/audit_log.rb +106 -0
  18. data/app/models/pg_sql_triggers/trigger_registry.rb +218 -13
  19. data/app/views/layouts/pg_sql_triggers/application.html.erb +25 -6
  20. data/app/views/pg_sql_triggers/audit_logs/index.html.erb +177 -0
  21. data/app/views/pg_sql_triggers/dashboard/index.html.erb +34 -12
  22. data/app/views/pg_sql_triggers/tables/index.html.erb +75 -5
  23. data/app/views/pg_sql_triggers/tables/show.html.erb +17 -6
  24. data/app/views/pg_sql_triggers/triggers/_drop_modal.html.erb +16 -7
  25. data/app/views/pg_sql_triggers/triggers/_re_execute_modal.html.erb +16 -7
  26. data/app/views/pg_sql_triggers/triggers/show.html.erb +26 -6
  27. data/config/routes.rb +2 -14
  28. data/db/migrate/20260103000001_create_pg_sql_triggers_audit_log.rb +28 -0
  29. data/db/migrate/20260228000001_add_for_each_to_pg_sql_triggers_registry.rb +8 -0
  30. data/docs/README.md +15 -5
  31. data/docs/api-reference.md +233 -151
  32. data/docs/audit-trail.md +413 -0
  33. data/docs/configuration.md +28 -7
  34. data/docs/getting-started.md +17 -16
  35. data/docs/permissions.md +369 -0
  36. data/docs/troubleshooting.md +486 -0
  37. data/docs/ui-guide.md +211 -0
  38. data/docs/usage-guide.md +38 -67
  39. data/docs/web-ui.md +251 -128
  40. data/lib/generators/pg_sql_triggers/templates/trigger_dsl.rb.tt +11 -0
  41. data/lib/generators/pg_sql_triggers/templates/trigger_migration_full.rb.tt +29 -0
  42. data/lib/generators/pg_sql_triggers/trigger_generator.rb +83 -0
  43. data/lib/pg_sql_triggers/drift/db_queries.rb +12 -8
  44. data/lib/pg_sql_triggers/drift/detector.rb +51 -38
  45. data/lib/pg_sql_triggers/dsl/trigger_definition.rb +17 -23
  46. data/lib/pg_sql_triggers/engine.rb +14 -0
  47. data/lib/pg_sql_triggers/errors.rb +245 -0
  48. data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +8 -9
  49. data/lib/pg_sql_triggers/migrator/safety_validator.rb +32 -12
  50. data/lib/pg_sql_triggers/migrator.rb +53 -6
  51. data/lib/pg_sql_triggers/permissions/checker.rb +9 -2
  52. data/lib/pg_sql_triggers/registry/manager.rb +36 -11
  53. data/lib/pg_sql_triggers/registry/validator.rb +62 -5
  54. data/lib/pg_sql_triggers/registry.rb +141 -8
  55. data/lib/pg_sql_triggers/sql/kill_switch.rb +153 -247
  56. data/lib/pg_sql_triggers/sql.rb +0 -6
  57. data/lib/pg_sql_triggers/testing/function_tester.rb +2 -0
  58. data/lib/pg_sql_triggers/version.rb +1 -1
  59. data/lib/pg_sql_triggers.rb +7 -7
  60. data/pg_sql_triggers.gemspec +53 -0
  61. metadata +35 -18
  62. data/app/controllers/pg_sql_triggers/generator_controller.rb +0 -213
  63. data/app/controllers/pg_sql_triggers/sql_capsules_controller.rb +0 -161
  64. data/app/views/pg_sql_triggers/generator/new.html.erb +0 -388
  65. data/app/views/pg_sql_triggers/generator/preview.html.erb +0 -305
  66. data/app/views/pg_sql_triggers/sql_capsules/new.html.erb +0 -81
  67. data/app/views/pg_sql_triggers/sql_capsules/show.html.erb +0 -85
  68. data/docs/screenshots/.gitkeep +0 -1
  69. data/docs/screenshots/Generate Trigger.png +0 -0
  70. data/docs/screenshots/Triggers Page.png +0 -0
  71. data/docs/screenshots/kill error.png +0 -0
  72. data/docs/screenshots/kill modal for migration down.png +0 -0
  73. data/lib/generators/trigger/migration_generator.rb +0 -60
  74. data/lib/pg_sql_triggers/generator/form.rb +0 -80
  75. data/lib/pg_sql_triggers/generator/service.rb +0 -307
  76. data/lib/pg_sql_triggers/generator.rb +0 -8
  77. data/lib/pg_sql_triggers/sql/capsule.rb +0 -79
  78. data/lib/pg_sql_triggers/sql/executor.rb +0 -200
@@ -2,299 +2,205 @@
2
2
 
3
3
  module PgSqlTriggers
4
4
  module SQL
5
- # KillSwitch is a centralized safety gate that prevents dangerous operations
6
- # from being executed in protected environments (typically production).
7
- #
8
- # It operates on three levels:
9
- # 1. Configuration Level: Environment-based activation via PgSqlTriggers.kill_switch_enabled
10
- # 2. Runtime Level: ENV variable override support (KILL_SWITCH_OVERRIDE)
11
- # 3. Explicit Confirmation Level: Typed confirmation text for critical operations
12
- #
13
- # @example Basic usage in a dangerous operation
14
- # KillSwitch.check!(
15
- # operation: :migrate_up,
16
- # environment: Rails.env,
17
- # confirmation: params[:confirmation_text],
18
- # actor: { type: 'UI', id: current_user.email }
19
- # )
20
- #
21
- # @example Using override block
22
- # KillSwitch.override(confirmation: "EXECUTE MIGRATE_UP") do
23
- # # dangerous operation here
24
- # end
5
+ # Private helpers for KillSwitch not part of the public API.
6
+ module KillSwitchHelpers
7
+ def validate_confirmation!(confirmation, operation)
8
+ expected = expected_confirmation(operation)
9
+
10
+ if confirmation.nil? || confirmation.strip.empty?
11
+ raise PgSqlTriggers::KillSwitchError.new(
12
+ "Confirmation text required. Expected: '#{expected}'",
13
+ error_code: "KILL_SWITCH_CONFIRMATION_REQUIRED",
14
+ recovery_suggestion: "Provide the confirmation text: #{expected}",
15
+ context: { operation: operation, expected_confirmation: expected }
16
+ )
17
+ end
18
+
19
+ return if confirmation.strip == expected
20
+
21
+ raise PgSqlTriggers::KillSwitchError.new(
22
+ "Invalid confirmation text. Expected: '#{expected}', got: '#{confirmation.strip}'",
23
+ error_code: "KILL_SWITCH_CONFIRMATION_INVALID",
24
+ recovery_suggestion: "Use the exact confirmation text: #{expected}",
25
+ context: {
26
+ operation: operation,
27
+ expected_confirmation: expected,
28
+ provided_confirmation: confirmation.strip
29
+ }
30
+ )
31
+ end
32
+
33
+ def kill_switch_enabled?
34
+ return true unless PgSqlTriggers.respond_to?(:kill_switch_enabled)
35
+
36
+ value = PgSqlTriggers.kill_switch_enabled
37
+ value.nil? || value
38
+ end
39
+
40
+ def protected_environment?(env)
41
+ return false if env.nil?
42
+
43
+ configured = PgSqlTriggers.kill_switch_environments if PgSqlTriggers.respond_to?(:kill_switch_environments)
44
+ Array(configured || %i[production staging]).map(&:to_s).include?(env.to_s)
45
+ end
46
+
47
+ def resolve_environment(environment)
48
+ return environment.to_s if environment.present?
49
+ return Rails.env.to_s if defined?(Rails) && Rails.respond_to?(:env)
50
+
51
+ if PgSqlTriggers.respond_to?(:default_environment) && PgSqlTriggers.default_environment.respond_to?(:call)
52
+ begin
53
+ return PgSqlTriggers.default_environment.call.to_s
54
+ rescue NameError, NoMethodError => e # rubocop:disable Lint/ShadowedException
55
+ logger&.debug "[KILL_SWITCH] Could not resolve default_environment: #{e.message}"
56
+ end
57
+ end
58
+
59
+ ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
60
+ end
61
+
62
+ def confirmation_required?
63
+ return true unless PgSqlTriggers.respond_to?(:kill_switch_confirmation_required)
64
+
65
+ value = PgSqlTriggers.kill_switch_confirmation_required
66
+ value.nil? || value
67
+ end
68
+
69
+ def expected_confirmation(operation)
70
+ if PgSqlTriggers.respond_to?(:kill_switch_confirmation_pattern) &&
71
+ PgSqlTriggers.kill_switch_confirmation_pattern.respond_to?(:call)
72
+ PgSqlTriggers.kill_switch_confirmation_pattern.call(operation)
73
+ else
74
+ "EXECUTE #{operation.to_s.upcase}"
75
+ end
76
+ end
77
+
78
+ def logger
79
+ if PgSqlTriggers.respond_to?(:kill_switch_logger)
80
+ PgSqlTriggers.kill_switch_logger
81
+ elsif defined?(Rails) && Rails.respond_to?(:logger)
82
+ Rails.logger
83
+ end
84
+ end
85
+
86
+ def log(level, status, **context)
87
+ actor = context[:actor]
88
+ actor_str = if actor.is_a?(Hash)
89
+ "#{actor[:type] || 'unknown'}:#{actor[:id] || 'unknown'}"
90
+ else
91
+ actor&.to_s || "unknown"
92
+ end
93
+ msg = "[KILL_SWITCH] #{status}: operation=#{context[:operation]} " \
94
+ "environment=#{context[:environment]} actor=#{actor_str}"
95
+ msg = "#{msg} #{context[:extra]}" if context[:extra]
96
+ logger&.public_send(level, msg)
97
+ end
98
+ end
99
+ private_constant :KillSwitchHelpers
100
+
101
+ # KillSwitch: three-layer safety gate for dangerous operations.
25
102
  #
26
- # @example CLI usage with ENV override
27
- # KILL_SWITCH_OVERRIDE=true CONFIRMATION_TEXT="EXECUTE MIGRATE_UP" rake pg_sql_triggers:migrate
103
+ # Layer 1 config: PgSqlTriggers.kill_switch_enabled / kill_switch_environments
104
+ # Layer 2 – ENV: KILL_SWITCH_OVERRIDE=true (+ optional confirmation)
105
+ # Layer 3 – explicit: confirmation text passed directly to check!
28
106
  #
29
- # rubocop:disable Metrics/ModuleLength
107
+ # @example
108
+ # KillSwitch.check!(operation: :migrate_up, environment: Rails.env,
109
+ # confirmation: params[:confirmation_text],
110
+ # actor: { type: "UI", id: current_user.email })
30
111
  module KillSwitch
31
- # Thread-local storage key for override state
32
112
  OVERRIDE_KEY = :pg_sql_triggers_kill_switch_override
33
113
 
34
114
  class << self
35
- # Checks if the kill switch is active for the given environment and operation.
36
- #
37
- # @param environment [String, Symbol, nil] The environment to check (defaults to current environment)
38
- # @param operation [String, Symbol, nil] The operation being performed (for logging)
39
- # @return [Boolean] true if kill switch is active, false otherwise
40
- def active?(environment: nil, operation: nil)
41
- # Check if kill switch is globally disabled
42
- return false unless kill_switch_enabled?
43
-
44
- # Detect environment
45
- env = detect_environment(environment)
115
+ include KillSwitchHelpers
46
116
 
47
- # Check if this environment is protected
48
- is_active = protected_environment?(env)
117
+ private :kill_switch_enabled?, :protected_environment?,
118
+ :resolve_environment, :confirmation_required?, :expected_confirmation, :logger, :log
49
119
 
50
- # Log the check if logger is available and operation is provided
51
- if logger && operation
52
- logger.debug "[KILL_SWITCH] Check: operation=#{operation} environment=#{env} active=#{is_active}"
53
- end
120
+ def active?(environment: nil, operation: nil)
121
+ return false unless kill_switch_enabled?
54
122
 
55
- is_active
123
+ env = resolve_environment(environment)
124
+ active = protected_environment?(env)
125
+ logger&.debug "[KILL_SWITCH] Check: operation=#{operation} environment=#{env} active=#{active}" if operation
126
+ active
56
127
  end
57
128
 
58
- # Checks if an operation should be blocked by the kill switch.
59
- # Raises KillSwitchError if the operation is blocked.
60
- #
61
- # @param operation [String, Symbol] The operation being performed
62
- # @param environment [String, Symbol, nil] The environment (defaults to current)
63
- # @param confirmation [String, nil] The confirmation text provided by the user
64
- # @param actor [Hash, nil] Information about who is performing the operation
65
- # @raise [PgSqlTriggers::KillSwitchError] if the operation is blocked
66
- # @return [void]
67
129
  def check!(operation:, environment: nil, confirmation: nil, actor: nil)
68
- env = detect_environment(environment)
130
+ env = resolve_environment(environment)
69
131
 
70
- # Check if kill switch is active for this environment
71
132
  unless active?(environment: env, operation: operation)
72
- log_allowed(operation: operation, environment: env, actor: actor, reason: "not_protected_environment")
133
+ log(:info, "ALLOWED",
134
+ operation: operation,
135
+ environment: env,
136
+ actor: actor,
137
+ extra: "reason=not_protected_environment")
73
138
  return
74
139
  end
75
140
 
76
- # Check for thread-local override
77
- if thread_override_active?
78
- log_override(operation: operation, environment: env, actor: actor, source: "thread_local")
141
+ if Thread.current[OVERRIDE_KEY]
142
+ log(:warn, "OVERRIDDEN",
143
+ operation: operation,
144
+ environment: env,
145
+ actor: actor,
146
+ extra: "source=thread_local")
79
147
  return
80
148
  end
81
149
 
82
- # Check for ENV override
83
- if env_override_active?
84
- # If ENV override is present, check confirmation if required
150
+ if ENV["KILL_SWITCH_OVERRIDE"]&.downcase == "true"
85
151
  if confirmation_required?
86
152
  validate_confirmation!(confirmation, operation)
87
- log_override(operation: operation, environment: env, actor: actor, source: "env_with_confirmation",
88
- confirmation: confirmation)
153
+ log(:warn, "OVERRIDDEN",
154
+ operation: operation,
155
+ environment: env,
156
+ actor: actor,
157
+ extra: "source=env_with_confirmation confirmation=#{confirmation}")
89
158
  else
90
- log_override(operation: operation, environment: env, actor: actor, source: "env_without_confirmation")
159
+ log(:warn, "OVERRIDDEN",
160
+ operation: operation,
161
+ environment: env,
162
+ actor: actor,
163
+ extra: "source=env_without_confirmation")
91
164
  end
92
165
  return
93
166
  end
94
167
 
95
- # If confirmation is provided, validate it
96
168
  unless confirmation.nil?
97
169
  validate_confirmation!(confirmation, operation)
98
- log_override(operation: operation, environment: env, actor: actor, source: "explicit_confirmation",
99
- confirmation: confirmation)
170
+ log(:warn, "OVERRIDDEN",
171
+ operation: operation,
172
+ environment: env,
173
+ actor: actor,
174
+ extra: "source=explicit_confirmation confirmation=#{confirmation}")
100
175
  return
101
176
  end
102
177
 
103
- # No override mechanism satisfied - block the operation
104
- log_blocked(operation: operation, environment: env, actor: actor)
105
- raise_blocked_error(operation: operation, environment: env)
178
+ log(:error, "BLOCKED", operation: operation, environment: env, actor: actor)
179
+ expected = expected_confirmation(operation)
180
+ raise PgSqlTriggers::KillSwitchError.new(
181
+ "Kill switch is active for #{env} environment. Operation '#{operation}' has been blocked.\n\n" \
182
+ "To override: KILL_SWITCH_OVERRIDE=true or provide confirmation text: #{expected}",
183
+ error_code: "KILL_SWITCH_ACTIVE",
184
+ recovery_suggestion: "Provide the confirmation text: #{expected}",
185
+ context: { operation: operation, environment: env, expected_confirmation: expected }
186
+ )
106
187
  end
107
188
 
108
- # Temporarily overrides the kill switch for the duration of the block.
109
- # Uses thread-local storage to ensure thread safety.
110
- #
111
- # @param confirmation [String, nil] Optional confirmation text
112
- # @yield The block to execute with kill switch overridden
113
- # @return The return value of the block
114
189
  def override(confirmation: nil)
115
190
  raise ArgumentError, "Block required for kill switch override" unless block_given?
116
191
 
117
- # Validate confirmation if provided and required
118
- if confirmation.present? && confirmation_required?
119
- # NOTE: We can't validate against a specific operation here since we don't know it
120
- # The block itself will call check! with the operation, which will see the override
192
+ if confirmation.present?
121
193
  logger&.info "[KILL_SWITCH] Override block initiated with confirmation: #{confirmation}"
122
194
  end
123
-
124
- # Set thread-local override
125
- previous_value = Thread.current[OVERRIDE_KEY]
195
+ previous = Thread.current[OVERRIDE_KEY]
126
196
  Thread.current[OVERRIDE_KEY] = true
127
-
128
197
  begin
129
198
  yield
130
199
  ensure
131
- # Restore previous value
132
- Thread.current[OVERRIDE_KEY] = previous_value
200
+ Thread.current[OVERRIDE_KEY] = previous
133
201
  end
134
202
  end
135
-
136
- # Validates the confirmation text against the expected pattern for the operation.
137
- #
138
- # @param confirmation [String, nil] The confirmation text to validate
139
- # @param operation [String, Symbol] The operation being confirmed
140
- # @raise [PgSqlTriggers::KillSwitchError] if confirmation is invalid
141
- # @return [void]
142
- def validate_confirmation!(confirmation, operation)
143
- expected = expected_confirmation(operation)
144
-
145
- if confirmation.nil? || confirmation.strip.empty?
146
- raise PgSqlTriggers::KillSwitchError,
147
- "Confirmation text required. Expected: '#{expected}'"
148
- end
149
-
150
- return if confirmation.strip == expected
151
-
152
- raise PgSqlTriggers::KillSwitchError,
153
- "Invalid confirmation text. Expected: '#{expected}', got: '#{confirmation.strip}'"
154
- end
155
-
156
- private
157
-
158
- # Checks if kill switch is globally enabled in configuration
159
- def kill_switch_enabled?
160
- return true unless PgSqlTriggers.respond_to?(:kill_switch_enabled)
161
-
162
- # Default to true (fail-safe) if not configured
163
- value = PgSqlTriggers.kill_switch_enabled
164
- value.nil? || value
165
- end
166
-
167
- # Checks if the given environment is protected by the kill switch
168
- def protected_environment?(environment)
169
- return false if environment.nil?
170
-
171
- protected_envs = if PgSqlTriggers.respond_to?(:kill_switch_environments)
172
- value = PgSqlTriggers.kill_switch_environments
173
- value.nil? ? %i[production staging] : value
174
- else
175
- %i[production staging]
176
- end
177
-
178
- protected_envs = Array(protected_envs).map(&:to_s)
179
- protected_envs.include?(environment.to_s)
180
- end
181
-
182
- # Detects the current environment
183
- def detect_environment(environment)
184
- return environment.to_s if environment.present?
185
-
186
- # Try Rails environment
187
- return Rails.env.to_s if defined?(Rails) && Rails.respond_to?(:env)
188
-
189
- # Try PgSqlTriggers default_environment
190
- if PgSqlTriggers.respond_to?(:default_environment) && PgSqlTriggers.default_environment.respond_to?(:call)
191
- begin
192
- return PgSqlTriggers.default_environment.call.to_s
193
- rescue NameError, NoMethodError # rubocop:disable Lint/ShadowedException
194
- # Fall through to next option if default_environment proc references undefined constants
195
- end
196
- end
197
-
198
- # Fall back to RAILS_ENV or RACK_ENV
199
- ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
200
- end
201
-
202
- # Checks if thread-local override is active
203
- def thread_override_active?
204
- Thread.current[OVERRIDE_KEY] == true
205
- end
206
-
207
- # Checks if ENV override is active
208
- def env_override_active?
209
- ENV["KILL_SWITCH_OVERRIDE"]&.downcase == "true"
210
- end
211
-
212
- # Checks if confirmation is required for overrides
213
- def confirmation_required?
214
- return true unless PgSqlTriggers.respond_to?(:kill_switch_confirmation_required)
215
-
216
- # Default to true (safer) if not configured
217
- value = PgSqlTriggers.kill_switch_confirmation_required
218
- value.nil? || value
219
- end
220
-
221
- # Generates the expected confirmation text for an operation
222
- def expected_confirmation(operation)
223
- if PgSqlTriggers.respond_to?(:kill_switch_confirmation_pattern) &&
224
- PgSqlTriggers.kill_switch_confirmation_pattern.respond_to?(:call)
225
- PgSqlTriggers.kill_switch_confirmation_pattern.call(operation)
226
- else
227
- # Default pattern
228
- "EXECUTE #{operation.to_s.upcase}"
229
- end
230
- end
231
-
232
- # Returns the configured logger
233
- def logger
234
- if PgSqlTriggers.respond_to?(:kill_switch_logger)
235
- PgSqlTriggers.kill_switch_logger
236
- elsif defined?(Rails) && Rails.respond_to?(:logger)
237
- Rails.logger
238
- end
239
- end
240
-
241
- # Logs an allowed operation
242
- def log_allowed(operation:, environment:, actor:, reason:)
243
- actor_info = format_actor(actor)
244
- logger&.info "[KILL_SWITCH] ALLOWED: operation=#{operation} environment=#{environment} " \
245
- "actor=#{actor_info} reason=#{reason}"
246
- end
247
-
248
- # Logs an overridden operation
249
- def log_override(operation:, environment:, actor:, source:, confirmation: nil)
250
- actor_info = format_actor(actor)
251
- conf_info = confirmation ? " confirmation=#{confirmation}" : ""
252
- logger&.warn "[KILL_SWITCH] OVERRIDDEN: operation=#{operation} environment=#{environment} " \
253
- "actor=#{actor_info} source=#{source}#{conf_info}"
254
- end
255
-
256
- # Logs a blocked operation
257
- def log_blocked(operation:, environment:, actor:)
258
- actor_info = format_actor(actor)
259
- logger&.error "[KILL_SWITCH] BLOCKED: operation=#{operation} environment=#{environment} actor=#{actor_info}"
260
- end
261
-
262
- # Formats actor information for logging
263
- def format_actor(actor)
264
- return "unknown" if actor.nil?
265
- return actor.to_s unless actor.is_a?(Hash)
266
-
267
- "#{actor[:type] || 'unknown'}:#{actor[:id] || 'unknown'}"
268
- end
269
-
270
- # Raises a kill switch error with helpful message
271
- def raise_blocked_error(operation:, environment:)
272
- expected = expected_confirmation(operation)
273
-
274
- message = <<~ERROR
275
- Kill switch is active for #{environment} environment.
276
- Operation '#{operation}' has been blocked for safety.
277
-
278
- To override this protection, you must provide confirmation.
279
-
280
- For CLI/rake tasks, use:
281
- KILL_SWITCH_OVERRIDE=true CONFIRMATION_TEXT="#{expected}" rake your:task
282
-
283
- For console operations, use:
284
- PgSqlTriggers::SQL::KillSwitch.override(confirmation: "#{expected}") do
285
- # your dangerous operation here
286
- end
287
-
288
- For UI operations, enter the confirmation text: #{expected}
289
-
290
- This protection prevents accidental destructive operations in production.
291
- Make sure you understand the implications before proceeding.
292
- ERROR
293
-
294
- raise PgSqlTriggers::KillSwitchError, message
295
- end
296
203
  end
297
204
  end
298
- # rubocop:enable Metrics/ModuleLength
299
205
  end
300
206
  end
@@ -2,14 +2,8 @@
2
2
 
3
3
  module PgSqlTriggers
4
4
  module SQL
5
- autoload :Capsule, "pg_sql_triggers/sql/capsule"
6
- autoload :Executor, "pg_sql_triggers/sql/executor"
7
5
  autoload :KillSwitch, "pg_sql_triggers/sql/kill_switch"
8
6
 
9
- def self.execute_capsule(capsule_name, **options)
10
- Executor.execute_capsule(capsule_name, **options)
11
- end
12
-
13
7
  def self.kill_switch_active?
14
8
  KillSwitch.active?
15
9
  end
@@ -110,6 +110,8 @@ module PgSqlTriggers
110
110
  results[:function_executed] = false
111
111
  results[:success] = false
112
112
  results[:errors] << "Error during function verification: #{e.message}"
113
+ # Also add the original error message to ensure it's searchable in tests
114
+ results[:errors] << e.message unless results[:errors].include?(e.message)
113
115
  results[:output] << "✓ Function created (verification failed)"
114
116
  end
115
117
  else
@@ -11,5 +11,5 @@
11
11
  # 3. Run: bundle exec rake release
12
12
  # See RELEASE.md for detailed release instructions
13
13
  module PgSqlTriggers
14
- VERSION = "1.2.0"
14
+ VERSION = "1.4.0"
15
15
  end
@@ -1,15 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "pg_sql_triggers/version"
4
+ require_relative "pg_sql_triggers/errors"
4
5
  require_relative "pg_sql_triggers/engine"
5
6
 
6
7
  module PgSqlTriggers
7
- class Error < StandardError; end
8
- class PermissionError < Error; end
9
- class DriftError < Error; end
10
- class KillSwitchError < Error; end
11
- class ValidationError < Error; end
12
- class UnsafeMigrationError < Error; end
8
+ # Error classes are defined in lib/pg_sql_triggers/errors.rb
9
+ # They include error codes, standardized messages, and recovery suggestions
13
10
 
14
11
  # Configuration
15
12
  mattr_accessor :kill_switch_enabled
@@ -39,6 +36,10 @@ module PgSqlTriggers
39
36
  mattr_accessor :allow_unsafe_migrations
40
37
  self.allow_unsafe_migrations = false
41
38
 
39
+ # PostgreSQL schema used by DbQueries. Override for non-public schemas.
40
+ mattr_accessor :db_schema
41
+ self.db_schema = "public"
42
+
42
43
  # Drift states
43
44
  DRIFT_STATE_IN_SYNC = "in_sync"
44
45
  DRIFT_STATE_DRIFTED = "drifted"
@@ -58,7 +59,6 @@ module PgSqlTriggers
58
59
  autoload :Permissions, "pg_sql_triggers/permissions"
59
60
  autoload :SQL, "pg_sql_triggers/sql"
60
61
  autoload :DatabaseIntrospection, "pg_sql_triggers/database_introspection"
61
- autoload :Generator, "pg_sql_triggers/generator"
62
62
  autoload :Testing, "pg_sql_triggers/testing"
63
63
  autoload :Migration, "pg_sql_triggers/migration"
64
64
  autoload :Migrator, "pg_sql_triggers/migrator"
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/pg_sql_triggers/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "pg_sql_triggers"
7
+ spec.version = PgSqlTriggers::VERSION
8
+ spec.authors = ["samaswin"]
9
+ spec.email = ["samaswin@gmail.com"]
10
+
11
+ spec.summary = "A PostgreSQL Trigger Control Plane for Rails"
12
+ spec.description = "Production-grade PostgreSQL trigger management for Rails with " \
13
+ "lifecycle management, safe deploys, versioning, drift detection, " \
14
+ "and a mountable UI."
15
+ spec.homepage = "https://github.com/samaswin/pg_sql_triggers"
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = ">= 3.0.0"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/samaswin/pg_sql_triggers"
21
+ spec.metadata["changelog_uri"] = "https://github.com/samaswin/pg_sql_triggers/blob/main/CHANGELOG.md"
22
+ spec.metadata["github_repo"] = "ssh://github.com/samaswin/pg_sql_triggers"
23
+ spec.metadata["rubygems_mfa_required"] = "true"
24
+
25
+ # Specify which files should be added to the gem when it is released.
26
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
+ spec.files = Dir.chdir(__dir__) do
28
+ `git ls-files -z`.split("\x0").reject do |f|
29
+ (File.expand_path(f) == __FILE__) ||
30
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile docs/screenshots/])
31
+ end
32
+ end
33
+ spec.bindir = "exe"
34
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
35
+ spec.require_paths = ["lib"]
36
+
37
+ # Runtime dependencies
38
+ spec.add_dependency "csv", ">= 3.0"
39
+ spec.add_dependency "pg", ">= 1.0"
40
+ spec.add_dependency "rails", ">= 6.1"
41
+
42
+ # Development dependencies
43
+ spec.add_development_dependency "database_cleaner-active_record", "~> 2.0"
44
+ spec.add_development_dependency "erb_lint", "~> 0.9"
45
+ spec.add_development_dependency "factory_bot_rails", "~> 6.0"
46
+ spec.add_development_dependency "rails-controller-testing", "~> 1.0"
47
+ spec.add_development_dependency "rspec", "~> 3.0"
48
+ spec.add_development_dependency "rspec-rails", "~> 6.0"
49
+ spec.add_development_dependency "rubocop", "~> 1.50"
50
+ spec.add_development_dependency "rubocop-rails", "~> 2.19"
51
+ spec.add_development_dependency "rubocop-rspec", "~> 2.20"
52
+ spec.add_development_dependency "simplecov", "~> 0.21"
53
+ end