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,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: definition
|
|
3
|
+
description: Overview of Plutonium resource definitions - structure, inheritance, and best practices
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Definition Overview
|
|
7
|
+
|
|
8
|
+
Resource definitions configure **HOW** resources are rendered and interacted with. They are the central configuration point for UI behavior in Plutonium applications.
|
|
9
|
+
|
|
10
|
+
## Key Principle
|
|
11
|
+
|
|
12
|
+
**All model attributes are auto-detected** - you only declare when overriding defaults.
|
|
13
|
+
|
|
14
|
+
## File Location
|
|
15
|
+
|
|
16
|
+
- Main app: `app/definitions/model_name_definition.rb`
|
|
17
|
+
- Packages: `packages/pkg_name/app/definitions/pkg_name/model_name_definition.rb`
|
|
18
|
+
|
|
19
|
+
## Definition Structure
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
class PostDefinition < Plutonium::Resource::Definition
|
|
23
|
+
# Fields, inputs, displays, columns (see definition-fields skill)
|
|
24
|
+
field :content, as: :rich_text
|
|
25
|
+
input :title, hint: "Be descriptive"
|
|
26
|
+
display :content, as: :markdown
|
|
27
|
+
column :title, align: :center
|
|
28
|
+
|
|
29
|
+
# Search, filters, scopes, sorting (see definition-query skill)
|
|
30
|
+
search { |scope, q| scope.where("title ILIKE ?", "%#{q}%") }
|
|
31
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
32
|
+
scope :published
|
|
33
|
+
sort :created_at
|
|
34
|
+
|
|
35
|
+
# Actions (see definition-actions skill)
|
|
36
|
+
action :publish, interaction: PublishInteraction
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Definition Hierarchy
|
|
41
|
+
|
|
42
|
+
Definitions exist at multiple levels:
|
|
43
|
+
|
|
44
|
+
### Main App (created by generators)
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# app/definitions/resource_definition.rb (base - created during install)
|
|
48
|
+
class ResourceDefinition < Plutonium::Resource::Definition
|
|
49
|
+
action :archive, interaction: ArchiveInteraction, color: :danger, position: 1000
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# app/definitions/post_definition.rb (resource-specific - created by scaffold)
|
|
53
|
+
class PostDefinition < ResourceDefinition
|
|
54
|
+
scope :published
|
|
55
|
+
input :content, as: :rich_text
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Portal-Specific Overrides
|
|
60
|
+
|
|
61
|
+
After connecting a resource to a portal, you can create a portal-specific definition to override defaults for that portal only:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
# packages/admin_portal/app/definitions/admin_portal/post_definition.rb
|
|
65
|
+
class AdminPortal::PostDefinition < ::PostDefinition
|
|
66
|
+
# Override or extend for admin portal only
|
|
67
|
+
input :internal_notes, as: :text # Only admins see this field
|
|
68
|
+
scope :pending_review # Admin-specific scope
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This lets you:
|
|
73
|
+
- Show different fields per portal
|
|
74
|
+
- Add portal-specific actions
|
|
75
|
+
- Customize search/filters per context
|
|
76
|
+
- Keep main app definition clean
|
|
77
|
+
|
|
78
|
+
## Separation of Concerns
|
|
79
|
+
|
|
80
|
+
| Layer | Purpose | Example |
|
|
81
|
+
|-------|---------|---------|
|
|
82
|
+
| **Definition** | HOW fields render | `input :content, as: :rich_text` |
|
|
83
|
+
| **Policy** | WHAT is visible/editable | `permitted_attributes_for_read` |
|
|
84
|
+
| **Interaction** | Business logic | `resource.update!(state: :archived)` |
|
|
85
|
+
|
|
86
|
+
## Auto-Detection
|
|
87
|
+
|
|
88
|
+
Plutonium automatically detects from your model:
|
|
89
|
+
- Database columns (string, text, integer, boolean, datetime, etc.)
|
|
90
|
+
- Associations (belongs_to, has_many, has_one)
|
|
91
|
+
- Active Storage/Active Shrine attachments (has_one_attached, has_many_attached)
|
|
92
|
+
- Enums
|
|
93
|
+
- Virtual attributes (with accessor methods)
|
|
94
|
+
|
|
95
|
+
**Only declare fields when you need to override the auto-detected behavior.**
|
|
96
|
+
|
|
97
|
+
## When to Declare
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class PostDefinition < ResourceDefinition
|
|
101
|
+
# 1. Override auto-detected type
|
|
102
|
+
field :content, as: :rich_text # text -> rich_text
|
|
103
|
+
input :published_at, as: :date # datetime -> date only
|
|
104
|
+
|
|
105
|
+
# 2. Add custom options
|
|
106
|
+
input :title, hint: "Be descriptive", placeholder: "Enter title"
|
|
107
|
+
|
|
108
|
+
# 3. Configure select choices
|
|
109
|
+
input :category, as: :select, choices: %w[Tech Business]
|
|
110
|
+
|
|
111
|
+
# 4. Add conditional logic
|
|
112
|
+
display :published_at, condition: -> { object.published? }
|
|
113
|
+
|
|
114
|
+
# 5. Custom rendering
|
|
115
|
+
display :status do |field|
|
|
116
|
+
StatusBadgeComponent.new(value: field.value)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Minimal Definition Example
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
class PostDefinition < ResourceDefinition
|
|
125
|
+
# No field declarations needed - all auto-detected!
|
|
126
|
+
|
|
127
|
+
# Only customize what you need:
|
|
128
|
+
input :content, as: :rich_text
|
|
129
|
+
display :content, as: :markdown
|
|
130
|
+
|
|
131
|
+
scope :published
|
|
132
|
+
scope :draft
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Runtime Customization Hooks
|
|
137
|
+
|
|
138
|
+
Override these methods for dynamic behavior:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
class PostDefinition < ResourceDefinition
|
|
142
|
+
def customize_fields
|
|
143
|
+
field :debug_info if Rails.env.development?
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def customize_inputs
|
|
147
|
+
# Add/modify inputs at runtime
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def customize_displays
|
|
151
|
+
# Add/modify displays at runtime
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def customize_filters
|
|
155
|
+
# Add/modify filters at runtime
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def customize_actions
|
|
159
|
+
# Add/modify actions at runtime
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Context Availability
|
|
165
|
+
|
|
166
|
+
| Context | current_user | object | current_parent |
|
|
167
|
+
|---------|-------------|--------|----------------|
|
|
168
|
+
| Definition file (class level) | No | No | No |
|
|
169
|
+
| `condition` procs | Yes | Yes | Yes |
|
|
170
|
+
| `input` blocks | Yes | Yes | Yes |
|
|
171
|
+
| Page title procs | Yes | Yes (current_record!) | Yes |
|
|
172
|
+
|
|
173
|
+
## Page Customization
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
class PostDefinition < ResourceDefinition
|
|
177
|
+
# Titles (static or dynamic)
|
|
178
|
+
index_page_title "All Posts"
|
|
179
|
+
show_page_title -> { "#{current_record!.title} - Details" }
|
|
180
|
+
|
|
181
|
+
# Breadcrumbs
|
|
182
|
+
breadcrumbs true
|
|
183
|
+
show_page_breadcrumbs false
|
|
184
|
+
|
|
185
|
+
# Custom page classes (inherit from parent's nested class)
|
|
186
|
+
class IndexPage < IndexPage
|
|
187
|
+
def view_template(&block)
|
|
188
|
+
div(class: "custom-header") { h1 { "Custom" } }
|
|
189
|
+
super(&block)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class Form < Form
|
|
194
|
+
def form_template
|
|
195
|
+
div(class: "grid grid-cols-2") do
|
|
196
|
+
render field(:title).input_tag
|
|
197
|
+
render field(:content).easymde_tag
|
|
198
|
+
end
|
|
199
|
+
render_actions
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Best Practices
|
|
206
|
+
|
|
207
|
+
1. **Let auto-detection work** - Don't declare unless overriding
|
|
208
|
+
2. **Use portal-specific definitions** - Override per-portal when needed
|
|
209
|
+
3. **Keep definitions focused** - Configuration only, no business logic
|
|
210
|
+
4. **Use policies for authorization** - Not `condition` procs
|
|
211
|
+
5. **Group related declarations** - Use comments to organize sections
|
|
212
|
+
|
|
213
|
+
## Related Skills
|
|
214
|
+
|
|
215
|
+
- `definition-fields` - Fields, inputs, displays, columns
|
|
216
|
+
- `definition-actions` - Actions and interactions
|
|
217
|
+
- `definition-query` - Search, filters, scopes, sorting
|
|
218
|
+
- `views` - Custom page, form, display, and table classes
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: definition-actions
|
|
3
|
+
description: Add custom actions and interactions to Plutonium resources
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Definition Actions
|
|
7
|
+
|
|
8
|
+
Actions define custom operations that can be performed on resources. They can be simple (navigation) or interactive (with business logic via Interactions).
|
|
9
|
+
|
|
10
|
+
## Action Types
|
|
11
|
+
|
|
12
|
+
| Type | Shows In | Use Case |
|
|
13
|
+
|------|----------|----------|
|
|
14
|
+
| `resource_action` | Index page | Import, Export, Create |
|
|
15
|
+
| `record_action` | Show page | Edit, Delete, Archive |
|
|
16
|
+
| `collection_record_action` | Table rows | Quick actions per row |
|
|
17
|
+
| `bulk_action` | Selected records | Bulk operations |
|
|
18
|
+
|
|
19
|
+
## Simple Actions (Navigation)
|
|
20
|
+
|
|
21
|
+
Simple actions link to existing routes. **The target route must already exist** - these don't create new functionality, just navigation links.
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
class PostDefinition < ResourceDefinition
|
|
25
|
+
# Link to external URL
|
|
26
|
+
action :documentation,
|
|
27
|
+
label: "Documentation",
|
|
28
|
+
route_options: {url: "https://docs.example.com"},
|
|
29
|
+
icon: Phlex::TablerIcons::Book,
|
|
30
|
+
resource_action: true
|
|
31
|
+
|
|
32
|
+
# Link to custom controller action (you must add the action + route yourself)
|
|
33
|
+
action :reports,
|
|
34
|
+
route_options: {action: :reports},
|
|
35
|
+
icon: Phlex::TablerIcons::ChartBar,
|
|
36
|
+
resource_action: true
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Note:** For custom operations with business logic, use **Interactive Actions** with an Interaction class instead. That's the recommended approach for most custom actions.
|
|
41
|
+
|
|
42
|
+
## Interactive Actions (with Interaction)
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
class PostDefinition < ResourceDefinition
|
|
46
|
+
action :publish,
|
|
47
|
+
interaction: PublishInteraction,
|
|
48
|
+
icon: Phlex::TablerIcons::Send
|
|
49
|
+
|
|
50
|
+
action :archive,
|
|
51
|
+
interaction: ArchiveInteraction,
|
|
52
|
+
color: :danger,
|
|
53
|
+
category: :danger,
|
|
54
|
+
position: 1000,
|
|
55
|
+
confirmation: "Are you sure?"
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Action Options
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
action :name,
|
|
63
|
+
# Display
|
|
64
|
+
label: "Custom Label", # Button text (default: name.titleize)
|
|
65
|
+
description: "What it does", # Tooltip/description
|
|
66
|
+
icon: Phlex::TablerIcons::Star, # Icon component
|
|
67
|
+
color: :danger, # :primary, :secondary, :danger
|
|
68
|
+
|
|
69
|
+
# Visibility
|
|
70
|
+
resource_action: true, # Show on index page
|
|
71
|
+
record_action: true, # Show on show page
|
|
72
|
+
collection_record_action: true, # Show in table rows
|
|
73
|
+
bulk_action: true, # For selected records
|
|
74
|
+
|
|
75
|
+
# Grouping
|
|
76
|
+
category: :primary, # :primary, :secondary, :danger
|
|
77
|
+
position: 50, # Order (lower = first)
|
|
78
|
+
|
|
79
|
+
# Behavior
|
|
80
|
+
confirmation: "Are you sure?", # Confirmation dialog
|
|
81
|
+
turbo_frame: "_top", # Turbo frame target
|
|
82
|
+
route_options: {action: :foo} # Route configuration
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Creating an Interaction
|
|
86
|
+
|
|
87
|
+
### Basic Structure
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
# app/interactions/resource_interaction.rb (generated during install)
|
|
91
|
+
class ResourceInteraction < Plutonium::Resource::Interaction
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# app/interactions/archive_interaction.rb
|
|
95
|
+
class ArchiveInteraction < ResourceInteraction
|
|
96
|
+
presents label: "Archive",
|
|
97
|
+
icon: Phlex::TablerIcons::Archive,
|
|
98
|
+
description: "Archive this record"
|
|
99
|
+
|
|
100
|
+
attribute :resource # The record being acted on
|
|
101
|
+
|
|
102
|
+
def execute
|
|
103
|
+
resource.archived!
|
|
104
|
+
succeed(resource).with_message("Record archived successfully.")
|
|
105
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
106
|
+
failed(e.record.errors)
|
|
107
|
+
rescue => error
|
|
108
|
+
failed("Archive failed. Please try again.")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### With Additional Inputs
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
# app/interactions/company/invite_user_interaction.rb
|
|
117
|
+
class Company::InviteUserInteraction < Plutonium::Resource::Interaction
|
|
118
|
+
presents label: "Invite User", icon: Phlex::TablerIcons::Mail
|
|
119
|
+
|
|
120
|
+
attribute :resource # The company
|
|
121
|
+
attribute :email
|
|
122
|
+
attribute :role
|
|
123
|
+
|
|
124
|
+
# Configure form inputs
|
|
125
|
+
input :email, as: :email, hint: "User's email address"
|
|
126
|
+
input :role, as: :select, choices: %w[admin member viewer]
|
|
127
|
+
|
|
128
|
+
# Validations
|
|
129
|
+
validates :email, presence: true, format: {with: URI::MailTo::EMAIL_REGEXP}
|
|
130
|
+
validates :role, presence: true, inclusion: {in: %w[admin member viewer]}
|
|
131
|
+
|
|
132
|
+
def execute
|
|
133
|
+
UserInvite.create!(
|
|
134
|
+
company: resource,
|
|
135
|
+
email: email,
|
|
136
|
+
role: role,
|
|
137
|
+
invited_by: current_user
|
|
138
|
+
)
|
|
139
|
+
succeed(resource).with_message("Invitation sent to #{email}.")
|
|
140
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
141
|
+
failed(e.record.errors)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Bulk Action (Multiple Records)
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
class BulkArchiveInteraction < Plutonium::Resource::Interaction
|
|
150
|
+
presents label: "Archive Selected", icon: Phlex::TablerIcons::Archive
|
|
151
|
+
|
|
152
|
+
attribute :resources # Array of records (note: plural)
|
|
153
|
+
|
|
154
|
+
def execute
|
|
155
|
+
count = 0
|
|
156
|
+
resources.each do |record|
|
|
157
|
+
record.archived!
|
|
158
|
+
count += 1
|
|
159
|
+
end
|
|
160
|
+
succeed(resources).with_message("#{count} records archived.")
|
|
161
|
+
rescue => error
|
|
162
|
+
failed("Bulk archive failed: #{error.message}")
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Resource Action (No Record)
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
class ImportInteraction < Plutonium::Resource::Interaction
|
|
171
|
+
presents label: "Import CSV", icon: Phlex::TablerIcons::Upload
|
|
172
|
+
|
|
173
|
+
# No :resource or :resources attribute = resource action
|
|
174
|
+
attribute :file
|
|
175
|
+
|
|
176
|
+
input :file, as: :file
|
|
177
|
+
|
|
178
|
+
validates :file, presence: true
|
|
179
|
+
|
|
180
|
+
def execute
|
|
181
|
+
# Import logic...
|
|
182
|
+
succeed(nil).with_message("Import completed.")
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Interaction Responses
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
def execute
|
|
191
|
+
# Success with message
|
|
192
|
+
succeed(resource).with_message("Done!")
|
|
193
|
+
|
|
194
|
+
# Success with redirect
|
|
195
|
+
succeed(resource)
|
|
196
|
+
.with_redirect_response(resource_url_for(resource))
|
|
197
|
+
.with_message("Redirecting...")
|
|
198
|
+
|
|
199
|
+
# Failure with field errors
|
|
200
|
+
failed(resource.errors)
|
|
201
|
+
|
|
202
|
+
# Failure with custom message
|
|
203
|
+
failed("Something went wrong")
|
|
204
|
+
|
|
205
|
+
# Failure with specific field
|
|
206
|
+
failed("Invalid value", :email)
|
|
207
|
+
end
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Interaction Context
|
|
211
|
+
|
|
212
|
+
Inside an interaction:
|
|
213
|
+
- `current_user` - The authenticated user
|
|
214
|
+
- `view_context` - Access to helpers and view methods
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
def execute
|
|
218
|
+
resource.update!(
|
|
219
|
+
archived_by: current_user,
|
|
220
|
+
archived_at: Time.current
|
|
221
|
+
)
|
|
222
|
+
succeed(resource)
|
|
223
|
+
end
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Defining in Definition
|
|
227
|
+
|
|
228
|
+
### Basic
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
class PostDefinition < ResourceDefinition
|
|
232
|
+
action :publish, interaction: PublishInteraction
|
|
233
|
+
action :archive, interaction: ArchiveInteraction
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### With Overrides
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
class PostDefinition < ResourceDefinition
|
|
241
|
+
action :archive,
|
|
242
|
+
interaction: ArchiveInteraction,
|
|
243
|
+
collection_record_action: false, # Don't show in table
|
|
244
|
+
color: :danger,
|
|
245
|
+
position: 1000 # Show last
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Inherited Actions
|
|
250
|
+
|
|
251
|
+
Actions defined in `ResourceDefinition` (created during install) are inherited by all definitions:
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
# app/definitions/resource_definition.rb (created during install)
|
|
255
|
+
class ResourceDefinition < Plutonium::Resource::Definition
|
|
256
|
+
action :archive, interaction: ArchiveInteraction, color: :danger, position: 1000
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# All definitions inherit the archive action automatically
|
|
260
|
+
class PostDefinition < ResourceDefinition
|
|
261
|
+
end
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Portal-Specific Actions
|
|
265
|
+
|
|
266
|
+
Override actions for a specific portal:
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
# packages/admin_portal/app/definitions/admin_portal/post_definition.rb
|
|
270
|
+
class AdminPortal::PostDefinition < ::PostDefinition
|
|
271
|
+
# Add admin-only actions
|
|
272
|
+
action :feature, interaction: FeaturePostInteraction
|
|
273
|
+
action :bulk_publish, interaction: BulkPublishInteraction
|
|
274
|
+
end
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Default CRUD Actions
|
|
278
|
+
|
|
279
|
+
Plutonium provides these by default:
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
action :new, resource_action: true, position: 10
|
|
283
|
+
action :show, collection_record_action: true, position: 10
|
|
284
|
+
action :edit, record_action: true, position: 20
|
|
285
|
+
action :destroy, record_action: true, position: 100, category: :danger
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Authorization
|
|
289
|
+
|
|
290
|
+
Actions are authorized via policies:
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
# app/policies/post_policy.rb
|
|
294
|
+
class PostPolicy < ResourcePolicy
|
|
295
|
+
def publish?
|
|
296
|
+
user.admin? || record.author == user
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def archive?
|
|
300
|
+
user.admin?
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The action only appears if the policy method returns `true`.
|
|
306
|
+
|
|
307
|
+
## Immediate vs Form Actions
|
|
308
|
+
|
|
309
|
+
**Immediate** - Executes without showing a form (when interaction has no extra inputs):
|
|
310
|
+
|
|
311
|
+
```ruby
|
|
312
|
+
class ArchiveInteraction < Plutonium::Resource::Interaction
|
|
313
|
+
attribute :resource # Only resource, no other inputs
|
|
314
|
+
# No input declarations
|
|
315
|
+
|
|
316
|
+
def execute
|
|
317
|
+
resource.archived!
|
|
318
|
+
succeed(resource)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Form** - Shows a form first (when interaction has additional inputs):
|
|
324
|
+
|
|
325
|
+
```ruby
|
|
326
|
+
class InviteUserInteraction < Plutonium::Resource::Interaction
|
|
327
|
+
attribute :resource
|
|
328
|
+
attribute :email
|
|
329
|
+
attribute :role
|
|
330
|
+
|
|
331
|
+
input :email
|
|
332
|
+
input :role, as: :select, choices: %w[admin member]
|
|
333
|
+
# Has inputs = shows form first
|
|
334
|
+
end
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Common Patterns
|
|
338
|
+
|
|
339
|
+
### Archive/Restore
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
class ArchiveInteraction < Plutonium::Resource::Interaction
|
|
343
|
+
presents label: "Archive", icon: Phlex::TablerIcons::Archive
|
|
344
|
+
attribute :resource
|
|
345
|
+
|
|
346
|
+
def execute
|
|
347
|
+
resource.archived!
|
|
348
|
+
succeed(resource).with_message("Archived.")
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
class RestoreInteraction < Plutonium::Resource::Interaction
|
|
353
|
+
presents label: "Restore", icon: Phlex::TablerIcons::Refresh
|
|
354
|
+
attribute :resource
|
|
355
|
+
|
|
356
|
+
def execute
|
|
357
|
+
resource.active!
|
|
358
|
+
succeed(resource).with_message("Restored.")
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Send Notification
|
|
364
|
+
|
|
365
|
+
```ruby
|
|
366
|
+
class SendReminderInteraction < Plutonium::Resource::Interaction
|
|
367
|
+
presents label: "Send Reminder", icon: Phlex::TablerIcons::Bell
|
|
368
|
+
attribute :resource
|
|
369
|
+
attribute :message
|
|
370
|
+
|
|
371
|
+
input :message, as: :text, hint: "Custom message (optional)"
|
|
372
|
+
|
|
373
|
+
def execute
|
|
374
|
+
ReminderMailer.with(record: resource, message: message).deliver_later
|
|
375
|
+
succeed(resource).with_message("Reminder sent.")
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Related Skills
|
|
381
|
+
|
|
382
|
+
- `definition` - Overview and structure
|
|
383
|
+
- `definition-fields` - Fields, inputs, displays
|
|
384
|
+
- `definition-query` - Search, filters, scopes
|
|
385
|
+
- `interaction` - Writing interaction classes
|
|
386
|
+
- `policy` - Controlling action access
|