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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.css +2 -2
  3. data/config/initializers/sqlite_json_alias.rb +1 -1
  4. data/docs/.vitepress/config.ts +60 -19
  5. data/docs/guide/cursor-rules.md +75 -0
  6. data/docs/guide/deep-dive/authorization.md +189 -0
  7. data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
  8. data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
  9. data/docs/guide/index.md +28 -0
  10. data/docs/guide/introduction/02-core-concepts.md +440 -0
  11. data/docs/guide/tutorial/01-project-setup.md +75 -0
  12. data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
  13. data/docs/guide/tutorial/03-defining-resources.md +90 -0
  14. data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
  15. data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
  16. data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
  17. data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
  18. data/docs/index.md +24 -31
  19. data/docs/modules/action.md +190 -0
  20. data/docs/modules/authentication.md +236 -0
  21. data/docs/modules/configuration.md +599 -0
  22. data/docs/modules/controller.md +398 -0
  23. data/docs/modules/core.md +316 -0
  24. data/docs/modules/definition.md +876 -0
  25. data/docs/modules/display.md +759 -0
  26. data/docs/modules/form.md +605 -0
  27. data/docs/modules/generator.md +288 -0
  28. data/docs/modules/index.md +167 -0
  29. data/docs/modules/interaction.md +470 -0
  30. data/docs/modules/package.md +151 -0
  31. data/docs/modules/policy.md +176 -0
  32. data/docs/modules/portal.md +710 -0
  33. data/docs/modules/query.md +287 -0
  34. data/docs/modules/resource_record.md +618 -0
  35. data/docs/modules/routing.md +641 -0
  36. data/docs/modules/table.md +293 -0
  37. data/docs/modules/ui.md +631 -0
  38. data/docs/public/plutonium.mdc +667 -0
  39. data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
  40. data/lib/plutonium/ui/display/resource.rb +7 -2
  41. data/lib/plutonium/ui/table/resource.rb +8 -3
  42. data/lib/plutonium/version.rb +1 -1
  43. metadata +36 -9
  44. data/docs/guide/getting-started/authorization.md +0 -296
  45. data/docs/guide/getting-started/core-concepts.md +0 -432
  46. data/docs/guide/getting-started/index.md +0 -21
  47. data/docs/guide/tutorial.md +0 -401
  48. /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
+ ![Plutonium Empty Dashboard](/tutorial/plutonium-posts-dashboard.png)
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
+ ![Plutonium Posts Dashboard (Customized)](/tutorial/plutonium-posts-dashboard-customized.png)
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
+ ![Plutonium Posts Detail (Customized)](/tutorial/plutonium-posts-detail-customized.png)
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
+ ![Plutonium Posts Publish Action](/tutorial/plutonium-publish-post.png)
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 RADKit
7
- tagline: The Ultimate Rapid Application Development Toolkit (RADKit) for Rails
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: Get started
14
- link: /guide/getting-started/
14
+ text: Start the Tutorial
15
+ link: /guide/tutorial/01-project-setup
15
16
  - theme: alt
16
- text: Learn more
17
- link: /guide/what-is-plutonium
17
+ text: Core Concepts
18
+ link: /guide/introduction/02-core-concepts
18
19
 
19
20
  features:
20
- # Core Value Proposition - Start with the main benefit
21
- - title: Rich Resource Management
22
- details: Beautiful, modern interfaces with production-ready components for rapid development
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
- # Core Architecture Features - Foundation pieces
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
- # Extensibility Features - How it grows with your needs
42
- - title: Extensible Workflows
43
- details: Add custom actions and business workflows with a simple, declarative API
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
- - title: Flexible UI Customization
46
- details: Customize any aspect of your interfaces with elegant builder APIs
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