source_monitor 0.2.0 → 0.3.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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/agents/rails-concern.md +464 -0
  3. data/.claude/agents/rails-controller.md +424 -0
  4. data/.claude/agents/rails-hotwire.md +446 -0
  5. data/.claude/agents/rails-implement.md +374 -0
  6. data/.claude/agents/rails-job.md +334 -0
  7. data/.claude/agents/rails-lint.md +294 -0
  8. data/.claude/agents/rails-mailer.md +371 -0
  9. data/.claude/agents/rails-migration.md +449 -0
  10. data/.claude/agents/rails-model.md +420 -0
  11. data/.claude/agents/rails-policy.md +443 -0
  12. data/.claude/agents/rails-presenter.md +427 -0
  13. data/.claude/agents/rails-query.md +412 -0
  14. data/.claude/agents/rails-review.md +490 -0
  15. data/.claude/agents/rails-service.md +458 -0
  16. data/.claude/agents/rails-state-records.md +465 -0
  17. data/.claude/agents/rails-tdd.md +314 -0
  18. data/.claude/agents/rails-test.md +441 -0
  19. data/.claude/agents/rails-view-component.md +418 -0
  20. data/.claude/hooks/block-secrets.sh +52 -0
  21. data/.claude/settings.json +85 -0
  22. data/.claude/skills/action-cable-patterns/SKILL.md +296 -0
  23. data/.claude/skills/action-mailer-patterns/SKILL.md +295 -0
  24. data/.claude/skills/active-storage-setup/SKILL.md +311 -0
  25. data/.claude/skills/api-versioning/SKILL.md +294 -0
  26. data/.claude/skills/authentication-flow/SKILL.md +335 -0
  27. data/.claude/skills/authentication-flow/reference/current.md +248 -0
  28. data/.claude/skills/authentication-flow/reference/passwordless.md +253 -0
  29. data/.claude/skills/authentication-flow/reference/sessions.md +201 -0
  30. data/.claude/skills/authorization-pundit/SKILL.md +462 -0
  31. data/.claude/skills/caching-strategies/SKILL.md +350 -0
  32. data/.claude/skills/database-migrations/SKILL.md +354 -0
  33. data/.claude/skills/form-object-patterns/SKILL.md +399 -0
  34. data/.claude/skills/hotwire-patterns/SKILL.md +247 -0
  35. data/.claude/skills/hotwire-patterns/reference/stimulus.md +307 -0
  36. data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +112 -0
  37. data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +158 -0
  38. data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +218 -0
  39. data/.claude/skills/i18n-patterns/SKILL.md +320 -0
  40. data/.claude/skills/install/SKILL.md +367 -0
  41. data/.claude/skills/performance-optimization/SKILL.md +311 -0
  42. data/.claude/skills/rails-architecture/SKILL.md +259 -0
  43. data/.claude/skills/rails-architecture/reference/error-handling.md +333 -0
  44. data/.claude/skills/rails-architecture/reference/event-tracking.md +142 -0
  45. data/.claude/skills/rails-architecture/reference/layer-interactions.md +417 -0
  46. data/.claude/skills/rails-architecture/reference/multi-tenancy.md +152 -0
  47. data/.claude/skills/rails-architecture/reference/query-patterns.md +342 -0
  48. data/.claude/skills/rails-architecture/reference/service-patterns.md +286 -0
  49. data/.claude/skills/rails-architecture/reference/state-records.md +250 -0
  50. data/.claude/skills/rails-architecture/reference/testing-strategy.md +326 -0
  51. data/.claude/skills/rails-concern/SKILL.md +399 -0
  52. data/.claude/skills/rails-controller/SKILL.md +336 -0
  53. data/.claude/skills/rails-model-generator/SKILL.md +321 -0
  54. data/.claude/skills/rails-model-generator/reference/validations.md +298 -0
  55. data/.claude/skills/rails-presenter/SKILL.md +274 -0
  56. data/.claude/skills/rails-query-object/SKILL.md +289 -0
  57. data/.claude/skills/rails-service-object/SKILL.md +349 -0
  58. data/.claude/skills/solid-queue-setup/SKILL.md +307 -0
  59. data/.claude/skills/tdd-cycle/SKILL.md +359 -0
  60. data/.claude/skills/viewcomponent-patterns/SKILL.md +333 -0
  61. data/.gitignore +1 -0
  62. data/.rubocop.yml +2 -0
  63. data/.ruby-version +1 -1
  64. data/.vbw-planning/.notification-log.jsonl +192 -0
  65. data/.vbw-planning/.session-log.jsonl +871 -0
  66. data/.vbw-planning/PROJECT.md +51 -0
  67. data/.vbw-planning/REQUIREMENTS.md +50 -0
  68. data/.vbw-planning/SHIPPED.md +28 -0
  69. data/.vbw-planning/codebase/ARCHITECTURE.md +147 -0
  70. data/.vbw-planning/codebase/CONCERNS.md +99 -0
  71. data/.vbw-planning/codebase/CONVENTIONS.md +97 -0
  72. data/.vbw-planning/codebase/DEPENDENCIES.md +100 -0
  73. data/.vbw-planning/codebase/INDEX.md +86 -0
  74. data/.vbw-planning/codebase/META.md +42 -0
  75. data/.vbw-planning/codebase/PATTERNS.md +262 -0
  76. data/.vbw-planning/codebase/STACK.md +101 -0
  77. data/.vbw-planning/codebase/STRUCTURE.md +324 -0
  78. data/.vbw-planning/codebase/TESTING.md +154 -0
  79. data/.vbw-planning/config.json +12 -0
  80. data/.vbw-planning/discovery.json +24 -0
  81. data/.vbw-planning/milestones/default/ROADMAP.md +115 -0
  82. data/.vbw-planning/milestones/default/STATE.md +83 -0
  83. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01-SUMMARY.md +56 -0
  84. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01.md +187 -0
  85. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02-SUMMARY.md +64 -0
  86. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02.md +137 -0
  87. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01-SUMMARY.md +67 -0
  88. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01.md +142 -0
  89. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02-SUMMARY.md +64 -0
  90. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02.md +138 -0
  91. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03-SUMMARY.md +85 -0
  92. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03.md +147 -0
  93. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04-SUMMARY.md +63 -0
  94. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04.md +129 -0
  95. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05-SUMMARY.md +74 -0
  96. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05.md +154 -0
  97. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION-wave1.md +303 -0
  98. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION.md +510 -0
  99. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01-SUMMARY.md +61 -0
  100. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01.md +161 -0
  101. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02-SUMMARY.md +66 -0
  102. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02.md +132 -0
  103. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03-SUMMARY.md +59 -0
  104. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03.md +171 -0
  105. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04-SUMMARY.md +56 -0
  106. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04.md +152 -0
  107. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/04-CONTEXT.md +33 -0
  108. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01-SUMMARY.md +42 -0
  109. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01.md +119 -0
  110. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02-SUMMARY.md +52 -0
  111. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02.md +195 -0
  112. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03-SUMMARY.md +79 -0
  113. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03.md +130 -0
  114. data/CHANGELOG.md +28 -0
  115. data/CLAUDE.md +179 -0
  116. data/Gemfile +8 -0
  117. data/Gemfile.lock +114 -101
  118. data/Rakefile +2 -0
  119. data/app/assets/builds/source_monitor/application.css +2076 -0
  120. data/app/assets/builds/source_monitor/application.js +2758 -0
  121. data/app/assets/builds/source_monitor/application.js.map +7 -0
  122. data/app/controllers/source_monitor/application_controller.rb +2 -0
  123. data/app/controllers/source_monitor/health_controller.rb +2 -0
  124. data/app/controllers/source_monitor/import_sessions/bulk_configuration.rb +106 -0
  125. data/app/controllers/source_monitor/import_sessions/entry_annotation.rb +187 -0
  126. data/app/controllers/source_monitor/import_sessions/health_check_management.rb +112 -0
  127. data/app/controllers/source_monitor/import_sessions/opml_parser.rb +130 -0
  128. data/app/controllers/source_monitor/import_sessions_controller.rb +6 -507
  129. data/app/controllers/source_monitor/items_controller.rb +2 -0
  130. data/app/controllers/source_monitor/sources_controller.rb +0 -14
  131. data/app/helpers/source_monitor/application_helper.rb +4 -112
  132. data/app/helpers/source_monitor/health_badge_helper.rb +69 -0
  133. data/app/helpers/source_monitor/table_sort_helper.rb +53 -0
  134. data/app/jobs/source_monitor/application_job.rb +2 -0
  135. data/app/models/source_monitor/application_record.rb +2 -0
  136. data/app/models/source_monitor/log_entry.rb +0 -2
  137. data/config/coverage_baseline.json +217 -1862
  138. data/config/routes.rb +2 -0
  139. data/db/migrate/20251009103000_add_feed_content_readability_to_sources.rb +2 -0
  140. data/db/migrate/20251014171659_add_performance_indexes.rb +2 -0
  141. data/db/migrate/20251014172525_add_fetch_status_check_constraint.rb +2 -0
  142. data/db/migrate/20251108120116_refresh_fetch_status_constraint.rb +2 -0
  143. data/db/migrate/20260210204022_add_composite_index_to_log_entries.rb +17 -0
  144. data/lib/source_monitor/assets/bundler.rb +2 -0
  145. data/lib/source_monitor/assets.rb +2 -0
  146. data/lib/source_monitor/configuration/authentication_settings.rb +62 -0
  147. data/lib/source_monitor/configuration/events.rb +60 -0
  148. data/lib/source_monitor/configuration/fetching_settings.rb +27 -0
  149. data/lib/source_monitor/configuration/health_settings.rb +27 -0
  150. data/lib/source_monitor/configuration/http_settings.rb +43 -0
  151. data/lib/source_monitor/configuration/model_definition.rb +108 -0
  152. data/lib/source_monitor/configuration/models.rb +36 -0
  153. data/lib/source_monitor/configuration/realtime_settings.rb +95 -0
  154. data/lib/source_monitor/configuration/retention_settings.rb +45 -0
  155. data/lib/source_monitor/configuration/scraper_registry.rb +67 -0
  156. data/lib/source_monitor/configuration/scraping_settings.rb +39 -0
  157. data/lib/source_monitor/configuration/validation_definition.rb +32 -0
  158. data/lib/source_monitor/configuration.rb +12 -579
  159. data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +138 -0
  160. data/lib/source_monitor/dashboard/queries/stats_query.rb +71 -0
  161. data/lib/source_monitor/dashboard/queries.rb +2 -195
  162. data/lib/source_monitor/engine.rb +2 -0
  163. data/lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb +141 -0
  164. data/lib/source_monitor/fetching/feed_fetcher/entry_processor.rb +89 -0
  165. data/lib/source_monitor/fetching/feed_fetcher/source_updater.rb +200 -0
  166. data/lib/source_monitor/fetching/feed_fetcher.rb +37 -379
  167. data/lib/source_monitor/items/item_creator/content_extractor.rb +113 -0
  168. data/lib/source_monitor/items/item_creator/entry_parser/media_extraction.rb +96 -0
  169. data/lib/source_monitor/items/item_creator/entry_parser.rb +294 -0
  170. data/lib/source_monitor/items/item_creator.rb +28 -455
  171. data/lib/source_monitor/setup/bundle_installer.rb +2 -0
  172. data/lib/source_monitor/setup/cli.rb +2 -0
  173. data/lib/source_monitor/setup/dependency_checker.rb +2 -0
  174. data/lib/source_monitor/setup/detectors.rb +2 -0
  175. data/lib/source_monitor/setup/gemfile_editor.rb +2 -0
  176. data/lib/source_monitor/setup/initializer_patcher.rb +2 -0
  177. data/lib/source_monitor/setup/install_generator.rb +2 -0
  178. data/lib/source_monitor/setup/migration_installer.rb +2 -0
  179. data/lib/source_monitor/setup/node_installer.rb +2 -0
  180. data/lib/source_monitor/setup/prompter.rb +2 -0
  181. data/lib/source_monitor/setup/requirements.rb +2 -0
  182. data/lib/source_monitor/setup/shell_runner.rb +2 -0
  183. data/lib/source_monitor/setup/verification/action_cable_verifier.rb +2 -0
  184. data/lib/source_monitor/setup/verification/printer.rb +2 -0
  185. data/lib/source_monitor/setup/verification/result.rb +2 -0
  186. data/lib/source_monitor/setup/verification/runner.rb +2 -0
  187. data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +2 -0
  188. data/lib/source_monitor/setup/verification/telemetry_logger.rb +2 -0
  189. data/lib/source_monitor/setup/workflow.rb +2 -0
  190. data/lib/source_monitor/version.rb +3 -1
  191. data/lib/source_monitor.rb +140 -58
  192. data/lib/tasks/source_monitor_assets.rake +2 -0
  193. data/lib/tasks/source_monitor_setup.rake +2 -0
  194. data/lib/tasks/source_monitor_tasks.rake +2 -0
  195. data/source_monitor.gemspec +3 -1
  196. metadata +144 -4
@@ -0,0 +1,462 @@
1
+ ---
2
+ name: authorization-pundit
3
+ description: Implements policy-based authorization with Pundit for resource access control. Use when adding authorization rules, checking permissions, restricting actions, role-based access, or when user mentions Pundit, policies, authorization, or permissions.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # Authorization with Pundit for Rails 8
8
+
9
+ ## Overview
10
+
11
+ Pundit provides policy-based authorization:
12
+ - Plain Ruby policy objects
13
+ - Convention over configuration
14
+ - Easy to test with Minitest
15
+ - Scoped queries for collections
16
+ - Works with any authentication system
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ bundle add pundit
22
+ bin/rails generate pundit:install
23
+ bin/rails generate pundit:policy Event
24
+ ```
25
+
26
+ ## TDD Workflow
27
+
28
+ ```
29
+ Authorization Progress:
30
+ - [ ] Step 1: Write policy test (RED)
31
+ - [ ] Step 2: Run test (fails)
32
+ - [ ] Step 3: Implement policy
33
+ - [ ] Step 4: Run test (GREEN)
34
+ - [ ] Step 5: Add policy to controller
35
+ - [ ] Step 6: Test integration
36
+ ```
37
+
38
+ ## Base Policy
39
+
40
+ ```ruby
41
+ # app/policies/application_policy.rb
42
+ class ApplicationPolicy
43
+ attr_reader :user, :record
44
+
45
+ def initialize(user, record)
46
+ @user = user
47
+ @record = record
48
+ end
49
+
50
+ def index?
51
+ false
52
+ end
53
+
54
+ def show?
55
+ false
56
+ end
57
+
58
+ def create?
59
+ false
60
+ end
61
+
62
+ def new?
63
+ create?
64
+ end
65
+
66
+ def update?
67
+ false
68
+ end
69
+
70
+ def edit?
71
+ update?
72
+ end
73
+
74
+ def destroy?
75
+ false
76
+ end
77
+
78
+ class Scope
79
+ def initialize(user, scope)
80
+ @user = user
81
+ @scope = scope
82
+ end
83
+
84
+ def resolve
85
+ raise NotImplementedError, "Define #resolve in #{self.class}"
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :user, :scope
91
+ end
92
+ end
93
+ ```
94
+
95
+ ## Policy Testing (Minitest)
96
+
97
+ ### Basic Policy Test
98
+
99
+ ```ruby
100
+ # test/policies/event_policy_test.rb
101
+ require "test_helper"
102
+
103
+ class EventPolicyTest < ActiveSupport::TestCase
104
+ setup do
105
+ @account = accounts(:one)
106
+ @user = users(:one) # belongs to @account
107
+ @other_user = users(:other_account) # different account
108
+ @event = events(:one) # belongs to @account
109
+ end
110
+
111
+ # -- index --
112
+ test "index? permits any authenticated user" do
113
+ policy = EventPolicy.new(@user, Event)
114
+ assert policy.index?
115
+ end
116
+
117
+ # -- show --
118
+ test "show? permits user from same account" do
119
+ policy = EventPolicy.new(@user, @event)
120
+ assert policy.show?
121
+ end
122
+
123
+ test "show? denies user from different account" do
124
+ policy = EventPolicy.new(@other_user, @event)
125
+ assert_not policy.show?
126
+ end
127
+
128
+ # -- create --
129
+ test "create? permits user from same account" do
130
+ new_event = Event.new(account: @account)
131
+ policy = EventPolicy.new(@user, new_event)
132
+ assert policy.create?
133
+ end
134
+
135
+ # -- update --
136
+ test "update? permits user from same account" do
137
+ policy = EventPolicy.new(@user, @event)
138
+ assert policy.update?
139
+ end
140
+
141
+ test "update? denies user from different account" do
142
+ policy = EventPolicy.new(@other_user, @event)
143
+ assert_not policy.update?
144
+ end
145
+
146
+ # -- destroy --
147
+ test "destroy? permits user from same account" do
148
+ policy = EventPolicy.new(@user, @event)
149
+ assert policy.destroy?
150
+ end
151
+
152
+ test "destroy? denies user from different account" do
153
+ policy = EventPolicy.new(@other_user, @event)
154
+ assert_not policy.destroy?
155
+ end
156
+
157
+ # -- Scope --
158
+ test "Scope returns events for user account only" do
159
+ scope = EventPolicy::Scope.new(@user, Event).resolve
160
+
161
+ scope.each do |event|
162
+ assert_equal @user.account_id, event.account_id
163
+ end
164
+ end
165
+
166
+ test "Scope excludes other account events" do
167
+ other_event = events(:other_account)
168
+ scope = EventPolicy::Scope.new(@user, Event).resolve
169
+
170
+ assert_not_includes scope, other_event
171
+ end
172
+ end
173
+ ```
174
+
175
+ ### Role-Based Policy Test
176
+
177
+ ```ruby
178
+ # test/policies/event_policy_test.rb (role-based extension)
179
+ class EventPolicyRoleTest < ActiveSupport::TestCase
180
+ setup do
181
+ @admin = users(:admin)
182
+ @member = users(:one)
183
+ @event = events(:one)
184
+ end
185
+
186
+ test "destroy? permits admin" do
187
+ policy = EventPolicy.new(@admin, @event)
188
+ assert policy.destroy?
189
+ end
190
+
191
+ test "publish? permits owner for draft events" do
192
+ @event.update(status: :draft)
193
+ policy = EventPolicy.new(@member, @event)
194
+ assert policy.publish?
195
+ end
196
+
197
+ test "publish? denies for non-draft events" do
198
+ @event.update(status: :published)
199
+ policy = EventPolicy.new(@member, @event)
200
+ assert_not policy.publish?
201
+ end
202
+ end
203
+ ```
204
+
205
+ ## Policy Implementation
206
+
207
+ ### Basic Policy (Account-Scoped)
208
+
209
+ ```ruby
210
+ # app/policies/event_policy.rb
211
+ class EventPolicy < ApplicationPolicy
212
+ def index?
213
+ true
214
+ end
215
+
216
+ def show?
217
+ owner?
218
+ end
219
+
220
+ def create?
221
+ true
222
+ end
223
+
224
+ def update?
225
+ owner?
226
+ end
227
+
228
+ def destroy?
229
+ owner?
230
+ end
231
+
232
+ private
233
+
234
+ def owner?
235
+ record.account_id == user.account_id
236
+ end
237
+
238
+ class Scope < ApplicationPolicy::Scope
239
+ def resolve
240
+ scope.where(account_id: user.account_id)
241
+ end
242
+ end
243
+ end
244
+ ```
245
+
246
+ ### Role-Based Policy
247
+
248
+ ```ruby
249
+ # app/policies/event_policy.rb
250
+ class EventPolicy < ApplicationPolicy
251
+ def index?
252
+ true
253
+ end
254
+
255
+ def show?
256
+ owner? || admin?
257
+ end
258
+
259
+ def create?
260
+ member_or_above?
261
+ end
262
+
263
+ def update?
264
+ owner_or_admin?
265
+ end
266
+
267
+ def destroy?
268
+ admin?
269
+ end
270
+
271
+ def publish?
272
+ owner_or_admin? && record.draft?
273
+ end
274
+
275
+ private
276
+
277
+ def owner?
278
+ record.account_id == user.account_id
279
+ end
280
+
281
+ def admin?
282
+ user.admin?
283
+ end
284
+
285
+ def member_or_above?
286
+ user.member? || user.admin?
287
+ end
288
+
289
+ def owner_or_admin?
290
+ owner? || admin?
291
+ end
292
+
293
+ class Scope < ApplicationPolicy::Scope
294
+ def resolve
295
+ if user.admin?
296
+ scope.all
297
+ else
298
+ scope.where(account_id: user.account_id)
299
+ end
300
+ end
301
+ end
302
+ end
303
+ ```
304
+
305
+ ## Controller Integration
306
+
307
+ ```ruby
308
+ # app/controllers/events_controller.rb
309
+ class EventsController < ApplicationController
310
+ def index
311
+ @events = policy_scope(Event)
312
+ end
313
+
314
+ def show
315
+ @event = Event.find(params[:id])
316
+ authorize @event
317
+ end
318
+
319
+ def create
320
+ @event = current_account.events.build(event_params)
321
+ authorize @event
322
+
323
+ if @event.save
324
+ redirect_to @event, notice: t(".success")
325
+ else
326
+ render :new, status: :unprocessable_entity
327
+ end
328
+ end
329
+
330
+ def destroy
331
+ @event = Event.find(params[:id])
332
+ authorize @event
333
+ @event.destroy
334
+ redirect_to events_path, notice: t(".success")
335
+ end
336
+ end
337
+ ```
338
+
339
+ ### Ensuring Authorization
340
+
341
+ ```ruby
342
+ # app/controllers/application_controller.rb
343
+ class ApplicationController < ActionController::Base
344
+ include Pundit::Authorization
345
+
346
+ after_action :verify_authorized, except: :index
347
+ after_action :verify_policy_scoped, only: :index
348
+
349
+ rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
350
+
351
+ private
352
+
353
+ def user_not_authorized
354
+ flash[:alert] = t("pundit.not_authorized")
355
+ redirect_back(fallback_location: root_path)
356
+ end
357
+ end
358
+ ```
359
+
360
+ ## Testing Controller Authorization
361
+
362
+ ```ruby
363
+ # test/controllers/events_controller_test.rb
364
+ require "test_helper"
365
+
366
+ class EventsControllerTest < ActionDispatch::IntegrationTest
367
+ setup do
368
+ @user = users(:one)
369
+ @other_user = users(:other_account)
370
+ @event = events(:one) # belongs to @user's account
371
+ @other_event = events(:other_account)
372
+ sign_in @user
373
+ end
374
+
375
+ test "allows access to own events" do
376
+ get event_path(@event)
377
+ assert_response :success
378
+ end
379
+
380
+ test "denies access to other account events" do
381
+ get event_path(@other_event)
382
+ assert_redirected_to root_path
383
+ end
384
+
385
+ test "allows deletion of own events" do
386
+ assert_difference("Event.count", -1) do
387
+ delete event_path(@event)
388
+ end
389
+ assert_redirected_to events_path
390
+ end
391
+
392
+ test "denies deletion of other account events" do
393
+ assert_no_difference("Event.count") do
394
+ delete event_path(@other_event)
395
+ end
396
+ assert_redirected_to root_path
397
+ end
398
+ end
399
+ ```
400
+
401
+ ## View Integration
402
+
403
+ ```erb
404
+ <%# app/views/events/show.html.erb %>
405
+ <h1><%= @event.name %></h1>
406
+
407
+ <% if policy(@event).edit? %>
408
+ <%= link_to t("common.edit"), edit_event_path(@event) %>
409
+ <% end %>
410
+
411
+ <% if policy(@event).destroy? %>
412
+ <%= button_to t("common.delete"), @event, method: :delete,
413
+ data: { confirm: t("common.confirm_delete") } %>
414
+ <% end %>
415
+ ```
416
+
417
+ ## Headless Policies
418
+
419
+ For actions not tied to a specific record:
420
+
421
+ ```ruby
422
+ # app/policies/dashboard_policy.rb
423
+ class DashboardPolicy < ApplicationPolicy
424
+ def initialize(user, _record = nil)
425
+ @user = user
426
+ end
427
+
428
+ def show?
429
+ true
430
+ end
431
+
432
+ def admin_panel?
433
+ user.admin?
434
+ end
435
+ end
436
+ ```
437
+
438
+ ```ruby
439
+ # Controller
440
+ authorize :dashboard, :admin_panel?
441
+ ```
442
+
443
+ ## Error Messages
444
+
445
+ ```yaml
446
+ # config/locales/en.yml
447
+ en:
448
+ pundit:
449
+ not_authorized: You are not authorized to perform this action.
450
+ ```
451
+
452
+ ## Checklist
453
+
454
+ - [ ] Policy test written first (RED)
455
+ - [ ] Policy inherits from ApplicationPolicy
456
+ - [ ] Scope defined for collections
457
+ - [ ] Controller uses `authorize` and `policy_scope`
458
+ - [ ] `verify_authorized` after_action enabled
459
+ - [ ] Views use `policy(@record).action?`
460
+ - [ ] Error handling configured
461
+ - [ ] Multi-tenancy enforced in Scope
462
+ - [ ] All tests GREEN