plutonium 0.33.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
|
@@ -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
|
-

|
|
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
|
-

|
|
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.
|
|
@@ -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
|
-

|
|
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!
|
data/docs/markdown-examples.md
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Markdown Extension Examples
|
|
2
|
-
|
|
3
|
-
This page demonstrates some of the built-in markdown extensions provided by VitePress.
|
|
4
|
-
|
|
5
|
-
## Syntax Highlighting
|
|
6
|
-
|
|
7
|
-
VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting:
|
|
8
|
-
|
|
9
|
-
**Input**
|
|
10
|
-
|
|
11
|
-
````md
|
|
12
|
-
```js{4}
|
|
13
|
-
export default {
|
|
14
|
-
data () {
|
|
15
|
-
return {
|
|
16
|
-
msg: 'Highlighted!'
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
````
|
|
22
|
-
|
|
23
|
-
**Output**
|
|
24
|
-
|
|
25
|
-
```js{4}
|
|
26
|
-
export default {
|
|
27
|
-
data () {
|
|
28
|
-
return {
|
|
29
|
-
msg: 'Highlighted!'
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Custom Containers
|
|
36
|
-
|
|
37
|
-
**Input**
|
|
38
|
-
|
|
39
|
-
```md
|
|
40
|
-
::: info
|
|
41
|
-
This is an info box.
|
|
42
|
-
:::
|
|
43
|
-
|
|
44
|
-
::: tip
|
|
45
|
-
This is a tip.
|
|
46
|
-
:::
|
|
47
|
-
|
|
48
|
-
::: warning
|
|
49
|
-
This is a warning.
|
|
50
|
-
:::
|
|
51
|
-
|
|
52
|
-
::: danger
|
|
53
|
-
This is a dangerous warning.
|
|
54
|
-
:::
|
|
55
|
-
|
|
56
|
-
::: details
|
|
57
|
-
This is a details block.
|
|
58
|
-
:::
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**Output**
|
|
62
|
-
|
|
63
|
-
::: info
|
|
64
|
-
This is an info box.
|
|
65
|
-
:::
|
|
66
|
-
|
|
67
|
-
::: tip
|
|
68
|
-
This is a tip.
|
|
69
|
-
:::
|
|
70
|
-
|
|
71
|
-
::: warning
|
|
72
|
-
This is a warning.
|
|
73
|
-
:::
|
|
74
|
-
|
|
75
|
-
::: danger
|
|
76
|
-
This is a dangerous warning.
|
|
77
|
-
:::
|
|
78
|
-
|
|
79
|
-
::: details
|
|
80
|
-
This is a details block.
|
|
81
|
-
:::
|
|
82
|
-
|
|
83
|
-
## More
|
|
84
|
-
|
|
85
|
-
Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown).
|