plutonium 0.33.1 → 0.34.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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/# Plutonium: The pre-alpha demo.md +4 -2
  3. data/.claude/skills/assets/SKILL.md +416 -0
  4. data/.claude/skills/connect-resource/SKILL.md +112 -0
  5. data/.claude/skills/controller/SKILL.md +302 -0
  6. data/.claude/skills/create-resource/SKILL.md +240 -0
  7. data/.claude/skills/definition/SKILL.md +218 -0
  8. data/.claude/skills/definition-actions/SKILL.md +386 -0
  9. data/.claude/skills/definition-fields/SKILL.md +474 -0
  10. data/.claude/skills/definition-query/SKILL.md +334 -0
  11. data/.claude/skills/forms/SKILL.md +439 -0
  12. data/.claude/skills/installation/SKILL.md +300 -0
  13. data/.claude/skills/interaction/SKILL.md +382 -0
  14. data/.claude/skills/model/SKILL.md +267 -0
  15. data/.claude/skills/model-features/SKILL.md +286 -0
  16. data/.claude/skills/nested-resources/SKILL.md +274 -0
  17. data/.claude/skills/package/SKILL.md +191 -0
  18. data/.claude/skills/policy/SKILL.md +352 -0
  19. data/.claude/skills/portal/SKILL.md +400 -0
  20. data/.claude/skills/resource/SKILL.md +281 -0
  21. data/.claude/skills/rodauth/SKILL.md +452 -0
  22. data/.claude/skills/views/SKILL.md +563 -0
  23. data/Appraisals +46 -4
  24. data/CHANGELOG.md +32 -1
  25. data/app/assets/plutonium.css +2 -2
  26. data/config/brakeman.ignore +239 -0
  27. data/config/initializers/action_policy.rb +1 -1
  28. data/docs/.vitepress/config.ts +132 -47
  29. data/docs/concepts/architecture.md +226 -0
  30. data/docs/concepts/auto-detection.md +254 -0
  31. data/docs/concepts/index.md +61 -0
  32. data/docs/concepts/packages-portals.md +304 -0
  33. data/docs/concepts/resources.md +224 -0
  34. data/docs/cookbook/blog.md +412 -0
  35. data/docs/cookbook/index.md +289 -0
  36. data/docs/cookbook/saas.md +481 -0
  37. data/docs/getting-started/index.md +56 -0
  38. data/docs/getting-started/installation.md +146 -0
  39. data/docs/getting-started/tutorial/01-setup.md +118 -0
  40. data/docs/getting-started/tutorial/02-first-resource.md +180 -0
  41. data/docs/getting-started/tutorial/03-authentication.md +246 -0
  42. data/docs/getting-started/tutorial/04-authorization.md +170 -0
  43. data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
  44. data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
  45. data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
  46. data/docs/getting-started/tutorial/index.md +64 -0
  47. data/docs/guides/adding-resources.md +420 -0
  48. data/docs/guides/authentication.md +551 -0
  49. data/docs/guides/authorization.md +468 -0
  50. data/docs/guides/creating-packages.md +380 -0
  51. data/docs/guides/custom-actions.md +523 -0
  52. data/docs/guides/index.md +45 -0
  53. data/docs/guides/multi-tenancy.md +302 -0
  54. data/docs/guides/nested-resources.md +411 -0
  55. data/docs/guides/search-filtering.md +266 -0
  56. data/docs/guides/theming.md +321 -0
  57. data/docs/index.md +67 -26
  58. data/docs/public/CLAUDE.md +64 -21
  59. data/docs/reference/assets/index.md +496 -0
  60. data/docs/reference/controller/index.md +363 -0
  61. data/docs/reference/definition/actions.md +400 -0
  62. data/docs/reference/definition/fields.md +350 -0
  63. data/docs/reference/definition/index.md +252 -0
  64. data/docs/reference/definition/query.md +342 -0
  65. data/docs/reference/generators/index.md +469 -0
  66. data/docs/reference/index.md +49 -0
  67. data/docs/reference/interaction/index.md +445 -0
  68. data/docs/reference/model/features.md +248 -0
  69. data/docs/reference/model/index.md +219 -0
  70. data/docs/reference/policy/index.md +385 -0
  71. data/docs/reference/portal/index.md +382 -0
  72. data/docs/reference/views/forms.md +396 -0
  73. data/docs/reference/views/index.md +479 -0
  74. data/gemfiles/rails_7.gemfile +9 -2
  75. data/gemfiles/rails_7.gemfile.lock +146 -111
  76. data/gemfiles/rails_8.0.gemfile +20 -0
  77. data/gemfiles/rails_8.0.gemfile.lock +417 -0
  78. data/gemfiles/rails_8.1.gemfile +20 -0
  79. data/gemfiles/rails_8.1.gemfile.lock +419 -0
  80. data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
  81. data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
  82. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
  83. data/lib/generators/pu/pkg/portal/USAGE +65 -0
  84. data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
  85. data/lib/generators/pu/res/conn/USAGE +71 -0
  86. data/lib/generators/pu/res/model/USAGE +106 -110
  87. data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
  88. data/lib/generators/pu/res/scaffold/USAGE +85 -0
  89. data/lib/generators/pu/rodauth/install_generator.rb +2 -6
  90. data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
  91. data/lib/generators/pu/skills/sync/USAGE +14 -0
  92. data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
  93. data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
  94. data/lib/plutonium/core/controller.rb +2 -2
  95. data/lib/plutonium/interaction/base.rb +1 -0
  96. data/lib/plutonium/package/engine.rb +2 -2
  97. data/lib/plutonium/query/adhoc_block.rb +6 -2
  98. data/lib/plutonium/query/model_scope.rb +1 -1
  99. data/lib/plutonium/railtie.rb +4 -0
  100. data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
  101. data/lib/plutonium/resource/query_object.rb +38 -8
  102. data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
  103. data/lib/plutonium/version.rb +1 -1
  104. data/lib/tasks/release.rake +19 -4
  105. data/package.json +1 -1
  106. metadata +76 -39
  107. data/brakeman.ignore +0 -28
  108. data/docs/api-examples.md +0 -49
  109. data/docs/guide/claude-code-guide.md +0 -74
  110. data/docs/guide/deep-dive/authorization.md +0 -189
  111. data/docs/guide/deep-dive/multitenancy.md +0 -256
  112. data/docs/guide/deep-dive/resources.md +0 -390
  113. data/docs/guide/getting-started/01-installation.md +0 -165
  114. data/docs/guide/index.md +0 -28
  115. data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
  116. data/docs/guide/introduction/02-core-concepts.md +0 -440
  117. data/docs/guide/tutorial/01-project-setup.md +0 -75
  118. data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
  119. data/docs/guide/tutorial/03-defining-resources.md +0 -90
  120. data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
  121. data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
  122. data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
  123. data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
  124. data/docs/markdown-examples.md +0 -85
  125. data/docs/modules/action.md +0 -244
  126. data/docs/modules/authentication.md +0 -236
  127. data/docs/modules/configuration.md +0 -599
  128. data/docs/modules/controller.md +0 -443
  129. data/docs/modules/core.md +0 -316
  130. data/docs/modules/definition.md +0 -1308
  131. data/docs/modules/display.md +0 -759
  132. data/docs/modules/form.md +0 -495
  133. data/docs/modules/generator.md +0 -400
  134. data/docs/modules/index.md +0 -167
  135. data/docs/modules/interaction.md +0 -642
  136. data/docs/modules/package.md +0 -151
  137. data/docs/modules/policy.md +0 -176
  138. data/docs/modules/portal.md +0 -710
  139. data/docs/modules/query.md +0 -297
  140. data/docs/modules/resource_record.md +0 -618
  141. data/docs/modules/routing.md +0 -690
  142. data/docs/modules/table.md +0 -301
  143. data/docs/modules/ui.md +0 -631
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: package
3
+ description: Plutonium packages - modular Rails engines for organizing features and portals
4
+ ---
5
+
6
+ # Plutonium Packages
7
+
8
+ Packages are specialized Rails engines for organizing code. There are two types:
9
+
10
+ | Type | Purpose | Generator |
11
+ |------|---------|-----------|
12
+ | **Feature** | Business logic (models, policies, interactions) | `rails g pu:pkg:package NAME` |
13
+ | **Portal** | Web interface (controllers, views, auth) | `rails g pu:pkg:portal NAME` |
14
+
15
+ ## Feature Packages
16
+
17
+ Contain domain logic without web interface:
18
+
19
+ ```bash
20
+ rails g pu:pkg:package blogging
21
+ ```
22
+
23
+ ### Structure
24
+
25
+ ```
26
+ packages/blogging/
27
+ ├── app/
28
+ │ ├── models/blogging/
29
+ │ │ ├── post.rb
30
+ │ │ └── comment.rb
31
+ │ ├── definitions/blogging/
32
+ │ │ ├── post_definition.rb
33
+ │ │ └── comment_definition.rb
34
+ │ ├── policies/blogging/
35
+ │ │ ├── post_policy.rb
36
+ │ │ └── comment_policy.rb
37
+ │ └── interactions/blogging/
38
+ │ └── publish_post_interaction.rb
39
+ ├── db/migrate/
40
+ └── lib/
41
+ └── engine.rb
42
+ ```
43
+
44
+ ### Engine
45
+
46
+ ```ruby
47
+ module Blogging
48
+ class Engine < Rails::Engine
49
+ include Plutonium::Package::Engine
50
+ end
51
+ end
52
+ ```
53
+
54
+ ### Namespacing
55
+
56
+ All classes are auto-namespaced:
57
+ - `app/models/blogging/post.rb` → `Blogging::Post`
58
+ - `app/policies/blogging/post_policy.rb` → `Blogging::PostPolicy`
59
+
60
+ ## Portal Packages
61
+
62
+ Provide web interfaces for specific user types:
63
+
64
+ ```bash
65
+ rails g pu:pkg:portal admin
66
+ rails g pu:pkg:portal dashboard
67
+ ```
68
+
69
+ ### Structure
70
+
71
+ ```
72
+ packages/admin_portal/
73
+ ├── app/
74
+ │ ├── controllers/admin_portal/
75
+ │ │ ├── concerns/controller.rb
76
+ │ │ ├── dashboard_controller.rb
77
+ │ │ ├── plutonium_controller.rb
78
+ │ │ └── resource_controller.rb
79
+ │ ├── definitions/admin_portal/ # Portal-specific overrides
80
+ │ ├── policies/admin_portal/ # Portal-specific overrides
81
+ │ └── views/
82
+ │ └── layouts/admin_portal.html.erb
83
+ ├── config/
84
+ │ └── routes.rb
85
+ └── lib/
86
+ └── engine.rb
87
+ ```
88
+
89
+ ### Engine
90
+
91
+ ```ruby
92
+ module AdminPortal
93
+ class Engine < Rails::Engine
94
+ include Plutonium::Portal::Engine
95
+
96
+ config.after_initialize do
97
+ # Optional: multi-tenancy
98
+ scope_to_entity Organization, strategy: :path
99
+ end
100
+ end
101
+ end
102
+ ```
103
+
104
+ See `portal` skill for portal-specific features.
105
+
106
+ ## Package Loading
107
+
108
+ Packages are loaded via `config/packages.rb`:
109
+
110
+ ```ruby
111
+ # config/packages.rb (generated during install)
112
+ Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) do |package|
113
+ load package
114
+ end
115
+ ```
116
+
117
+ This is required in `config/application.rb`.
118
+
119
+ ## Creating Resources in Packages
120
+
121
+ ```bash
122
+ # In main app
123
+ rails g pu:res:scaffold Post title:string --dest=main_app
124
+
125
+ # In feature package
126
+ rails g pu:res:scaffold Blogging::Post title:string --dest=blogging
127
+ ```
128
+
129
+ ## Connecting Resources to Portals
130
+
131
+ Resources must be connected to portals to be accessible:
132
+
133
+ ```bash
134
+ rails g pu:res:conn Post --dest=admin_portal
135
+ rails g pu:res:conn Blogging::Post --dest=admin_portal
136
+ ```
137
+
138
+ This creates:
139
+ - Portal-specific controller
140
+ - Portal-specific policy (optional)
141
+ - Portal-specific definition (optional)
142
+ - Route registration
143
+
144
+ ## When to Use Each Type
145
+
146
+ ### Feature Packages
147
+
148
+ Use for:
149
+ - Domain-specific models and logic
150
+ - Reusable business functionality
151
+ - Shared code across portals
152
+
153
+ Examples: `blogging`, `billing`, `inventory`, `user_management`
154
+
155
+ ### Portal Packages
156
+
157
+ Use for:
158
+ - User-facing interfaces
159
+ - Role-specific access (admin, customer, public)
160
+ - Different authentication requirements
161
+
162
+ Examples: `admin_portal`, `dashboard_portal`, `public_portal`, `api_portal`
163
+
164
+ ## Typical Architecture
165
+
166
+ ```
167
+ packages/
168
+ ├── blogging/ # Feature: blog functionality
169
+ │ └── models, definitions, policies
170
+ ├── billing/ # Feature: payment/invoicing
171
+ │ └── models, definitions, policies
172
+ ├── admin_portal/ # Portal: admin interface
173
+ │ └── controllers, views, routes
174
+ └── dashboard_portal/ # Portal: user dashboard
175
+ └── controllers, views, routes
176
+ ```
177
+
178
+ ## Migration Integration
179
+
180
+ Package migrations are automatically integrated:
181
+
182
+ ```bash
183
+ rails db:migrate # Runs migrations from all packages
184
+ ```
185
+
186
+ ## Related Skills
187
+
188
+ - `portal` - Portal-specific features (auth, entity scoping, routes)
189
+ - `resource` - Resource architecture overview
190
+ - `connect-resource` - Connecting resources to portals
191
+ - `create-resource` - Creating resources
@@ -0,0 +1,352 @@
1
+ ---
2
+ name: policy
3
+ description: Plutonium resource policies - authorization, attribute permissions, and scoping
4
+ ---
5
+
6
+ # Plutonium Policies
7
+
8
+ Policies control WHO can do WHAT with resources. Built on [ActionPolicy](https://actionpolicy.evilmartians.io/).
9
+
10
+ Plutonium extends ActionPolicy with:
11
+ - Attribute permissions (`permitted_attributes_for_*`)
12
+ - Association permissions (`permitted_associations`)
13
+ - Automatic entity scoping for multi-tenancy
14
+ - Derived action methods (e.g., `update?` inherits from `create?`)
15
+
16
+ ## Base Class
17
+
18
+ ```ruby
19
+ # app/policies/resource_policy.rb (generated during install)
20
+ class ResourcePolicy < Plutonium::Resource::Policy
21
+ # App-wide authorization defaults
22
+ end
23
+
24
+ # app/policies/post_policy.rb (per resource)
25
+ class PostPolicy < ResourcePolicy
26
+ def create?
27
+ user.present?
28
+ end
29
+
30
+ def read?
31
+ true
32
+ end
33
+
34
+ def permitted_attributes_for_create
35
+ %i[title content]
36
+ end
37
+
38
+ def permitted_attributes_for_read
39
+ %i[title content author created_at]
40
+ end
41
+ end
42
+ ```
43
+
44
+ ## Action Permissions
45
+
46
+ ### Core Actions (Must Override)
47
+
48
+ ```ruby
49
+ def create? # Default: false - MUST override
50
+ user.present?
51
+ end
52
+
53
+ def read? # Default: false - MUST override
54
+ true
55
+ end
56
+ ```
57
+
58
+ ### Derived Actions (Inherit by Default)
59
+
60
+ | Method | Inherits From | Override When |
61
+ |--------|---------------|---------------|
62
+ | `update?` | `create?` | Different update rules |
63
+ | `destroy?` | `create?` | Different delete rules |
64
+ | `index?` | `read?` | Custom listing rules |
65
+ | `show?` | `read?` | Record-specific read rules |
66
+ | `new?` | `create?` | Rarely needed |
67
+ | `edit?` | `update?` | Rarely needed |
68
+ | `search?` | `index?` | Search-specific rules |
69
+
70
+ ### Custom Actions
71
+
72
+ Define methods matching your action names:
73
+
74
+ ```ruby
75
+ def publish?
76
+ update? && record.draft?
77
+ end
78
+
79
+ def archive?
80
+ create? && !record.archived?
81
+ end
82
+
83
+ def invite_user?
84
+ user.admin?
85
+ end
86
+ ```
87
+
88
+ Actions are secure by default - undefined methods return `false`.
89
+
90
+ ## Attribute Permissions
91
+
92
+ ### Core Methods (Must Override for Production)
93
+
94
+ ```ruby
95
+ # What users can see (index, show)
96
+ def permitted_attributes_for_read
97
+ %i[title content author published_at created_at]
98
+ end
99
+
100
+ # What users can set (create, update)
101
+ def permitted_attributes_for_create
102
+ %i[title content]
103
+ end
104
+ ```
105
+
106
+ ### Derived Methods (Inherit by Default)
107
+
108
+ | Method | Inherits From |
109
+ |--------|---------------|
110
+ | `permitted_attributes_for_update` | `permitted_attributes_for_create` |
111
+ | `permitted_attributes_for_index` | `permitted_attributes_for_read` |
112
+ | `permitted_attributes_for_show` | `permitted_attributes_for_read` |
113
+ | `permitted_attributes_for_new` | `permitted_attributes_for_create` |
114
+ | `permitted_attributes_for_edit` | `permitted_attributes_for_update` |
115
+
116
+ ### Per-Action Attributes
117
+
118
+ Show different fields for different views:
119
+
120
+ ```ruby
121
+ def permitted_attributes_for_index
122
+ %i[title author created_at] # Minimal for list
123
+ end
124
+
125
+ def permitted_attributes_for_read
126
+ %i[title content author tags created_at updated_at] # Full for detail
127
+ end
128
+ ```
129
+
130
+ ### Auto-Detection (Development Only)
131
+
132
+ In development, undefined attribute methods auto-detect from the model. This raises errors in production - always define explicitly.
133
+
134
+ ## Association Permissions
135
+
136
+ Control which associations can be rendered:
137
+
138
+ ```ruby
139
+ def permitted_associations
140
+ %i[comments tags author]
141
+ end
142
+ ```
143
+
144
+ Used for:
145
+ - Nested forms
146
+ - Related data displays
147
+ - Association fields in tables
148
+
149
+ ## Collection Scoping
150
+
151
+ Filter which records users can see:
152
+
153
+ ```ruby
154
+ relation_scope do |relation|
155
+ if user.admin?
156
+ relation
157
+ else
158
+ relation.where(author: user)
159
+ end
160
+ end
161
+ ```
162
+
163
+ ### With Entity Scoping
164
+
165
+ Call `super` to preserve automatic entity scoping:
166
+
167
+ ```ruby
168
+ relation_scope do |relation|
169
+ relation = super(relation) # Apply entity scope first
170
+
171
+ if user.admin?
172
+ relation
173
+ else
174
+ relation.where(published: true)
175
+ end
176
+ end
177
+ ```
178
+
179
+ ## Portal-Specific Policies
180
+
181
+ Override policies per portal:
182
+
183
+ ```ruby
184
+ # Base policy
185
+ class PostPolicy < ResourcePolicy
186
+ def create?
187
+ user.present?
188
+ end
189
+ end
190
+
191
+ # Admin portal - more permissive
192
+ class AdminPortal::PostPolicy < ::PostPolicy
193
+ include AdminPortal::ResourcePolicy
194
+
195
+ def destroy?
196
+ true # Admins can always delete
197
+ end
198
+
199
+ def permitted_attributes_for_create
200
+ %i[title content featured internal_notes] # More fields
201
+ end
202
+ end
203
+
204
+ # Public portal - restricted
205
+ class PublicPortal::PostPolicy < ::PostPolicy
206
+ include PublicPortal::ResourcePolicy
207
+
208
+ def create?
209
+ false # No public creation
210
+ end
211
+ end
212
+ ```
213
+
214
+ ## Common Patterns
215
+
216
+ ### Check Model Capabilities
217
+
218
+ ```ruby
219
+ def archive?
220
+ return false unless record.respond_to?(:archived!)
221
+ return false if record.archived?
222
+
223
+ user.admin?
224
+ end
225
+ ```
226
+
227
+ ### Prevent Actions on Archived Records
228
+
229
+ ```ruby
230
+ def update?
231
+ return false if record.try(:archived?)
232
+ super
233
+ end
234
+
235
+ def destroy?
236
+ return false if record.try(:archived?)
237
+ super
238
+ end
239
+ ```
240
+
241
+ ### Owner-Based Permissions
242
+
243
+ ```ruby
244
+ def update?
245
+ record.author == user || user.admin?
246
+ end
247
+
248
+ def destroy?
249
+ update? # Same rules as update
250
+ end
251
+ ```
252
+
253
+ ### Role-Based Permissions
254
+
255
+ ```ruby
256
+ def create?
257
+ user.admin? || user.editor?
258
+ end
259
+
260
+ def read?
261
+ true # Everyone can read
262
+ end
263
+
264
+ def update?
265
+ return true if user.admin?
266
+ return true if user.editor? && record.author == user
267
+ false
268
+ end
269
+ ```
270
+
271
+ ### Conditional Attribute Access
272
+
273
+ ```ruby
274
+ def permitted_attributes_for_create
275
+ attrs = %i[title content]
276
+ attrs << :featured if user.admin?
277
+ attrs << :author_id if user.admin? # Only admins can set author
278
+ attrs
279
+ end
280
+ ```
281
+
282
+ ## Authorization Context
283
+
284
+ Policies have access to:
285
+
286
+ ```ruby
287
+ user # Current user (required)
288
+ record # The resource being authorized
289
+ entity_scope # Current scoped entity (for multi-tenancy)
290
+ ```
291
+
292
+ ### Custom Context
293
+
294
+ Add custom context in controllers:
295
+
296
+ ```ruby
297
+ # In policy
298
+ class PostPolicy < ResourcePolicy
299
+ authorize :department, allow_nil: true
300
+
301
+ def create?
302
+ department&.allows_posting?
303
+ end
304
+ end
305
+
306
+ # In controller
307
+ class PostsController < ResourceController
308
+ authorize :department, through: :current_department
309
+
310
+ private
311
+
312
+ def current_department
313
+ current_user.department
314
+ end
315
+ end
316
+ ```
317
+
318
+ ## Controller Integration
319
+
320
+ Built-in CRUD actions automatically:
321
+ - Call `authorize_current!` at the start of each action
322
+ - Apply `relation_scope` for index/listings
323
+ - Filter params through `permitted_attributes`
324
+
325
+ After-action callbacks verify authorization was performed - if you add custom actions, you must call `authorize_current!` yourself or skip verification.
326
+
327
+ ### Skip Verification (When Needed)
328
+
329
+ ```ruby
330
+ class PostsController < ResourceController
331
+ skip_verify_authorize_current only: [:custom_action]
332
+
333
+ def custom_action
334
+ # Handle authorization manually
335
+ end
336
+ end
337
+ ```
338
+
339
+ ## Best Practices
340
+
341
+ 1. **Always override `create?` and `read?`** - They default to `false`
342
+ 2. **Define attributes explicitly** - Auto-detection only works in development
343
+ 3. **Call `super` in `relation_scope`** - Preserves entity scoping
344
+ 4. **Use derived methods** - Let `update?` inherit from `create?` when appropriate
345
+ 5. **Keep policies focused** - Authorization logic only, no business logic
346
+ 6. **Test edge cases** - Archived records, nil associations, role combinations
347
+
348
+ ## Related Skills
349
+
350
+ - `resource` - How policies fit in the resource architecture
351
+ - `definition-actions` - Actions that need policy methods
352
+ - `controller` - How controllers use policies