plutonium 0.33.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: package
|
|
3
|
+
description: Plutonium packages - modular Rails engines for organizing features and portals
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plutonium Packages
|
|
7
|
+
|
|
8
|
+
Packages are specialized Rails engines for organizing code. There are two types:
|
|
9
|
+
|
|
10
|
+
| Type | Purpose | Generator |
|
|
11
|
+
|------|---------|-----------|
|
|
12
|
+
| **Feature** | Business logic (models, policies, interactions) | `rails g pu:pkg:package NAME` |
|
|
13
|
+
| **Portal** | Web interface (controllers, views, auth) | `rails g pu:pkg:portal NAME` |
|
|
14
|
+
|
|
15
|
+
## Feature Packages
|
|
16
|
+
|
|
17
|
+
Contain domain logic without web interface:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
rails g pu:pkg:package blogging
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
packages/blogging/
|
|
27
|
+
├── app/
|
|
28
|
+
│ ├── models/blogging/
|
|
29
|
+
│ │ ├── post.rb
|
|
30
|
+
│ │ └── comment.rb
|
|
31
|
+
│ ├── definitions/blogging/
|
|
32
|
+
│ │ ├── post_definition.rb
|
|
33
|
+
│ │ └── comment_definition.rb
|
|
34
|
+
│ ├── policies/blogging/
|
|
35
|
+
│ │ ├── post_policy.rb
|
|
36
|
+
│ │ └── comment_policy.rb
|
|
37
|
+
│ └── interactions/blogging/
|
|
38
|
+
│ └── publish_post_interaction.rb
|
|
39
|
+
├── db/migrate/
|
|
40
|
+
└── lib/
|
|
41
|
+
└── engine.rb
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Engine
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
module Blogging
|
|
48
|
+
class Engine < Rails::Engine
|
|
49
|
+
include Plutonium::Package::Engine
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Namespacing
|
|
55
|
+
|
|
56
|
+
All classes are auto-namespaced:
|
|
57
|
+
- `app/models/blogging/post.rb` → `Blogging::Post`
|
|
58
|
+
- `app/policies/blogging/post_policy.rb` → `Blogging::PostPolicy`
|
|
59
|
+
|
|
60
|
+
## Portal Packages
|
|
61
|
+
|
|
62
|
+
Provide web interfaces for specific user types:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
rails g pu:pkg:portal admin
|
|
66
|
+
rails g pu:pkg:portal dashboard
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Structure
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
packages/admin_portal/
|
|
73
|
+
├── app/
|
|
74
|
+
│ ├── controllers/admin_portal/
|
|
75
|
+
│ │ ├── concerns/controller.rb
|
|
76
|
+
│ │ ├── dashboard_controller.rb
|
|
77
|
+
│ │ ├── plutonium_controller.rb
|
|
78
|
+
│ │ └── resource_controller.rb
|
|
79
|
+
│ ├── definitions/admin_portal/ # Portal-specific overrides
|
|
80
|
+
│ ├── policies/admin_portal/ # Portal-specific overrides
|
|
81
|
+
│ └── views/
|
|
82
|
+
│ └── layouts/admin_portal.html.erb
|
|
83
|
+
├── config/
|
|
84
|
+
│ └── routes.rb
|
|
85
|
+
└── lib/
|
|
86
|
+
└── engine.rb
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Engine
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
module AdminPortal
|
|
93
|
+
class Engine < Rails::Engine
|
|
94
|
+
include Plutonium::Portal::Engine
|
|
95
|
+
|
|
96
|
+
config.after_initialize do
|
|
97
|
+
# Optional: multi-tenancy
|
|
98
|
+
scope_to_entity Organization, strategy: :path
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
See `portal` skill for portal-specific features.
|
|
105
|
+
|
|
106
|
+
## Package Loading
|
|
107
|
+
|
|
108
|
+
Packages are loaded via `config/packages.rb`:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
# config/packages.rb (generated during install)
|
|
112
|
+
Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) do |package|
|
|
113
|
+
load package
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This is required in `config/application.rb`.
|
|
118
|
+
|
|
119
|
+
## Creating Resources in Packages
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# In main app
|
|
123
|
+
rails g pu:res:scaffold Post title:string --dest=main_app
|
|
124
|
+
|
|
125
|
+
# In feature package
|
|
126
|
+
rails g pu:res:scaffold Blogging::Post title:string --dest=blogging
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Connecting Resources to Portals
|
|
130
|
+
|
|
131
|
+
Resources must be connected to portals to be accessible:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
rails g pu:res:conn Post --dest=admin_portal
|
|
135
|
+
rails g pu:res:conn Blogging::Post --dest=admin_portal
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This creates:
|
|
139
|
+
- Portal-specific controller
|
|
140
|
+
- Portal-specific policy (optional)
|
|
141
|
+
- Portal-specific definition (optional)
|
|
142
|
+
- Route registration
|
|
143
|
+
|
|
144
|
+
## When to Use Each Type
|
|
145
|
+
|
|
146
|
+
### Feature Packages
|
|
147
|
+
|
|
148
|
+
Use for:
|
|
149
|
+
- Domain-specific models and logic
|
|
150
|
+
- Reusable business functionality
|
|
151
|
+
- Shared code across portals
|
|
152
|
+
|
|
153
|
+
Examples: `blogging`, `billing`, `inventory`, `user_management`
|
|
154
|
+
|
|
155
|
+
### Portal Packages
|
|
156
|
+
|
|
157
|
+
Use for:
|
|
158
|
+
- User-facing interfaces
|
|
159
|
+
- Role-specific access (admin, customer, public)
|
|
160
|
+
- Different authentication requirements
|
|
161
|
+
|
|
162
|
+
Examples: `admin_portal`, `dashboard_portal`, `public_portal`, `api_portal`
|
|
163
|
+
|
|
164
|
+
## Typical Architecture
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
packages/
|
|
168
|
+
├── blogging/ # Feature: blog functionality
|
|
169
|
+
│ └── models, definitions, policies
|
|
170
|
+
├── billing/ # Feature: payment/invoicing
|
|
171
|
+
│ └── models, definitions, policies
|
|
172
|
+
├── admin_portal/ # Portal: admin interface
|
|
173
|
+
│ └── controllers, views, routes
|
|
174
|
+
└── dashboard_portal/ # Portal: user dashboard
|
|
175
|
+
└── controllers, views, routes
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Migration Integration
|
|
179
|
+
|
|
180
|
+
Package migrations are automatically integrated:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
rails db:migrate # Runs migrations from all packages
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Related Skills
|
|
187
|
+
|
|
188
|
+
- `portal` - Portal-specific features (auth, entity scoping, routes)
|
|
189
|
+
- `resource` - Resource architecture overview
|
|
190
|
+
- `connect-resource` - Connecting resources to portals
|
|
191
|
+
- `create-resource` - Creating resources
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: policy
|
|
3
|
+
description: Plutonium resource policies - authorization, attribute permissions, and scoping
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plutonium Policies
|
|
7
|
+
|
|
8
|
+
Policies control WHO can do WHAT with resources. Built on [ActionPolicy](https://actionpolicy.evilmartians.io/).
|
|
9
|
+
|
|
10
|
+
Plutonium extends ActionPolicy with:
|
|
11
|
+
- Attribute permissions (`permitted_attributes_for_*`)
|
|
12
|
+
- Association permissions (`permitted_associations`)
|
|
13
|
+
- Automatic entity scoping for multi-tenancy
|
|
14
|
+
- Derived action methods (e.g., `update?` inherits from `create?`)
|
|
15
|
+
|
|
16
|
+
## Base Class
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
# app/policies/resource_policy.rb (generated during install)
|
|
20
|
+
class ResourcePolicy < Plutonium::Resource::Policy
|
|
21
|
+
# App-wide authorization defaults
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# app/policies/post_policy.rb (per resource)
|
|
25
|
+
class PostPolicy < ResourcePolicy
|
|
26
|
+
def create?
|
|
27
|
+
user.present?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def read?
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def permitted_attributes_for_create
|
|
35
|
+
%i[title content]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def permitted_attributes_for_read
|
|
39
|
+
%i[title content author created_at]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Action Permissions
|
|
45
|
+
|
|
46
|
+
### Core Actions (Must Override)
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
def create? # Default: false - MUST override
|
|
50
|
+
user.present?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def read? # Default: false - MUST override
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Derived Actions (Inherit by Default)
|
|
59
|
+
|
|
60
|
+
| Method | Inherits From | Override When |
|
|
61
|
+
|--------|---------------|---------------|
|
|
62
|
+
| `update?` | `create?` | Different update rules |
|
|
63
|
+
| `destroy?` | `create?` | Different delete rules |
|
|
64
|
+
| `index?` | `read?` | Custom listing rules |
|
|
65
|
+
| `show?` | `read?` | Record-specific read rules |
|
|
66
|
+
| `new?` | `create?` | Rarely needed |
|
|
67
|
+
| `edit?` | `update?` | Rarely needed |
|
|
68
|
+
| `search?` | `index?` | Search-specific rules |
|
|
69
|
+
|
|
70
|
+
### Custom Actions
|
|
71
|
+
|
|
72
|
+
Define methods matching your action names:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
def publish?
|
|
76
|
+
update? && record.draft?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def archive?
|
|
80
|
+
create? && !record.archived?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def invite_user?
|
|
84
|
+
user.admin?
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Actions are secure by default - undefined methods return `false`.
|
|
89
|
+
|
|
90
|
+
## Attribute Permissions
|
|
91
|
+
|
|
92
|
+
### Core Methods (Must Override for Production)
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# What users can see (index, show)
|
|
96
|
+
def permitted_attributes_for_read
|
|
97
|
+
%i[title content author published_at created_at]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# What users can set (create, update)
|
|
101
|
+
def permitted_attributes_for_create
|
|
102
|
+
%i[title content]
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Derived Methods (Inherit by Default)
|
|
107
|
+
|
|
108
|
+
| Method | Inherits From |
|
|
109
|
+
|--------|---------------|
|
|
110
|
+
| `permitted_attributes_for_update` | `permitted_attributes_for_create` |
|
|
111
|
+
| `permitted_attributes_for_index` | `permitted_attributes_for_read` |
|
|
112
|
+
| `permitted_attributes_for_show` | `permitted_attributes_for_read` |
|
|
113
|
+
| `permitted_attributes_for_new` | `permitted_attributes_for_create` |
|
|
114
|
+
| `permitted_attributes_for_edit` | `permitted_attributes_for_update` |
|
|
115
|
+
|
|
116
|
+
### Per-Action Attributes
|
|
117
|
+
|
|
118
|
+
Show different fields for different views:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
def permitted_attributes_for_index
|
|
122
|
+
%i[title author created_at] # Minimal for list
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def permitted_attributes_for_read
|
|
126
|
+
%i[title content author tags created_at updated_at] # Full for detail
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Auto-Detection (Development Only)
|
|
131
|
+
|
|
132
|
+
In development, undefined attribute methods auto-detect from the model. This raises errors in production - always define explicitly.
|
|
133
|
+
|
|
134
|
+
## Association Permissions
|
|
135
|
+
|
|
136
|
+
Control which associations can be rendered:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
def permitted_associations
|
|
140
|
+
%i[comments tags author]
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Used for:
|
|
145
|
+
- Nested forms
|
|
146
|
+
- Related data displays
|
|
147
|
+
- Association fields in tables
|
|
148
|
+
|
|
149
|
+
## Collection Scoping
|
|
150
|
+
|
|
151
|
+
Filter which records users can see:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
relation_scope do |relation|
|
|
155
|
+
if user.admin?
|
|
156
|
+
relation
|
|
157
|
+
else
|
|
158
|
+
relation.where(author: user)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### With Entity Scoping
|
|
164
|
+
|
|
165
|
+
Call `super` to preserve automatic entity scoping:
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
relation_scope do |relation|
|
|
169
|
+
relation = super(relation) # Apply entity scope first
|
|
170
|
+
|
|
171
|
+
if user.admin?
|
|
172
|
+
relation
|
|
173
|
+
else
|
|
174
|
+
relation.where(published: true)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Portal-Specific Policies
|
|
180
|
+
|
|
181
|
+
Override policies per portal:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# Base policy
|
|
185
|
+
class PostPolicy < ResourcePolicy
|
|
186
|
+
def create?
|
|
187
|
+
user.present?
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Admin portal - more permissive
|
|
192
|
+
class AdminPortal::PostPolicy < ::PostPolicy
|
|
193
|
+
include AdminPortal::ResourcePolicy
|
|
194
|
+
|
|
195
|
+
def destroy?
|
|
196
|
+
true # Admins can always delete
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def permitted_attributes_for_create
|
|
200
|
+
%i[title content featured internal_notes] # More fields
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Public portal - restricted
|
|
205
|
+
class PublicPortal::PostPolicy < ::PostPolicy
|
|
206
|
+
include PublicPortal::ResourcePolicy
|
|
207
|
+
|
|
208
|
+
def create?
|
|
209
|
+
false # No public creation
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Common Patterns
|
|
215
|
+
|
|
216
|
+
### Check Model Capabilities
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
def archive?
|
|
220
|
+
return false unless record.respond_to?(:archived!)
|
|
221
|
+
return false if record.archived?
|
|
222
|
+
|
|
223
|
+
user.admin?
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Prevent Actions on Archived Records
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
def update?
|
|
231
|
+
return false if record.try(:archived?)
|
|
232
|
+
super
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def destroy?
|
|
236
|
+
return false if record.try(:archived?)
|
|
237
|
+
super
|
|
238
|
+
end
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Owner-Based Permissions
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
def update?
|
|
245
|
+
record.author == user || user.admin?
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def destroy?
|
|
249
|
+
update? # Same rules as update
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Role-Based Permissions
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
def create?
|
|
257
|
+
user.admin? || user.editor?
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def read?
|
|
261
|
+
true # Everyone can read
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def update?
|
|
265
|
+
return true if user.admin?
|
|
266
|
+
return true if user.editor? && record.author == user
|
|
267
|
+
false
|
|
268
|
+
end
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Conditional Attribute Access
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
def permitted_attributes_for_create
|
|
275
|
+
attrs = %i[title content]
|
|
276
|
+
attrs << :featured if user.admin?
|
|
277
|
+
attrs << :author_id if user.admin? # Only admins can set author
|
|
278
|
+
attrs
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Authorization Context
|
|
283
|
+
|
|
284
|
+
Policies have access to:
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
user # Current user (required)
|
|
288
|
+
record # The resource being authorized
|
|
289
|
+
entity_scope # Current scoped entity (for multi-tenancy)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Custom Context
|
|
293
|
+
|
|
294
|
+
Add custom context in controllers:
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
# In policy
|
|
298
|
+
class PostPolicy < ResourcePolicy
|
|
299
|
+
authorize :department, allow_nil: true
|
|
300
|
+
|
|
301
|
+
def create?
|
|
302
|
+
department&.allows_posting?
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# In controller
|
|
307
|
+
class PostsController < ResourceController
|
|
308
|
+
authorize :department, through: :current_department
|
|
309
|
+
|
|
310
|
+
private
|
|
311
|
+
|
|
312
|
+
def current_department
|
|
313
|
+
current_user.department
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Controller Integration
|
|
319
|
+
|
|
320
|
+
Built-in CRUD actions automatically:
|
|
321
|
+
- Call `authorize_current!` at the start of each action
|
|
322
|
+
- Apply `relation_scope` for index/listings
|
|
323
|
+
- Filter params through `permitted_attributes`
|
|
324
|
+
|
|
325
|
+
After-action callbacks verify authorization was performed - if you add custom actions, you must call `authorize_current!` yourself or skip verification.
|
|
326
|
+
|
|
327
|
+
### Skip Verification (When Needed)
|
|
328
|
+
|
|
329
|
+
```ruby
|
|
330
|
+
class PostsController < ResourceController
|
|
331
|
+
skip_verify_authorize_current only: [:custom_action]
|
|
332
|
+
|
|
333
|
+
def custom_action
|
|
334
|
+
# Handle authorization manually
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Best Practices
|
|
340
|
+
|
|
341
|
+
1. **Always override `create?` and `read?`** - They default to `false`
|
|
342
|
+
2. **Define attributes explicitly** - Auto-detection only works in development
|
|
343
|
+
3. **Call `super` in `relation_scope`** - Preserves entity scoping
|
|
344
|
+
4. **Use derived methods** - Let `update?` inherit from `create?` when appropriate
|
|
345
|
+
5. **Keep policies focused** - Authorization logic only, no business logic
|
|
346
|
+
6. **Test edge cases** - Archived records, nil associations, role combinations
|
|
347
|
+
|
|
348
|
+
## Related Skills
|
|
349
|
+
|
|
350
|
+
- `resource` - How policies fit in the resource architecture
|
|
351
|
+
- `definition-actions` - Actions that need policy methods
|
|
352
|
+
- `controller` - How controllers use policies
|