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.
- checksums.yaml +4 -4
- data/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- 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
|