plutonium 0.33.0 → 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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/# Plutonium: The pre-alpha demo.md +4 -2
  3. data/.claude/skills/assets/SKILL.md +416 -0
  4. data/.claude/skills/connect-resource/SKILL.md +112 -0
  5. data/.claude/skills/controller/SKILL.md +302 -0
  6. data/.claude/skills/create-resource/SKILL.md +240 -0
  7. data/.claude/skills/definition/SKILL.md +218 -0
  8. data/.claude/skills/definition-actions/SKILL.md +386 -0
  9. data/.claude/skills/definition-fields/SKILL.md +474 -0
  10. data/.claude/skills/definition-query/SKILL.md +334 -0
  11. data/.claude/skills/forms/SKILL.md +439 -0
  12. data/.claude/skills/installation/SKILL.md +300 -0
  13. data/.claude/skills/interaction/SKILL.md +382 -0
  14. data/.claude/skills/model/SKILL.md +267 -0
  15. data/.claude/skills/model-features/SKILL.md +286 -0
  16. data/.claude/skills/nested-resources/SKILL.md +274 -0
  17. data/.claude/skills/package/SKILL.md +191 -0
  18. data/.claude/skills/policy/SKILL.md +352 -0
  19. data/.claude/skills/portal/SKILL.md +400 -0
  20. data/.claude/skills/resource/SKILL.md +281 -0
  21. data/.claude/skills/rodauth/SKILL.md +452 -0
  22. data/.claude/skills/views/SKILL.md +563 -0
  23. data/Appraisals +46 -4
  24. data/CHANGELOG.md +36 -0
  25. data/app/assets/plutonium.css +2 -2
  26. data/config/brakeman.ignore +239 -0
  27. data/config/initializers/action_policy.rb +1 -1
  28. data/docs/.vitepress/config.ts +132 -47
  29. data/docs/concepts/architecture.md +226 -0
  30. data/docs/concepts/auto-detection.md +254 -0
  31. data/docs/concepts/index.md +61 -0
  32. data/docs/concepts/packages-portals.md +304 -0
  33. data/docs/concepts/resources.md +224 -0
  34. data/docs/cookbook/blog.md +412 -0
  35. data/docs/cookbook/index.md +289 -0
  36. data/docs/cookbook/saas.md +481 -0
  37. data/docs/getting-started/index.md +56 -0
  38. data/docs/getting-started/installation.md +146 -0
  39. data/docs/getting-started/tutorial/01-setup.md +118 -0
  40. data/docs/getting-started/tutorial/02-first-resource.md +180 -0
  41. data/docs/getting-started/tutorial/03-authentication.md +246 -0
  42. data/docs/getting-started/tutorial/04-authorization.md +170 -0
  43. data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
  44. data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
  45. data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
  46. data/docs/getting-started/tutorial/index.md +64 -0
  47. data/docs/guides/adding-resources.md +420 -0
  48. data/docs/guides/authentication.md +551 -0
  49. data/docs/guides/authorization.md +468 -0
  50. data/docs/guides/creating-packages.md +380 -0
  51. data/docs/guides/custom-actions.md +523 -0
  52. data/docs/guides/index.md +45 -0
  53. data/docs/guides/multi-tenancy.md +302 -0
  54. data/docs/guides/nested-resources.md +411 -0
  55. data/docs/guides/search-filtering.md +266 -0
  56. data/docs/guides/theming.md +321 -0
  57. data/docs/index.md +67 -26
  58. data/docs/public/CLAUDE.md +64 -21
  59. data/docs/reference/assets/index.md +496 -0
  60. data/docs/reference/controller/index.md +363 -0
  61. data/docs/reference/definition/actions.md +400 -0
  62. data/docs/reference/definition/fields.md +350 -0
  63. data/docs/reference/definition/index.md +252 -0
  64. data/docs/reference/definition/query.md +342 -0
  65. data/docs/reference/generators/index.md +469 -0
  66. data/docs/reference/index.md +49 -0
  67. data/docs/reference/interaction/index.md +445 -0
  68. data/docs/reference/model/features.md +248 -0
  69. data/docs/reference/model/index.md +219 -0
  70. data/docs/reference/policy/index.md +385 -0
  71. data/docs/reference/portal/index.md +382 -0
  72. data/docs/reference/views/forms.md +396 -0
  73. data/docs/reference/views/index.md +479 -0
  74. data/gemfiles/rails_7.gemfile +9 -2
  75. data/gemfiles/rails_7.gemfile.lock +146 -111
  76. data/gemfiles/rails_8.0.gemfile +20 -0
  77. data/gemfiles/rails_8.0.gemfile.lock +417 -0
  78. data/gemfiles/rails_8.1.gemfile +20 -0
  79. data/gemfiles/rails_8.1.gemfile.lock +419 -0
  80. data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
  81. data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
  82. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
  83. data/lib/generators/pu/pkg/portal/USAGE +65 -0
  84. data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
  85. data/lib/generators/pu/res/conn/USAGE +71 -0
  86. data/lib/generators/pu/res/model/USAGE +106 -110
  87. data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
  88. data/lib/generators/pu/res/scaffold/USAGE +85 -0
  89. data/lib/generators/pu/rodauth/install_generator.rb +2 -6
  90. data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
  91. data/lib/generators/pu/skills/sync/USAGE +14 -0
  92. data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
  93. data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
  94. data/lib/plutonium/core/controller.rb +2 -2
  95. data/lib/plutonium/interaction/base.rb +1 -0
  96. data/lib/plutonium/package/engine.rb +2 -2
  97. data/lib/plutonium/query/adhoc_block.rb +6 -2
  98. data/lib/plutonium/query/model_scope.rb +1 -1
  99. data/lib/plutonium/railtie.rb +4 -0
  100. data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
  101. data/lib/plutonium/resource/controllers/crud_actions.rb +21 -18
  102. data/lib/plutonium/resource/controllers/interactive_actions.rb +21 -25
  103. data/lib/plutonium/resource/query_object.rb +38 -8
  104. data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
  105. data/lib/plutonium/version.rb +1 -1
  106. data/lib/tasks/release.rake +19 -4
  107. data/package.json +1 -1
  108. metadata +76 -39
  109. data/brakeman.ignore +0 -28
  110. data/docs/api-examples.md +0 -49
  111. data/docs/guide/claude-code-guide.md +0 -74
  112. data/docs/guide/deep-dive/authorization.md +0 -189
  113. data/docs/guide/deep-dive/multitenancy.md +0 -256
  114. data/docs/guide/deep-dive/resources.md +0 -390
  115. data/docs/guide/getting-started/01-installation.md +0 -165
  116. data/docs/guide/index.md +0 -28
  117. data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
  118. data/docs/guide/introduction/02-core-concepts.md +0 -440
  119. data/docs/guide/tutorial/01-project-setup.md +0 -75
  120. data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
  121. data/docs/guide/tutorial/03-defining-resources.md +0 -90
  122. data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
  123. data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
  124. data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
  125. data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
  126. data/docs/markdown-examples.md +0 -85
  127. data/docs/modules/action.md +0 -244
  128. data/docs/modules/authentication.md +0 -236
  129. data/docs/modules/configuration.md +0 -599
  130. data/docs/modules/controller.md +0 -443
  131. data/docs/modules/core.md +0 -316
  132. data/docs/modules/definition.md +0 -1308
  133. data/docs/modules/display.md +0 -759
  134. data/docs/modules/form.md +0 -495
  135. data/docs/modules/generator.md +0 -400
  136. data/docs/modules/index.md +0 -167
  137. data/docs/modules/interaction.md +0 -642
  138. data/docs/modules/package.md +0 -151
  139. data/docs/modules/policy.md +0 -176
  140. data/docs/modules/portal.md +0 -710
  141. data/docs/modules/query.md +0 -297
  142. data/docs/modules/resource_record.md +0 -618
  143. data/docs/modules/routing.md +0 -690
  144. data/docs/modules/table.md +0 -301
  145. data/docs/modules/ui.md +0 -631
@@ -1,75 +0,0 @@
1
- # 1. Project Setup
2
-
3
- Welcome to the Plutonium tutorial! In this guide, we'll build a complete blog application from scratch.
4
-
5
- First, let's get a new Rails application up and running with Plutonium installed.
6
-
7
- ## Prerequisites
8
-
9
- Before you begin, make sure you have the following installed:
10
-
11
- - Ruby 3.2.2 or higher
12
- - Rails 7.1 or higher
13
- - Node.js and Yarn
14
-
15
- If you're new to Rails, we highly recommend the official [Rails Getting Started Guide](https://guides.rubyonrails.org/getting_started.html) first.
16
-
17
- ## Create a New Rails App
18
-
19
- We'll use the Plutonium Rails template to create a new application. This is the fastest way to get started.
20
-
21
- Open your terminal and run the following command:
22
-
23
- ```bash
24
- rails new plutonium_blog -a propshaft -j esbuild -c tailwind \
25
- -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
26
- ```
27
-
28
- This command creates a new Rails app named `plutonium_blog` and configures it with:
29
- - `propshaft` for assets
30
- - `esbuild` for JavaScript bundling
31
- - `TailwindCSS` for styling
32
- - The base Plutonium gem and configurations
33
-
34
- ## Explore the Project
35
-
36
- Once the command finishes, navigate into your new project's directory:
37
-
38
- ```bash
39
- cd plutonium_blog
40
- ```
41
-
42
- The installer has already set up the core files you need. Here are the most important ones for now:
43
-
44
- ```
45
- plutonium_blog/
46
- ├── app/
47
- │ ├── controllers/
48
- │ │ ├── plutonium_controller.rb # Base controller for Plutonium
49
- │ │ └── resource_controller.rb # Base for all resource controllers
50
- │ └── views/
51
- │ └── layouts/
52
- │ └── resource.html.erb # Default layout for Plutonium views
53
- ├── config/
54
- │ ├── initializers/
55
- │ │ └── plutonium.rb # Main Plutonium configuration
56
- │ └── packages.rb # How custom packages are loaded
57
- └── packages/ # Where you'll build your features
58
- └── .keep
59
- ```
60
-
61
- ## Start the Server
62
-
63
- Let's boot up the Rails server to make sure everything is working correctly.
64
-
65
- ```bash
66
- bin/dev
67
- ```
68
-
69
- Open your web browser and navigate to [http://localhost:3000](http://localhost:3000). You should see the default Rails welcome page.
70
-
71
- ## Next Steps
72
-
73
- Congratulations! You have a fresh Plutonium application ready to go.
74
-
75
- In the next chapter, we'll create our first **Feature Package** to house all the logic for our blog.
@@ -1,45 +0,0 @@
1
- # 2. Creating a Feature Package
2
-
3
- Now that we have our Rails application, it's time to start organizing our code. In Plutonium, we do this using **Packages**.
4
-
5
- ## What are Packages?
6
-
7
- Packages are like mini-Rails applications (Rails Engines) that live inside your main app. They help you group related code together, making your application more modular and easier to maintain.
8
-
9
- There are two main types of packages, but for now, we'll focus on **Feature Packages**.
10
-
11
- A **Feature Package** holds all the business logic for a specific feature. In our case, we'll create a `Blogging` package to hold everything related to blog posts and comments.
12
-
13
- ## Generate the Package
14
-
15
- Plutonium comes with a generator to create feature packages. Run the following command in your terminal:
16
-
17
- ```bash
18
- rails generate pu:pkg:package blogging
19
- ```
20
-
21
- This command creates a new directory at `packages/blogging` with the structure for your new package.
22
-
23
- ```
24
- packages/
25
- └── blogging/
26
- ├── app/
27
- │ ├── controllers/
28
- │ │ └── blogging/ # Controllers for this package
29
- │ ├── definitions/
30
- │ │ └── blogging/ # Resource definitions
31
- │ ├── interactions/
32
- │ │ └── blogging/ # Business logic actions
33
- │ ├── models/
34
- │ │ └── blogging/ # ActiveRecord models
35
- │ └── policies/
36
- │ └── blogging/ # Authorization policies
37
- └── lib/
38
- └── engine.rb # The engine configuration
39
- ```
40
-
41
- Every file and class inside this package will be automatically namespaced under the `Blogging` module to prevent conflicts with other parts of your application.
42
-
43
- ## Next Steps
44
-
45
- With our `Blogging` package in place, we're ready to define the core data models for our application. In the next chapter, we'll create the `Post` and `Comment` resources.
@@ -1,90 +0,0 @@
1
- # 3. Defining Resources
2
-
3
- With our `Blogging` package ready, we can now define the core **Resources** of our feature: `Post` and `Comment`. But first, we need a way to represent users, since posts and comments will belong to a user.
4
-
5
- ## Setting Up Users and Authentication
6
-
7
- Plutonium provides generators to quickly set up user authentication using the popular [Rodauth](https://rodauth.jeremyevans.net/) library.
8
-
9
- Run the following commands in your terminal:
10
-
11
- ```bash
12
- # 1. Install Rodauth configuration
13
- rails generate pu:rodauth:install
14
-
15
- # 2. Generate a User model and authentication logic
16
- rails generate pu:rodauth:account user
17
-
18
- # 3. Run the database migrations
19
- rails db:migrate
20
- ```
21
-
22
- This is a huge time-saver! It creates:
23
- - A `User` model (`app/models/user.rb`).
24
- - Database migrations for the `users` table.
25
- - All the necessary authentication logic (login, logout, sign up, password reset, etc.).
26
-
27
- Now that we have a `User` model, we can create our blog-specific resources.
28
-
29
- ## Scaffolding the Post Resource
30
-
31
- A **Resource** in Plutonium is more than just a model; it's a combination of the model, its definition (how it's displayed), and its policy (who can do what).
32
-
33
- Let's scaffold our `Post` resource. It will have a title, some text content, and will belong to a `User`.
34
-
35
- ```bash
36
- rails generate pu:res:scaffold Post user:belongs_to title:string content:text 'published_at:datetime?'
37
- ```
38
-
39
- The generator will prompt you to choose which package this resource belongs to. **Select `blogging`**.
40
-
41
- This command generates several important files inside your `packages/blogging` directory:
42
-
43
- - `app/models/blogging/post.rb`: The ActiveRecord model.
44
- - `app/definitions/blogging/post_definition.rb`: The resource definition for UI.
45
- - `app/policies/blogging/post_policy.rb`: The authorization policy.
46
- - A database migration to create the `blogging_posts` table.
47
-
48
- Let's look at the generated model at `packages/blogging/app/models/blogging/post.rb`:
49
-
50
- ```ruby
51
- # packages/blogging/app/models/blogging/post.rb
52
- class Blogging::Post < Blogging::ResourceRecord
53
- belongs_to :user
54
- validates :title, presence: true
55
- validates :content, presence: true
56
- end
57
- ```
58
- Notice it's correctly namespaced under `Blogging` and associated with the `User` model.
59
-
60
- Before we continue, run the migration to update your database:
61
- ```bash
62
- rails db:migrate
63
- ```
64
-
65
- ## Scaffolding the Comment Resource
66
-
67
- Now, let's do the same for `Comment`. A comment will belong to a `User` and a `Post`.
68
-
69
- ```bash
70
- rails generate pu:res:scaffold Comment user:belongs_to blogging/post:belongs_to body:text
71
- ```
72
-
73
- Again, select the **`blogging`** package when prompted.
74
-
75
- ::: tip Namespaced Associations
76
- Notice that we used `blogging/post` to specify the association. Plutonium's generators understand this and will correctly create the namespaced `Blogging::Post` association.
77
- :::
78
-
79
- This generates the model, definition, policy, and migration for comments.
80
-
81
- Run the migration for comments:
82
- ```bash
83
- rails db:migrate
84
- ```
85
-
86
- ## Next Steps
87
-
88
- We've now set up users, authentication, and the core `Post` and `Comment` resources for our blog. However, there's no way to interact with them yet.
89
-
90
- In the next chapter, we'll create a **Portal** to provide a web interface for managing our new resources.
@@ -1,101 +0,0 @@
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 (`bin/dev`) 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.
@@ -1,128 +0,0 @@
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.
@@ -1,101 +0,0 @@
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.
@@ -1,90 +0,0 @@
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!