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,226 @@
1
+ # Architecture
2
+
3
+ Plutonium follows a layered architecture where each layer has a specific responsibility. This separation makes applications easier to understand, test, and maintain.
4
+
5
+ ## The Four Layers
6
+
7
+ ### 1. Model Layer
8
+
9
+ The Model layer handles **data and business rules**.
10
+
11
+ ```ruby
12
+ class Post < ResourceRecord
13
+ belongs_to :user
14
+ has_many :comments
15
+
16
+ validates :title, presence: true
17
+ validates :body, presence: true
18
+
19
+ scope :published, -> { where(published: true) }
20
+ end
21
+ ```
22
+
23
+ Responsibilities:
24
+ - Database schema and migrations
25
+ - Validations
26
+ - Associations
27
+ - Scopes and queries
28
+ - Core business logic
29
+
30
+ ### 2. Definition Layer
31
+
32
+ The Definition layer controls **how resources render**.
33
+
34
+ ```ruby
35
+ class PostDefinition < Plutonium::Resource::Definition
36
+ # Fields shown in forms
37
+ field :title
38
+ field :body, as: :rich_text
39
+ field :published, as: :switch
40
+
41
+ # Columns shown in tables
42
+ column :title, sortable: true
43
+ column :published
44
+ column :created_at
45
+
46
+ # Custom actions
47
+ action :publish, interaction: PublishPost
48
+ end
49
+ ```
50
+
51
+ Responsibilities:
52
+ - Which fields appear in forms
53
+ - How fields are rendered (input types)
54
+ - Table column configuration
55
+ - Search and filtering
56
+ - Custom actions
57
+ - Field groups and layout
58
+
59
+ ### 3. Policy Layer
60
+
61
+ The Policy layer controls **authorization**.
62
+
63
+ ```ruby
64
+ class PostPolicy < Plutonium::Resource::Policy
65
+ def read?
66
+ record.published? || owner?
67
+ end
68
+
69
+ def update?
70
+ owner?
71
+ end
72
+
73
+ def permitted_attributes_for_update
74
+ [:title, :body, :published]
75
+ end
76
+
77
+ def relation_scope(relation)
78
+ relation.where(published: true).or(relation.where(user: user))
79
+ end
80
+ end
81
+ ```
82
+
83
+ Responsibilities:
84
+ - Action permissions (can user perform action?)
85
+ - Attribute permissions (which fields can user see/modify?)
86
+ - Collection scoping (which records can user access?)
87
+ - Multi-tenancy isolation
88
+
89
+ ### 4. Controller Layer
90
+
91
+ The Controller layer handles **HTTP requests**.
92
+
93
+ ```ruby
94
+ class PostsController < Plutonium::Resource::Controller
95
+ private
96
+
97
+ def build_resource
98
+ super.tap do |post|
99
+ post.user = current_user
100
+ end
101
+ end
102
+
103
+ def after_create_success
104
+ notify_subscribers(@resource)
105
+ super
106
+ end
107
+ end
108
+ ```
109
+
110
+ Responsibilities:
111
+ - CRUD actions
112
+ - Request/response handling
113
+ - Resource building hooks
114
+ - Redirects and rendering
115
+ - Format handling (HTML, JSON, etc.)
116
+
117
+ ## Request Flow
118
+
119
+ When a request comes in, it flows through the layers:
120
+
121
+ ```
122
+ 1. REQUEST arrives at Portal
123
+
124
+ 2. CONTROLLER receives request
125
+
126
+ 3. POLICY checks authorization
127
+
128
+ 4. DEFINITION determines rendering
129
+
130
+ 5. MODEL provides data
131
+
132
+ 6. RESPONSE rendered and returned
133
+ ```
134
+
135
+ ### Example: Viewing a Post
136
+
137
+ ```
138
+ GET /admin/posts/1
139
+
140
+ 1. AdminPortal routes to PostsController#show
141
+ 2. PostsController loads Post.find(1)
142
+ 3. PostPolicy#read? checks if user can view
143
+ 4. PostDefinition provides field configuration
144
+ 5. UI renders the post display
145
+ 6. HTML response returned
146
+ ```
147
+
148
+ ### Example: Creating a Post
149
+
150
+ ```
151
+ POST /admin/posts
152
+
153
+ 1. AdminPortal routes to PostsController#create
154
+ 2. PostsController builds new Post
155
+ 3. PostPolicy#create? checks if user can create
156
+ 4. PostPolicy#permitted_attributes_for_create filters params
157
+ 5. Post validates and saves
158
+ 6. Redirect to show page
159
+ ```
160
+
161
+ ## Layer Interaction
162
+
163
+ Layers communicate through well-defined interfaces:
164
+
165
+ ```ruby
166
+ # Controller asks Policy
167
+ policy = policy_for(resource)
168
+ policy.authorize!(:update)
169
+
170
+ # Controller uses Definition
171
+ definition = definition_for(resource)
172
+ definition.fields_for(:form)
173
+
174
+ # Policy uses Model
175
+ def owner?
176
+ record.user_id == user.id
177
+ end
178
+ ```
179
+
180
+ ## Customization Points
181
+
182
+ Each layer provides hooks for customization:
183
+
184
+ ### Model Hooks
185
+ - Callbacks (before_save, after_create, etc.)
186
+ - Custom methods
187
+ - Scopes
188
+
189
+ ### Definition Hooks
190
+ - Field configuration
191
+ - Custom renderers
192
+ - Conditional display
193
+
194
+ ### Policy Hooks
195
+ - Custom permission methods
196
+ - Attribute filtering
197
+ - Scope customization
198
+
199
+ ### Controller Hooks
200
+ - `build_resource` - Customize resource initialization
201
+ - `before_action` - Standard Rails callbacks
202
+ - `after_*_success/failure` - Action result hooks
203
+
204
+ ## Why This Architecture?
205
+
206
+ ### 1. Separation of Concerns
207
+ Each layer has one job. Forms don't know about authorization. Policies don't know about rendering.
208
+
209
+ ### 2. Testability
210
+ Each layer can be tested in isolation:
211
+ - Model specs test validations and queries
212
+ - Policy specs test authorization
213
+ - Definition specs test field configuration
214
+ - Controller specs test request handling
215
+
216
+ ### 3. Reusability
217
+ Definitions and policies can be shared across portals. Models are independent of the UI.
218
+
219
+ ### 4. Maintainability
220
+ Changes to authorization don't affect forms. UI changes don't affect data logic.
221
+
222
+ ## Related Topics
223
+
224
+ - [Resources](./resources) - Understanding resource classes
225
+ - [Packages and Portals](./packages-portals) - Organizing your application
226
+ - [Auto-Detection](./auto-detection) - How defaults are determined
@@ -0,0 +1,254 @@
1
+ # Auto-Detection
2
+
3
+ Plutonium automatically detects configuration from your models, reducing boilerplate code. You only write configuration when you need to override the defaults.
4
+
5
+ ## How Auto-Detection Works
6
+
7
+ When Plutonium renders a resource, it examines:
8
+ 1. Database columns (types, constraints)
9
+ 2. Model validations
10
+ 3. Model associations
11
+ 4. Existing configuration
12
+
13
+ From this, it determines sensible defaults for forms, tables, and displays.
14
+
15
+ ## Field Detection
16
+
17
+ ### From Column Types
18
+
19
+ | Column Type | Default Input | Default Display |
20
+ |-------------|---------------|-----------------|
21
+ | `string` | Text input | Text |
22
+ | `text` | Textarea | Formatted text |
23
+ | `integer` | Number input | Number |
24
+ | `float`/`decimal` | Decimal input | Formatted number |
25
+ | `boolean` | Checkbox | Yes/No badge |
26
+ | `date` | Date picker | Formatted date |
27
+ | `datetime` | Datetime picker | Formatted datetime |
28
+ | `time` | Time picker | Formatted time |
29
+ | `json`/`jsonb` | JSON editor | JSON display |
30
+
31
+ ### From Constraints
32
+
33
+ ```ruby
34
+ # Schema
35
+ t.string :email, null: false
36
+
37
+ # Auto-detects: required field
38
+ field :email # Marked as required in form
39
+ ```
40
+
41
+ ### From Validations
42
+
43
+ ```ruby
44
+ # Model
45
+ validates :title, presence: true, length: { maximum: 100 }
46
+
47
+ # Auto-detects:
48
+ # - Required field
49
+ # - Max length constraint
50
+ ```
51
+
52
+ ## Association Detection
53
+
54
+ ### belongs_to
55
+
56
+ ```ruby
57
+ # Model
58
+ belongs_to :user
59
+
60
+ # Auto-detects:
61
+ # - Select input with user options
62
+ # - Link to user in display
63
+ # - Automatic eager loading in queries
64
+ ```
65
+
66
+ ### has_many
67
+
68
+ ```ruby
69
+ # Model
70
+ has_many :comments
71
+
72
+ # Auto-detects:
73
+ # - Association panel on detail page
74
+ # - Count display in tables (optional)
75
+ # - Nested forms support (if configured)
76
+ ```
77
+
78
+ ### has_one
79
+
80
+ ```ruby
81
+ # Model
82
+ has_one :profile
83
+
84
+ # Auto-detects:
85
+ # - Inline display on detail page
86
+ # - Nested form support
87
+ ```
88
+
89
+ ## Validation Detection
90
+
91
+ | Validation | Effect |
92
+ |------------|--------|
93
+ | `presence` | Field marked required |
94
+ | `length` | Min/max constraints |
95
+ | `numericality` | Number input with constraints |
96
+ | `inclusion` | Select input with options |
97
+ | `format` | Pattern attribute on input |
98
+
99
+ ```ruby
100
+ # Model
101
+ validates :status, inclusion: { in: %w[draft published archived] }
102
+
103
+ # Auto-detects: Select with options
104
+ field :status # Renders as select with draft/published/archived options
105
+ ```
106
+
107
+ ## Overriding Defaults
108
+
109
+ Auto-detection provides a starting point. Override when needed:
110
+
111
+ ### Field Type Override
112
+
113
+ ```ruby
114
+ # Auto-detected: text input
115
+ # Override: rich text editor
116
+ field :body, as: :rich_text
117
+ ```
118
+
119
+ ### Field Options
120
+
121
+ ```ruby
122
+ # Auto-detected: select with model options
123
+ # Override: custom collection
124
+ field :user, collection: -> { User.active.pluck(:name, :id) }
125
+ ```
126
+
127
+ ### Hiding Fields
128
+
129
+ ```ruby
130
+ # Exclude from forms
131
+ exclude_from_form :created_at, :updated_at
132
+
133
+ # Or per-field
134
+ field :user, as: :hidden
135
+ ```
136
+
137
+ ### Required Override
138
+
139
+ ```ruby
140
+ # Model allows null, but form requires it
141
+ field :nickname, required: true
142
+ ```
143
+
144
+ ## Table Column Detection
145
+
146
+ By default, tables show a subset of fields. Plutonium prioritizes:
147
+ 1. Non-association fields
148
+ 2. Non-text fields (text is too long for tables)
149
+ 3. Fields with meaningful data
150
+
151
+ ### Override Columns
152
+
153
+ ```ruby
154
+ # Explicitly set columns
155
+ column :title
156
+ column :published
157
+ column :user
158
+ column :created_at
159
+ ```
160
+
161
+ ## Search Detection
162
+
163
+ Plutonium can auto-detect searchable fields:
164
+ - String columns are searchable by default
165
+ - Text columns can be included
166
+
167
+ ### Override Search
168
+
169
+ ```ruby
170
+ # Custom search implementation
171
+ search do |scope, query|
172
+ scope.where("title ILIKE ?", "%#{query}%")
173
+ end
174
+ ```
175
+
176
+ ## Filter Detection
177
+
178
+ For associations, Plutonium can auto-generate filters:
179
+
180
+ ```ruby
181
+ # Auto-detected from belongs_to :user
182
+ filter :user # Select filter with user options
183
+ ```
184
+
185
+ ### Override Filters
186
+
187
+ ```ruby
188
+ filter :status, as: :select, collection: %w[draft published archived]
189
+ filter :created_at, as: :date_range
190
+ ```
191
+
192
+ ## When Auto-Detection Runs
193
+
194
+ Auto-detection happens at render time, not definition time. This means:
195
+
196
+ 1. Changes to models are reflected immediately
197
+ 2. New columns appear in forms automatically
198
+ 3. Removed columns disappear from forms
199
+
200
+ ## Caching
201
+
202
+ In production, auto-detection results are cached for performance. Clear the cache after schema changes:
203
+
204
+ ```ruby
205
+ Rails.cache.clear
206
+ ```
207
+
208
+ ## Best Practices
209
+
210
+ ### 1. Start with Defaults
211
+ Let auto-detection do its job. Only configure what you need to change.
212
+
213
+ ### 2. Be Explicit When It Matters
214
+ For important forms, explicitly declare fields to prevent surprise changes:
215
+
216
+ ```ruby
217
+ # Explicit field list
218
+ field :title
219
+ field :body
220
+ field :published
221
+
222
+ # vs relying on auto-detection
223
+ # (new columns would appear automatically)
224
+ ```
225
+
226
+ ### 3. Use Models as Documentation
227
+ Your model's validations and associations document the expected UI:
228
+
229
+ ```ruby
230
+ class Post < ResourceRecord
231
+ belongs_to :user # Select input
232
+ validates :title, presence: true # Required field
233
+ validates :status, inclusion: {...} # Select options
234
+ end
235
+ ```
236
+
237
+ ### 4. Test After Schema Changes
238
+ After adding/removing columns, verify forms still work correctly.
239
+
240
+ ## Debugging Auto-Detection
241
+
242
+ To see what Plutonium detected:
243
+
244
+ ```ruby
245
+ definition = PostDefinition.new
246
+ puts definition.detected_fields.inspect
247
+ puts definition.detected_columns.inspect
248
+ ```
249
+
250
+ ## Related Topics
251
+
252
+ - [Fields Reference](/reference/definition/fields) - All field options
253
+ - [Model Reference](/reference/model/) - Model configuration
254
+ - [Resources](./resources) - Understanding resources
@@ -0,0 +1,61 @@
1
+ # Core Concepts
2
+
3
+ This section explains the fundamental concepts behind Plutonium's architecture.
4
+
5
+ ## Overview
6
+
7
+ Plutonium is built around a few key principles:
8
+
9
+ 1. **Separation of Concerns** - Each layer has a single responsibility
10
+ 2. **Convention over Configuration** - Smart defaults, override when needed
11
+ 3. **Modularity** - Features organized into independent packages
12
+ 4. **Full Customization** - Every layer can be overridden
13
+
14
+ ## Key Concepts
15
+
16
+ ### [Architecture](./architecture)
17
+ How the Model → Definition → Policy → Controller layers work together to create a resource.
18
+
19
+ ### [Resources](./resources)
20
+ What resources are and how they differ from plain Rails models.
21
+
22
+ ### [Packages and Portals](./packages-portals)
23
+ How to organize your application using Feature Packages and Portal Packages.
24
+
25
+ ### [Auto-Detection](./auto-detection)
26
+ How Plutonium automatically discovers fields, associations, and validations.
27
+
28
+ ## The Big Picture
29
+
30
+ ```
31
+ ┌─────────────────────────────────────────────────────────────┐
32
+ │ PORTAL │
33
+ │ (Web Interface - routes, authentication, UI customization) │
34
+ └─────────────────────────────────────────────────────────────┘
35
+
36
+
37
+ ┌─────────────────────────────────────────────────────────────┐
38
+ │ CONTROLLER │
39
+ │ (HTTP handling - request/response, rendering) │
40
+ └─────────────────────────────────────────────────────────────┘
41
+
42
+
43
+ ┌─────────────────────────────────────────────────────────────┐
44
+ │ POLICY │
45
+ │ (Authorization - who can do what) │
46
+ └─────────────────────────────────────────────────────────────┘
47
+
48
+
49
+ ┌─────────────────────────────────────────────────────────────┐
50
+ │ DEFINITION │
51
+ │ (Presentation - how resources render) │
52
+ └─────────────────────────────────────────────────────────────┘
53
+
54
+
55
+ ┌─────────────────────────────────────────────────────────────┐
56
+ │ MODEL │
57
+ │ (Data - structure, validations, business rules) │
58
+ └─────────────────────────────────────────────────────────────┘
59
+ ```
60
+
61
+ Each layer builds on the one below it, and each can be customized independently.