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,304 @@
1
+ # Packages and Portals
2
+
3
+ Plutonium organizes applications using two types of packages: **Feature Packages** for business logic and **Portal Packages** for web interfaces.
4
+
5
+ ## Why Packages?
6
+
7
+ Packages provide:
8
+ - **Modularity** - Features are isolated and self-contained
9
+ - **Reusability** - Share features across multiple interfaces
10
+ - **Scalability** - Large apps stay organized
11
+ - **Team collaboration** - Teams can own specific packages
12
+
13
+ ## Feature Packages
14
+
15
+ Feature packages contain your business logic: models, definitions, policies, interactions, and controllers.
16
+
17
+ ### Creating a Feature Package
18
+
19
+ ```bash
20
+ rails generate pu:pkg:package blogging
21
+ ```
22
+
23
+ This creates:
24
+
25
+ ```
26
+ packages/blogging/
27
+ ├── app/
28
+ │ ├── controllers/blogging/
29
+ │ ├── definitions/blogging/
30
+ │ ├── interactions/blogging/
31
+ │ ├── models/blogging/
32
+ │ ├── policies/blogging/
33
+ │ └── views/blogging/
34
+ └── lib/
35
+ └── engine.rb
36
+ ```
37
+
38
+ ### Feature Package Structure
39
+
40
+ ```ruby
41
+ # packages/blogging/lib/engine.rb
42
+ module Blogging
43
+ class Engine < Rails::Engine
44
+ include Plutonium::Package::Engine
45
+
46
+ # Package configuration here
47
+ end
48
+ end
49
+ ```
50
+
51
+ ### Adding Resources to a Feature Package
52
+
53
+ ```bash
54
+ rails generate pu:res:scaffold Post title:string body:text --package blogging
55
+ ```
56
+
57
+ Resources are namespaced under the package:
58
+
59
+ ```ruby
60
+ # packages/blogging/app/models/blogging/post.rb
61
+ module Blogging
62
+ class Post < Blogging::ResourceRecord
63
+ end
64
+ end
65
+ ```
66
+
67
+ ## Portal Packages
68
+
69
+ Portal packages are web interfaces that expose resources to users. Each portal can have its own authentication, authorization, and UI customizations.
70
+
71
+ ### Creating a Portal Package
72
+
73
+ ```bash
74
+ rails generate pu:pkg:portal admin
75
+ ```
76
+
77
+ This creates:
78
+
79
+ ```
80
+ packages/admin_portal/
81
+ ├── app/
82
+ │ ├── controllers/admin_portal/
83
+ │ └── views/admin_portal/
84
+ ├── config/
85
+ │ └── routes.rb
86
+ ├── lib/
87
+ │ └── admin_portal/
88
+ │ └── engine.rb
89
+ ├── admin_portal.gemspec
90
+ └── Gemfile
91
+ ```
92
+
93
+ ### Portal Engine
94
+
95
+ ```ruby
96
+ # packages/admin_portal/lib/admin_portal/engine.rb
97
+ module AdminPortal
98
+ class Engine < Rails::Engine
99
+ include Plutonium::Portal::Engine
100
+
101
+ config.after_initialize do
102
+ # Optional: Scope to an entity (multi-tenancy)
103
+ scope_to_entity Organization
104
+ end
105
+ end
106
+ end
107
+ ```
108
+
109
+ ### Portal Authentication
110
+
111
+ Authentication is configured in the controller concern:
112
+
113
+ ```ruby
114
+ # packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
115
+ module AdminPortal
116
+ module Concerns
117
+ module Controller
118
+ extend ActiveSupport::Concern
119
+ include Plutonium::Portal::Controller
120
+ include Plutonium::Auth::Rodauth(:admin)
121
+ end
122
+ end
123
+ end
124
+ ```
125
+
126
+ ### Connecting Resources to Portals
127
+
128
+ ```bash
129
+ rails generate pu:res:conn Post --package blogging --portal admin
130
+ ```
131
+
132
+ This:
133
+ 1. Creates portal-specific routes
134
+ 2. Optionally creates portal-specific controller
135
+ 3. Registers the resource with the portal
136
+
137
+ ## Multiple Portals
138
+
139
+ A common pattern is having different portals for different user types:
140
+
141
+ ```
142
+ packages/
143
+ ├── admin_portal/ # Full access for administrators
144
+ ├── author_portal/ # Content management for authors
145
+ └── customer_portal/ # Public-facing interface
146
+ ```
147
+
148
+ Each portal can:
149
+ - Use different authentication
150
+ - Show different fields
151
+ - Apply different policies
152
+ - Have unique UI customization
153
+
154
+ ### Example: Same Resource, Different Portals
155
+
156
+ ```ruby
157
+ # Admin sees everything
158
+ # packages/admin_portal/app/policies/admin_portal/blogging/post_policy.rb
159
+ module AdminPortal
160
+ module Blogging
161
+ class PostPolicy < ::Blogging::PostPolicy
162
+ def read?
163
+ true # Admins see all posts
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ # Authors see only their posts
170
+ # packages/author_portal/app/policies/author_portal/blogging/post_policy.rb
171
+ module AuthorPortal
172
+ module Blogging
173
+ class PostPolicy < ::Blogging::PostPolicy
174
+ def read?
175
+ record.user_id == user.id
176
+ end
177
+ end
178
+ end
179
+ end
180
+ ```
181
+
182
+ ## Package Dependencies
183
+
184
+ Feature packages can depend on each other:
185
+
186
+ ```ruby
187
+ # packages/blogging/blogging.gemspec
188
+ Gem::Specification.new do |spec|
189
+ spec.add_dependency "users" # Depends on users package
190
+ end
191
+ ```
192
+
193
+ ## Mounting Packages
194
+
195
+ Packages are mounted in the main application routes:
196
+
197
+ ```ruby
198
+ # config/routes.rb
199
+ Rails.application.routes.draw do
200
+ mount AdminPortal::Engine, at: "/admin"
201
+ mount AuthorPortal::Engine, at: "/author"
202
+ mount CustomerPortal::Engine, at: "/"
203
+ end
204
+ ```
205
+
206
+ ## Authentication per Portal
207
+
208
+ Each portal can use different authentication via its controller concern:
209
+
210
+ ```ruby
211
+ # packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
212
+ module AdminPortal
213
+ module Concerns
214
+ module Controller
215
+ extend ActiveSupport::Concern
216
+ include Plutonium::Portal::Controller
217
+ include Plutonium::Auth::Rodauth(:admin)
218
+ end
219
+ end
220
+ end
221
+
222
+ # packages/customer_portal/app/controllers/customer_portal/concerns/controller.rb
223
+ module CustomerPortal
224
+ module Concerns
225
+ module Controller
226
+ extend ActiveSupport::Concern
227
+ include Plutonium::Portal::Controller
228
+ include Plutonium::Auth::Rodauth(:customer)
229
+ end
230
+ end
231
+ end
232
+ ```
233
+
234
+ ## Entity Scoping (Multi-tenancy)
235
+
236
+ Portals can be scoped to an entity:
237
+
238
+ ```ruby
239
+ module CustomerPortal
240
+ class Engine < Rails::Engine
241
+ include Plutonium::Portal::Engine
242
+
243
+ config.after_initialize do
244
+ # All resources scoped to current organization
245
+ scope_to_entity Organization
246
+ end
247
+ end
248
+ end
249
+ ```
250
+
251
+ With entity scoping:
252
+ - All queries automatically filter by entity
253
+ - New records automatically belong to entity
254
+ - Users can only access their entity's data
255
+
256
+ ## Portal Customization
257
+
258
+ ### Custom Layouts
259
+
260
+ ```ruby
261
+ # packages/admin_portal/app/views/layouts/admin_portal/application.rb
262
+ module AdminPortal
263
+ class ApplicationLayout < Plutonium::UI::Layout::Application
264
+ def render_logo
265
+ img(src: asset_path("admin-logo.svg"))
266
+ end
267
+ end
268
+ end
269
+ ```
270
+
271
+ ### Portal-Specific Definitions
272
+
273
+ ```ruby
274
+ # packages/admin_portal/app/definitions/admin_portal/blogging/post_definition.rb
275
+ module AdminPortal
276
+ module Blogging
277
+ class PostDefinition < ::Blogging::PostDefinition
278
+ # Add admin-specific fields
279
+ field :internal_notes, as: :text
280
+ end
281
+ end
282
+ end
283
+ ```
284
+
285
+ ## Best Practices
286
+
287
+ ### 1. One Feature, One Package
288
+ Keep packages focused. A "blogging" package shouldn't handle user management.
289
+
290
+ ### 2. Portal-Specific Overrides
291
+ Put customizations in the portal package, not the feature package.
292
+
293
+ ### 3. Shared Logic in Features
294
+ Business logic goes in feature packages, UI customization in portals.
295
+
296
+ ### 4. Clear Naming
297
+ - Feature packages: noun (blogging, inventory, billing)
298
+ - Portal packages: role + portal (admin_portal, customer_portal)
299
+
300
+ ## Related Topics
301
+
302
+ - [Architecture](./architecture) - How layers work together
303
+ - [Resources](./resources) - Understanding resources
304
+ - [Portal Reference](/reference/portal/) - Portal configuration
@@ -0,0 +1,224 @@
1
+ # Resources
2
+
3
+ In Plutonium, a **resource** is a complete unit that represents a domain concept. Unlike plain Rails models, a Plutonium resource combines data, presentation, and authorization.
4
+
5
+ ## What Makes a Resource?
6
+
7
+ A resource consists of four parts:
8
+
9
+ | Component | Purpose | Example |
10
+ |-----------|---------|---------|
11
+ | **Model** | Data structure and validation | `Post` |
12
+ | **Definition** | How it renders | `PostDefinition` |
13
+ | **Policy** | Who can do what | `PostPolicy` |
14
+ | **Controller** | HTTP handling | `PostsController` |
15
+
16
+ ## Resource Models
17
+
18
+ Resource models inherit from `ResourceRecord`:
19
+
20
+ ```ruby
21
+ class Post < ResourceRecord
22
+ belongs_to :user
23
+ has_many :comments
24
+
25
+ validates :title, presence: true
26
+ end
27
+ ```
28
+
29
+ This base class adds:
30
+ - Automatic field introspection
31
+ - Association detection
32
+ - Integration with definitions and policies
33
+
34
+ ## Creating Resources
35
+
36
+ ### Using the Generator
37
+
38
+ The fastest way to create a resource:
39
+
40
+ ```bash
41
+ rails generate pu:res:scaffold Post title:string body:text published:boolean
42
+ ```
43
+
44
+ This generates:
45
+ - Model with attributes and validations
46
+ - Definition with default configuration
47
+ - Policy with standard permissions
48
+ - Migration
49
+
50
+ ### Manual Creation
51
+
52
+ You can also create resources manually:
53
+
54
+ ```ruby
55
+ # app/models/post.rb
56
+ class Post < ResourceRecord
57
+ validates :title, presence: true
58
+ end
59
+
60
+ # app/definitions/post_definition.rb
61
+ class PostDefinition < Plutonium::Resource::Definition
62
+ end
63
+
64
+ # app/policies/post_policy.rb
65
+ class PostPolicy < Plutonium::Resource::Policy
66
+ end
67
+ ```
68
+
69
+ ## Resource vs Model
70
+
71
+ | Aspect | Plain Model | Resource |
72
+ |--------|-------------|----------|
73
+ | Inheritance | `ApplicationRecord` | `ResourceRecord` |
74
+ | Fields | Manual configuration | Auto-detected |
75
+ | Authorization | Separate concern | Integrated via Policy |
76
+ | UI | Manual forms/views | Auto-generated |
77
+ | CRUD | Write manually | Generated |
78
+
79
+ ## Resource Discovery
80
+
81
+ Plutonium automatically discovers resources based on naming conventions:
82
+
83
+ ```
84
+ Post → PostDefinition, PostPolicy, PostsController
85
+ Blogging::Post → Blogging::PostDefinition, Blogging::PostPolicy
86
+ ```
87
+
88
+ ## Field Introspection
89
+
90
+ Resources automatically detect their fields from the database schema:
91
+
92
+ ```ruby
93
+ # Given this schema:
94
+ create_table :posts do |t|
95
+ t.string :title, null: false
96
+ t.text :body
97
+ t.boolean :published, default: false
98
+ t.belongs_to :user
99
+ t.timestamps
100
+ end
101
+
102
+ # Plutonium auto-detects:
103
+ # - title: string input, required
104
+ # - body: textarea
105
+ # - published: checkbox
106
+ # - user: association select
107
+ # - created_at, updated_at: datetime displays
108
+ ```
109
+
110
+ ## Resource Registration
111
+
112
+ Resources must be registered with a portal to be accessible:
113
+
114
+ ```bash
115
+ rails generate pu:res:conn Post --portal admin
116
+ ```
117
+
118
+ Or manually in routes:
119
+
120
+ ```ruby
121
+ # packages/admin_portal/config/routes.rb
122
+ AdminPortal::Engine.routes.draw do
123
+ resources :posts
124
+ end
125
+ ```
126
+
127
+ ## Nested Resources
128
+
129
+ Resources are automatically nested via `belongs_to` associations:
130
+
131
+ ```ruby
132
+ class Comment < ResourceRecord
133
+ belongs_to :post
134
+ end
135
+ ```
136
+
137
+ When both resources are registered in a portal, Plutonium creates nested URLs like `/posts/:post_id/comments`.
138
+
139
+ ## Resource Features
140
+
141
+ ### Entity Scoping (Multi-tenancy)
142
+
143
+ Entity scoping is configured on the portal engine, not the model:
144
+
145
+ ```ruby
146
+ # packages/customer_portal/lib/engine.rb
147
+ module CustomerPortal
148
+ class Engine < Rails::Engine
149
+ include Plutonium::Portal::Engine
150
+
151
+ config.after_initialize do
152
+ scope_to_entity Organization
153
+ end
154
+ end
155
+ end
156
+ ```
157
+
158
+ ### Monetary Fields
159
+
160
+ ```ruby
161
+ class Product < ResourceRecord
162
+ # Store as cents, expose as decimal
163
+ has_cents :price_cents
164
+ end
165
+ ```
166
+
167
+ ## Resource Lifecycle
168
+
169
+ ```
170
+ 1. User requests /posts/new
171
+ 2. Controller builds new Post instance
172
+ 3. Policy checks create? permission
173
+ 4. Definition provides form fields
174
+ 5. Form rendered to user
175
+
176
+ 6. User submits form
177
+ 7. Controller receives params
178
+ 8. Policy filters permitted attributes
179
+ 9. Model validates and saves
180
+ 10. Controller redirects or re-renders
181
+ ```
182
+
183
+ ## Best Practices
184
+
185
+ ### Keep Models Thin
186
+ Put business logic in Interactions, not models.
187
+
188
+ ```ruby
189
+ # Good: Model handles data
190
+ class Post < ResourceRecord
191
+ validates :title, presence: true
192
+ end
193
+
194
+ # Interaction handles logic
195
+ class PublishPost < Plutonium::Interaction::Base
196
+ def execute
197
+ resource.update!(published: true, published_at: Time.current)
198
+ notify_subscribers
199
+ succeed(resource)
200
+ end
201
+ end
202
+ ```
203
+
204
+ ### Use Meaningful Scopes
205
+
206
+ ```ruby
207
+ class Post < ResourceRecord
208
+ scope :published, -> { where(published: true) }
209
+ scope :recent, -> { order(created_at: :desc) }
210
+ scope :by_author, ->(user) { where(user: user) }
211
+ end
212
+ ```
213
+
214
+ ### Validate at the Right Level
215
+
216
+ - **Model**: Data integrity (presence, format, uniqueness)
217
+ - **Interaction**: Business rules (can only publish once)
218
+ - **Policy**: Authorization (user must own post)
219
+
220
+ ## Related Topics
221
+
222
+ - [Architecture](./architecture) - How layers work together
223
+ - [Model Reference](/reference/model/) - Complete model documentation
224
+ - [Definition Reference](/reference/definition/) - Field configuration