plutonium 0.45.2 → 0.46.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 +146 -0
- data/.claude/skills/plutonium-assets/SKILL.md +248 -157
- data/.claude/skills/{plutonium-rodauth → plutonium-auth}/SKILL.md +195 -229
- data/.claude/skills/plutonium-controller/SKILL.md +9 -2
- data/.claude/skills/plutonium-create-resource/SKILL.md +22 -1
- data/.claude/skills/plutonium-definition/SKILL.md +521 -7
- data/.claude/skills/plutonium-entity-scoping/SKILL.md +317 -0
- data/.claude/skills/plutonium-forms/SKILL.md +8 -1
- data/.claude/skills/plutonium-installation/SKILL.md +25 -2
- data/.claude/skills/plutonium-interaction/SKILL.md +9 -2
- data/.claude/skills/plutonium-invites/SKILL.md +11 -7
- data/.claude/skills/plutonium-model/SKILL.md +50 -50
- data/.claude/skills/plutonium-nested-resources/SKILL.md +8 -1
- data/.claude/skills/plutonium-package/SKILL.md +8 -1
- data/.claude/skills/plutonium-policy/SKILL.md +69 -78
- data/.claude/skills/plutonium-portal/SKILL.md +26 -70
- data/.claude/skills/plutonium-views/SKILL.md +9 -2
- data/CHANGELOG.md +33 -0
- data/app/assets/plutonium.css +1 -1
- data/app/views/rodauth/_login_form.html.erb +0 -3
- data/app/views/rodauth/confirm_password.html.erb +0 -4
- data/app/views/rodauth/create_account.html.erb +0 -3
- data/app/views/rodauth/logout.html.erb +0 -3
- data/config/initializers/pagy.rb +1 -1
- data/docs/superpowers/plans/2026-04-08-plutonium-skills-overhaul.md +481 -0
- data/docs/superpowers/specs/2026-04-08-plutonium-skills-overhaul-design.md +236 -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/update/update_generator.rb +8 -0
- data/lib/generators/pu/gem/active_shrine/active_shrine_generator.rb +56 -0
- data/lib/generators/pu/invites/install_generator.rb +8 -1
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +43 -0
- data/lib/generators/pu/profile/concerns/profile_arguments.rb +10 -4
- data/lib/generators/pu/profile/conn_generator.rb +9 -12
- data/lib/generators/pu/profile/install_generator.rb +5 -2
- data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
- data/lib/generators/pu/saas/portal_generator.rb +4 -9
- data/lib/generators/pu/saas/welcome/templates/app/views/welcome/onboarding.html.erb.tt +2 -2
- data/lib/plutonium/engine.rb +18 -5
- data/lib/plutonium/ui/layout/rodauth_layout.rb +6 -1
- data/lib/plutonium/version.rb +1 -1
- data/package.json +1 -1
- metadata +7 -8
- data/.claude/skills/plutonium/skill.md +0 -130
- data/.claude/skills/plutonium-definition-actions/SKILL.md +0 -424
- data/.claude/skills/plutonium-definition-query/SKILL.md +0 -364
- data/.claude/skills/plutonium-profile/SKILL.md +0 -276
- data/.claude/skills/plutonium-theming/SKILL.md +0 -424
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: plutonium-definition-query
|
|
3
|
-
description: Use when adding search, filters, named scopes, or sorting to a Plutonium resource definition
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Definition Query
|
|
7
|
-
|
|
8
|
-
Configure how users can search, filter, and sort resource collections.
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
```ruby
|
|
13
|
-
class PostDefinition < ResourceDefinition
|
|
14
|
-
# Search - global text search
|
|
15
|
-
search do |scope, query|
|
|
16
|
-
scope.where("title ILIKE ?", "%#{query}%")
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Filters - dropdown filter panel
|
|
20
|
-
filter :title, with: :text, predicate: :contains
|
|
21
|
-
filter :status, with: :select, choices: %w[draft published archived]
|
|
22
|
-
filter :published, with: :boolean
|
|
23
|
-
filter :created_at, with: :date_range
|
|
24
|
-
filter :category, with: :association
|
|
25
|
-
|
|
26
|
-
# Scopes - quick filter buttons
|
|
27
|
-
scope :published
|
|
28
|
-
scope :draft
|
|
29
|
-
|
|
30
|
-
# Default scope
|
|
31
|
-
default_scope :published
|
|
32
|
-
|
|
33
|
-
# Sorting - sortable columns
|
|
34
|
-
sort :title
|
|
35
|
-
sort :created_at
|
|
36
|
-
|
|
37
|
-
# Default sort
|
|
38
|
-
default_sort :created_at, :desc
|
|
39
|
-
end
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Search
|
|
43
|
-
|
|
44
|
-
Define global search across fields:
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
# Single field
|
|
48
|
-
search do |scope, query|
|
|
49
|
-
scope.where("title ILIKE ?", "%#{query}%")
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Multiple fields
|
|
53
|
-
search do |scope, query|
|
|
54
|
-
scope.where(
|
|
55
|
-
"title ILIKE :q OR content ILIKE :q OR author_name ILIKE :q",
|
|
56
|
-
q: "%#{query}%"
|
|
57
|
-
)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# With associations
|
|
61
|
-
search do |scope, query|
|
|
62
|
-
scope.joins(:author).where(
|
|
63
|
-
"posts.title ILIKE :q OR users.name ILIKE :q",
|
|
64
|
-
q: "%#{query}%"
|
|
65
|
-
).distinct
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Split search terms
|
|
69
|
-
search do |scope, query|
|
|
70
|
-
terms = query.split(/\s+/)
|
|
71
|
-
terms.reduce(scope) do |current_scope, term|
|
|
72
|
-
current_scope.where("title ILIKE ?", "%#{term}%")
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Filters
|
|
78
|
-
|
|
79
|
-
Plutonium provides **6 built-in filter types**. Use shorthand symbols or full class names.
|
|
80
|
-
|
|
81
|
-
### Text Filter
|
|
82
|
-
|
|
83
|
-
String/text filtering with pattern matching.
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
# Shorthand (recommended)
|
|
87
|
-
filter :title, with: :text, predicate: :contains
|
|
88
|
-
filter :status, with: :text, predicate: :eq
|
|
89
|
-
|
|
90
|
-
# Full class name
|
|
91
|
-
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**Predicates:**
|
|
95
|
-
|
|
96
|
-
| Predicate | SQL | Description |
|
|
97
|
-
|-----------|-----|-------------|
|
|
98
|
-
| `:eq` | `= value` | Exact match (default) |
|
|
99
|
-
| `:not_eq` | `!= value` | Not equal |
|
|
100
|
-
| `:contains` | `LIKE %value%` | Contains substring |
|
|
101
|
-
| `:not_contains` | `NOT LIKE %value%` | Does not contain |
|
|
102
|
-
| `:starts_with` | `LIKE value%` | Starts with |
|
|
103
|
-
| `:ends_with` | `LIKE %value` | Ends with |
|
|
104
|
-
| `:matches` | `LIKE value` | Pattern match (`*` becomes `%`) |
|
|
105
|
-
| `:not_matches` | `NOT LIKE value` | Does not match pattern |
|
|
106
|
-
|
|
107
|
-
### Boolean Filter
|
|
108
|
-
|
|
109
|
-
True/false filtering for boolean columns.
|
|
110
|
-
|
|
111
|
-
```ruby
|
|
112
|
-
# Basic
|
|
113
|
-
filter :active, with: :boolean
|
|
114
|
-
|
|
115
|
-
# Custom labels
|
|
116
|
-
filter :published, with: :boolean, true_label: "Published", false_label: "Draft"
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Renders a select dropdown with "All", true label ("Yes"), and false label ("No").
|
|
120
|
-
|
|
121
|
-
### Date Filter
|
|
122
|
-
|
|
123
|
-
Single date filtering with comparison predicates.
|
|
124
|
-
|
|
125
|
-
```ruby
|
|
126
|
-
filter :created_at, with: :date, predicate: :gteq # On or after
|
|
127
|
-
filter :due_date, with: :date, predicate: :lt # Before
|
|
128
|
-
filter :published_at, with: :date, predicate: :eq # On exact date
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
**Predicates:**
|
|
132
|
-
|
|
133
|
-
| Predicate | Description |
|
|
134
|
-
|-----------|-------------|
|
|
135
|
-
| `:eq` | On this date (default) |
|
|
136
|
-
| `:not_eq` | Not on this date |
|
|
137
|
-
| `:lt` | Before date |
|
|
138
|
-
| `:lteq` | On or before date |
|
|
139
|
-
| `:gt` | After date |
|
|
140
|
-
| `:gteq` | On or after date |
|
|
141
|
-
|
|
142
|
-
### Date Range Filter
|
|
143
|
-
|
|
144
|
-
Filter between two dates (from/to).
|
|
145
|
-
|
|
146
|
-
```ruby
|
|
147
|
-
# Basic
|
|
148
|
-
filter :created_at, with: :date_range
|
|
149
|
-
|
|
150
|
-
# Custom labels
|
|
151
|
-
filter :published_at, with: :date_range,
|
|
152
|
-
from_label: "Published from",
|
|
153
|
-
to_label: "Published to"
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Renders two date pickers. Both are optional - users can filter with just "from" or just "to".
|
|
157
|
-
|
|
158
|
-
### Select Filter
|
|
159
|
-
|
|
160
|
-
Filter from predefined choices.
|
|
161
|
-
|
|
162
|
-
```ruby
|
|
163
|
-
# Static choices (array)
|
|
164
|
-
filter :status, with: :select, choices: %w[draft published archived]
|
|
165
|
-
|
|
166
|
-
# Dynamic choices (proc)
|
|
167
|
-
filter :category, with: :select, choices: -> { Category.pluck(:name) }
|
|
168
|
-
|
|
169
|
-
# Multiple selection
|
|
170
|
-
filter :tags, with: :select, choices: %w[ruby rails js], multiple: true
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Association Filter
|
|
174
|
-
|
|
175
|
-
Filter by associated record.
|
|
176
|
-
|
|
177
|
-
```ruby
|
|
178
|
-
# Basic - infers Category class from :category key
|
|
179
|
-
filter :category, with: :association
|
|
180
|
-
|
|
181
|
-
# Explicit class
|
|
182
|
-
filter :author, with: :association, class_name: User
|
|
183
|
-
|
|
184
|
-
# Multiple selection
|
|
185
|
-
filter :tags, with: :association, class_name: Tag, multiple: true
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
Renders a resource select dropdown. Converts filter key to foreign key (`:category` -> `:category_id`).
|
|
189
|
-
|
|
190
|
-
## Custom Filters
|
|
191
|
-
|
|
192
|
-
### Custom Filter Class
|
|
193
|
-
|
|
194
|
-
```ruby
|
|
195
|
-
class PriceRangeFilter < Plutonium::Query::Filter
|
|
196
|
-
def apply(scope, min: nil, max: nil)
|
|
197
|
-
scope = scope.where("price >= ?", min) if min.present?
|
|
198
|
-
scope = scope.where("price <= ?", max) if max.present?
|
|
199
|
-
scope
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def customize_inputs
|
|
203
|
-
input :min, as: :number
|
|
204
|
-
input :max, as: :number
|
|
205
|
-
field :min, placeholder: "Min price..."
|
|
206
|
-
field :max, placeholder: "Max price..."
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
# Use in definition
|
|
211
|
-
filter :price, with: PriceRangeFilter
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
## Scopes
|
|
215
|
-
|
|
216
|
-
Scopes appear as quick filter buttons. They reference model scopes.
|
|
217
|
-
|
|
218
|
-
### Basic Usage
|
|
219
|
-
|
|
220
|
-
```ruby
|
|
221
|
-
class PostDefinition < ResourceDefinition
|
|
222
|
-
scope :published # Uses Post.published
|
|
223
|
-
scope :draft # Uses Post.draft
|
|
224
|
-
scope :featured # Uses Post.featured
|
|
225
|
-
end
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Inline Scope
|
|
229
|
-
|
|
230
|
-
Use block syntax with the scope passed as an argument:
|
|
231
|
-
|
|
232
|
-
```ruby
|
|
233
|
-
scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
|
|
234
|
-
scope(:this_month) { |scope| scope.where(created_at: Time.current.all_month) }
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### With Controller Context
|
|
238
|
-
|
|
239
|
-
Inline scopes have access to controller context like `current_user`:
|
|
240
|
-
|
|
241
|
-
```ruby
|
|
242
|
-
scope(:mine) { |scope| scope.where(author: current_user) }
|
|
243
|
-
scope(:my_team) { |scope| scope.where(team: current_user.team) }
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Default Scope
|
|
247
|
-
|
|
248
|
-
Set a scope as default to apply it when no scope is explicitly selected:
|
|
249
|
-
|
|
250
|
-
```ruby
|
|
251
|
-
class PostDefinition < ResourceDefinition
|
|
252
|
-
scope :published
|
|
253
|
-
scope :draft
|
|
254
|
-
scope :archived
|
|
255
|
-
|
|
256
|
-
default_scope :published # Applied by default
|
|
257
|
-
end
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
When a default scope is set:
|
|
261
|
-
- The default scope is applied on initial page load
|
|
262
|
-
- The default scope button is highlighted (not "All")
|
|
263
|
-
- Clicking "All" shows all records without any scope filter
|
|
264
|
-
- URL without scope param uses the default; URL with `?q[scope]=` uses "All"
|
|
265
|
-
|
|
266
|
-
## Sorting
|
|
267
|
-
|
|
268
|
-
### Basic Sorting
|
|
269
|
-
|
|
270
|
-
```ruby
|
|
271
|
-
sort :title
|
|
272
|
-
sort :created_at
|
|
273
|
-
sort :view_count
|
|
274
|
-
|
|
275
|
-
# Multiple at once
|
|
276
|
-
sorts :title, :created_at, :view_count
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### Default Sort
|
|
280
|
-
|
|
281
|
-
```ruby
|
|
282
|
-
# Field and direction
|
|
283
|
-
default_sort :created_at, :desc
|
|
284
|
-
default_sort :title, :asc
|
|
285
|
-
|
|
286
|
-
# Complex sorting with block
|
|
287
|
-
default_sort { |scope| scope.order(featured: :desc, created_at: :desc) }
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
**Note:** Default sort only applies when no sort params are provided.
|
|
291
|
-
|
|
292
|
-
## URL Parameters
|
|
293
|
-
|
|
294
|
-
Query parameters are structured under `q`:
|
|
295
|
-
|
|
296
|
-
```
|
|
297
|
-
/posts?q[search]=rails
|
|
298
|
-
/posts?q[title][query]=widget
|
|
299
|
-
/posts?q[status][value]=published
|
|
300
|
-
/posts?q[created_at][from]=2024-01-01&q[created_at][to]=2024-12-31
|
|
301
|
-
/posts?q[scope]=recent
|
|
302
|
-
/posts?q[sort_fields][]=created_at&q[sort_directions][created_at]=desc
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
## Filter Summary Table
|
|
306
|
-
|
|
307
|
-
| Type | Symbol | Input Params | Options |
|
|
308
|
-
|------|--------|--------------|---------|
|
|
309
|
-
| Text | `:text` | `query` | `predicate:` |
|
|
310
|
-
| Boolean | `:boolean` | `value` | `true_label:`, `false_label:` |
|
|
311
|
-
| Date | `:date` | `value` | `predicate:` |
|
|
312
|
-
| Date Range | `:date_range` | `from`, `to` | `from_label:`, `to_label:` |
|
|
313
|
-
| Select | `:select` | `value` | `choices:`, `multiple:` |
|
|
314
|
-
| Association | `:association` | `value` | `class_name:`, `multiple:` |
|
|
315
|
-
|
|
316
|
-
## Complete Example
|
|
317
|
-
|
|
318
|
-
```ruby
|
|
319
|
-
class ProductDefinition < ResourceDefinition
|
|
320
|
-
# Full-text search
|
|
321
|
-
search do |scope, query|
|
|
322
|
-
scope.where(
|
|
323
|
-
"name ILIKE :q OR description ILIKE :q",
|
|
324
|
-
q: "%#{query}%"
|
|
325
|
-
)
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# Filters
|
|
329
|
-
filter :name, with: :text, predicate: :contains
|
|
330
|
-
filter :status, with: :select, choices: %w[draft active discontinued]
|
|
331
|
-
filter :featured, with: :boolean
|
|
332
|
-
filter :created_at, with: :date_range
|
|
333
|
-
filter :price, with: :date, predicate: :gteq
|
|
334
|
-
filter :category, with: :association
|
|
335
|
-
|
|
336
|
-
# Quick scopes
|
|
337
|
-
scope :active
|
|
338
|
-
scope :featured
|
|
339
|
-
scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
|
|
340
|
-
|
|
341
|
-
# Default scope
|
|
342
|
-
default_scope :active
|
|
343
|
-
|
|
344
|
-
# Sortable columns
|
|
345
|
-
sorts :name, :price, :created_at
|
|
346
|
-
|
|
347
|
-
# Default sort
|
|
348
|
-
default_sort :created_at, :desc
|
|
349
|
-
end
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
## Performance Tips
|
|
353
|
-
|
|
354
|
-
1. **Add indexes** for filtered/sorted columns
|
|
355
|
-
2. **Use `.distinct`** when joining associations in search
|
|
356
|
-
3. **Consider `pg_search`** for complex full-text search
|
|
357
|
-
4. **Limit search fields** to indexed columns
|
|
358
|
-
5. **Use scopes** instead of filters for common queries
|
|
359
|
-
|
|
360
|
-
## Related Skills
|
|
361
|
-
|
|
362
|
-
- `plutonium-definition` - Overview and structure
|
|
363
|
-
- `plutonium-definition` - Fields, inputs, displays
|
|
364
|
-
- `plutonium-definition-actions` - Actions and interactions
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: plutonium-profile
|
|
3
|
-
description: Use when adding a user profile or account settings page with Rodauth security features
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Plutonium User Profile
|
|
7
|
-
|
|
8
|
-
Plutonium provides a Profile resource generator for managing Rodauth account settings. The profile resource allows users to:
|
|
9
|
-
- View and edit their profile information
|
|
10
|
-
- Access Rodauth security features (change password, 2FA, etc.)
|
|
11
|
-
- Manage their account settings in one place
|
|
12
|
-
|
|
13
|
-
## Quick Setup
|
|
14
|
-
|
|
15
|
-
Use the setup generator to create and connect the profile in one command:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
rails g pu:profile:setup date_of_birth:date bio:text \
|
|
19
|
-
--dest=competition \
|
|
20
|
-
--portal=competition_portal
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Step-by-Step Installation
|
|
24
|
-
|
|
25
|
-
### Install the Profile Resource
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
rails generate pu:profile:install --dest=main_app
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**Options:**
|
|
32
|
-
|
|
33
|
-
| Option | Default | Description |
|
|
34
|
-
|--------|---------|-------------|
|
|
35
|
-
| `--dest=DESTINATION` | (prompts) | Target package or main_app |
|
|
36
|
-
| `--user-model=NAME` | User | Rodauth user model name |
|
|
37
|
-
|
|
38
|
-
**With custom fields:**
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
rails g pu:profile:install \
|
|
42
|
-
bio:text \
|
|
43
|
-
avatar:attachment \
|
|
44
|
-
'timezone:string?' \
|
|
45
|
-
--dest=customer
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
**With custom name:**
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
rails g pu:profile:install AccountSettings \
|
|
52
|
-
bio:text \
|
|
53
|
-
--dest=main_app
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## What Gets Created
|
|
57
|
-
|
|
58
|
-
The generator creates a standard Plutonium resource:
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
app/models/[package/]profile.rb # Profile model
|
|
62
|
-
db/migrate/xxx_create_profiles.rb # Migration
|
|
63
|
-
app/controllers/[package/]profiles_controller.rb
|
|
64
|
-
app/policies/[package/]profile_policy.rb
|
|
65
|
-
app/definitions/[package/]profile_definition.rb
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
And modifies:
|
|
69
|
-
- **User model**: Adds `has_one :profile, dependent: :destroy`
|
|
70
|
-
- **Definition**: Injects custom ShowPage with SecuritySection
|
|
71
|
-
|
|
72
|
-
## The SecuritySection Component
|
|
73
|
-
|
|
74
|
-
The generator injects a custom `ShowPage` that renders `Plutonium::Profile::SecuritySection`:
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
class ProfileDefinition < Plutonium::Resource::Definition
|
|
78
|
-
class ShowPage < ShowPage
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
def render_after_content
|
|
82
|
-
render Plutonium::Profile::SecuritySection.new
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
The `SecuritySection` component dynamically checks which Rodauth features are enabled and displays links for:
|
|
89
|
-
|
|
90
|
-
| Feature | Label | Description |
|
|
91
|
-
|---------|-------|-------------|
|
|
92
|
-
| `change_password` | Change Password | Update account password |
|
|
93
|
-
| `change_login` | Change Email | Update email address |
|
|
94
|
-
| `otp` | Two-Factor Authentication | Set up TOTP |
|
|
95
|
-
| `recovery_codes` | Recovery Codes | View/regenerate backup codes |
|
|
96
|
-
| `webauthn` | Security Keys | Manage passkeys |
|
|
97
|
-
| `active_sessions` | Active Sessions | View/manage sessions |
|
|
98
|
-
| `close_account` | Close Account | Delete account |
|
|
99
|
-
|
|
100
|
-
Only enabled Rodauth features are displayed.
|
|
101
|
-
|
|
102
|
-
## After Generation
|
|
103
|
-
|
|
104
|
-
### 1. Run Migrations
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
rails db:migrate
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### 2. Connect to Portal
|
|
111
|
-
|
|
112
|
-
Use the profile connect generator to register as a singular resource and configure the `profile_url` helper:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
rails g pu:profile:conn --dest=customer_portal
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
This:
|
|
119
|
-
- Registers the Profile as a singular resource (`/profile` instead of `/profiles/:id`)
|
|
120
|
-
- Adds `profile_url` helper to enable the "Profile" link in the user menu
|
|
121
|
-
|
|
122
|
-
### 3. Create Profile Automatically
|
|
123
|
-
|
|
124
|
-
Users need a profile created. Add a callback or use `find_or_create`:
|
|
125
|
-
|
|
126
|
-
```ruby
|
|
127
|
-
# Option A: Callback on User
|
|
128
|
-
class User < ApplicationRecord
|
|
129
|
-
after_create :create_profile!
|
|
130
|
-
|
|
131
|
-
private
|
|
132
|
-
|
|
133
|
-
def create_profile!
|
|
134
|
-
create_profile
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# Option B: In controller or before_action
|
|
139
|
-
def current_profile
|
|
140
|
-
@current_profile ||= current_user.profile || current_user.create_profile
|
|
141
|
-
end
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Customization
|
|
145
|
-
|
|
146
|
-
### Adding Profile Fields
|
|
147
|
-
|
|
148
|
-
Add fields during generation:
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
rails g pu:profile:install \
|
|
152
|
-
bio:text \
|
|
153
|
-
avatar:attachment \
|
|
154
|
-
website:string \
|
|
155
|
-
'company:string?' \
|
|
156
|
-
--dest=main_app
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Or add to the migration manually before running.
|
|
160
|
-
|
|
161
|
-
### Customizing the Definition
|
|
162
|
-
|
|
163
|
-
Edit the generated definition:
|
|
164
|
-
|
|
165
|
-
```ruby
|
|
166
|
-
# app/definitions/profile_definition.rb
|
|
167
|
-
class ProfileDefinition < Plutonium::Resource::Definition
|
|
168
|
-
# Form configuration
|
|
169
|
-
form do |f|
|
|
170
|
-
f.field :bio
|
|
171
|
-
f.field :avatar
|
|
172
|
-
f.field :website
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Display configuration
|
|
176
|
-
display do |d|
|
|
177
|
-
d.field :bio
|
|
178
|
-
d.field :avatar
|
|
179
|
-
d.field :website
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
class ShowPage < ShowPage
|
|
183
|
-
private
|
|
184
|
-
|
|
185
|
-
def render_after_content
|
|
186
|
-
render Plutonium::Profile::SecuritySection.new
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Custom Security Section
|
|
193
|
-
|
|
194
|
-
Override the SecuritySection or create your own:
|
|
195
|
-
|
|
196
|
-
```ruby
|
|
197
|
-
class ProfileDefinition < Plutonium::Resource::Definition
|
|
198
|
-
class ShowPage < ShowPage
|
|
199
|
-
private
|
|
200
|
-
|
|
201
|
-
def render_after_content
|
|
202
|
-
render CustomSecuritySection.new
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### Adding Custom Actions
|
|
209
|
-
|
|
210
|
-
Add profile-specific actions:
|
|
211
|
-
|
|
212
|
-
```ruby
|
|
213
|
-
class ProfileDefinition < Plutonium::Resource::Definition
|
|
214
|
-
action :export_data,
|
|
215
|
-
interaction: Profile::ExportDataInteraction
|
|
216
|
-
|
|
217
|
-
action :verify_email,
|
|
218
|
-
interaction: Profile::VerifyEmailInteraction,
|
|
219
|
-
category: :secondary
|
|
220
|
-
end
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
## Profile Link in Header
|
|
224
|
-
|
|
225
|
-
To add a profile link to the resource header, the `profile_url` helper is available via `Plutonium::Auth::Rodauth`:
|
|
226
|
-
|
|
227
|
-
```ruby
|
|
228
|
-
# In your controller or view
|
|
229
|
-
if respond_to?(:profile_url)
|
|
230
|
-
link_to "Profile", profile_url
|
|
231
|
-
end
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
This helper is automatically available when Profile is connected to a portal.
|
|
235
|
-
|
|
236
|
-
## Troubleshooting
|
|
237
|
-
|
|
238
|
-
### "User model not found"
|
|
239
|
-
|
|
240
|
-
Ensure the User model exists at `app/models/user.rb` with the marker comment:
|
|
241
|
-
|
|
242
|
-
```ruby
|
|
243
|
-
class User < ApplicationRecord
|
|
244
|
-
# add has_one associations above.
|
|
245
|
-
end
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### "Definition path not found"
|
|
249
|
-
|
|
250
|
-
If using a package destination, ensure the package exists:
|
|
251
|
-
|
|
252
|
-
```bash
|
|
253
|
-
# Check available packages
|
|
254
|
-
ls packages/
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### Profile Not Loading
|
|
258
|
-
|
|
259
|
-
Ensure the Profile is connected to your portal:
|
|
260
|
-
|
|
261
|
-
```bash
|
|
262
|
-
rails g pu:res:conn Profile --dest=my_portal --singular
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
And the user has a profile:
|
|
266
|
-
|
|
267
|
-
```ruby
|
|
268
|
-
current_user.profile || current_user.create_profile
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
## Related Skills
|
|
272
|
-
|
|
273
|
-
- `plutonium-rodauth` - Authentication configuration
|
|
274
|
-
- `plutonium-definition` - Customizing the profile definition
|
|
275
|
-
- `plutonium-views` - Custom pages and components
|
|
276
|
-
- `plutonium-portal` - Connecting resources to portals
|