pg_sql_triggers 1.3.0 → 1.5.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 +0 -0
- data/.rspec +0 -0
- data/.rubocop.yml +6 -16
- data/AGENTS.md +8 -0
- data/CHANGELOG.md +354 -0
- data/COVERAGE.md +39 -41
- data/LICENSE +0 -0
- data/README.md +44 -26
- data/RELEASE.md +0 -0
- data/Rakefile +5 -0
- data/app/assets/javascripts/pg_sql_triggers/application.js +0 -0
- data/app/assets/javascripts/pg_sql_triggers/trigger_actions.js +0 -0
- data/app/assets/stylesheets/pg_sql_triggers/application.css +0 -0
- data/app/controllers/concerns/pg_sql_triggers/error_handling.rb +0 -0
- data/app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb +0 -0
- data/app/controllers/concerns/pg_sql_triggers/permission_checking.rb +6 -5
- data/app/controllers/pg_sql_triggers/application_controller.rb +0 -0
- data/app/controllers/pg_sql_triggers/audit_logs_controller.rb +81 -64
- data/app/controllers/pg_sql_triggers/dashboard_controller.rb +111 -34
- data/app/controllers/pg_sql_triggers/migrations_controller.rb +13 -14
- data/app/controllers/pg_sql_triggers/tables_controller.rb +8 -0
- data/app/controllers/pg_sql_triggers/triggers_controller.rb +1 -0
- data/app/helpers/pg_sql_triggers/dashboard_helper.rb +19 -0
- data/app/helpers/pg_sql_triggers/permissions_helper.rb +3 -2
- data/app/models/pg_sql_triggers/application_record.rb +0 -0
- data/app/models/pg_sql_triggers/audit_log.rb +29 -47
- data/app/models/pg_sql_triggers/trigger_registry.rb +137 -74
- data/app/views/layouts/pg_sql_triggers/application.html.erb +0 -1
- data/app/views/pg_sql_triggers/audit_logs/index.html.erb +9 -5
- data/app/views/pg_sql_triggers/dashboard/index.html.erb +107 -27
- data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +0 -0
- data/app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb +0 -0
- data/app/views/pg_sql_triggers/tables/index.html.erb +27 -18
- data/app/views/pg_sql_triggers/tables/show.html.erb +0 -2
- data/app/views/pg_sql_triggers/triggers/_drop_modal.html.erb +0 -0
- data/app/views/pg_sql_triggers/triggers/_re_execute_modal.html.erb +0 -0
- data/app/views/pg_sql_triggers/triggers/show.html.erb +33 -0
- data/config/initializers/pg_sql_triggers.rb +0 -0
- data/config/routes.rb +0 -14
- data/db/migrate/{20251222000001_create_pg_sql_triggers_tables.rb → 20251222104327_create_pg_sql_triggers_tables.rb} +0 -0
- data/db/migrate/20251229071916_add_timing_to_pg_sql_triggers_registry.rb +0 -0
- data/db/migrate/{20260103000001_create_pg_sql_triggers_audit_log.rb → 20260103114508_create_pg_sql_triggers_audit_log.rb} +0 -0
- data/db/migrate/20260228162233_add_for_each_to_pg_sql_triggers_registry.rb +8 -0
- data/db/migrate/20260412185841_add_constraint_deferral_to_pg_sql_triggers_registry.rb +9 -0
- data/docs/README.md +3 -0
- data/docs/api-reference.md +176 -152
- data/docs/audit-trail.md +1 -1
- data/docs/configuration.md +196 -3
- data/docs/getting-started.md +31 -16
- data/docs/kill-switch.md +0 -0
- data/docs/permissions.md +6 -9
- data/docs/troubleshooting.md +0 -0
- data/docs/ui-guide.md +0 -0
- data/docs/usage-guide.md +112 -67
- data/docs/web-ui.md +3 -103
- data/lib/generators/pg_sql_triggers/install_generator.rb +0 -0
- data/lib/generators/pg_sql_triggers/templates/README +0 -0
- data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +0 -0
- data/lib/generators/pg_sql_triggers/templates/initializer.rb +14 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_dsl.rb.tt +11 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_migration.rb.erb +0 -0
- data/lib/generators/pg_sql_triggers/templates/trigger_migration_full.rb.tt +29 -0
- data/lib/generators/pg_sql_triggers/trigger_generator.rb +83 -0
- data/lib/generators/pg_sql_triggers/trigger_migration_generator.rb +0 -0
- data/lib/pg_sql_triggers/alerting.rb +77 -0
- data/lib/pg_sql_triggers/database_introspection.rb +0 -0
- data/lib/pg_sql_triggers/deferral_checksum.rb +54 -0
- data/lib/pg_sql_triggers/drift/db_queries.rb +26 -13
- data/lib/pg_sql_triggers/drift/detector.rb +59 -38
- data/lib/pg_sql_triggers/drift/reporter.rb +0 -0
- data/lib/pg_sql_triggers/drift.rb +5 -0
- data/lib/pg_sql_triggers/dsl/trigger_definition.rb +68 -20
- data/lib/pg_sql_triggers/dsl.rb +0 -0
- data/lib/pg_sql_triggers/engine.rb +49 -0
- data/lib/pg_sql_triggers/errors.rb +0 -0
- data/lib/pg_sql_triggers/events_checksum.rb +114 -0
- data/lib/pg_sql_triggers/migration.rb +5 -6
- data/lib/pg_sql_triggers/migrator/pre_apply_comparator.rb +85 -82
- data/lib/pg_sql_triggers/migrator/pre_apply_diff_reporter.rb +0 -0
- data/lib/pg_sql_triggers/migrator/safety_validator.rb +34 -12
- data/lib/pg_sql_triggers/migrator.rb +137 -94
- data/lib/pg_sql_triggers/permissions/checker.rb +12 -15
- data/lib/pg_sql_triggers/permissions.rb +1 -0
- data/lib/pg_sql_triggers/rake_development_boot.rb +65 -0
- data/lib/pg_sql_triggers/registry/manager.rb +60 -21
- data/lib/pg_sql_triggers/registry/validator.rb +287 -6
- data/lib/pg_sql_triggers/registry.rb +0 -0
- data/lib/pg_sql_triggers/schema_dumper_extension.rb +32 -0
- data/lib/pg_sql_triggers/sql/kill_switch.rb +154 -275
- data/lib/pg_sql_triggers/sql.rb +0 -6
- data/lib/pg_sql_triggers/testing/dry_run.rb +0 -0
- data/lib/pg_sql_triggers/testing/function_tester.rb +97 -107
- data/lib/pg_sql_triggers/testing/safe_executor.rb +0 -0
- data/lib/pg_sql_triggers/testing/syntax_validator.rb +0 -0
- data/lib/pg_sql_triggers/testing.rb +0 -0
- data/lib/pg_sql_triggers/trigger_structure_dumper.rb +111 -0
- data/lib/pg_sql_triggers/version.rb +1 -1
- data/lib/pg_sql_triggers.rb +21 -1
- data/lib/tasks/trigger_migrations.rake +235 -152
- data/rakelib/pg_sql_triggers_environment.rake +9 -0
- data/scripts/generate_coverage_report.rb +4 -1
- data/sig/pg_sql_triggers.rbs +0 -0
- metadata +68 -22
- data/Goal.md +0 -742
- data/app/controllers/pg_sql_triggers/generator_controller.rb +0 -213
- data/app/controllers/pg_sql_triggers/sql_capsules_controller.rb +0 -161
- data/app/views/pg_sql_triggers/generator/new.html.erb +0 -388
- data/app/views/pg_sql_triggers/generator/preview.html.erb +0 -305
- data/app/views/pg_sql_triggers/sql_capsules/new.html.erb +0 -81
- data/app/views/pg_sql_triggers/sql_capsules/show.html.erb +0 -85
- data/lib/generators/trigger/migration_generator.rb +0 -60
- data/lib/pg_sql_triggers/generator/form.rb +0 -80
- data/lib/pg_sql_triggers/generator/service.rb +0 -339
- data/lib/pg_sql_triggers/generator.rb +0 -8
- data/lib/pg_sql_triggers/sql/capsule.rb +0 -79
- data/lib/pg_sql_triggers/sql/executor.rb +0 -200
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
<h2 style="margin: 0;">Trigger Dashboard</h2>
|
|
3
3
|
<div style="display: flex; gap: 1rem;">
|
|
4
4
|
<%= link_to "View Tables", tables_path, class: "btn btn-primary", style: "font-size: 1rem; padding: 0.75rem 1.5rem; text-decoration: none;" %>
|
|
5
|
-
<%= link_to "Generate New Trigger", new_generator_path,
|
|
6
|
-
class: "btn btn-success",
|
|
7
|
-
style: "font-size: 1rem; padding: 0.75rem 1.5rem; text-decoration: none;" %>
|
|
8
5
|
</div>
|
|
9
6
|
</div>
|
|
10
7
|
|
|
@@ -30,8 +27,67 @@
|
|
|
30
27
|
</div>
|
|
31
28
|
</div>
|
|
32
29
|
|
|
33
|
-
<% if @
|
|
34
|
-
<
|
|
30
|
+
<% if @stats[:total].positive? %>
|
|
31
|
+
<div style="background: white; padding: 1rem 1.5rem; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem;">
|
|
32
|
+
<h3 style="margin-top: 0; margin-bottom: 1rem;">Search & filters</h3>
|
|
33
|
+
<%= form_with url: dashboard_path, method: :get, local: true, style: "display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; align-items: end;" do %>
|
|
34
|
+
<%= hidden_field_tag :page, params[:page] if params[:page].present? %>
|
|
35
|
+
<%= hidden_field_tag :per_page, params[:per_page] if params[:per_page].present? %>
|
|
36
|
+
<%= hidden_field_tag :trigger_page, 1 %>
|
|
37
|
+
<%= hidden_field_tag :trigger_per_page, @trigger_per_page %>
|
|
38
|
+
<div>
|
|
39
|
+
<label style="display: block; margin-bottom: 0.35rem; font-weight: 600; font-size: 0.875rem; color: #495057;">Search</label>
|
|
40
|
+
<%= text_field_tag :q, @filter_query, placeholder: "Trigger or table name…", style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
41
|
+
</div>
|
|
42
|
+
<div>
|
|
43
|
+
<label style="display: block; margin-bottom: 0.35rem; font-weight: 600; font-size: 0.875rem; color: #495057;">Table</label>
|
|
44
|
+
<%= select_tag :table, options_for_select([["All tables", ""]] + @filter_table_names.map { |t| [t, t] }, @filter_table), style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
45
|
+
</div>
|
|
46
|
+
<div>
|
|
47
|
+
<label style="display: block; margin-bottom: 0.35rem; font-weight: 600; font-size: 0.875rem; color: #495057;">Drift state</label>
|
|
48
|
+
<%= select_tag :state, options_for_select([
|
|
49
|
+
["Any state", ""],
|
|
50
|
+
["In sync", "in_sync"],
|
|
51
|
+
["Drifted", "drifted"],
|
|
52
|
+
["Disabled", "disabled"],
|
|
53
|
+
["Dropped", "dropped"],
|
|
54
|
+
["Unknown (external)", "unknown"],
|
|
55
|
+
["Manual override", "manual_override"]
|
|
56
|
+
], @filter_state), style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
57
|
+
</div>
|
|
58
|
+
<div>
|
|
59
|
+
<label style="display: block; margin-bottom: 0.35rem; font-weight: 600; font-size: 0.875rem; color: #495057;">Source</label>
|
|
60
|
+
<%= select_tag :source, options_for_select([
|
|
61
|
+
["Any source", ""],
|
|
62
|
+
["DSL", "dsl"],
|
|
63
|
+
["Generated", "generated"],
|
|
64
|
+
["Manual SQL", "manual_sql"]
|
|
65
|
+
], @filter_source), style: "width: 100%; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
66
|
+
</div>
|
|
67
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
68
|
+
<%= submit_tag "Apply", style: "padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: 600;" %>
|
|
69
|
+
<%= link_to "Clear", dashboard_path(page: @page, per_page: @per_page), style: "padding: 0.5rem 1rem; background: #6c757d; color: white; text-decoration: none; border-radius: 4px; display: inline-block; line-height: 1.25;" %>
|
|
70
|
+
</div>
|
|
71
|
+
<% end %>
|
|
72
|
+
</div>
|
|
73
|
+
<% end %>
|
|
74
|
+
|
|
75
|
+
<% if @stats[:total].zero? %>
|
|
76
|
+
<div style="background: #e7f3ff; border-left: 4px solid #007bff; padding: 1.5rem; border-radius: 4px; margin-bottom: 2rem;">
|
|
77
|
+
<h3 style="margin-top: 0;">No triggers yet</h3>
|
|
78
|
+
<p style="margin-bottom: 1rem;">Get started by generating your first trigger using the form-based wizard.</p>
|
|
79
|
+
<%= link_to "View Tables", tables_path, class: "btn btn-primary" %>
|
|
80
|
+
</div>
|
|
81
|
+
<% elsif @trigger_list_total.zero? %>
|
|
82
|
+
<div style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 1.5rem; border-radius: 4px; margin-bottom: 2rem;">
|
|
83
|
+
<h3 style="margin-top: 0;">No triggers match your filters</h3>
|
|
84
|
+
<p style="margin-bottom: 0;">Try adjusting search or filters, or <%= link_to "clear filters", dashboard_path(page: @page, per_page: @per_page), style: "color: #007bff;" %>.</p>
|
|
85
|
+
</div>
|
|
86
|
+
<% else %>
|
|
87
|
+
<h3>Triggers</h3>
|
|
88
|
+
<div style="margin-bottom: 1rem; color: #6c757d; font-size: 0.875rem;">
|
|
89
|
+
Showing <%= (@trigger_page - 1) * @trigger_per_page + 1 %>–<%= [@trigger_page * @trigger_per_page, @trigger_list_total].min %> of <%= @trigger_list_total %> trigger<%= @trigger_list_total == 1 ? "" : "s" %>
|
|
90
|
+
</div>
|
|
35
91
|
<table>
|
|
36
92
|
<thead>
|
|
37
93
|
<tr>
|
|
@@ -45,7 +101,7 @@
|
|
|
45
101
|
</tr>
|
|
46
102
|
</thead>
|
|
47
103
|
<tbody>
|
|
48
|
-
<% @triggers.
|
|
104
|
+
<% @triggers.each do |trigger| %>
|
|
49
105
|
<tr>
|
|
50
106
|
<td>
|
|
51
107
|
<%= link_to trigger.trigger_name, trigger_path(trigger), style: "color: #007bff; text-decoration: none; font-weight: 600;" %>
|
|
@@ -75,7 +131,7 @@
|
|
|
75
131
|
<% if trigger.enabled %>
|
|
76
132
|
<% form_id = "trigger-disable-#{trigger.id}-form" %>
|
|
77
133
|
<%= form_with url: disable_trigger_path(trigger), method: :post, local: false, id: form_id, style: "margin: 0;" do |f| %>
|
|
78
|
-
<%= f.hidden_field :redirect_to, value:
|
|
134
|
+
<%= f.hidden_field :redirect_to, value: request.fullpath %>
|
|
79
135
|
<button type="button" onclick="showKillSwitchModal('<%= form_id %>')"
|
|
80
136
|
style="padding: 0.25rem 0.75rem; background: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 0.75rem;">
|
|
81
137
|
Disable
|
|
@@ -89,7 +145,7 @@
|
|
|
89
145
|
<% else %>
|
|
90
146
|
<% form_id = "trigger-enable-#{trigger.id}-form" %>
|
|
91
147
|
<%= form_with url: enable_trigger_path(trigger), method: :post, local: false, id: form_id, style: "margin: 0;" do |f| %>
|
|
92
|
-
<%= f.hidden_field :redirect_to, value:
|
|
148
|
+
<%= f.hidden_field :redirect_to, value: request.fullpath %>
|
|
93
149
|
<button type="button" onclick="showKillSwitchModal('<%= form_id %>')"
|
|
94
150
|
style="padding: 0.25rem 0.75rem; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 0.75rem;">
|
|
95
151
|
Enable
|
|
@@ -107,13 +163,13 @@
|
|
|
107
163
|
<% begin %>
|
|
108
164
|
<% drift_info = trigger.drift_result %>
|
|
109
165
|
<% if drift_info && drift_info[:state] == 'drifted' %>
|
|
110
|
-
<%= render 'pg_sql_triggers/triggers/re_execute_modal', trigger: trigger, drift_info: drift_info, redirect_to:
|
|
166
|
+
<%= render 'pg_sql_triggers/triggers/re_execute_modal', trigger: trigger, drift_info: drift_info, redirect_to: request.fullpath, button_size: :small %>
|
|
111
167
|
<% end %>
|
|
112
168
|
<% rescue StandardError %>
|
|
113
169
|
<%# Skip if drift detection fails %>
|
|
114
170
|
<% end %>
|
|
115
171
|
|
|
116
|
-
<%= render 'pg_sql_triggers/triggers/drop_modal', trigger: trigger, redirect_to:
|
|
172
|
+
<%= render 'pg_sql_triggers/triggers/drop_modal', trigger: trigger, redirect_to: request.fullpath, button_size: :small %>
|
|
117
173
|
<% end %>
|
|
118
174
|
|
|
119
175
|
<% unless PgSqlTriggers::Permissions.can?(current_actor, :enable_trigger) || PgSqlTriggers::Permissions.can?(current_actor, :drop_trigger) %>
|
|
@@ -125,11 +181,35 @@
|
|
|
125
181
|
<% end %>
|
|
126
182
|
</tbody>
|
|
127
183
|
</table>
|
|
128
|
-
|
|
129
|
-
<div style="
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
184
|
+
|
|
185
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #dee2e6; flex-wrap: wrap; gap: 0.75rem;">
|
|
186
|
+
<div>
|
|
187
|
+
<% if @trigger_total_pages > 1 %>
|
|
188
|
+
<% if @trigger_page > 1 %>
|
|
189
|
+
<%= link_to "← Previous", dashboard_path(dashboard_list_params(trigger_page: @trigger_page - 1)),
|
|
190
|
+
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin-right: 0.5rem;" %>
|
|
191
|
+
<% end %>
|
|
192
|
+
<% if @trigger_page < @trigger_total_pages %>
|
|
193
|
+
<%= link_to "Next →", dashboard_path(dashboard_list_params(trigger_page: @trigger_page + 1)),
|
|
194
|
+
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
195
|
+
<% end %>
|
|
196
|
+
<% end %>
|
|
197
|
+
</div>
|
|
198
|
+
<% if @trigger_total_pages > 1 %>
|
|
199
|
+
<div style="color: #6c757d; font-size: 0.875rem;">
|
|
200
|
+
Page <%= @trigger_page %> of <%= @trigger_total_pages %>
|
|
201
|
+
</div>
|
|
202
|
+
<% end %>
|
|
203
|
+
<%= form_with url: dashboard_path, method: :get, local: true, style: "display: flex; align-items: center; gap: 0.5rem; margin: 0;" do %>
|
|
204
|
+
<% dashboard_list_params.except(:trigger_page, :trigger_per_page).each do |key, value| %>
|
|
205
|
+
<%= hidden_field_tag key, value %>
|
|
206
|
+
<% end %>
|
|
207
|
+
<%= hidden_field_tag :trigger_page, 1 %>
|
|
208
|
+
<label style="color: #6c757d; font-size: 0.875rem;">Per page:</label>
|
|
209
|
+
<%= select_tag :trigger_per_page, options_for_select([10, 20, 50, 100], @trigger_per_page),
|
|
210
|
+
onchange: "this.form.submit()",
|
|
211
|
+
style: "padding: 0.25rem 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
212
|
+
<% end %>
|
|
133
213
|
</div>
|
|
134
214
|
<% end %>
|
|
135
215
|
|
|
@@ -293,30 +373,30 @@
|
|
|
293
373
|
|
|
294
374
|
<!-- Pagination Controls -->
|
|
295
375
|
<% if @total_pages > 1 %>
|
|
296
|
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #dee2e6;">
|
|
376
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #dee2e6; flex-wrap: wrap; gap: 0.75rem;">
|
|
297
377
|
<div>
|
|
298
378
|
<% if @page > 1 %>
|
|
299
|
-
<%= link_to "← Previous", dashboard_path(page: @page - 1, per_page: @per_page),
|
|
379
|
+
<%= link_to "← Previous", dashboard_path(dashboard_list_params(page: @page - 1, per_page: @per_page)),
|
|
300
380
|
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin-right: 0.5rem;" %>
|
|
301
381
|
<% end %>
|
|
302
382
|
<% if @page < @total_pages %>
|
|
303
|
-
<%= link_to "Next →", dashboard_path(page: @page + 1, per_page: @per_page),
|
|
383
|
+
<%= link_to "Next →", dashboard_path(dashboard_list_params(page: @page + 1, per_page: @per_page)),
|
|
304
384
|
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
305
385
|
<% end %>
|
|
306
386
|
</div>
|
|
307
387
|
<div style="color: #6c757d; font-size: 0.875rem;">
|
|
308
388
|
Page <%= @page %> of <%= @total_pages %>
|
|
309
389
|
</div>
|
|
310
|
-
|
|
390
|
+
<%= form_with url: dashboard_path, method: :get, local: true, style: "display: flex; align-items: center; gap: 0.5rem; margin: 0;" do %>
|
|
391
|
+
<% dashboard_list_params.except(:page, :per_page).each do |key, value| %>
|
|
392
|
+
<%= hidden_field_tag key, value %>
|
|
393
|
+
<% end %>
|
|
394
|
+
<%= hidden_field_tag :page, 1 %>
|
|
311
395
|
<label style="color: #6c757d; font-size: 0.875rem;">Per page:</label>
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
<option value="50" <%= 'selected' if @per_page == 50 %>>50</option>
|
|
317
|
-
<option value="100" <%= 'selected' if @per_page == 100 %>>100</option>
|
|
318
|
-
</select>
|
|
319
|
-
</div>
|
|
396
|
+
<%= select_tag :per_page, options_for_select([10, 20, 50, 100], @per_page),
|
|
397
|
+
onchange: "this.form.submit()",
|
|
398
|
+
style: "padding: 0.25rem 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
399
|
+
<% end %>
|
|
320
400
|
</div>
|
|
321
401
|
<% end %>
|
|
322
402
|
<% else %>
|
|
File without changes
|
|
File without changes
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
|
2
2
|
<h2 style="margin: 0;">Database Tables & Triggers</h2>
|
|
3
|
-
<%= link_to "Generate New Trigger", new_generator_path, class: "btn btn-success" %>
|
|
4
3
|
</div>
|
|
5
4
|
|
|
6
5
|
<!-- Statistics -->
|
|
@@ -21,14 +20,27 @@
|
|
|
21
20
|
|
|
22
21
|
<!-- Filter Controls -->
|
|
23
22
|
<div style="background: white; padding: 1rem 1.5rem; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem;">
|
|
23
|
+
<%= form_with url: tables_path, method: :get, local: true, style: "margin-bottom: 1rem;" do %>
|
|
24
|
+
<%= hidden_field_tag :filter, @filter %>
|
|
25
|
+
<%= hidden_field_tag :page, 1 %>
|
|
26
|
+
<%= hidden_field_tag :per_page, @per_page %>
|
|
27
|
+
<div style="display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;">
|
|
28
|
+
<label style="color: #495057; font-weight: 600; font-size: 0.875rem;">Search tables:</label>
|
|
29
|
+
<%= text_field_tag :q, @search_query, placeholder: "Table name…", style: "flex: 1; min-width: 200px; padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
30
|
+
<%= submit_tag "Search", style: "padding: 0.5rem 1rem; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: 600;" %>
|
|
31
|
+
<% if @search_query.present? %>
|
|
32
|
+
<%= link_to "Clear search", tables_path(filter: @filter, page: 1, per_page: @per_page), style: "padding: 0.5rem 1rem; background: #6c757d; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
24
36
|
<div style="display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap;">
|
|
25
37
|
<label style="color: #495057; font-weight: 600; font-size: 0.875rem;">Filter:</label>
|
|
26
38
|
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
27
|
-
<%= link_to "All Tables", tables_path(filter: 'all', page: 1, per_page: @per_page),
|
|
39
|
+
<%= link_to "All Tables", tables_path(filter: 'all', page: 1, per_page: @per_page, q: @search_query),
|
|
28
40
|
style: "padding: 0.5rem 1rem; background: #{@filter == 'all' ? '#007bff' : '#f8f9fa'}; color: #{@filter == 'all' ? 'white' : '#495057'}; text-decoration: none; border-radius: 4px; border: 1px solid #{@filter == 'all' ? '#007bff' : '#dee2e6'}; font-size: 0.875rem; font-weight: #{@filter == 'all' ? '600' : '400'};" %>
|
|
29
|
-
<%= link_to "With Triggers", tables_path(filter: 'with_triggers', page: 1, per_page: @per_page),
|
|
41
|
+
<%= link_to "With Triggers", tables_path(filter: 'with_triggers', page: 1, per_page: @per_page, q: @search_query),
|
|
30
42
|
style: "padding: 0.5rem 1rem; background: #{@filter == 'with_triggers' ? '#28a745' : '#f8f9fa'}; color: #{@filter == 'with_triggers' ? 'white' : '#495057'}; text-decoration: none; border-radius: 4px; border: 1px solid #{@filter == 'with_triggers' ? '#28a745' : '#dee2e6'}; font-size: 0.875rem; font-weight: #{@filter == 'with_triggers' ? '600' : '400'};" %>
|
|
31
|
-
<%= link_to "Without Triggers", tables_path(filter: 'without_triggers', page: 1, per_page: @per_page),
|
|
43
|
+
<%= link_to "Without Triggers", tables_path(filter: 'without_triggers', page: 1, per_page: @per_page, q: @search_query),
|
|
32
44
|
style: "padding: 0.5rem 1rem; background: #{@filter == 'without_triggers' ? '#6c757d' : '#f8f9fa'}; color: #{@filter == 'without_triggers' ? 'white' : '#495057'}; text-decoration: none; border-radius: 4px; border: 1px solid #{@filter == 'without_triggers' ? '#6c757d' : '#dee2e6'}; font-size: 0.875rem; font-weight: #{@filter == 'without_triggers' ? '600' : '400'};" %>
|
|
33
45
|
</div>
|
|
34
46
|
</div>
|
|
@@ -113,7 +125,6 @@
|
|
|
113
125
|
</td>
|
|
114
126
|
<td>
|
|
115
127
|
<%= link_to "View Details", table_path(table[:table_name]), class: "btn btn-primary", style: "padding: 0.25rem 0.5rem; font-size: 0.875rem;" %>
|
|
116
|
-
<%= link_to "Create Trigger", new_generator_path(pg_sql_triggers_generator_form: { table_name: table[:table_name] }), class: "btn btn-success", style: "padding: 0.25rem 0.5rem; font-size: 0.875rem; margin-top: 0.25rem; display: block;" %>
|
|
117
128
|
</td>
|
|
118
129
|
</tr>
|
|
119
130
|
<% end %>
|
|
@@ -125,27 +136,26 @@
|
|
|
125
136
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; padding: 1rem; border-top: 1px solid #dee2e6;">
|
|
126
137
|
<div>
|
|
127
138
|
<% if @page > 1 %>
|
|
128
|
-
<%= link_to "← Previous", tables_path(filter: @filter, page: @page - 1, per_page: @per_page),
|
|
139
|
+
<%= link_to "← Previous", tables_path(filter: @filter, page: @page - 1, per_page: @per_page, q: @search_query),
|
|
129
140
|
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin-right: 0.5rem;" %>
|
|
130
141
|
<% end %>
|
|
131
142
|
<% if @page < @total_pages %>
|
|
132
|
-
<%= link_to "Next →", tables_path(filter: @filter, page: @page + 1, per_page: @per_page),
|
|
143
|
+
<%= link_to "Next →", tables_path(filter: @filter, page: @page + 1, per_page: @per_page, q: @search_query),
|
|
133
144
|
style: "padding: 0.5rem 1rem; background: #007bff; color: white; text-decoration: none; border-radius: 4px;" %>
|
|
134
145
|
<% end %>
|
|
135
146
|
</div>
|
|
136
147
|
<div style="color: #6c757d; font-size: 0.875rem;">
|
|
137
148
|
Page <%= @page %> of <%= @total_pages %>
|
|
138
149
|
</div>
|
|
139
|
-
|
|
150
|
+
<%= form_with url: tables_path, method: :get, local: true, style: "display: flex; align-items: center; gap: 0.5rem; margin: 0;" do %>
|
|
151
|
+
<%= hidden_field_tag :filter, @filter %>
|
|
152
|
+
<%= hidden_field_tag :page, 1 %>
|
|
153
|
+
<%= hidden_field_tag :q, @search_query if @search_query.present? %>
|
|
140
154
|
<label style="color: #6c757d; font-size: 0.875rem;">Per page:</label>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<option value="50" <%= 'selected' if @per_page == 50 %>>50</option>
|
|
146
|
-
<option value="100" <%= 'selected' if @per_page == 100 %>>100</option>
|
|
147
|
-
</select>
|
|
148
|
-
</div>
|
|
155
|
+
<%= select_tag :per_page, options_for_select([10, 20, 50, 100], @per_page),
|
|
156
|
+
onchange: "this.form.submit()",
|
|
157
|
+
style: "padding: 0.25rem 0.5rem; border: 1px solid #ced4da; border-radius: 4px;" %>
|
|
158
|
+
<% end %>
|
|
149
159
|
</div>
|
|
150
160
|
<% end %>
|
|
151
161
|
</div>
|
|
@@ -170,7 +180,6 @@
|
|
|
170
180
|
<% end %>
|
|
171
181
|
</p>
|
|
172
182
|
<% if @filter == 'with_triggers' || @filter == 'all' %>
|
|
173
|
-
|
|
174
|
-
<% end %>
|
|
183
|
+
<% end %>
|
|
175
184
|
</div>
|
|
176
185
|
<% end %>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<div style="margin-bottom: 2rem;">
|
|
2
2
|
<h2>Table: <%= @table_info[:table_name] %></h2>
|
|
3
3
|
<%= link_to "← Back to Tables", tables_path, class: "btn", style: "background: #6c757d; color: white; text-decoration: none; margin-right: 1rem;" %>
|
|
4
|
-
<%= link_to "Create Trigger for this Table", new_generator_path(pg_sql_triggers_generator_form: { table_name: @table_info[:table_name] }), class: "btn btn-success" %>
|
|
5
4
|
</div>
|
|
6
5
|
|
|
7
6
|
<!-- Table Information -->
|
|
@@ -138,7 +137,6 @@
|
|
|
138
137
|
<% else %>
|
|
139
138
|
<div style="background: #e7f3ff; border-left: 4px solid #007bff; padding: 1rem; border-radius: 4px;">
|
|
140
139
|
<p style="margin: 0;">No registered triggers for this table.</p>
|
|
141
|
-
<%= link_to "Create a trigger", new_generator_path(pg_sql_triggers_generator_form: { table_name: @table_info[:table_name] }), class: "btn btn-primary", style: "margin-top: 0.5rem;" %>
|
|
142
140
|
</div>
|
|
143
141
|
<% end %>
|
|
144
142
|
</div>
|
|
File without changes
|
|
File without changes
|
|
@@ -131,6 +131,39 @@
|
|
|
131
131
|
</div>
|
|
132
132
|
<% end %>
|
|
133
133
|
|
|
134
|
+
<% rel = @dependency_related || { prerequisites: [], dependents: [] } %>
|
|
135
|
+
<% if rel[:prerequisites].any? || rel[:dependents].any? %>
|
|
136
|
+
<div style="background: white; padding: 1.5rem; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem;">
|
|
137
|
+
<h3 style="margin-top: 0;">Trigger ordering (depends_on)</h3>
|
|
138
|
+
<p style="color: #6c757d; font-size: 0.875rem; margin-bottom: 1rem;">
|
|
139
|
+
PostgreSQL runs triggers of the same kind in alphabetical order by name. The DSL <code>depends_on</code> records intended order;
|
|
140
|
+
<code>rake trigger:validate_order</code> and <code>PgSqlTriggers::Registry.validate!</code> check that names and dependencies line up.
|
|
141
|
+
</p>
|
|
142
|
+
|
|
143
|
+
<% if rel[:prerequisites].any? %>
|
|
144
|
+
<div style="margin-bottom: 1rem;">
|
|
145
|
+
<strong>Runs after (depends_on):</strong>
|
|
146
|
+
<ul style="margin-top: 0.5rem;">
|
|
147
|
+
<% rel[:prerequisites].each do |dep_row| %>
|
|
148
|
+
<li><%= link_to dep_row.trigger_name, trigger_path(dep_row), style: "color: #007bff;" %></li>
|
|
149
|
+
<% end %>
|
|
150
|
+
</ul>
|
|
151
|
+
</div>
|
|
152
|
+
<% end %>
|
|
153
|
+
|
|
154
|
+
<% if rel[:dependents].any? %>
|
|
155
|
+
<div>
|
|
156
|
+
<strong>Runs before (other triggers depend on this one):</strong>
|
|
157
|
+
<ul style="margin-top: 0.5rem;">
|
|
158
|
+
<% rel[:dependents].each do |dep_row| %>
|
|
159
|
+
<li><%= link_to dep_row.trigger_name, trigger_path(dep_row), style: "color: #007bff;" %></li>
|
|
160
|
+
<% end %>
|
|
161
|
+
</ul>
|
|
162
|
+
</div>
|
|
163
|
+
<% end %>
|
|
164
|
+
</div>
|
|
165
|
+
<% end %>
|
|
166
|
+
|
|
134
167
|
<!-- SQL Diff (if drift detected) -->
|
|
135
168
|
<% if @drift_info[:has_drift] && @drift_info[:expected_sql].present? %>
|
|
136
169
|
<div style="background: white; padding: 1.5rem; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem;">
|
|
File without changes
|
data/config/routes.rb
CHANGED
|
@@ -7,20 +7,6 @@ begin
|
|
|
7
7
|
|
|
8
8
|
resources :tables, only: %i[index show]
|
|
9
9
|
|
|
10
|
-
resources :generator, only: %i[new create] do
|
|
11
|
-
collection do
|
|
12
|
-
post :preview
|
|
13
|
-
post :validate_table
|
|
14
|
-
get :tables
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
resources :sql_capsules, only: %i[new create show] do
|
|
19
|
-
member do
|
|
20
|
-
post :execute
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
10
|
resources :migrations, only: [] do
|
|
25
11
|
collection do
|
|
26
12
|
post :up
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddForEachToPgSqlTriggersRegistry < ActiveRecord::Migration[6.1]
|
|
4
|
+
def change
|
|
5
|
+
add_column :pg_sql_triggers_registry, :for_each, :string, default: "row", null: false
|
|
6
|
+
add_index :pg_sql_triggers_registry, :for_each
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddConstraintDeferralToPgSqlTriggersRegistry < ActiveRecord::Migration[6.1]
|
|
4
|
+
def change
|
|
5
|
+
add_column :pg_sql_triggers_registry, :constraint_trigger, :boolean, default: false, null: false
|
|
6
|
+
add_column :pg_sql_triggers_registry, :deferrable, :string
|
|
7
|
+
add_column :pg_sql_triggers_registry, :initially, :string
|
|
8
|
+
end
|
|
9
|
+
end
|
data/docs/README.md
CHANGED
|
@@ -27,6 +27,9 @@ Welcome to the PgSqlTriggers documentation. This directory contains comprehensiv
|
|
|
27
27
|
#### Install PgSqlTriggers
|
|
28
28
|
Start with [Getting Started](getting-started.md)
|
|
29
29
|
|
|
30
|
+
#### See bundled Rails schema migrations (`db/migrate`)
|
|
31
|
+
[Getting Started — Gem schema migrations](getting-started.md#gem-schema-migrations) lists versioned migration files and run order.
|
|
32
|
+
|
|
30
33
|
#### Learn the DSL syntax
|
|
31
34
|
See [Usage Guide - Declaring Triggers](usage-guide.md#declaring-triggers)
|
|
32
35
|
|