plutonium 0.50.0 → 0.52.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/.claude/skills/plutonium/SKILL.md +85 -102
- data/.claude/skills/plutonium-app/SKILL.md +574 -0
- data/.claude/skills/plutonium-auth/SKILL.md +167 -302
- data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
- data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
- data/.claude/skills/plutonium-tenancy/SKILL.md +674 -0
- data/.claude/skills/plutonium-testing/SKILL.md +9 -6
- data/.claude/skills/plutonium-ui/SKILL.md +900 -0
- data/CHANGELOG.md +44 -2
- data/Rakefile +2 -1
- data/app/assets/plutonium.css +1 -11
- data/app/assets/plutonium.js +1010 -1214
- data/app/assets/plutonium.js.map +3 -3
- data/app/assets/plutonium.min.js +52 -51
- data/app/assets/plutonium.min.js.map +3 -3
- data/docs/.vitepress/config.ts +38 -29
- data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
- data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
- data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
- data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
- data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
- data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
- data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
- data/docs/.vitepress/theme/custom.css +144 -0
- data/docs/.vitepress/theme/index.ts +58 -1
- data/docs/getting-started/index.md +33 -57
- data/docs/getting-started/installation.md +37 -80
- data/docs/getting-started/tutorial/02-first-resource.md +17 -8
- data/docs/getting-started/tutorial/03-authentication.md +31 -23
- data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
- data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
- data/docs/getting-started/tutorial/07-author-portal.md +8 -0
- data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
- data/docs/getting-started/tutorial/index.md +4 -5
- data/docs/guides/adding-resources.md +66 -377
- data/docs/guides/authentication.md +98 -462
- data/docs/guides/authorization.md +124 -370
- data/docs/guides/creating-packages.md +93 -298
- data/docs/guides/custom-actions.md +126 -441
- data/docs/guides/customizing-ui.md +258 -0
- data/docs/guides/index.md +49 -52
- data/docs/guides/multi-tenancy.md +123 -186
- data/docs/guides/nested-resources.md +137 -396
- data/docs/guides/search-filtering.md +127 -238
- data/docs/guides/testing.md +10 -5
- data/docs/guides/theming.md +168 -405
- data/docs/guides/troubleshooting.md +5 -3
- data/docs/guides/user-invites.md +112 -425
- data/docs/guides/user-profile.md +82 -241
- data/docs/index.md +10 -219
- data/docs/public/asciinema/home-scaffold.cast +305 -0
- data/docs/public/images/guides/custom-actions-bulk.png +0 -0
- data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
- data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
- data/docs/public/images/guides/nested-inputs.png +0 -0
- data/docs/public/images/guides/nested-resources-tab.png +0 -0
- data/docs/public/images/guides/search-filtering-index.png +0 -0
- data/docs/public/images/guides/search-filtering-panel.png +0 -0
- data/docs/public/images/guides/theming-after.png +0 -0
- data/docs/public/images/guides/theming-before.png +0 -0
- data/docs/public/images/guides/user-invites-landing.png +0 -0
- data/docs/public/images/guides/user-profile-edit.png +0 -0
- data/docs/public/images/guides/user-profile-show.png +0 -0
- data/docs/public/images/home-index.png +0 -0
- data/docs/public/images/home-new.png +0 -0
- data/docs/public/images/home-show.png +0 -0
- data/docs/public/images/tutorial/02-empty-index.png +0 -0
- data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
- data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
- data/docs/public/images/tutorial/02-new-form.png +0 -0
- data/docs/public/images/tutorial/03-create-account.png +0 -0
- data/docs/public/images/tutorial/03-login.png +0 -0
- data/docs/public/images/tutorial/04-admin-index.png +0 -0
- data/docs/public/images/tutorial/05-actions-menu.png +0 -0
- data/docs/public/images/tutorial/05-row-actions.png +0 -0
- data/docs/public/images/tutorial/06-comments-tab.png +0 -0
- data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
- data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
- data/docs/public/images/tutorial/07-author-portal.png +0 -0
- data/docs/public/images/tutorial/08-customized-index.png +0 -0
- data/docs/reference/app/generators.md +517 -0
- data/docs/reference/app/index.md +158 -0
- data/docs/reference/app/packages.md +146 -0
- data/docs/reference/app/portals.md +377 -0
- data/docs/reference/auth/accounts.md +229 -0
- data/docs/reference/auth/index.md +88 -0
- data/docs/reference/auth/profile.md +185 -0
- data/docs/reference/behavior/controllers.md +395 -0
- data/docs/reference/behavior/index.md +22 -0
- data/docs/reference/behavior/interactions.md +341 -0
- data/docs/reference/behavior/policies.md +417 -0
- data/docs/reference/index.md +67 -48
- data/docs/reference/resource/actions.md +423 -0
- data/docs/reference/resource/definition.md +508 -0
- data/docs/reference/resource/index.md +50 -0
- data/docs/reference/resource/model.md +348 -0
- data/docs/reference/resource/query.md +305 -0
- data/docs/reference/tenancy/entity-scoping.md +368 -0
- data/docs/reference/tenancy/index.md +36 -0
- data/docs/reference/tenancy/invites.md +400 -0
- data/docs/reference/tenancy/nested-resources.md +267 -0
- data/docs/reference/testing/index.md +287 -0
- data/docs/reference/ui/assets.md +400 -0
- data/docs/reference/ui/components.md +165 -0
- data/docs/reference/ui/displays.md +104 -0
- data/docs/reference/ui/forms.md +284 -0
- data/docs/reference/ui/index.md +30 -0
- data/docs/reference/ui/layouts.md +106 -0
- data/docs/reference/ui/pages.md +189 -0
- data/docs/reference/ui/tables.md +121 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
- data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
- data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
- data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
- data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
- data/lib/generators/pu/core/update/update_generator.rb +0 -20
- data/lib/generators/pu/invites/install_generator.rb +45 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
- data/lib/generators/pu/profile/conn_generator.rb +2 -2
- data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
- data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
- data/lib/generators/pu/rodauth/account_generator.rb +2 -1
- data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
- data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
- data/lib/generators/pu/rodauth/views_generator.rb +0 -2
- data/lib/generators/pu/saas/membership/USAGE +4 -1
- data/lib/generators/pu/saas/setup_generator.rb +16 -4
- data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
- data/lib/plutonium/definition/base.rb +1 -1
- data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
- data/lib/plutonium/helpers/turbo_helper.rb +30 -0
- data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
- data/lib/plutonium/resource/controller.rb +1 -0
- data/lib/plutonium/resource/controllers/crud_actions.rb +23 -5
- data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
- data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
- data/lib/plutonium/resource/policy.rb +7 -0
- data/lib/plutonium/routing/mapper_extensions.rb +15 -0
- data/lib/plutonium/ui/component/methods.rb +5 -0
- data/lib/plutonium/ui/form/base.rb +23 -3
- data/lib/plutonium/ui/form/components/json.rb +58 -0
- data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
- data/lib/plutonium/ui/form/components/secure_association.rb +103 -22
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
- data/lib/plutonium/ui/form/interaction.rb +1 -1
- data/lib/plutonium/ui/form/resource.rb +0 -4
- data/lib/plutonium/ui/form/theme.rb +1 -1
- data/lib/plutonium/ui/grid/resource.rb +1 -1
- data/lib/plutonium/ui/layout/base.rb +1 -0
- data/lib/plutonium/ui/page/base.rb +0 -7
- data/lib/plutonium/ui/page/edit.rb +1 -1
- data/lib/plutonium/ui/page/index.rb +4 -4
- data/lib/plutonium/ui/page/new.rb +1 -1
- data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
- data/lib/plutonium/ui/table/resource.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium.rb +8 -0
- data/lib/tasks/release.rake +15 -1
- data/package.json +13 -10
- data/src/css/slim_select.css +4 -0
- data/src/js/controllers/form_controller.js +5 -4
- data/src/js/controllers/slim_select_controller.js +61 -0
- data/src/js/turbo/turbo_actions.js +33 -0
- data/yarn.lock +661 -544
- metadata +86 -33
- data/.claude/skills/plutonium-assets/SKILL.md +0 -512
- data/.claude/skills/plutonium-controller/SKILL.md +0 -396
- data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
- data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
- data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
- data/.claude/skills/plutonium-forms/SKILL.md +0 -465
- data/.claude/skills/plutonium-installation/SKILL.md +0 -331
- data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
- data/.claude/skills/plutonium-invites/SKILL.md +0 -408
- data/.claude/skills/plutonium-model/SKILL.md +0 -440
- data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
- data/.claude/skills/plutonium-package/SKILL.md +0 -198
- data/.claude/skills/plutonium-policy/SKILL.md +0 -456
- data/.claude/skills/plutonium-portal/SKILL.md +0 -410
- data/.claude/skills/plutonium-views/SKILL.md +0 -651
- data/docs/reference/assets/index.md +0 -496
- data/docs/reference/controller/index.md +0 -412
- data/docs/reference/definition/actions.md +0 -462
- data/docs/reference/definition/fields.md +0 -383
- data/docs/reference/definition/index.md +0 -326
- data/docs/reference/definition/query.md +0 -351
- data/docs/reference/generators/index.md +0 -648
- data/docs/reference/interaction/index.md +0 -449
- data/docs/reference/model/features.md +0 -248
- data/docs/reference/model/index.md +0 -218
- data/docs/reference/policy/index.md +0 -456
- data/docs/reference/portal/index.md +0 -379
- data/docs/reference/views/forms.md +0 -411
- data/docs/reference/views/index.md +0 -544
data/docs/guides/user-profile.md
CHANGED
|
@@ -1,322 +1,163 @@
|
|
|
1
1
|
# User Profile
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Add a profile / account-settings page where users edit personal fields and access Rodauth security features (change password, 2FA, etc.) in one place.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Goal
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Profile Resource**: A standard Plutonium resource linked to the User model
|
|
9
|
-
- **Security Section**: Automatic display of enabled Rodauth security features
|
|
10
|
-
- **Customizable Fields**: Add any fields you need (bio, avatar, preferences, etc.)
|
|
7
|
+
A `/profile` URL that shows the user's personal fields plus a "Security" section linking to Rodauth-managed features.
|
|
11
8
|
|
|
12
|
-
##
|
|
9
|
+
## 🚨 Critical
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
- **Profile association is always `:profile`** regardless of the model class — `current_user.profile`, `build_profile`, `params.require(:profile)` always work.
|
|
12
|
+
- **Profile needs `pu:profile:conn` to be visible** — without it, no `/profile` route, no `profile_url` helper.
|
|
13
|
+
- **Every user needs a profile row.** Add an `after_create :create_profile!` callback to the user model. Without it, `current_user.profile` is nil.
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
2. **Model Markers**: The User model with marker comments
|
|
15
|
+
The show page renders the user's fields, then the **Security Settings** block with Rodauth-backed actions:
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+

|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
rails g pu:saas:user User
|
|
23
|
-
```
|
|
19
|
+
Editing produces a regular Plutonium form — same form generator the rest of your resources use:
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+

|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
## Quick path
|
|
28
24
|
|
|
29
25
|
```bash
|
|
30
|
-
rails
|
|
26
|
+
rails g pu:profile:setup date_of_birth:date bio:text \
|
|
27
|
+
--dest=competition \
|
|
28
|
+
--portal=competition_portal
|
|
31
29
|
```
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
`pu:profile:setup` is a meta-generator — runs `pu:profile:install` + `pu:profile:conn` in one shot.
|
|
32
|
+
|
|
33
|
+
## Step-by-step
|
|
34
|
+
|
|
35
|
+
### 1. Install
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
|
-
rails
|
|
37
|
-
|
|
38
|
-
avatar:attachment \
|
|
39
|
-
'timezone:string?' \
|
|
40
|
-
notifications_enabled:boolean \
|
|
41
|
-
--dest=main_app
|
|
38
|
+
rails generate pu:profile:install bio:text avatar:attachment 'timezone:string?' \
|
|
39
|
+
--dest=customer
|
|
42
40
|
```
|
|
43
41
|
|
|
44
|
-
### Options
|
|
45
|
-
|
|
46
42
|
| Option | Default | Description |
|
|
47
|
-
|
|
48
|
-
| `--dest` | (prompts) | Target
|
|
49
|
-
| `--user-model` | User | Rodauth user model
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `--dest=DEST` | (prompts) | Target package or `main_app` |
|
|
45
|
+
| `--user-model=NAME` | `User` | Rodauth user model |
|
|
50
46
|
|
|
51
|
-
|
|
47
|
+
Custom resource name (first positional argument):
|
|
52
48
|
|
|
53
49
|
```bash
|
|
54
|
-
rails
|
|
50
|
+
rails g pu:profile:install AccountSettings bio:text --dest=main_app
|
|
55
51
|
```
|
|
56
52
|
|
|
57
|
-
|
|
53
|
+
By default the model is `{UserModel}Profile` (`UserProfile`, `StaffUserProfile`, etc.).
|
|
58
54
|
|
|
59
|
-
|
|
55
|
+
### 2. Migrate
|
|
60
56
|
|
|
61
57
|
```bash
|
|
62
|
-
rails
|
|
58
|
+
rails db:migrate
|
|
63
59
|
```
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
- Registers the Profile as a singular resource (`/profile` instead of `/profiles/:id`)
|
|
67
|
-
- Configures the `profile_url` helper to enable the "Profile" link in the user menu
|
|
68
|
-
|
|
69
|
-
## Generated Files
|
|
61
|
+
### 3. Connect to a portal
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
app/models/profile.rb
|
|
75
|
-
db/migrate/xxx_create_profiles.rb
|
|
76
|
-
app/controllers/profiles_controller.rb
|
|
77
|
-
app/policies/profile_policy.rb
|
|
78
|
-
app/definitions/profile_definition.rb
|
|
63
|
+
```bash
|
|
64
|
+
rails g pu:profile:conn --dest=customer_portal
|
|
79
65
|
```
|
|
80
66
|
|
|
81
|
-
|
|
82
|
-
- **User model**: Adds `has_one :profile, dependent: :destroy`
|
|
83
|
-
- **Definition**: Injects custom ShowPage with security links
|
|
84
|
-
|
|
85
|
-
## Security Section
|
|
86
|
-
|
|
87
|
-
The ShowPage automatically displays links to enabled Rodauth security features.
|
|
88
|
-
|
|
89
|
-
Available features (only shown if enabled in Rodauth):
|
|
90
|
-
|
|
91
|
-
| Feature | Link Label | Description |
|
|
92
|
-
|---------|------------|-------------|
|
|
93
|
-
| `change_password` | Change Password | Update account password |
|
|
94
|
-
| `change_login` | Change Email | Update email address |
|
|
95
|
-
| `otp` | Two-Factor Authentication | Set up TOTP authenticator |
|
|
96
|
-
| `recovery_codes` | Recovery Codes | View or regenerate backup codes |
|
|
97
|
-
| `webauthn` | Security Keys | Manage passkeys and hardware keys |
|
|
98
|
-
| `active_sessions` | Active Sessions | View and revoke sessions |
|
|
99
|
-
| `close_account` | Close Account | Permanently delete account |
|
|
100
|
-
|
|
101
|
-
## Creating Profiles for Users
|
|
67
|
+
This registers the profile as a **singular** resource — exposes `/profile` (no `:id`) and the `profile_url` helper.
|
|
102
68
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
### Option A: Automatic Creation on User Signup
|
|
69
|
+
### 4. Add the auto-create callback
|
|
106
70
|
|
|
107
71
|
```ruby
|
|
108
|
-
# app/models/user.rb
|
|
72
|
+
# app/models/user.rb (modified by pu:profile:install)
|
|
109
73
|
class User < ApplicationRecord
|
|
110
|
-
has_one :profile, dependent: :destroy
|
|
74
|
+
has_one :profile, class_name: "UserProfile", dependent: :destroy
|
|
111
75
|
|
|
112
76
|
after_create :create_profile!
|
|
113
77
|
|
|
114
78
|
private
|
|
115
|
-
|
|
116
|
-
def create_profile!
|
|
117
|
-
create_profile
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# add has_one associations above.
|
|
79
|
+
def create_profile! = create_profile
|
|
121
80
|
end
|
|
122
81
|
```
|
|
123
82
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
In your portal's application controller:
|
|
83
|
+
For existing users at migration time:
|
|
127
84
|
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
before_action :ensure_profile
|
|
131
|
-
|
|
132
|
-
private
|
|
133
|
-
|
|
134
|
-
def ensure_profile
|
|
135
|
-
return unless current_user
|
|
136
|
-
current_user.profile || current_user.create_profile
|
|
137
|
-
end
|
|
138
|
-
end
|
|
85
|
+
```bash
|
|
86
|
+
rails runner "User.find_each(&:create_profile)"
|
|
139
87
|
```
|
|
140
88
|
|
|
141
|
-
|
|
89
|
+
## What you get
|
|
142
90
|
|
|
143
|
-
|
|
144
|
-
# app/controllers/profiles_controller.rb
|
|
145
|
-
class ProfilesController < ResourceController
|
|
146
|
-
private
|
|
91
|
+
The generated definition injects a custom `ShowPage` that renders `SecuritySection` — dynamically lists Rodauth security links based on which features are enabled:
|
|
147
92
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
93
|
+
| Feature enabled | Link rendered |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `change_password` | Change Password |
|
|
96
|
+
| `change_login` | Change Email |
|
|
97
|
+
| `otp` | Two-Factor Authentication |
|
|
98
|
+
| `recovery_codes` | Recovery Codes |
|
|
99
|
+
| `webauthn` | Security Keys |
|
|
100
|
+
| `active_sessions` | Active Sessions |
|
|
101
|
+
| `close_account` | Close Account |
|
|
153
102
|
|
|
154
|
-
|
|
103
|
+
If a feature isn't enabled, its link doesn't render — no configuration needed.
|
|
155
104
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
Edit the generated definition to configure how fields appear:
|
|
105
|
+
## Linking to the profile
|
|
159
106
|
|
|
160
107
|
```ruby
|
|
161
|
-
|
|
162
|
-
class ProfileDefinition < Plutonium::Resource::Definition
|
|
163
|
-
form do |f|
|
|
164
|
-
f.field :bio, as: :text
|
|
165
|
-
f.field :avatar, as: :attachment
|
|
166
|
-
f.field :timezone, collection: ActiveSupport::TimeZone.all.map(&:name)
|
|
167
|
-
f.field :notifications_enabled
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
display do |d|
|
|
171
|
-
d.field :bio
|
|
172
|
-
d.field :avatar
|
|
173
|
-
d.field :timezone
|
|
174
|
-
d.field :notifications_enabled
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
class ShowPage < ShowPage
|
|
178
|
-
private
|
|
179
|
-
|
|
180
|
-
def render_after_content
|
|
181
|
-
render Plutonium::Profile::SecuritySection.new
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
108
|
+
link_to("Profile", profile_url) if respond_to?(:profile_url)
|
|
185
109
|
```
|
|
186
110
|
|
|
187
|
-
|
|
111
|
+
The `respond_to?` guard is defensive — only portals that ran `pu:profile:conn` have the helper.
|
|
112
|
+
|
|
113
|
+
## Customizing the definition
|
|
188
114
|
|
|
189
|
-
|
|
115
|
+
The generated profile is a normal Plutonium definition. Customize like any other:
|
|
190
116
|
|
|
191
117
|
```ruby
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
h2(class: "text-lg font-semibold") { "Account Security" }
|
|
197
|
-
|
|
198
|
-
if rodauth.features.include?(:change_password)
|
|
199
|
-
a(href: rodauth.change_password_path) { "Change Password" }
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Add your custom links
|
|
203
|
-
a(href: "/settings/notifications") { "Notification Preferences" }
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
```
|
|
118
|
+
class UserProfileDefinition < Plutonium::Resource::Definition
|
|
119
|
+
field :bio, as: :markdown
|
|
120
|
+
input :avatar, as: :uppy
|
|
121
|
+
field :timezone, as: :select, choices: ActiveSupport::TimeZone.all.map(&:name)
|
|
208
122
|
|
|
209
|
-
|
|
123
|
+
metadata :created_at, :updated_at
|
|
210
124
|
|
|
211
|
-
```ruby
|
|
212
|
-
class ProfileDefinition < Plutonium::Resource::Definition
|
|
213
125
|
class ShowPage < ShowPage
|
|
214
126
|
private
|
|
215
127
|
|
|
216
128
|
def render_after_content
|
|
217
|
-
render
|
|
129
|
+
render Plutonium::Profile::SecuritySection.new
|
|
218
130
|
end
|
|
219
131
|
end
|
|
220
132
|
end
|
|
221
133
|
```
|
|
222
134
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Add custom actions to the profile:
|
|
226
|
-
|
|
227
|
-
```ruby
|
|
228
|
-
class ProfileDefinition < Plutonium::Resource::Definition
|
|
229
|
-
action :export_data,
|
|
230
|
-
interaction: Profile::ExportDataInteraction,
|
|
231
|
-
icon: Phlex::TablerIcons::Download
|
|
232
|
-
|
|
233
|
-
action :verify_email,
|
|
234
|
-
interaction: Profile::VerifyEmailInteraction,
|
|
235
|
-
category: :secondary
|
|
236
|
-
end
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Profile Link in Navigation
|
|
240
|
-
|
|
241
|
-
Add a profile link to your application layout or header:
|
|
242
|
-
|
|
243
|
-
```ruby
|
|
244
|
-
# In your header component
|
|
245
|
-
if current_user && respond_to?(:profile_path)
|
|
246
|
-
a(href: profile_path) { "My Profile" }
|
|
247
|
-
end
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
## Policy Configuration
|
|
251
|
-
|
|
252
|
-
The generated policy controls access:
|
|
253
|
-
|
|
254
|
-
```ruby
|
|
255
|
-
# app/policies/profile_policy.rb
|
|
256
|
-
class ProfilePolicy < Plutonium::Resource::Policy
|
|
257
|
-
# Users can only access their own profile
|
|
258
|
-
def read?
|
|
259
|
-
resource.user == user
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
def update?
|
|
263
|
-
resource.user == user
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def destroy?
|
|
267
|
-
false # Disable deletion through the UI
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
# Only allow editing these attributes
|
|
271
|
-
def permitted_attributes_for_update
|
|
272
|
-
[:bio, :avatar, :timezone, :notifications_enabled]
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
## Troubleshooting
|
|
278
|
-
|
|
279
|
-
### "Profile not found" Error
|
|
135
|
+
See [Reference › Resource › Definition](/reference/resource/definition) for the full definition surface.
|
|
280
136
|
|
|
281
|
-
|
|
137
|
+
## Multiple account types
|
|
282
138
|
|
|
283
|
-
|
|
139
|
+
If your app has both `User` and `StaffUser` accounts, run `pu:profile:install` once per:
|
|
284
140
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
# app/rodauth/user_rodauth_plugin.rb
|
|
289
|
-
class UserRodauthPlugin < Plutonium::Auth::RodauthPlugin
|
|
290
|
-
configure do
|
|
291
|
-
enable :change_password, :change_login, :otp, :active_sessions
|
|
292
|
-
# Only these features will show in the security section
|
|
293
|
-
end
|
|
294
|
-
end
|
|
141
|
+
```bash
|
|
142
|
+
rails g pu:profile:install --user-model=User --dest=main_app
|
|
143
|
+
rails g pu:profile:install --user-model=StaffUser --dest=main_app
|
|
295
144
|
```
|
|
296
145
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
Ensure you use `--singular` when connecting:
|
|
146
|
+
Each gets its own `*Profile` model with `:profile` association on the respective user. Connect each to the appropriate portal:
|
|
300
147
|
|
|
301
148
|
```bash
|
|
302
|
-
rails g pu:
|
|
149
|
+
rails g pu:profile:conn UserProfile --dest=customer_portal
|
|
150
|
+
rails g pu:profile:conn StaffUserProfile --dest=admin_portal
|
|
303
151
|
```
|
|
304
152
|
|
|
305
|
-
|
|
153
|
+
## Common issues
|
|
306
154
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
```ruby
|
|
312
|
-
def permitted_attributes_for_update
|
|
313
|
-
[:bio, :avatar, :timezone, :notifications_enabled]
|
|
314
|
-
end
|
|
315
|
-
```
|
|
155
|
+
- **`current_user.profile` is nil** — every user needs a profile row. Add `after_create :create_profile!` to the user model.
|
|
156
|
+
- **`profile_url` is undefined** — the profile isn't connected to this portal. Run `pu:profile:conn --dest=<portal>`.
|
|
157
|
+
- **`SecuritySection` shows nothing** — none of the relevant Rodauth features are enabled. Enable `change_password`, `otp`, etc. on the Rodauth plugin.
|
|
316
158
|
|
|
317
|
-
##
|
|
159
|
+
## Related
|
|
318
160
|
|
|
319
|
-
- [
|
|
320
|
-
- [
|
|
321
|
-
- [
|
|
322
|
-
- [Authorization](/guides/authorization) - Configure profile policies
|
|
161
|
+
- [Reference › Auth › Profile](/reference/auth/profile) — full surface
|
|
162
|
+
- [Reference › Auth › Accounts](/reference/auth/accounts) — Rodauth feature flags that gate SecuritySection
|
|
163
|
+
- [Authentication](./authentication) — the underlying auth setup
|
data/docs/index.md
CHANGED
|
@@ -1,228 +1,19 @@
|
|
|
1
1
|
---
|
|
2
|
-
layout:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
text: Ship Rails Apps 10x Faster
|
|
6
|
-
tagline: Build production-ready Rails applications in minutes, not days. Convention-driven, fully customizable. Built for the AI era.
|
|
7
|
-
image:
|
|
8
|
-
src: /plutonium.png
|
|
9
|
-
alt: Plutonium
|
|
10
|
-
actions:
|
|
11
|
-
- theme: brand
|
|
12
|
-
text: Get Started →
|
|
13
|
-
link: /getting-started/
|
|
14
|
-
- theme: alt
|
|
15
|
-
text: GitHub
|
|
16
|
-
link: https://github.com/radioactive-labs/plutonium-core
|
|
2
|
+
layout: page
|
|
3
|
+
sidebar: false
|
|
4
|
+
aside: false
|
|
17
5
|
---
|
|
18
6
|
|
|
19
|
-
<
|
|
7
|
+
<HomeHero />
|
|
20
8
|
|
|
21
|
-
<
|
|
22
|
-
<h2>The Old Way vs The Plutonium Way</h2>
|
|
23
|
-
<div class="comparison">
|
|
24
|
-
<div class="before">
|
|
25
|
-
<h3>Without Plutonium</h3>
|
|
26
|
-
<div class="file-tree">
|
|
27
|
-
<div class="file">app/controllers/posts_controller.rb</div>
|
|
28
|
-
<div class="file">app/controllers/comments_controller.rb</div>
|
|
29
|
-
<div class="file">app/views/posts/index.html.erb</div>
|
|
30
|
-
<div class="file">app/views/posts/show.html.erb</div>
|
|
31
|
-
<div class="file">app/views/posts/new.html.erb</div>
|
|
32
|
-
<div class="file">app/views/posts/edit.html.erb</div>
|
|
33
|
-
<div class="file">app/views/posts/_form.html.erb</div>
|
|
34
|
-
<div class="file dim">...12 more files</div>
|
|
35
|
-
</div>
|
|
36
|
-
<div class="stats">
|
|
37
|
-
<span>~200 lines</span>
|
|
38
|
-
<span>30+ minutes</span>
|
|
39
|
-
<span>No auth yet</span>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
<div class="arrow">→</div>
|
|
43
|
-
<div class="after">
|
|
44
|
-
<h3>With Plutonium</h3>
|
|
9
|
+
<HomeStopWriting />
|
|
45
10
|
|
|
46
|
-
|
|
47
|
-
rails g pu:res:scaffold Post \
|
|
48
|
-
title:string body:text
|
|
11
|
+
<HomePillars />
|
|
49
12
|
|
|
50
|
-
|
|
51
|
-
--dest=admin_portal
|
|
52
|
-
```
|
|
13
|
+
<HomeWalkthrough />
|
|
53
14
|
|
|
54
|
-
|
|
55
|
-
<span>2 commands</span>
|
|
56
|
-
<span>30 seconds</span>
|
|
57
|
-
<span>Auth included</span>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
</section>
|
|
15
|
+
<HomeAudienceSplit />
|
|
62
16
|
|
|
63
|
-
<
|
|
64
|
-
<h2>Built for the AI Era</h2>
|
|
65
|
-
<p class="ai-intro">Plutonium is the first Rails framework designed from the ground up for AI-assisted development. Every pattern, every convention, every file structure is optimized for AI comprehension.</p>
|
|
17
|
+
<HomeInTheBox />
|
|
66
18
|
|
|
67
|
-
|
|
68
|
-
<div class="ai-feature">
|
|
69
|
-
<div class="ai-icon">🧠</div>
|
|
70
|
-
<h3>Claude Code Skills</h3>
|
|
71
|
-
<p>20+ built-in skills teach AI assistants your app's patterns. Resources, policies, definitions, interactions - Claude understands them all.</p>
|
|
72
|
-
</div>
|
|
73
|
-
<div class="ai-feature">
|
|
74
|
-
<div class="ai-icon">⚡</div>
|
|
75
|
-
<h3>Predictable Patterns</h3>
|
|
76
|
-
<p>Convention-heavy architecture means AI can accurately predict file locations, naming, and relationships. Less hallucination, more precision.</p>
|
|
77
|
-
</div>
|
|
78
|
-
<div class="ai-feature">
|
|
79
|
-
<div class="ai-icon">🔄</div>
|
|
80
|
-
<h3>Generate & Iterate</h3>
|
|
81
|
-
<p>Tell Claude what you need. It generates the scaffold, policy, and definition. You refine. Ship in minutes what used to take hours.</p>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<div class="ai-example">
|
|
86
|
-
<div class="ai-prompt">
|
|
87
|
-
<span class="prompt-label">You say:</span>
|
|
88
|
-
<p>"Add a blog with posts and comments. Posts belong to users. Only authors can edit their posts. Add a publish action."</p>
|
|
89
|
-
</div>
|
|
90
|
-
<div class="ai-result">
|
|
91
|
-
<span class="result-label">Claude generates:</span>
|
|
92
|
-
<p>Model, migration, policy, definition, interaction, and connects it to your portal. Ready to customize.</p>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
</section>
|
|
96
|
-
|
|
97
|
-
<section class="features-detailed">
|
|
98
|
-
<h2>Everything You Need, Nothing You Don't</h2>
|
|
99
|
-
|
|
100
|
-
<div class="feature-row">
|
|
101
|
-
<div class="feature-text">
|
|
102
|
-
<h3>Resources Are Your Foundation</h3>
|
|
103
|
-
<p>Just ActiveRecord. Associations, scopes, validations you already know. No new ORM to learn.</p>
|
|
104
|
-
</div>
|
|
105
|
-
<div class="feature-code">
|
|
106
|
-
|
|
107
|
-
```ruby
|
|
108
|
-
class Post < ApplicationRecord
|
|
109
|
-
include Plutonium::Resource::Record
|
|
110
|
-
|
|
111
|
-
belongs_to :author, class_name: "User"
|
|
112
|
-
has_many :comments
|
|
113
|
-
|
|
114
|
-
scope :published, -> { where.not(published_at: nil) }
|
|
115
|
-
scope :drafts, -> { where(published_at: nil) }
|
|
116
|
-
end
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
<div class="feature-row reverse">
|
|
123
|
-
<div class="feature-text">
|
|
124
|
-
<h3>Definitions Control UI</h3>
|
|
125
|
-
<p>Declare how fields render. Add search, filters, scopes. Custom actions. All in one place.</p>
|
|
126
|
-
</div>
|
|
127
|
-
<div class="feature-code">
|
|
128
|
-
|
|
129
|
-
```ruby
|
|
130
|
-
class PostDefinition < ResourceDefinition
|
|
131
|
-
input :body, as: :markdown
|
|
132
|
-
|
|
133
|
-
search do |scope, query|
|
|
134
|
-
scope.where("title ILIKE ?", "%#{query}%")
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
scope :published
|
|
138
|
-
scope :drafts
|
|
139
|
-
|
|
140
|
-
action :publish, interaction: PublishPost
|
|
141
|
-
end
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
<div class="feature-row">
|
|
148
|
-
<div class="feature-text">
|
|
149
|
-
<h3>Interactions Encapsulate Logic</h3>
|
|
150
|
-
<p>Complex actions become simple classes. Validated inputs. Clear outcomes. Easy to test.</p>
|
|
151
|
-
</div>
|
|
152
|
-
<div class="feature-code">
|
|
153
|
-
|
|
154
|
-
```ruby
|
|
155
|
-
class PublishPost < ResourceInteraction
|
|
156
|
-
attribute :resource
|
|
157
|
-
attribute :publish_at, :datetime
|
|
158
|
-
|
|
159
|
-
def execute
|
|
160
|
-
resource.published_at = publish_at
|
|
161
|
-
if resource.save
|
|
162
|
-
succeed(resource).with_message("Published!")
|
|
163
|
-
else
|
|
164
|
-
failed(resource.errors)
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div class="feature-row reverse">
|
|
174
|
-
<div class="feature-text">
|
|
175
|
-
<h3>Policies Control Access</h3>
|
|
176
|
-
<p>Define who can do what. Attribute-level permissions. Automatic scoping. No more <code>if current_user.admin?</code> scattered everywhere.</p>
|
|
177
|
-
</div>
|
|
178
|
-
<div class="feature-code">
|
|
179
|
-
|
|
180
|
-
```ruby
|
|
181
|
-
class PostPolicy < ResourcePolicy
|
|
182
|
-
def update?
|
|
183
|
-
record.author == user || user.admin?
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def permitted_attributes_for_create
|
|
187
|
-
%i[title body]
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
</section>
|
|
195
|
-
|
|
196
|
-
<section class="feature-grid">
|
|
197
|
-
<div class="grid-item">
|
|
198
|
-
<div class="icon">📦</div>
|
|
199
|
-
<h3>Modular Packages</h3>
|
|
200
|
-
<p>Split your app into Feature Packages and Portals. Each isolated, testable, and reusable.</p>
|
|
201
|
-
</div>
|
|
202
|
-
<div class="grid-item">
|
|
203
|
-
<div class="icon">🔐</div>
|
|
204
|
-
<h3>Auth Built In</h3>
|
|
205
|
-
<p>Rodauth integration with login, registration, 2FA, and password reset. Ready in one command.</p>
|
|
206
|
-
</div>
|
|
207
|
-
<div class="grid-item">
|
|
208
|
-
<div class="icon">🏢</div>
|
|
209
|
-
<h3>Multi-Tenancy</h3>
|
|
210
|
-
<p>Entity scoping works out of the box. Path-based or custom strategies. Data isolation guaranteed.</p>
|
|
211
|
-
</div>
|
|
212
|
-
<div class="grid-item">
|
|
213
|
-
<div class="icon">🎨</div>
|
|
214
|
-
<h3>Fully Customizable</h3>
|
|
215
|
-
<p>Override any layer. Custom views with Phlex. Your CSS. No black boxes.</p>
|
|
216
|
-
</div>
|
|
217
|
-
</section>
|
|
218
|
-
|
|
219
|
-
<section class="cta-section">
|
|
220
|
-
<h2>Ready to Build Faster?</h2>
|
|
221
|
-
<p>Get a complete admin interface running in under 5 minutes.</p>
|
|
222
|
-
<div class="cta-buttons">
|
|
223
|
-
<a href="./getting-started/" class="cta-primary">Get Started</a>
|
|
224
|
-
<a href="./getting-started/tutorial/" class="cta-secondary">Follow the Tutorial</a>
|
|
225
|
-
</div>
|
|
226
|
-
</section>
|
|
227
|
-
|
|
228
|
-
</div>
|
|
19
|
+
<HomeCta />
|