plutonium 0.23.4 → 0.23.5
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/app/assets/plutonium.css +2 -2
- data/config/initializers/sqlite_json_alias.rb +1 -1
- data/docs/.vitepress/config.ts +60 -19
- data/docs/guide/cursor-rules.md +75 -0
- data/docs/guide/deep-dive/authorization.md +189 -0
- data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
- data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
- data/docs/guide/index.md +28 -0
- data/docs/guide/introduction/02-core-concepts.md +440 -0
- data/docs/guide/tutorial/01-project-setup.md +75 -0
- data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
- data/docs/guide/tutorial/03-defining-resources.md +90 -0
- data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
- data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
- data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
- data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
- data/docs/index.md +24 -31
- data/docs/modules/action.md +190 -0
- data/docs/modules/authentication.md +236 -0
- data/docs/modules/configuration.md +599 -0
- data/docs/modules/controller.md +398 -0
- data/docs/modules/core.md +316 -0
- data/docs/modules/definition.md +876 -0
- data/docs/modules/display.md +759 -0
- data/docs/modules/form.md +605 -0
- data/docs/modules/generator.md +288 -0
- data/docs/modules/index.md +167 -0
- data/docs/modules/interaction.md +470 -0
- data/docs/modules/package.md +151 -0
- data/docs/modules/policy.md +176 -0
- data/docs/modules/portal.md +710 -0
- data/docs/modules/query.md +287 -0
- data/docs/modules/resource_record.md +618 -0
- data/docs/modules/routing.md +641 -0
- data/docs/modules/table.md +293 -0
- data/docs/modules/ui.md +631 -0
- data/docs/public/plutonium.mdc +667 -0
- data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
- data/lib/plutonium/ui/display/resource.rb +7 -2
- data/lib/plutonium/ui/table/resource.rb +8 -3
- data/lib/plutonium/version.rb +1 -1
- metadata +36 -9
- data/docs/guide/getting-started/authorization.md +0 -296
- data/docs/guide/getting-started/core-concepts.md +0 -432
- data/docs/guide/getting-started/index.md +0 -21
- data/docs/guide/tutorial.md +0 -401
- /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -0,0 +1,440 @@
|
|
1
|
+
# Core Concepts
|
2
|
+
|
3
|
+
::: tip What you'll learn
|
4
|
+
- Plutonium's architecture and how its core components work together
|
5
|
+
- The role of Resources as your application's building blocks
|
6
|
+
- How Packages help organize and modularize your code
|
7
|
+
- Entity Scoping for building multi-tenant applications
|
8
|
+
- Best practices for maintainable, scalable applications
|
9
|
+
:::
|
10
|
+
|
11
|
+
## Understanding Resources
|
12
|
+
|
13
|
+
Think of Resources as the DNA of your Plutonium application. Every meaningful piece of data or functionality in your app—whether it's a User, Product, or Blog Post—is represented as a Resource. But unlike traditional Rails models, Plutonium Resources are rich, self-contained units that know how to display themselves, authorize access, and handle user interactions.
|
14
|
+
|
15
|
+
### The Anatomy of a Resource
|
16
|
+
|
17
|
+
Each Resource is composed of four key components that work together seamlessly:
|
18
|
+
|
19
|
+
```mermaid
|
20
|
+
graph TD
|
21
|
+
A[Resource] --> B[Model]
|
22
|
+
A --> C[Definition]
|
23
|
+
A --> D[Policy]
|
24
|
+
A --> G[Actions]
|
25
|
+
|
26
|
+
B --> H[Database Schema]
|
27
|
+
C --> I[Fields & UI Logic]
|
28
|
+
C --> F[QueryObject]
|
29
|
+
D --> J[Authorization]
|
30
|
+
F --> L[Querying & Filtering]
|
31
|
+
G --> M[User Operations]
|
32
|
+
```
|
33
|
+
|
34
|
+
- **Model**: Your familiar Active Record model with validations, associations, and business logic
|
35
|
+
- **Definition**: Describes how the Resource appears and behaves in the UI
|
36
|
+
- **Policy**: Controls who can access what and when
|
37
|
+
- **Actions**: Custom operations users can perform on the Resource
|
38
|
+
|
39
|
+
::: details Complete Resource Example
|
40
|
+
|
41
|
+
Let's see how all these pieces fit together in a real User resource:
|
42
|
+
|
43
|
+
::: code-group
|
44
|
+
```ruby [Model]
|
45
|
+
# app/models/user.rb
|
46
|
+
class User < ApplicationRecord
|
47
|
+
include Plutonium::Resource::Record
|
48
|
+
|
49
|
+
# Standard Rails associations
|
50
|
+
has_many :posts
|
51
|
+
has_many :comments
|
52
|
+
belongs_to :organization
|
53
|
+
|
54
|
+
# Helpful scopes for filtering
|
55
|
+
scope :active, -> { where(status: :active) }
|
56
|
+
|
57
|
+
# Standard validations
|
58
|
+
validates :name, presence: true
|
59
|
+
validates :email, presence: true, uniqueness: true
|
60
|
+
validates :role, presence: true, inclusion: {in: %w[admin user]}
|
61
|
+
|
62
|
+
def admin? = role == "admin"
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
```ruby [Definition]
|
67
|
+
# app/definitions/user_definition.rb
|
68
|
+
class UserDefinition < Plutonium::Resource::Definition
|
69
|
+
# How fields appear in forms and tables
|
70
|
+
field :name, as: :string
|
71
|
+
field :email, as: :email
|
72
|
+
|
73
|
+
# Enable search functionality
|
74
|
+
search do |scope, query|
|
75
|
+
scope.where("name LIKE :q OR email LIKE :q", q: "%#{query}%")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Add filters for easy data browsing
|
79
|
+
filter :role, with: SelectFilter, choices: %w[admin user guest]
|
80
|
+
filter :status, with: SelectFilter, choices: %w[active inactive]
|
81
|
+
|
82
|
+
# Expose useful scopes
|
83
|
+
scope :active
|
84
|
+
|
85
|
+
scope :admins do
|
86
|
+
where(role: :admin)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Custom actions users can perform
|
90
|
+
action :deactivate,
|
91
|
+
interaction: DeactivateUser,
|
92
|
+
color: :warning,
|
93
|
+
icon: Phlex::TablerIcons::UserOff
|
94
|
+
|
95
|
+
# Customize the detail page
|
96
|
+
show_page_title "User Details"
|
97
|
+
show_page_description "View and manage user information"
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
```ruby [Policy]
|
102
|
+
# app/policies/user_policy.rb
|
103
|
+
class UserPolicy < Plutonium::Resource::Policy
|
104
|
+
# Basic CRUD permissions
|
105
|
+
def read?
|
106
|
+
true # Everyone can view users
|
107
|
+
end
|
108
|
+
|
109
|
+
def create?
|
110
|
+
user.admin? # Only admins can create users
|
111
|
+
end
|
112
|
+
|
113
|
+
def update?
|
114
|
+
user.admin? || record.id == user.id # Admins or self
|
115
|
+
end
|
116
|
+
|
117
|
+
def destroy?
|
118
|
+
user.admin? && record.id != user.id # Admins, but not themselves
|
119
|
+
end
|
120
|
+
|
121
|
+
# Custom action permissions
|
122
|
+
def deactivate?
|
123
|
+
user.admin? && record.status == :active && record.id != user.id
|
124
|
+
end
|
125
|
+
|
126
|
+
# Control which fields are visible/editable
|
127
|
+
def permitted_attributes_for_read
|
128
|
+
if user.admin?
|
129
|
+
%i[name email role status created_at updated_at]
|
130
|
+
else
|
131
|
+
%i[name email status]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def permitted_attributes_for_create
|
136
|
+
if user.admin?
|
137
|
+
%i[name email role status password]
|
138
|
+
else
|
139
|
+
%i[name email password]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def permitted_attributes_for_update
|
144
|
+
if user.admin?
|
145
|
+
%i[name email role]
|
146
|
+
else
|
147
|
+
%i[name email]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Control access to related resources
|
152
|
+
def permitted_associations
|
153
|
+
%i[posts comments]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
```ruby [Deactivate Interaction]
|
159
|
+
# app/interactions/user_interactions/deactivate.rb
|
160
|
+
module UserInteractions
|
161
|
+
class Deactivate < Plutonium::Resource::Interaction
|
162
|
+
# How this action appears in the UI
|
163
|
+
presents label: "Deactivate User",
|
164
|
+
icon: Phlex::TablerIcons::UserOff,
|
165
|
+
description: "Deactivate user account"
|
166
|
+
|
167
|
+
# What data this action needs
|
168
|
+
attribute :resource, class: User
|
169
|
+
attribute :reason, :string
|
170
|
+
|
171
|
+
# Validation rules
|
172
|
+
validates :resource, presence: true
|
173
|
+
validates :reason, presence: true
|
174
|
+
|
175
|
+
# The actual business logic
|
176
|
+
def execute
|
177
|
+
resource.transaction do
|
178
|
+
resource.status = :inactive
|
179
|
+
resource.deactivated_at = Time.current
|
180
|
+
resource.deactivation_reason = reason
|
181
|
+
|
182
|
+
if resource.save
|
183
|
+
succeed(resource)
|
184
|
+
.with_message("User was successfully deactivated")
|
185
|
+
.with_redirect_response(resource)
|
186
|
+
else
|
187
|
+
failed(resource.errors)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
:::
|
195
|
+
|
196
|
+
## Organizing with Packages
|
197
|
+
|
198
|
+
As your application grows, you need a way to organize your code that doesn't turn into a tangled mess. That's where Packages come in. Think of them as specialized containers that help you group related functionality and keep your application modular.
|
199
|
+
|
200
|
+
Plutonium uses two types of packages, each with a specific purpose:
|
201
|
+
|
202
|
+
### Feature Packages: Your Business Logic Containers
|
203
|
+
|
204
|
+
Feature packages are where your core business logic lives. They're self-contained modules that focus on a specific domain area of your application—like blogging, e-commerce, or user management.
|
205
|
+
|
206
|
+
::: tip What Makes a Good Feature Package
|
207
|
+
- **Domain-focused**: Centers around a specific business area
|
208
|
+
- **Self-contained**: Has everything it needs to function
|
209
|
+
- **No web interface**: Pure business logic, no controllers or views
|
210
|
+
- **Reusable**: Can be used across different parts of your app
|
211
|
+
:::
|
212
|
+
|
213
|
+
::: code-group
|
214
|
+
```ruby [Directory Structure]
|
215
|
+
packages/
|
216
|
+
└── blogging/
|
217
|
+
├── app/
|
218
|
+
│ ├── models/
|
219
|
+
│ │ └── blogging/
|
220
|
+
│ │ ├── post.rb
|
221
|
+
│ │ └── comment.rb
|
222
|
+
│ ├── definitions/
|
223
|
+
│ │ └── blogging/
|
224
|
+
│ │ ├── post_definition.rb
|
225
|
+
│ │ └── comment_definition.rb
|
226
|
+
│ ├── policies/
|
227
|
+
│ │ └── blogging/
|
228
|
+
│ │ ├── post_policy.rb
|
229
|
+
│ │ └── comment_policy.rb
|
230
|
+
│ └── interactions/
|
231
|
+
│ └── blogging/
|
232
|
+
│ └── post_interactions/
|
233
|
+
│ ├── publish.rb
|
234
|
+
│ └── archive.rb
|
235
|
+
├── config/
|
236
|
+
│ └── routes.rb
|
237
|
+
└── lib/
|
238
|
+
└── engine.rb
|
239
|
+
```
|
240
|
+
|
241
|
+
```ruby [Engine Configuration]
|
242
|
+
# packages/blogging/lib/engine.rb
|
243
|
+
module Blogging
|
244
|
+
class Engine < ::Rails::Engine
|
245
|
+
include Plutonium::Package::Engine
|
246
|
+
end
|
247
|
+
end
|
248
|
+
```
|
249
|
+
:::
|
250
|
+
|
251
|
+
### Portal Packages: Your User Interface Gateways
|
252
|
+
|
253
|
+
Portal packages are the web-facing part of your application. They take the business logic from Feature packages and present it to users through a web interface. Each portal typically serves a specific audience—like administrators, customers, or partners.
|
254
|
+
|
255
|
+
::: tip What Makes a Good Portal Package
|
256
|
+
- **User-focused**: Designed for a specific type of user
|
257
|
+
- **Interface-heavy**: Controllers, views, and authentication
|
258
|
+
- **Feature composition**: Combines multiple feature packages
|
259
|
+
- **Access control**: Manages who can see and do what
|
260
|
+
:::
|
261
|
+
|
262
|
+
::: code-group
|
263
|
+
```ruby [Directory Structure]
|
264
|
+
packages/
|
265
|
+
└── admin_portal/
|
266
|
+
├── app/
|
267
|
+
│ ├── controllers/
|
268
|
+
│ │ └── admin_portal/
|
269
|
+
│ │ ├── concerns/
|
270
|
+
│ │ │ └── controller.rb
|
271
|
+
│ │ ├── plutonium_controller.rb
|
272
|
+
│ │ └── resource_controller.rb
|
273
|
+
│ └── views/
|
274
|
+
│ └── layouts/
|
275
|
+
│ └── admin_portal.html.erb
|
276
|
+
├── config/
|
277
|
+
│ └── routes.rb
|
278
|
+
└── lib/
|
279
|
+
└── engine.rb
|
280
|
+
```
|
281
|
+
|
282
|
+
```ruby [Engine Configuration]
|
283
|
+
# packages/admin_portal/lib/engine.rb
|
284
|
+
module AdminPortal
|
285
|
+
class Engine < ::Rails::Engine
|
286
|
+
include Plutonium::Portal::Engine
|
287
|
+
|
288
|
+
# All resources in this portal are scoped to an organization
|
289
|
+
scope_to_entity Organization, strategy: :path
|
290
|
+
end
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
```ruby [Routes Configuration]
|
295
|
+
# packages/admin_portal/config/routes.rb
|
296
|
+
AdminPortal::Engine.routes.draw do
|
297
|
+
root to: "dashboard#index"
|
298
|
+
|
299
|
+
# Make resources from feature packages available in this portal
|
300
|
+
register_resource Blogging::Post
|
301
|
+
register_resource Blogging::Comment
|
302
|
+
end
|
303
|
+
```
|
304
|
+
|
305
|
+
```ruby [Controller Configuration]
|
306
|
+
# packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
|
307
|
+
module AdminPortal
|
308
|
+
module Concerns
|
309
|
+
module Controller
|
310
|
+
extend ActiveSupport::Concern
|
311
|
+
include Plutonium::Portal::Controller
|
312
|
+
|
313
|
+
# Add authentication to this portal
|
314
|
+
include Plutonium::Auth::Rodauth(:admin)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
```
|
319
|
+
:::
|
320
|
+
|
321
|
+
## Entity Scoping: Building Multi-Tenant Applications
|
322
|
+
|
323
|
+
One of Plutonium's most powerful features is Entity Scoping—a clean way to build multi-tenant applications where data is automatically partitioned based on a parent entity like Organization, Account, or Team.
|
324
|
+
|
325
|
+
Here's the magic: when you properly set up entity scoping, Plutonium automatically ensures users only see and can access data that belongs to their organization. No manual filtering required!
|
326
|
+
|
327
|
+
### How It Works
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
# 1. Configure scoping in your portal engine
|
331
|
+
module AdminPortal
|
332
|
+
class Engine < ::Rails::Engine
|
333
|
+
include Plutonium::Portal::Engine
|
334
|
+
|
335
|
+
# Path-based scoping creates URLs like /org_123/posts
|
336
|
+
scope_to_entity Organization, strategy: :path
|
337
|
+
|
338
|
+
# Or use a custom strategy for more control
|
339
|
+
scope_to_entity Organization, strategy: :current_organization
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# 2. Set up your models with proper associations
|
344
|
+
class Post < ApplicationRecord
|
345
|
+
include Plutonium::Resource::Record
|
346
|
+
|
347
|
+
# Direct relationship to the scoping entity
|
348
|
+
belongs_to :user
|
349
|
+
belongs_to :organization, through: :user
|
350
|
+
|
351
|
+
# Alternative: custom scoping for complex relationships
|
352
|
+
scope :associated_with_organization, ->(organization) do
|
353
|
+
joins(:user).where(users: { organization_id: organization.id })
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# 3. Implement the scoping strategy in your controller
|
358
|
+
module AdminPortal::Concerns::Controller
|
359
|
+
private
|
360
|
+
|
361
|
+
# This method name must match your strategy name
|
362
|
+
def current_organization
|
363
|
+
# Extract tenant from subdomain, path, or any other source
|
364
|
+
@current_organization ||= Organization.find_by!(subdomain: request.subdomain)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
```
|
368
|
+
|
369
|
+
### The Result
|
370
|
+
|
371
|
+
With this setup, when a user visits `/posts`, they automatically see only posts from their organization. When they create a new post, it's automatically associated with their organization. All of this happens transparently—no extra code needed in your controllers or views.
|
372
|
+
|
373
|
+
## Best Practices for Success
|
374
|
+
|
375
|
+
### Organizing Your Packages
|
376
|
+
|
377
|
+
::: tip Feature Packages
|
378
|
+
- **Keep domains separate**: Each package should focus on one business area
|
379
|
+
- **Minimize dependencies**: Packages should be as independent as possible
|
380
|
+
- **Clear interfaces**: Use well-defined APIs for package interactions
|
381
|
+
- **Single responsibility**: One clear purpose per package
|
382
|
+
:::
|
383
|
+
|
384
|
+
::: tip Portal Packages
|
385
|
+
- **User-centric design**: Each portal serves a specific user type
|
386
|
+
- **Consistent authentication**: Use the same auth strategy throughout a portal
|
387
|
+
- **Clear scoping rules**: Be explicit about data access boundaries
|
388
|
+
- **Compose, don't duplicate**: Reuse feature packages rather than rebuilding
|
389
|
+
:::
|
390
|
+
|
391
|
+
### Designing Robust Resources
|
392
|
+
|
393
|
+
::: tip Model Layer
|
394
|
+
- **Clear validations**: Make invalid states impossible
|
395
|
+
- **Meaningful associations**: Reflect real business relationships
|
396
|
+
- **Useful scopes**: Provide common query patterns
|
397
|
+
- **Business methods**: Encode domain logic in the model
|
398
|
+
:::
|
399
|
+
|
400
|
+
::: tip Definition Layer
|
401
|
+
- **Appropriate field types**: Match the UI to the data
|
402
|
+
- **Efficient search**: Index searchable fields properly
|
403
|
+
- **Logical grouping**: Organize fields and actions intuitively
|
404
|
+
- **Clear labeling**: Use human-friendly names and descriptions
|
405
|
+
:::
|
406
|
+
|
407
|
+
::: tip Policy Layer
|
408
|
+
- **Granular permissions**: Control access at the field level when needed
|
409
|
+
- **Explicit rules**: Make authorization logic clear and testable
|
410
|
+
- **Action-specific controls**: Different actions may need different rules
|
411
|
+
- **Association security**: Control access to related data
|
412
|
+
:::
|
413
|
+
|
414
|
+
### Security First
|
415
|
+
|
416
|
+
::: warning Critical Security Practices
|
417
|
+
- **Always implement policies**: Never rely on "security through obscurity"
|
418
|
+
- **Use entity scoping consistently**: Don't mix scoped and unscoped access
|
419
|
+
- **Validate all inputs**: Trust nothing from the user
|
420
|
+
- **Control associations**: Prevent unauthorized data access through relationships
|
421
|
+
- **Audit sensitive actions**: Log important operations for compliance
|
422
|
+
:::
|
423
|
+
|
424
|
+
## Generator Support
|
425
|
+
|
426
|
+
Plutonium provides generators to quickly scaffold components:
|
427
|
+
|
428
|
+
```bash
|
429
|
+
# Create a new feature package
|
430
|
+
rails generate pu:pkg:package blogging
|
431
|
+
|
432
|
+
# Create a new portal package
|
433
|
+
rails generate pu:pkg:portal admin
|
434
|
+
|
435
|
+
# Create a new resource
|
436
|
+
rails generate pu:res:scaffold post title:string content:text
|
437
|
+
|
438
|
+
# Connect a resource to a portal
|
439
|
+
rails generate pu:res:conn
|
440
|
+
```
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# 1. Project Setup
|
2
|
+
|
3
|
+
Welcome to the Plutonium tutorial! In this guide, we'll build a complete blog application from scratch.
|
4
|
+
|
5
|
+
First, let's get a new Rails application up and running with Plutonium installed.
|
6
|
+
|
7
|
+
## Prerequisites
|
8
|
+
|
9
|
+
Before you begin, make sure you have the following installed:
|
10
|
+
|
11
|
+
- Ruby 3.2.2 or higher
|
12
|
+
- Rails 7.1 or higher
|
13
|
+
- Node.js and Yarn
|
14
|
+
|
15
|
+
If you're new to Rails, we highly recommend the official [Rails Getting Started Guide](https://guides.rubyonrails.org/getting_started.html) first.
|
16
|
+
|
17
|
+
## Create a New Rails App
|
18
|
+
|
19
|
+
We'll use the Plutonium Rails template to create a new application. This is the fastest way to get started.
|
20
|
+
|
21
|
+
Open your terminal and run the following command:
|
22
|
+
|
23
|
+
```bash
|
24
|
+
rails new plutonium_blog -a propshaft -j esbuild -c tailwind \
|
25
|
+
-m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
|
26
|
+
```
|
27
|
+
|
28
|
+
This command creates a new Rails app named `plutonium_blog` and configures it with:
|
29
|
+
- `propshaft` for assets
|
30
|
+
- `esbuild` for JavaScript bundling
|
31
|
+
- `TailwindCSS` for styling
|
32
|
+
- The base Plutonium gem and configurations
|
33
|
+
|
34
|
+
## Explore the Project
|
35
|
+
|
36
|
+
Once the command finishes, navigate into your new project's directory:
|
37
|
+
|
38
|
+
```bash
|
39
|
+
cd plutonium_blog
|
40
|
+
```
|
41
|
+
|
42
|
+
The installer has already set up the core files you need. Here are the most important ones for now:
|
43
|
+
|
44
|
+
```
|
45
|
+
plutonium_blog/
|
46
|
+
├── app/
|
47
|
+
│ ├── controllers/
|
48
|
+
│ │ ├── plutonium_controller.rb # Base controller for Plutonium
|
49
|
+
│ │ └── resource_controller.rb # Base for all resource controllers
|
50
|
+
│ └── views/
|
51
|
+
│ └── layouts/
|
52
|
+
│ └── resource.html.erb # Default layout for Plutonium views
|
53
|
+
├── config/
|
54
|
+
│ ├── initializers/
|
55
|
+
│ │ └── plutonium.rb # Main Plutonium configuration
|
56
|
+
│ └── packages.rb # How custom packages are loaded
|
57
|
+
└── packages/ # Where you'll build your features
|
58
|
+
└── .keep
|
59
|
+
```
|
60
|
+
|
61
|
+
## Start the Server
|
62
|
+
|
63
|
+
Let's boot up the Rails server to make sure everything is working correctly.
|
64
|
+
|
65
|
+
```bash
|
66
|
+
rails server
|
67
|
+
```
|
68
|
+
|
69
|
+
Open your web browser and navigate to [http://localhost:3000](http://localhost:3000). You should see the default Rails welcome page.
|
70
|
+
|
71
|
+
## Next Steps
|
72
|
+
|
73
|
+
Congratulations! You have a fresh Plutonium application ready to go.
|
74
|
+
|
75
|
+
In the next chapter, we'll create our first **Feature Package** to house all the logic for our blog.
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# 2. Creating a Feature Package
|
2
|
+
|
3
|
+
Now that we have our Rails application, it's time to start organizing our code. In Plutonium, we do this using **Packages**.
|
4
|
+
|
5
|
+
## What are Packages?
|
6
|
+
|
7
|
+
Packages are like mini-Rails applications (Rails Engines) that live inside your main app. They help you group related code together, making your application more modular and easier to maintain.
|
8
|
+
|
9
|
+
There are two main types of packages, but for now, we'll focus on **Feature Packages**.
|
10
|
+
|
11
|
+
A **Feature Package** holds all the business logic for a specific feature. In our case, we'll create a `Blogging` package to hold everything related to blog posts and comments.
|
12
|
+
|
13
|
+
## Generate the Package
|
14
|
+
|
15
|
+
Plutonium comes with a generator to create feature packages. Run the following command in your terminal:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
rails generate pu:pkg:package blogging
|
19
|
+
```
|
20
|
+
|
21
|
+
This command creates a new directory at `packages/blogging` with the structure for your new package.
|
22
|
+
|
23
|
+
```
|
24
|
+
packages/
|
25
|
+
└── blogging/
|
26
|
+
├── app/
|
27
|
+
│ ├── controllers/
|
28
|
+
│ │ └── blogging/ # Controllers for this package
|
29
|
+
│ ├── definitions/
|
30
|
+
│ │ └── blogging/ # Resource definitions
|
31
|
+
│ ├── interactions/
|
32
|
+
│ │ └── blogging/ # Business logic actions
|
33
|
+
│ ├── models/
|
34
|
+
│ │ └── blogging/ # ActiveRecord models
|
35
|
+
│ └── policies/
|
36
|
+
│ └── blogging/ # Authorization policies
|
37
|
+
└── lib/
|
38
|
+
└── engine.rb # The engine configuration
|
39
|
+
```
|
40
|
+
|
41
|
+
Every file and class inside this package will be automatically namespaced under the `Blogging` module to prevent conflicts with other parts of your application.
|
42
|
+
|
43
|
+
## Next Steps
|
44
|
+
|
45
|
+
With our `Blogging` package in place, we're ready to define the core data models for our application. In the next chapter, we'll create the `Post` and `Comment` resources.
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# 3. Defining Resources
|
2
|
+
|
3
|
+
With our `Blogging` package ready, we can now define the core **Resources** of our feature: `Post` and `Comment`. But first, we need a way to represent users, since posts and comments will belong to a user.
|
4
|
+
|
5
|
+
## Setting Up Users and Authentication
|
6
|
+
|
7
|
+
Plutonium provides generators to quickly set up user authentication using the popular [Rodauth](https://rodauth.jeremyevans.net/) library.
|
8
|
+
|
9
|
+
Run the following commands in your terminal:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
# 1. Install Rodauth configuration
|
13
|
+
rails generate pu:rodauth:install
|
14
|
+
|
15
|
+
# 2. Generate a User model and authentication logic
|
16
|
+
rails generate pu:rodauth:account user
|
17
|
+
|
18
|
+
# 3. Run the database migrations
|
19
|
+
rails db:migrate
|
20
|
+
```
|
21
|
+
|
22
|
+
This is a huge time-saver! It creates:
|
23
|
+
- A `User` model (`app/models/user.rb`).
|
24
|
+
- Database migrations for the `users` table.
|
25
|
+
- All the necessary authentication logic (login, logout, sign up, password reset, etc.).
|
26
|
+
|
27
|
+
Now that we have a `User` model, we can create our blog-specific resources.
|
28
|
+
|
29
|
+
## Scaffolding the Post Resource
|
30
|
+
|
31
|
+
A **Resource** in Plutonium is more than just a model; it's a combination of the model, its definition (how it's displayed), and its policy (who can do what).
|
32
|
+
|
33
|
+
Let's scaffold our `Post` resource. It will have a title, some text content, and will belong to a `User`.
|
34
|
+
|
35
|
+
```bash
|
36
|
+
rails generate pu:res:scaffold Post user:belongs_to title:string content:text 'published_at:datetime?'
|
37
|
+
```
|
38
|
+
|
39
|
+
The generator will prompt you to choose which package this resource belongs to. **Select `blogging`**.
|
40
|
+
|
41
|
+
This command generates several important files inside your `packages/blogging` directory:
|
42
|
+
|
43
|
+
- `app/models/blogging/post.rb`: The ActiveRecord model.
|
44
|
+
- `app/definitions/blogging/post_definition.rb`: The resource definition for UI.
|
45
|
+
- `app/policies/blogging/post_policy.rb`: The authorization policy.
|
46
|
+
- A database migration to create the `blogging_posts` table.
|
47
|
+
|
48
|
+
Let's look at the generated model at `packages/blogging/app/models/blogging/post.rb`:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# packages/blogging/app/models/blogging/post.rb
|
52
|
+
class Blogging::Post < Blogging::ResourceRecord
|
53
|
+
belongs_to :user
|
54
|
+
validates :title, presence: true
|
55
|
+
validates :content, presence: true
|
56
|
+
end
|
57
|
+
```
|
58
|
+
Notice it's correctly namespaced under `Blogging` and associated with the `User` model.
|
59
|
+
|
60
|
+
Before we continue, run the migration to update your database:
|
61
|
+
```bash
|
62
|
+
rails db:migrate
|
63
|
+
```
|
64
|
+
|
65
|
+
## Scaffolding the Comment Resource
|
66
|
+
|
67
|
+
Now, let's do the same for `Comment`. A comment will belong to a `User` and a `Post`.
|
68
|
+
|
69
|
+
```bash
|
70
|
+
rails generate pu:res:scaffold Comment user:belongs_to blogging/post:belongs_to body:text
|
71
|
+
```
|
72
|
+
|
73
|
+
Again, select the **`blogging`** package when prompted.
|
74
|
+
|
75
|
+
::: tip Namespaced Associations
|
76
|
+
Notice that we used `blogging/post` to specify the association. Plutonium's generators understand this and will correctly create the namespaced `Blogging::Post` association.
|
77
|
+
:::
|
78
|
+
|
79
|
+
This generates the model, definition, policy, and migration for comments.
|
80
|
+
|
81
|
+
Run the migration for comments:
|
82
|
+
```bash
|
83
|
+
rails db:migrate
|
84
|
+
```
|
85
|
+
|
86
|
+
## Next Steps
|
87
|
+
|
88
|
+
We've now set up users, authentication, and the core `Post` and `Comment` resources for our blog. However, there's no way to interact with them yet.
|
89
|
+
|
90
|
+
In the next chapter, we'll create a **Portal** to provide a web interface for managing our new resources.
|