plutonium 0.15.4 → 0.15.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.ico +0 -0
- data/app/assets/plutonium.png +0 -0
- data/app/views/components/table_search_input/table_search_input_component.html.erb +3 -3
- data/app/views/resource/_resource_table.html.erb +0 -321
- data/docs/.vitepress/config.ts +61 -0
- data/docs/.vitepress/theme/custom.css +61 -0
- data/docs/.vitepress/theme/index.ts +4 -0
- data/docs/api-examples.md +49 -0
- data/docs/guide/getting-started/authorization.md +296 -0
- data/docs/guide/getting-started/core-concepts.md +432 -0
- data/docs/guide/getting-started/index.md +18 -0
- data/docs/guide/getting-started/installation.md +269 -0
- data/docs/guide/getting-started/resources.md +254 -0
- data/docs/guide/what-is-plutonium.md +211 -0
- data/docs/index.md +43 -0
- data/docs/markdown-examples.md +85 -0
- data/docs/public/android-chrome-192x192.png +0 -0
- data/docs/public/android-chrome-512x512.png +0 -0
- data/docs/public/apple-touch-icon.png +0 -0
- data/docs/public/favicon-16x16.png +0 -0
- data/docs/public/favicon-32x32.png +0 -0
- data/docs/public/favicon.ico +0 -0
- data/docs/public/plutonium.png +0 -0
- data/docs/public/site.webmanifest +1 -0
- data/docs/public/templates/plutonium.rb +21 -0
- data/lib/generators/pu/core/assets/assets_generator.rb +2 -3
- data/lib/generators/pu/core/assets/templates/tailwind.config.js +2 -2
- data/lib/generators/pu/core/install/install_generator.rb +9 -1
- data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +0 -1
- data/lib/plutonium/core/controllers/authorizable.rb +1 -1
- data/lib/plutonium/definition/base.rb +8 -0
- data/lib/plutonium/definition/defineable_props.rb +1 -1
- data/lib/plutonium/definition/presentable.rb +71 -0
- data/lib/plutonium/interaction/README.md +1 -1
- data/lib/plutonium/interaction/base.rb +6 -6
- data/lib/plutonium/lib/deep_freezer.rb +31 -0
- data/lib/plutonium/query/adhoc_block.rb +19 -0
- data/lib/plutonium/query/base.rb +29 -0
- data/lib/plutonium/query/filter.rb +12 -0
- data/lib/plutonium/query/filters/text.rb +77 -0
- data/lib/plutonium/query/model_scope.rb +19 -0
- data/lib/plutonium/railtie.rb +0 -10
- data/lib/plutonium/resource/controller.rb +0 -3
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +26 -0
- data/lib/plutonium/resource/controllers/crud_actions.rb +3 -6
- data/lib/plutonium/resource/controllers/defineable.rb +0 -2
- data/lib/plutonium/resource/controllers/queryable.rb +36 -20
- data/lib/plutonium/resource/policy.rb +5 -6
- data/lib/plutonium/resource/query_object.rb +61 -147
- data/lib/plutonium/resource/register.rb +3 -0
- data/lib/plutonium/{refinements/parameter_refinements.rb → support/parameters.rb} +5 -7
- data/lib/plutonium/ui/action_button.rb +34 -19
- data/lib/plutonium/ui/component/methods.rb +1 -1
- data/lib/plutonium/ui/display/resource.rb +19 -15
- data/lib/plutonium/ui/form/query.rb +171 -0
- data/lib/plutonium/ui/form/resource.rb +22 -17
- data/lib/plutonium/ui/table/components/scopes_bar.rb +1 -1
- data/lib/plutonium/ui/table/components/search_bar.rb +6 -139
- data/lib/plutonium/ui/table/resource.rb +10 -9
- data/lib/plutonium/version.rb +1 -1
- data/package-lock.json +5769 -1853
- data/package.json +11 -5
- data/src/js/core.js +0 -1
- data/tailwind.options.js +89 -11
- metadata +37 -13
- data/app/assets/plutonium-original.png +0 -0
- data/app/assets/plutonium-white.png +0 -0
- data/lib/plutonium/interaction/concerns/presentable.rb +0 -73
- data/public/plutonium-assets/fonts/bootstrap-icons.woff +0 -0
- data/public/plutonium-assets/fonts/bootstrap-icons.woff2 +0 -0
- /data/{templates → docs/public/templates}/base.rb +0 -0
@@ -0,0 +1,296 @@
|
|
1
|
+
# Authorization and Access Control
|
2
|
+
|
3
|
+
Plutonium provides a robust authorization system built on top of [Action Policy](https://actionpolicy.evilmartians.io/), offering fine-grained access control, resource scoping, and entity-based authorization.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Authorization in Plutonium operates at multiple levels:
|
8
|
+
- Resource-level policies
|
9
|
+
- Action-based permissions
|
10
|
+
- Entity-based scoping
|
11
|
+
- Attribute-level access control
|
12
|
+
|
13
|
+
## Basic Policy Definition
|
14
|
+
|
15
|
+
Every resource in Plutonium requires a policy. Here's a basic example:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
class BlogPolicy < ResourcePolicy
|
19
|
+
# Core CRUD Permissions
|
20
|
+
|
21
|
+
def create?
|
22
|
+
# All authenticated users can create blogs
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def read?
|
27
|
+
# Allow anyone to read blogs
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def update?
|
32
|
+
# Allow only the blog owner to update
|
33
|
+
owner?
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy?
|
37
|
+
# Allow only the blog owner or admins to destroy
|
38
|
+
owner? || user.admin?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Attribute Control
|
42
|
+
|
43
|
+
def permitted_attributes_for_create
|
44
|
+
[:title, :content, :category]
|
45
|
+
end
|
46
|
+
|
47
|
+
def permitted_attributes_for_read
|
48
|
+
[:title, :content, :category, :created_at, :updated_at, :user_id]
|
49
|
+
end
|
50
|
+
|
51
|
+
def permitted_attributes_for_update
|
52
|
+
[:title, :content, :category]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Association Access
|
56
|
+
|
57
|
+
def permitted_associations
|
58
|
+
[:comments]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def owner?
|
64
|
+
record.user_id == user.id
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
## Default Behaviors in Plutonium Policies
|
70
|
+
|
71
|
+
::: info Overview
|
72
|
+
Plutonium's Policy system implements a secure-by-default pattern with clear inheritance chains for both permissions and attribute access.
|
73
|
+
:::
|
74
|
+
|
75
|
+
### Permission Defaults
|
76
|
+
|
77
|
+
Permission methods follow two key patterns: core permissions that default to `false`, and derived permissions that inherit from core ones.
|
78
|
+
|
79
|
+
#### Core Permission Chain
|
80
|
+
|
81
|
+
```mermaid
|
82
|
+
graph TD
|
83
|
+
subgraph Core Permissions
|
84
|
+
A[create? ❌] --> B[update? ❌]
|
85
|
+
A --> C[destroy? ❌]
|
86
|
+
D[read? ❌] --> E[index? ❌]
|
87
|
+
D --> F[show? ❌]
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
::: warning Security First
|
92
|
+
All core permissions (`create?` and `read?`) default to `false`. You must explicitly override these to grant access.
|
93
|
+
```ruby
|
94
|
+
# Default implementations
|
95
|
+
def create?
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
def read?
|
100
|
+
false
|
101
|
+
end
|
102
|
+
```
|
103
|
+
:::
|
104
|
+
|
105
|
+
#### Permission Inheritance
|
106
|
+
|
107
|
+
::: code-group
|
108
|
+
```ruby [Action Methods]
|
109
|
+
def update?
|
110
|
+
create? # Inherits from create?
|
111
|
+
end
|
112
|
+
|
113
|
+
def destroy?
|
114
|
+
create? # Inherits from create?
|
115
|
+
end
|
116
|
+
|
117
|
+
def index?
|
118
|
+
read? # Inherits from read?
|
119
|
+
end
|
120
|
+
|
121
|
+
def show?
|
122
|
+
read? # Inherits from read?
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
```ruby [Helper Methods]
|
127
|
+
def new?
|
128
|
+
create? # Matches create?
|
129
|
+
end
|
130
|
+
|
131
|
+
def edit?
|
132
|
+
update? # Matches update?
|
133
|
+
end
|
134
|
+
|
135
|
+
def search?
|
136
|
+
index? # Matches index?
|
137
|
+
end
|
138
|
+
```
|
139
|
+
:::
|
140
|
+
|
141
|
+
### Attribute Permission Defaults
|
142
|
+
|
143
|
+
Attribute permissions also follow an inheritance pattern, but with auto-detection in development:
|
144
|
+
|
145
|
+
::: details Inheritance Chain
|
146
|
+
```mermaid
|
147
|
+
graph TD
|
148
|
+
subgraph Core Attributes
|
149
|
+
A[permitted_attributes_for_create] --> B[permitted_attributes_for_update]
|
150
|
+
C[permitted_attributes_for_read] --> D[permitted_attributes_for_show]
|
151
|
+
C --> E[permitted_attributes_for_index]
|
152
|
+
end
|
153
|
+
```
|
154
|
+
:::
|
155
|
+
|
156
|
+
#### Core Implementation Details
|
157
|
+
|
158
|
+
::: code-group
|
159
|
+
```ruby [Create Attributes]
|
160
|
+
def permitted_attributes_for_create
|
161
|
+
# Auto-detects fields but excludes system columns
|
162
|
+
autodetect_permitted_fields(:permitted_attributes_for_create) - [
|
163
|
+
resource_class.primary_key.to_sym,
|
164
|
+
:created_at,
|
165
|
+
:updated_at
|
166
|
+
]
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
```ruby [Read Attributes]
|
171
|
+
def permitted_attributes_for_read
|
172
|
+
# Auto-detects all fields
|
173
|
+
autodetect_permitted_fields(:permitted_attributes_for_read)
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
```ruby [Update Attributes]
|
178
|
+
def permitted_attributes_for_update
|
179
|
+
# Inherits from create
|
180
|
+
permitted_attributes_for_create
|
181
|
+
end
|
182
|
+
```
|
183
|
+
:::
|
184
|
+
|
185
|
+
::: danger Auto-detection Warning
|
186
|
+
The default attribute detection:
|
187
|
+
- Only works in development
|
188
|
+
- Raises errors in other environments
|
189
|
+
- Shows warning messages
|
190
|
+
- Must be overridden in production
|
191
|
+
:::
|
192
|
+
|
193
|
+
### Association Defaults
|
194
|
+
|
195
|
+
By default, no associations are permitted:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
def permitted_associations
|
199
|
+
[] # Must be explicitly defined
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
### Context Object Defaults
|
204
|
+
|
205
|
+
Two built-in authorization contexts:
|
206
|
+
|
207
|
+
::: code-group
|
208
|
+
```ruby [Required Context]
|
209
|
+
# User must be present
|
210
|
+
authorize :user, allow_nil: false
|
211
|
+
```
|
212
|
+
|
213
|
+
```ruby [Optional Context]
|
214
|
+
# Entity scope is optional
|
215
|
+
authorize :entity_scope, allow_nil: true
|
216
|
+
```
|
217
|
+
:::
|
218
|
+
|
219
|
+
#### Default Scoping Behavior
|
220
|
+
|
221
|
+
When an entity scope exists, records are automatically filtered:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
relation_scope do |relation|
|
225
|
+
next relation unless entity_scope
|
226
|
+
relation.associated_with(entity_scope)
|
227
|
+
end
|
228
|
+
```
|
229
|
+
|
230
|
+
#### Adding Custom Contexts
|
231
|
+
|
232
|
+
You can add additional authorization contexts:
|
233
|
+
|
234
|
+
::: code-group
|
235
|
+
```ruby [Policy]
|
236
|
+
class BlogPolicy < ResourcePolicy
|
237
|
+
authorize :ability, allow_nil: true
|
238
|
+
|
239
|
+
def promote?
|
240
|
+
user.admin? && ability&.can?(:promote, record)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
```
|
244
|
+
|
245
|
+
```ruby [Controller]
|
246
|
+
class BlogsController < ResourceController
|
247
|
+
authorize :ability, through: :current_ability
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
def current_ability
|
252
|
+
@current_ability ||= Ability.new(current_user)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
```
|
256
|
+
:::
|
257
|
+
|
258
|
+
## Quick Reference
|
259
|
+
|
260
|
+
| Method Type | Default | Inherits From |
|
261
|
+
|------------|---------|---------------|
|
262
|
+
| create? | false | - |
|
263
|
+
| read? | false | - |
|
264
|
+
| update? | false | create? |
|
265
|
+
| destroy? | false | create? |
|
266
|
+
| index? | false | read? |
|
267
|
+
| show? | false | read? |
|
268
|
+
| attributes_for_create | auto* | - |
|
269
|
+
| attributes_for_read | auto* | - |
|
270
|
+
| attributes_for_update | auto* | create |
|
271
|
+
| associations | [] | - |
|
272
|
+
|
273
|
+
::: warning
|
274
|
+
\*Auto-detection only works in development
|
275
|
+
:::
|
276
|
+
|
277
|
+
## Security Best Practices
|
278
|
+
|
279
|
+
### 1. Never Skip Authorization
|
280
|
+
|
281
|
+
Plutonium controllers automatically verify authorization:
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
class BlogsController < ResourceController
|
285
|
+
def show
|
286
|
+
# This will raise an error if you forget to authorize
|
287
|
+
# authorize! resource_record
|
288
|
+
render :show
|
289
|
+
end
|
290
|
+
end
|
291
|
+
```
|
292
|
+
|
293
|
+
## Related Resources
|
294
|
+
|
295
|
+
- [Action Policy Documentation](https://actionpolicy.evilmartians.io/)
|
296
|
+
- [Rails Security Guide](https://guides.rubyonrails.org/security.html)
|
@@ -0,0 +1,432 @@
|
|
1
|
+
# Core Concepts
|
2
|
+
|
3
|
+
::: tip What you'll learn
|
4
|
+
- Understanding Plutonium's architecture and core abstractions
|
5
|
+
- How resources and packages work together
|
6
|
+
- How to organize your application effectively
|
7
|
+
- Best practices for building maintainable applications
|
8
|
+
:::
|
9
|
+
|
10
|
+
## Resources
|
11
|
+
|
12
|
+
Resources are the fundamental building blocks of a Plutonium application. They represent your business domain objects and their associated behavior.
|
13
|
+
|
14
|
+
### Anatomy of a Resource
|
15
|
+
|
16
|
+
A resource consists of several interconnected components:
|
17
|
+
|
18
|
+
```mermaid
|
19
|
+
graph TD
|
20
|
+
A[Resource] --> B[Model]
|
21
|
+
A --> C[Definition]
|
22
|
+
A --> D[Policy]
|
23
|
+
A --> G[Actions]
|
24
|
+
|
25
|
+
B --> H[Database Schema]
|
26
|
+
C --> I[Fields & UI Logic]
|
27
|
+
C --> F[QueryObject]
|
28
|
+
D --> J[Authorization]
|
29
|
+
F --> L[Querying & Filtering]
|
30
|
+
G --> M[User Operations]
|
31
|
+
```
|
32
|
+
|
33
|
+
::: details Complete Resource Example
|
34
|
+
|
35
|
+
::: code-group
|
36
|
+
```ruby [Model]
|
37
|
+
# app/models/user.rb
|
38
|
+
class User < ApplicationRecord
|
39
|
+
include Plutonium::Resource::Record
|
40
|
+
|
41
|
+
# Associations
|
42
|
+
has_many :posts
|
43
|
+
has_many :comments
|
44
|
+
belongs_to :organization
|
45
|
+
|
46
|
+
# Scopes
|
47
|
+
scope :active, -> { where(status: :active) }
|
48
|
+
|
49
|
+
# Validations
|
50
|
+
validates :name, presence: true
|
51
|
+
validates :email, presence: true, uniqueness: true
|
52
|
+
validates :role, presence: true, inclusion: {in: %w[admin user]}
|
53
|
+
|
54
|
+
def admin? = role == "admin"
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
```ruby [Definition]
|
59
|
+
# app/definitions/user_definition.rb
|
60
|
+
class UserDefinition < Plutonium::Resource::Definition
|
61
|
+
# Display configuration
|
62
|
+
field :name, as: :string
|
63
|
+
field :email, as: :email
|
64
|
+
|
65
|
+
# Search configuration
|
66
|
+
search do |scope, query|
|
67
|
+
scope.where("name LIKE :q OR email LIKE :q", q: "%#{query}%")
|
68
|
+
end
|
69
|
+
|
70
|
+
# Filters
|
71
|
+
filter :role, with: SelectFilter, choices: %w[admin user guest]
|
72
|
+
filter :status, with: SelectFilter, choices: %w[active inactive]
|
73
|
+
|
74
|
+
# Scopes
|
75
|
+
scope :active
|
76
|
+
|
77
|
+
scope :admins do
|
78
|
+
where(role: :admin)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Actions
|
82
|
+
action :deactivate,
|
83
|
+
interaction: DeactivateUser,
|
84
|
+
color: :warning,
|
85
|
+
icon: Phlex::TablerIcons::UserOff
|
86
|
+
|
87
|
+
# UI Customization
|
88
|
+
show_page_title "User Details"
|
89
|
+
show_page_description "View and manage user information"
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
```ruby [Policy]
|
94
|
+
# app/policies/user_policy.rb
|
95
|
+
class UserPolicy < Plutonium::Resource::Policy
|
96
|
+
# Basic permissions
|
97
|
+
def read?
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def create?
|
102
|
+
user.admin?
|
103
|
+
end
|
104
|
+
|
105
|
+
def update?
|
106
|
+
user.admin? || record.id == user.id
|
107
|
+
end
|
108
|
+
|
109
|
+
def destroy?
|
110
|
+
user.admin? && record.id != user.id
|
111
|
+
end
|
112
|
+
|
113
|
+
# Action permissions
|
114
|
+
def deactivate?
|
115
|
+
user.admin? && record.status == :active && record.id != user.id
|
116
|
+
end
|
117
|
+
|
118
|
+
# Attribute permissions
|
119
|
+
def permitted_attributes_for_read
|
120
|
+
if user.admin?
|
121
|
+
%i[name email role status created_at updated_at]
|
122
|
+
else
|
123
|
+
%i[name email status]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def permitted_attributes_for_create
|
128
|
+
if user.admin?
|
129
|
+
%i[name email role status password]
|
130
|
+
else
|
131
|
+
%i[name email password]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def permitted_attributes_for_update
|
136
|
+
if user.admin?
|
137
|
+
%i[name email role]
|
138
|
+
else
|
139
|
+
%i[name email]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Association permissions
|
144
|
+
def permitted_associations
|
145
|
+
%i[posts comments]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
|
151
|
+
```ruby [Deactivate Interaction]
|
152
|
+
# app/interactions/user_interactions/deactivate.rb
|
153
|
+
module UserInteractions
|
154
|
+
class Deactivate < Plutonium::Resource::Interaction
|
155
|
+
# Define presentable metadata
|
156
|
+
presents label: "Deactivate User",
|
157
|
+
icon: Phlex::TablerIcons::UserOff,
|
158
|
+
description: "Deactivate user account"
|
159
|
+
|
160
|
+
# Define attributes
|
161
|
+
attribute :resource, class: User
|
162
|
+
attribute :reason, :string
|
163
|
+
|
164
|
+
# Validations
|
165
|
+
validates :resource, presence: true
|
166
|
+
validates :reason, presence: true
|
167
|
+
|
168
|
+
# Business logic
|
169
|
+
def execute
|
170
|
+
resource.transaction do
|
171
|
+
resource.status = :inactive
|
172
|
+
resource.deactivated_at = Time.current
|
173
|
+
resource.deactivation_reason = reason
|
174
|
+
|
175
|
+
if resource.save
|
176
|
+
succeed(resource)
|
177
|
+
.with_message("User was successfully deactivated")
|
178
|
+
.with_redirect_response(resource)
|
179
|
+
else
|
180
|
+
failed(resource.errors)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
```
|
187
|
+
:::
|
188
|
+
|
189
|
+
## Packages
|
190
|
+
|
191
|
+
Packages are the way Plutonium helps you modularize your application. They're built on top of Rails Engines but provide additional structure and conventions.
|
192
|
+
|
193
|
+
There are two main types:
|
194
|
+
|
195
|
+
### Feature Packages
|
196
|
+
|
197
|
+
|
198
|
+
Feature packages help you organize your application into logical, reusable modules.
|
199
|
+
They contain your business domain logic and resources. They're self-contained and independent.
|
200
|
+
|
201
|
+
::: tip Key Characteristics
|
202
|
+
- Domain Models
|
203
|
+
- Business Logic
|
204
|
+
- No Web Interface
|
205
|
+
- Reusable Components
|
206
|
+
:::
|
207
|
+
|
208
|
+
::: code-group
|
209
|
+
```ruby [Directory Structure]
|
210
|
+
packages/
|
211
|
+
└── blogging/
|
212
|
+
├── app/
|
213
|
+
│ ├── models/
|
214
|
+
│ │ └── blogging/
|
215
|
+
│ │ ├── post.rb
|
216
|
+
│ │ └── comment.rb
|
217
|
+
│ ├── definitions/
|
218
|
+
│ │ └── blogging/
|
219
|
+
│ │ ├── post_definition.rb
|
220
|
+
│ │ └── comment_definition.rb
|
221
|
+
│ ├── policies/
|
222
|
+
│ │ └── blogging/
|
223
|
+
│ │ ├── post_policy.rb
|
224
|
+
│ │ └── comment_policy.rb
|
225
|
+
│ └── interactions/
|
226
|
+
│ └── blogging/
|
227
|
+
│ └── post_interactions/
|
228
|
+
│ ├── publish.rb
|
229
|
+
│ └── archive.rb
|
230
|
+
├── config/
|
231
|
+
│ └── routes.rb
|
232
|
+
└── lib/
|
233
|
+
└── engine.rb
|
234
|
+
```
|
235
|
+
|
236
|
+
```ruby [Engine Configuration]
|
237
|
+
# packages/blogging/lib/engine.rb
|
238
|
+
module Blogging
|
239
|
+
class Engine < ::Rails::Engine
|
240
|
+
include Plutonium::Package::Engine
|
241
|
+
|
242
|
+
# Package configuration goes here
|
243
|
+
isolate_namespace Blogging
|
244
|
+
end
|
245
|
+
end
|
246
|
+
```
|
247
|
+
:::
|
248
|
+
|
249
|
+
### Portal Packages
|
250
|
+
|
251
|
+
Portal packages provide web interfaces and control how users interact with features.
|
252
|
+
|
253
|
+
::: tip Key Characteristics
|
254
|
+
- Web Interface
|
255
|
+
- Authentication
|
256
|
+
- Resource Access Control
|
257
|
+
- Feature Composition
|
258
|
+
:::
|
259
|
+
|
260
|
+
::: code-group
|
261
|
+
```ruby [Directory Structure]
|
262
|
+
packages/
|
263
|
+
└── admin_portal/
|
264
|
+
├── app/
|
265
|
+
│ ├── controllers/
|
266
|
+
│ │ └── admin_portal/
|
267
|
+
│ │ ├── concerns/
|
268
|
+
│ │ │ └── controller.rb
|
269
|
+
│ │ ├── plutonium_controller.rb
|
270
|
+
│ │ └── resource_controller.rb
|
271
|
+
│ └── views/
|
272
|
+
│ └── layouts/
|
273
|
+
│ └── admin_portal.html.erb
|
274
|
+
├── config/
|
275
|
+
│ └── routes.rb
|
276
|
+
└── lib/
|
277
|
+
└── engine.rb
|
278
|
+
```
|
279
|
+
|
280
|
+
```ruby [Engine Configuration]
|
281
|
+
# packages/admin_portal/lib/engine.rb
|
282
|
+
module AdminPortal
|
283
|
+
class Engine < ::Rails::Engine
|
284
|
+
include Plutonium::Portal::Engine
|
285
|
+
|
286
|
+
# Scope all resources to organization
|
287
|
+
scope_to_entity Organization, strategy: :path
|
288
|
+
end
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
```ruby [Routes Configuration]
|
293
|
+
# packages/admin_portal/config/routes.rb
|
294
|
+
AdminPortal::Engine.routes.draw do
|
295
|
+
root to: "dashboard#index"
|
296
|
+
|
297
|
+
# Register resources from feature packages
|
298
|
+
register_resource Blogging::Post
|
299
|
+
register_resource Blogging::Comment
|
300
|
+
end
|
301
|
+
```
|
302
|
+
|
303
|
+
```ruby [Controller Configuration]
|
304
|
+
# packages/admin_portal/app/controllers/admin_portal/concerns/controller.rb
|
305
|
+
module AdminPortal
|
306
|
+
module Concerns
|
307
|
+
class Controller < ::Rails::Engine
|
308
|
+
extend ActiveSupport::Concern
|
309
|
+
include Plutonium::Portal::Controller
|
310
|
+
# Integrate authentication
|
311
|
+
include Plutonium::Auth::Rodauth(:admin)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
```
|
316
|
+
:::
|
317
|
+
|
318
|
+
## Entity Scoping
|
319
|
+
|
320
|
+
Entity scoping is a powerful feature that allows you to partition resources based on a parent entity (like Organization or Account).
|
321
|
+
It's how Plutonium achieve's multitenancy.
|
322
|
+
|
323
|
+
By properly defining associations to an entity, row-level multitenancy comes for free, out of the box.
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
# Scope definition in engine
|
327
|
+
module AdminPortal
|
328
|
+
class Engine < ::Rails::Engine
|
329
|
+
include Plutonium::Portal::Engine
|
330
|
+
|
331
|
+
# Path-based scoping (/org_123/posts)
|
332
|
+
scope_to_entity Organization, strategy: :path
|
333
|
+
|
334
|
+
# Or custom scoping
|
335
|
+
scope_to_entity Organization, strategy: :current_organization
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Model implementation
|
340
|
+
class Post < ApplicationRecord
|
341
|
+
include Plutonium::Resource::Record
|
342
|
+
|
343
|
+
# Define a direct relationship to the entity
|
344
|
+
belongs_to :user
|
345
|
+
belongs_to :organization, through: :user
|
346
|
+
|
347
|
+
# Alternatively, if there's no direct relationship
|
348
|
+
scope :associated_with_organization, ->(organization) do
|
349
|
+
# custom scoping logic goes here
|
350
|
+
joins(:user).where(users: { organization_id: organization.id })
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Controller config
|
355
|
+
class ResourceController < PlutoniumController
|
356
|
+
include Plutonium::Resource::Controller
|
357
|
+
|
358
|
+
private
|
359
|
+
|
360
|
+
def current_organization
|
361
|
+
# Get tenant from the current subdomain
|
362
|
+
@current_organization ||= Organization.where(subdomain: request.subdomain).first!
|
363
|
+
end
|
364
|
+
end
|
365
|
+
```
|
366
|
+
|
367
|
+
## Best Practices
|
368
|
+
|
369
|
+
### Package Organization
|
370
|
+
|
371
|
+
::: tip Feature Packages
|
372
|
+
1. Keep domain logic isolated
|
373
|
+
2. Clear boundaries between features
|
374
|
+
3. Minimal dependencies between packages
|
375
|
+
4. Well-defined interfaces
|
376
|
+
:::
|
377
|
+
|
378
|
+
::: tip Portal Packages
|
379
|
+
1. Single responsibility (admin, customer)
|
380
|
+
2. Consistent authentication strategy
|
381
|
+
3. Clear resource scoping rules
|
382
|
+
4. Feature composition over duplication
|
383
|
+
:::
|
384
|
+
|
385
|
+
### Resource Design
|
386
|
+
|
387
|
+
::: tip Model Layer
|
388
|
+
1. Clear validations and constraints
|
389
|
+
2. Proper association setup
|
390
|
+
3. Meaningful scopes
|
391
|
+
:::
|
392
|
+
|
393
|
+
::: tip Definition Layer
|
394
|
+
1. Appropriate field types
|
395
|
+
2. Clear action definitions
|
396
|
+
3. Efficient search implementation
|
397
|
+
:::
|
398
|
+
|
399
|
+
::: tip Policy Layer
|
400
|
+
1. Granular permissions
|
401
|
+
2. Attribute-level access control
|
402
|
+
3. Action-specific rules
|
403
|
+
4. Association permissions
|
404
|
+
:::
|
405
|
+
|
406
|
+
### Security Considerations
|
407
|
+
|
408
|
+
::: warning Important
|
409
|
+
1. Always implement proper policies
|
410
|
+
2. Use entity scoping consistently
|
411
|
+
3. Validate all inputs
|
412
|
+
4. Control association access
|
413
|
+
5. Audit sensitive actions
|
414
|
+
:::
|
415
|
+
|
416
|
+
## Generator Support
|
417
|
+
|
418
|
+
Plutonium provides generators to quickly scaffold components:
|
419
|
+
|
420
|
+
```bash
|
421
|
+
# Create a new feature package
|
422
|
+
rails generate pu:pkg:package blogging
|
423
|
+
|
424
|
+
# Create a new portal package
|
425
|
+
rails generate pu:pkg:portal admin
|
426
|
+
|
427
|
+
# Create a new resource
|
428
|
+
rails generate pu:res:scaffold post title:string content:text
|
429
|
+
|
430
|
+
# Connect a resource to a portal
|
431
|
+
rails generate pu:res:conn
|
432
|
+
```
|