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,101 @@
|
|
1
|
+
# 4. Creating a Portal
|
2
|
+
|
3
|
+
We have our `Blogging` feature package with `Post` and `Comment` resources, but no user interface to manage them. Let's create a **Portal** to serve as a web interface for our blog.
|
4
|
+
|
5
|
+
## What are Portal Packages?
|
6
|
+
|
7
|
+
While Feature Packages hold business logic, **Portal Packages** provide the web interface. They are also Rails Engines, but they are responsible for things like:
|
8
|
+
|
9
|
+
- Dashboards and layouts.
|
10
|
+
- Routing requests to the correct resources.
|
11
|
+
- Handling user authentication for the interface.
|
12
|
+
|
13
|
+
We'll create a `Dashboard` portal for managing our blog.
|
14
|
+
|
15
|
+
## Generate the Portal
|
16
|
+
|
17
|
+
Plutonium has a generator for portals. In your terminal, run:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
rails generate pu:pkg:portal dashboard
|
21
|
+
```
|
22
|
+
|
23
|
+
The generator will ask which authentication account to use. We already set up the `user` account with Rodauth, so **select `user`**.
|
24
|
+
|
25
|
+
This creates a new package at `packages/dashboard_portal/` and wires it up for authentication.
|
26
|
+
|
27
|
+
## Connect Resources to the Portal
|
28
|
+
|
29
|
+
Now we need to tell our new `Dashboard` portal which resources it should manage. We can use the `pu:res:conn` (connect) generator for this.
|
30
|
+
|
31
|
+
Run the connect generator:
|
32
|
+
```bash
|
33
|
+
rails generate pu:res:conn
|
34
|
+
```
|
35
|
+
|
36
|
+
It will ask you a series of questions:
|
37
|
+
1. **Select source feature**: Choose **`blogging`**.
|
38
|
+
2. **Select resources**: Use the spacebar to select both **`Blogging::Post`** and **`Blogging::Comment`**, then press Enter.
|
39
|
+
3. **Select destination portal**: Choose **`dashboard_portal`**.
|
40
|
+
|
41
|
+
This command does the magic of connecting your feature logic to your UI. It updates the portal's routes file (`packages/dashboard_portal/config/routes.rb`) to register the resources:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# packages/dashboard_portal/config/routes.rb
|
45
|
+
DashboardPortal::Engine.routes.draw do
|
46
|
+
root to: "dashboard#index"
|
47
|
+
|
48
|
+
register_resource Blogging::Post
|
49
|
+
register_resource Blogging::Comment
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
## Configure Application Routes
|
54
|
+
|
55
|
+
There's one last step. We need to tell our main Rails application how to handle routing for authentication and the new dashboard.
|
56
|
+
|
57
|
+
1. **Update Rodauth Redirects**: We want users to be sent to the dashboard after they log in or sign up.
|
58
|
+
|
59
|
+
```ruby [app/rodauth/user_rodauth_plugin.rb]
|
60
|
+
# ==> Redirects
|
61
|
+
|
62
|
+
# Redirect to home after login.
|
63
|
+
create_account_redirect "/" # [!code --]
|
64
|
+
create_account_redirect "/dashboard" # [!code ++]
|
65
|
+
|
66
|
+
# Redirect to home after login.
|
67
|
+
login_redirect "/" # [!code --]
|
68
|
+
login_redirect "/dashboard" # [!code ++]
|
69
|
+
|
70
|
+
# Redirect to home page after logout.
|
71
|
+
logout_redirect "/" # [!code --]
|
72
|
+
logout_redirect "/dashboard" # [!code ++]
|
73
|
+
```
|
74
|
+
|
75
|
+
2. **Update Root Route**: We'll make the dashboard the root of our application.
|
76
|
+
|
77
|
+
```ruby [config/routes.rb]
|
78
|
+
Rails.application.routes.draw do
|
79
|
+
# ...
|
80
|
+
# Defines the root path route ("/")
|
81
|
+
# root "posts#index" # [!code --]
|
82
|
+
root to: redirect("/dashboard") # [!code ++]
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
## See it in Action!
|
87
|
+
|
88
|
+
Let's check our progress. Start your Rails server (`rails s`) and navigate to [http://localhost:3000](http://localhost:3000).
|
89
|
+
|
90
|
+
You will be redirected to `/dashboard` and should see the login page.
|
91
|
+
|
92
|
+
- **Sign up** for a new account.
|
93
|
+
- After signing up, you'll be redirected to the dashboard. It will be empty for now, but this confirms the portal and authentication are working!
|
94
|
+
|
95
|
+

|
96
|
+
|
97
|
+
## Next Steps
|
98
|
+
|
99
|
+
We now have a functional dashboard! It's not very useful yet, though.
|
100
|
+
|
101
|
+
In the next chapter, we will customize the user interface to display our posts and comments, and add navigation to make them accessible.
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# 5. Customizing the UI
|
2
|
+
|
3
|
+
Our dashboard is functional, but it's not very user-friendly. In Plutonium, customizing the UI is a two-step process that respects the separation of concerns:
|
4
|
+
|
5
|
+
1. **Policy (`app/policies`)**: The policy file controls **what** a user is authorized to see or edit. This is a security layer.
|
6
|
+
2. **Definition (`app/definitions`)**: The definition file controls **how** those permitted fields are displayed and formatted. This is a presentation layer.
|
7
|
+
|
8
|
+
Let's customize our `Post` resource by following this two-step process for the table, detail page, and form.
|
9
|
+
|
10
|
+
## Customizing the Posts Table (Index View)
|
11
|
+
|
12
|
+
First, we'll define **what** columns should appear in the posts table. This is an authorization concern, so we'll use the policy file.
|
13
|
+
|
14
|
+
Open the post policy: `packages/blogging/app/policies/blogging/post_policy.rb`
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
# packages/blogging/app/policies/blogging/post_policy.rb
|
18
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
19
|
+
# ... (other methods)
|
20
|
+
|
21
|
+
# 1. Define WHAT columns are visible in the table.
|
22
|
+
def permitted_attributes_for_index
|
23
|
+
[:user, :title, :published_at, :created_at]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
Next, we'll configure **how** these columns are displayed in the definition file.
|
29
|
+
|
30
|
+
Open the post definition: `packages/blogging/app/definitions/blogging/post_definition.rb`
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
# packages/blogging/app/definitions/blogging/post_definition.rb
|
34
|
+
class Blogging::PostDefinition < Blogging::ResourceDefinition
|
35
|
+
# 2. Define HOW the permitted columns are rendered.
|
36
|
+
column :published_at, as: :datetime
|
37
|
+
column :created_at, as: :datetime
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
The table now only shows the permitted columns, formatted as we specified.
|
42
|
+
|
43
|
+

|
44
|
+
|
45
|
+
## Customizing the Post Detail Page (Show View)
|
46
|
+
|
47
|
+
The same two-step process applies to the detail page.
|
48
|
+
|
49
|
+
1. **Policy**: Define what fields are visible with `permitted_attributes_for_show`.
|
50
|
+
2. **Definition**: Define how they are rendered with `display`.
|
51
|
+
|
52
|
+
Let's define the fields in the policy. For this view, we want to show the `:content`.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# packages/blogging/app/policies/blogging/post_policy.rb
|
56
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
57
|
+
# ...
|
58
|
+
|
59
|
+
def permitted_attributes_for_index
|
60
|
+
[:user, :title, :published_at, :created_at]
|
61
|
+
end
|
62
|
+
|
63
|
+
# 1. Define WHAT fields are visible on the show page.
|
64
|
+
def permitted_attributes_for_show
|
65
|
+
[:user, :title, :content, :published_at, :created_at]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Now, let's configure the layout in the definition. We'll make each field take up the full width of the view.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
# packages/blogging/app/definitions/blogging/post_definition.rb
|
74
|
+
class Blogging::PostDefinition < Blogging::ResourceDefinition
|
75
|
+
display :user, wrapper: { class: "col-span-full" }
|
76
|
+
display :title, wrapper: { class: "col-span-full" }
|
77
|
+
display :content, wrapper: { class: "col-span-full" }
|
78
|
+
display :published_at, wrapper: { class: "col-span-full" }
|
79
|
+
display :created_at, wrapper: { class: "col-span-full" }
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+

|
84
|
+
|
85
|
+
## Customizing the Post Form
|
86
|
+
|
87
|
+
Finally, we'll customize the `new` and `edit` forms.
|
88
|
+
|
89
|
+
1. **Policy**: Use `permitted_attributes_for_create` and `_for_update` to control which fields can be submitted.
|
90
|
+
2. **Definition**: Use the `input` helper to control how the form inputs are rendered.
|
91
|
+
|
92
|
+
First, the policy. We don't want the user to be able to set the `published_at` date directly in the form.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
# packages/blogging/app/policies/blogging/post_policy.rb
|
96
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
97
|
+
# ...
|
98
|
+
|
99
|
+
# 1. Define WHAT fields can be submitted in the form.
|
100
|
+
def permitted_attributes_for_create
|
101
|
+
[:user_id, :title, :content]
|
102
|
+
end
|
103
|
+
|
104
|
+
def permitted_attributes_for_update
|
105
|
+
permitted_attributes_for_create
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
Now, the definition. We'll use the `input` helper to specify that `:content` should use a rich text editor.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
# packages/blogging/app/definitions/blogging/post_definition.rb
|
114
|
+
class Blogging::PostDefinition < Blogging::ResourceDefinition
|
115
|
+
# ... (display helpers from above)
|
116
|
+
|
117
|
+
# 2. Define HOW the form inputs are rendered.
|
118
|
+
input :content, as: :rich_text # Use a rich text editor
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
By separating what is permitted from how it is rendered, Plutonium gives you both security and flexibility.
|
123
|
+
|
124
|
+
## Next Steps
|
125
|
+
|
126
|
+
Our UI is now much more polished. We've used the **Policy** and **Definition** files together to customize our `Post` resource.
|
127
|
+
|
128
|
+
In the next chapter, we'll add custom business logic by creating an **Action** to publish a draft post.
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# 6. Adding Custom Actions
|
2
|
+
|
3
|
+
Our application now handles the basics of creating, reading, updating, and deleting posts and comments. But what about business logic that isn't simple CRUD? For this, Plutonium has **Actions**.
|
4
|
+
|
5
|
+
An Action is a piece of business logic you can execute on a resource. We'll create a "Publish" action for our posts.
|
6
|
+
|
7
|
+
Create a new file for our "interaction" at:
|
8
|
+
`packages/blogging/app/interactions/blogging/post_interactions/publish.rb`
|
9
|
+
|
10
|
+
## Implement the Interaction
|
11
|
+
|
12
|
+
The "Interaction" is where the business logic for an action lives. Open the newly created file and let's implement the logic to publish a post.
|
13
|
+
|
14
|
+
We want the action to set the `published_at` timestamp on the post.
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
# packages/blogging/app/interactions/blogging/post_interactions/publish.rb
|
18
|
+
module Blogging
|
19
|
+
module PostInteractions
|
20
|
+
class Publish < Plutonium::Resource::Interaction
|
21
|
+
# Define the attributes this interaction accepts
|
22
|
+
attribute :resource, class: "Blogging::Post"
|
23
|
+
|
24
|
+
# Define how this action is presented in the UI
|
25
|
+
presents label: "Publish Post",
|
26
|
+
description: "Make this post available for the public to see."
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# The core business logic
|
31
|
+
def execute
|
32
|
+
if resource.update(published_at: Time.current )
|
33
|
+
succeed(resource)
|
34
|
+
.with_message("Post was successfully published.")
|
35
|
+
.with_redirect_response(resource)
|
36
|
+
else
|
37
|
+
failed(resource.errors)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
This class defines what the action needs (`resource`, `published_at`), how it looks (`presents`), and what it does (`execute`).
|
46
|
+
|
47
|
+
## Configure the Action
|
48
|
+
|
49
|
+
Now that we've defined the logic, we need to add the action to our `Post` resource and configure its visibility. We'll do this in the post's **definition** file.
|
50
|
+
|
51
|
+
`packages/blogging/app/definitions/blogging/post_definition.rb`
|
52
|
+
|
53
|
+
Add the `action` configuration:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# packages/blogging/app/definitions/blogging/post_definition.rb
|
57
|
+
class Blogging::PostDefinition < Blogging::ResourceDefinition
|
58
|
+
# ... (display helpers from the previous chapter)
|
59
|
+
|
60
|
+
action :publish,
|
61
|
+
interaction: "Blogging::PostInteractions::Publish"
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
## Control Visibility with a Policy
|
66
|
+
|
67
|
+
Plutonium actions are secure by default. If an action doesn't have a corresponding policy method allowing it, it won't be displayed.
|
68
|
+
|
69
|
+
Let's define the logic for our `publish` action. We only want to show the "Publish" button if the post hasn't been published yet.
|
70
|
+
|
71
|
+
Open the post policy file:
|
72
|
+
`packages/blogging/app/policies/blogging/post_policy.rb`
|
73
|
+
|
74
|
+
Add the `publish?` method:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# packages/blogging/app/policies/blogging/post_policy.rb
|
78
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
79
|
+
# ... (other methods from previous chapters)
|
80
|
+
|
81
|
+
def publish?
|
82
|
+
# Only allow publishing if the user can update the post
|
83
|
+
# and the post has not been published yet.
|
84
|
+
update? && record.published_at.nil?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
This policy does two things:
|
90
|
+
1. It ensures the user has permission to `update?` the post.
|
91
|
+
2. It checks that `published_at` is `nil`.
|
92
|
+
|
93
|
+
Now, go to the show page for a post you created. You should see the "Publish" button. Click it, and the `published_at` field will be set. Because `record.published_at.nil?` is now false, the policy will prevent the action from being shown again. The button disappears!
|
94
|
+
|
95
|
+

|
96
|
+
|
97
|
+
## Next Steps
|
98
|
+
|
99
|
+
We've successfully added custom business logic to our application. Actions are a powerful way to build complex, maintainable features.
|
100
|
+
|
101
|
+
In the final chapter of this tutorial, we'll tighten up security by implementing proper authorization rules.
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# 7. Implementing Authorization
|
2
|
+
|
3
|
+
Our blog is now fully functional, but it's not very secure. Any user can edit or delete any other user's posts. Let's fix this by implementing proper authorization rules using Plutonium's **Policy** system.
|
4
|
+
|
5
|
+
## Understanding Policies
|
6
|
+
|
7
|
+
For every resource, there is a corresponding Policy class that controls access. The policy methods (like `update?` or `destroy?`) return `true` or `false` to grant or deny permission.
|
8
|
+
|
9
|
+
## Securing Post Actions
|
10
|
+
|
11
|
+
Let's start by restricting the `update` and `publish` actions for posts. Open the post policy file:
|
12
|
+
|
13
|
+
`packages/blogging/app/policies/blogging/post_policy.rb`
|
14
|
+
|
15
|
+
Now, let's modify the policy to check if the current user (`user`) is the owner of the post (`record`).
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# packages/blogging/app/policies/blogging/post_policy.rb
|
19
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
20
|
+
# Users can only update their own posts
|
21
|
+
def update?
|
22
|
+
record.user_id == user.id
|
23
|
+
end
|
24
|
+
|
25
|
+
# Only the owner can publish their post
|
26
|
+
def publish?
|
27
|
+
update? # Inherits from update?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
By default, `destroy?` also inherits from `update?`, so this one change secures all write operations.
|
32
|
+
|
33
|
+
## Securing Comment Actions
|
34
|
+
|
35
|
+
We should apply the same logic to comments. Open the comment policy:
|
36
|
+
|
37
|
+
`packages/blogging/app/policies/blogging/comment_policy.rb`
|
38
|
+
|
39
|
+
And add the same ownership check:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
# packages/blogging/app/policies/blogging/comment_policy.rb
|
43
|
+
class Blogging::CommentPolicy < Blogging::ResourcePolicy
|
44
|
+
def update?
|
45
|
+
record.user_id == user.id
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
## Scoping Visible Data
|
51
|
+
|
52
|
+
We've secured the actions, but there's one more problem: every user can see every post in the index view. We should scope the list of posts so that users only see their own.
|
53
|
+
|
54
|
+
This is done with the `relation_scope` in the post policy file. This scope is applied to all queries for the `Post` resource.
|
55
|
+
|
56
|
+
`packages/blogging/app/policies/blogging/post_policy.rb`
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# packages/blogging/app/policies/blogging/post_policy.rb
|
60
|
+
class Blogging::PostPolicy < Blogging::ResourcePolicy
|
61
|
+
# ... (update? and publish? methods)
|
62
|
+
|
63
|
+
# Scope all queries to only include the current user's posts
|
64
|
+
relation_scope do |scope|
|
65
|
+
scope.where(user: user)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
Now, the posts index page will only show posts created by the currently logged-in user. This is a critical feature for multi-tenant applications.
|
70
|
+
|
71
|
+
## Tutorial Complete!
|
72
|
+
|
73
|
+
Congratulations! You have built a complete, secure, and modular blog application with Plutonium.
|
74
|
+
|
75
|
+
You've learned how to:
|
76
|
+
- Set up a Plutonium project.
|
77
|
+
- Organize features into **Packages**.
|
78
|
+
- Define and scaffold **Resources**.
|
79
|
+
- Build a web interface with **Portals**.
|
80
|
+
- Customize the UI.
|
81
|
+
- Add custom business logic with **Actions**.
|
82
|
+
- Secure your application with **Policies**.
|
83
|
+
|
84
|
+
### Where to Go Next?
|
85
|
+
|
86
|
+
You now have a strong foundation to build your own applications. To learn more about specific topics, check out our **Deep Dive Guides**:
|
87
|
+
|
88
|
+
- **[Resources](./../deep-dive/resources.md)**: An in-depth look at fields, associations, filters, and more.
|
89
|
+
- **[Authorization](./../deep-dive/authorization.md)**: A detailed guide to policies, scopes, and entity-based authorization.
|
90
|
+
- ... more guides coming soon!
|
data/docs/index.md
CHANGED
@@ -3,45 +3,38 @@
|
|
3
3
|
layout: home
|
4
4
|
|
5
5
|
hero:
|
6
|
-
name: Plutonium
|
7
|
-
|
6
|
+
name: Plutonium
|
7
|
+
text: The Rails RAD Toolkit
|
8
|
+
tagline: Build feature-rich, enterprise-ready applications at lightning speed. Stop writing boilerplate, start building features.
|
8
9
|
image:
|
9
10
|
src: /plutonium.png
|
10
11
|
alt: Plutonium
|
11
12
|
actions:
|
12
13
|
- theme: brand
|
13
|
-
text:
|
14
|
-
link: /guide/
|
14
|
+
text: Start the Tutorial
|
15
|
+
link: /guide/tutorial/01-project-setup
|
15
16
|
- theme: alt
|
16
|
-
text:
|
17
|
-
link: /guide/
|
17
|
+
text: Core Concepts
|
18
|
+
link: /guide/introduction/02-core-concepts
|
18
19
|
|
19
20
|
features:
|
20
|
-
|
21
|
-
|
22
|
-
details:
|
21
|
+
- icon: 🚀
|
22
|
+
title: Unmatched Developer Experience
|
23
|
+
details: Smart generators, convention-over-configuration, and intelligent defaults let you focus on what matters.
|
24
|
+
- icon: 🏗️
|
25
|
+
title: Robust Architecture
|
26
|
+
details: Built-in multitenancy, modular packages, and advanced authorization provide a solid foundation for any project.
|
27
|
+
- icon: 🎨
|
28
|
+
title: Flexible UI & Theming
|
29
|
+
details: Start with a beautiful, modern UI out-of-the-box, then customize any aspect with a powerful and expressive API.
|
23
30
|
|
24
|
-
|
25
|
-
- title: Built-in Multitenancy
|
26
|
-
details: Row-level multitenancy that works out of the box - perfect for SaaS and enterprise apps
|
27
|
-
|
28
|
-
- title: Advanced Authorization
|
29
|
-
details: Comprehensive access control built on Action Policy's proven authorization framework
|
30
|
-
|
31
|
-
- title: Seamless Authentication
|
32
|
-
details: Integrate your existing auth or use our Rodauth integration - ready in seconds
|
33
|
-
|
34
|
-
# Developer Experience Features - How it makes development easier
|
35
|
-
- title: Intelligent Fields
|
36
|
-
details: Smart field detection with automatic Rails model mapping and zero configuration needed
|
37
|
-
|
38
|
-
- title: Advanced Nested Resources
|
39
|
-
details: Complex resource relationships made simple through intelligent relationship mapping
|
31
|
+
---
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
|
33
|
+
<style>
|
34
|
+
:root {
|
35
|
+
--vp-home-hero-name-color: transparent;
|
36
|
+
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #da8ee7 30%, #5f4dff);
|
44
37
|
|
45
|
-
-
|
46
|
-
|
47
|
-
|
38
|
+
--vp-home-hero-image-filter: blur(56px);
|
39
|
+
}
|
40
|
+
</style>
|
@@ -0,0 +1,190 @@
|
|
1
|
+
---
|
2
|
+
title: Action Module
|
3
|
+
---
|
4
|
+
|
5
|
+
# Action Module
|
6
|
+
|
7
|
+
The Action module provides a comprehensive system for defining and managing custom actions in Plutonium applications. Actions represent operations that can be performed on resources, from simple navigation to complex business logic.
|
8
|
+
|
9
|
+
::: tip
|
10
|
+
The Action module is located in `lib/plutonium/action/`. Actions are typically defined within a resource's Definition file.
|
11
|
+
:::
|
12
|
+
|
13
|
+
## Overview
|
14
|
+
|
15
|
+
- **Declarative Definition**: Define actions with metadata and behavior in your resource definition files.
|
16
|
+
- **Multiple Action Types**: Support for actions on individual records, entire resources, or bulk collections.
|
17
|
+
- **UI Integration**: Automatic button and form generation in the UI.
|
18
|
+
- **Authorization Support**: Integrates with policies to control action visibility and execution.
|
19
|
+
|
20
|
+
## Defining Actions
|
21
|
+
|
22
|
+
Actions are typically defined in a resource definition file using the `action` method.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
# app/definitions/post_definition.rb
|
26
|
+
class PostDefinition < Plutonium::Resource::Definition
|
27
|
+
# An action that runs a complex operation via an Interaction
|
28
|
+
action :publish,
|
29
|
+
interaction: PublishPostInteraction,
|
30
|
+
icon: Phlex::TablerIcons::Send,
|
31
|
+
category: :primary
|
32
|
+
|
33
|
+
# A simple navigation action
|
34
|
+
action :export,
|
35
|
+
route_options: { action: :export, format: :csv },
|
36
|
+
icon: Phlex::TablerIcons::Download,
|
37
|
+
resource_action: true # This is a resource-level action
|
38
|
+
|
39
|
+
# A destructive action with a confirmation
|
40
|
+
action :archive,
|
41
|
+
interaction: ArchivePostInteraction,
|
42
|
+
icon: Phlex::TablerIcons::Archive,
|
43
|
+
category: :danger,
|
44
|
+
confirmation: "Are you sure you want to archive this post?"
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
### Action Types
|
49
|
+
|
50
|
+
You can specify where an action should be available.
|
51
|
+
|
52
|
+
::: code-group
|
53
|
+
```ruby [Record Action]
|
54
|
+
# Shows on the record's #show page and in the table row.
|
55
|
+
action :edit,
|
56
|
+
record_action: true,
|
57
|
+
collection_record_action: true,
|
58
|
+
icon: Phlex::TablerIcons::Edit
|
59
|
+
```
|
60
|
+
```ruby [Resource Action]
|
61
|
+
# Shows on the resource's #index page (e.g., "Import").
|
62
|
+
action :import,
|
63
|
+
resource_action: true,
|
64
|
+
icon: Phlex::TablerIcons::Upload
|
65
|
+
```
|
66
|
+
```ruby [Bulk Action]
|
67
|
+
# Appears when records are selected on the #index page.
|
68
|
+
action :bulk_delete,
|
69
|
+
bulk_action: true,
|
70
|
+
category: :danger,
|
71
|
+
icon: Phlex::TablerIcons::Trash
|
72
|
+
```
|
73
|
+
:::
|
74
|
+
|
75
|
+
### Action Categories & Positioning
|
76
|
+
|
77
|
+
Categories and positioning control the visual appearance and order of action buttons.
|
78
|
+
|
79
|
+
- **`category`**: Can be `:primary`, `:secondary`, or `:danger`.
|
80
|
+
- **`position`**: A number used for sorting; lower numbers appear first (default: 50).
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# A prominent primary action
|
84
|
+
action :create, category: :primary, position: 10
|
85
|
+
|
86
|
+
# A standard secondary action
|
87
|
+
action :archive, category: :secondary, position: 50
|
88
|
+
|
89
|
+
# A destructive action, shown last
|
90
|
+
action :delete, category: :danger, position: 100
|
91
|
+
```
|
92
|
+
|
93
|
+
## Action Types
|
94
|
+
|
95
|
+
### Simple Actions
|
96
|
+
|
97
|
+
Simple actions are for basic navigation or links. They are defined with `route_options`.
|
98
|
+
|
99
|
+
::: code-group
|
100
|
+
```ruby [Internal Link]
|
101
|
+
# Navigates to the #reports action on the current controller
|
102
|
+
action :view_reports,
|
103
|
+
label: "View Reports",
|
104
|
+
route_options: { action: :reports },
|
105
|
+
icon: Phlex::TablerIcons::ChartBar
|
106
|
+
```
|
107
|
+
```ruby [External Link]
|
108
|
+
# Links to an external URL
|
109
|
+
action :documentation,
|
110
|
+
label: "Documentation",
|
111
|
+
route_options: { url: "https://docs.example.com" },
|
112
|
+
icon: Phlex::TablerIcons::Book
|
113
|
+
```
|
114
|
+
:::
|
115
|
+
|
116
|
+
### Interactive Actions
|
117
|
+
|
118
|
+
Interactive actions are powered by an `Interaction` class and handle business logic. The action's properties (label, description, etc.) are often inferred from the interaction itself.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
# The action is automatically configured based on the interaction
|
122
|
+
action :publish,
|
123
|
+
interaction: PublishPostInteraction,
|
124
|
+
icon: Phlex::TablerIcons::Send
|
125
|
+
```
|
126
|
+
|
127
|
+
::: details Automatic Type Detection from Interaction
|
128
|
+
The Action module inspects the interaction's attributes to determine its type (`record`, `resource`, or `bulk`).
|
129
|
+
```ruby
|
130
|
+
# This will be a RECORD action because it has a `:resource` attribute.
|
131
|
+
class PublishPostInteraction < Plutonium::Interaction::Base
|
132
|
+
attribute :resource
|
133
|
+
attribute :publish_date, :date
|
134
|
+
end
|
135
|
+
|
136
|
+
# This will be a BULK action because it has a `:resources` attribute.
|
137
|
+
class BulkArchiveInteraction < Plutonium::Interaction::Base
|
138
|
+
attribute :resources
|
139
|
+
attribute :archive_reason, :string
|
140
|
+
end
|
141
|
+
|
142
|
+
# This will be a RESOURCE action because it has neither.
|
143
|
+
class ImportPostsInteraction < Plutonium::Interaction::Base
|
144
|
+
attribute :csv_file, :string
|
145
|
+
end
|
146
|
+
```
|
147
|
+
:::
|
148
|
+
|
149
|
+
::: details Immediate vs. Modal Actions
|
150
|
+
- If an interaction has **only** a `resource` or `resources` attribute, the action will execute immediately on click.
|
151
|
+
- If it has **any other attributes**, a modal form will be rendered to collect user input before execution.
|
152
|
+
```ruby
|
153
|
+
# IMMEDIATE: No extra inputs, runs on click.
|
154
|
+
class SimplePublishInteraction < Plutonium::Interaction::Base
|
155
|
+
attribute :resource
|
156
|
+
end
|
157
|
+
|
158
|
+
# MODAL: Requires `publish_date`, so a form will be shown.
|
159
|
+
class ScheduledPublishInteraction < Plutonium::Interaction::Base
|
160
|
+
attribute :resource
|
161
|
+
attribute :publish_date, :datetime
|
162
|
+
end
|
163
|
+
```
|
164
|
+
:::
|
165
|
+
|
166
|
+
## Best Practices
|
167
|
+
|
168
|
+
### Action Design
|
169
|
+
|
170
|
+
1. **Clear Intent**: Use descriptive action names that clearly indicate what they do
|
171
|
+
2. **Consistent Categories**: Group related actions using consistent categories
|
172
|
+
3. **Appropriate Icons**: Choose icons that clearly represent the action
|
173
|
+
4. **Meaningful Confirmations**: Use confirmation messages for destructive actions
|
174
|
+
5. **Logical Positioning**: Order actions by importance and frequency of use
|
175
|
+
|
176
|
+
### Interactive Actions
|
177
|
+
|
178
|
+
1. **Single Purpose**: Each action should have a single, well-defined purpose
|
179
|
+
2. **Input Validation**: Always validate user inputs in the interaction
|
180
|
+
3. **Error Messages**: Provide clear, actionable error messages
|
181
|
+
4. **Success Feedback**: Give users clear feedback when actions complete successfully
|
182
|
+
5. **Idempotency**: Design actions to be safely repeatable when possible
|
183
|
+
|
184
|
+
### Security
|
185
|
+
|
186
|
+
1. **Authorization**: Always check user permissions before executing actions
|
187
|
+
2. **Input Sanitization**: Sanitize all user inputs
|
188
|
+
3. **CSRF Protection**: Include CSRF tokens in all action forms
|
189
|
+
4. **Rate Limiting**: Implement rate limiting for resource-intensive actions
|
190
|
+
5. **Audit Logging**: Log important actions for security auditing
|