plutonium 0.44.0 → 0.45.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/skill.md +88 -11
  3. data/.claude/skills/plutonium-assets/SKILL.md +1 -1
  4. data/.claude/skills/plutonium-controller/SKILL.md +6 -2
  5. data/.claude/skills/plutonium-create-resource/SKILL.md +1 -1
  6. data/.claude/skills/plutonium-definition/SKILL.md +445 -53
  7. data/.claude/skills/plutonium-definition-actions/SKILL.md +2 -2
  8. data/.claude/skills/plutonium-definition-query/SKILL.md +2 -2
  9. data/.claude/skills/plutonium-forms/SKILL.md +6 -2
  10. data/.claude/skills/plutonium-installation/SKILL.md +3 -3
  11. data/.claude/skills/plutonium-interaction/SKILL.md +3 -3
  12. data/.claude/skills/plutonium-invites/SKILL.md +1 -1
  13. data/.claude/skills/plutonium-model/SKILL.md +228 -55
  14. data/.claude/skills/plutonium-nested-resources/SKILL.md +9 -2
  15. data/.claude/skills/plutonium-package/SKILL.md +3 -3
  16. data/.claude/skills/plutonium-policy/SKILL.md +6 -2
  17. data/.claude/skills/plutonium-portal/SKILL.md +97 -59
  18. data/.claude/skills/plutonium-profile/SKILL.md +2 -2
  19. data/.claude/skills/plutonium-rodauth/SKILL.md +1 -1
  20. data/.claude/skills/plutonium-theming/SKILL.md +1 -1
  21. data/.claude/skills/plutonium-views/SKILL.md +2 -2
  22. data/CHANGELOG.md +25 -0
  23. data/app/assets/plutonium.css +1 -1
  24. data/gemfiles/rails_7.gemfile.lock +3 -3
  25. data/gemfiles/rails_8.0.gemfile.lock +3 -3
  26. data/gemfiles/rails_8.1.gemfile.lock +3 -3
  27. data/lib/generators/pu/invites/install_generator.rb +2 -2
  28. data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/show.html.erb.tt +7 -7
  29. data/lib/generators/pu/saas/portal_generator.rb +17 -0
  30. data/lib/generators/pu/skills/sync/sync_generator.rb +21 -0
  31. data/lib/plutonium/engine.rb +1 -1
  32. data/lib/plutonium/railtie.rb +1 -1
  33. data/lib/plutonium/ui/form/components/resource_select.rb +1 -1
  34. data/lib/plutonium/ui/form/components/secure_association.rb +2 -2
  35. data/lib/plutonium/ui/form/components/secure_polymorphic_association.rb +6 -11
  36. data/lib/plutonium/version.rb +1 -1
  37. data/package.json +1 -1
  38. data/plutonium.gemspec +1 -1
  39. data/src/css/tokens.css +2 -0
  40. metadata +4 -8
  41. data/.claude/skills/plutonium-connect-resource/SKILL.md +0 -130
  42. data/.claude/skills/plutonium-definition-fields/SKILL.md +0 -535
  43. data/.claude/skills/plutonium-model-features/SKILL.md +0 -286
  44. data/.claude/skills/plutonium-resource/SKILL.md +0 -281
@@ -1,286 +0,0 @@
1
- ---
2
- name: plutonium-model-features
3
- description: Plutonium model features - has_cents, associations, scopes, and routing
4
- ---
5
-
6
- # Plutonium Model Features
7
-
8
- Advanced features available in Plutonium resource models.
9
-
10
- ## Monetary Handling (has_cents)
11
-
12
- Store monetary values as integers (cents) while exposing decimal interfaces.
13
-
14
- ### Basic Usage
15
-
16
- ```ruby
17
- class Product < ResourceRecord
18
- has_cents :price_cents # Creates price getter/setter
19
- has_cents :cost_cents, name: :wholesale # Custom accessor name
20
- has_cents :tax_cents, rate: 1000 # 3 decimal places
21
- has_cents :quantity_cents, rate: 1 # Whole numbers only
22
- end
23
-
24
- product = Product.new
25
- product.price = 19.99
26
- product.price_cents # => 1999
27
- product.price # => 19.99
28
-
29
- # Truncates (doesn't round)
30
- product.price = 10.999
31
- product.price_cents # => 1099
32
- ```
33
-
34
- ### Options
35
-
36
- ```ruby
37
- has_cents :field_cents,
38
- name: :custom_name, # Accessor name (default: field without _cents)
39
- rate: 100, # Conversion rate (default: 100)
40
- suffix: "amount" # Suffix for generated name (default: "amount")
41
- ```
42
-
43
- ### Validation
44
-
45
- ```ruby
46
- class Product < ResourceRecord
47
- has_cents :price_cents
48
-
49
- # Validate the cents field
50
- validates :price_cents, numericality: {greater_than: 0}
51
- end
52
-
53
- product = Product.new(price: -10)
54
- product.valid? # => false
55
- product.errors[:price_cents] # => ["must be greater than 0"]
56
- product.errors[:price] # => ["is invalid"] (propagated)
57
- ```
58
-
59
- ### Introspection
60
-
61
- ```ruby
62
- Product.has_cents_attributes
63
- # => {price_cents: {name: :price, rate: 100}, ...}
64
-
65
- Product.has_cents_attribute?(:price_cents) # => true
66
- ```
67
-
68
- ## Association SGID Support
69
-
70
- All associations get Signed Global ID (SGID) methods for secure serialization.
71
-
72
- ### Singular Associations (belongs_to, has_one)
73
-
74
- ```ruby
75
- class Post < ResourceRecord
76
- belongs_to :user
77
- has_one :featured_image
78
- end
79
-
80
- post = Post.first
81
-
82
- # Get SGID
83
- post.user_sgid # => "BAh7CEkiCG..."
84
- post.featured_image_sgid # => "BAh7CEkiCG..."
85
-
86
- # Set by SGID (finds and assigns)
87
- post.user_sgid = "BAh7CEkiCG..."
88
- post.featured_image_sgid = "BAh7CEkiCG..."
89
- ```
90
-
91
- ### Collection Associations (has_many, has_and_belongs_to_many)
92
-
93
- ```ruby
94
- class User < ResourceRecord
95
- has_many :posts
96
- has_and_belongs_to_many :roles
97
- end
98
-
99
- user = User.first
100
-
101
- # Get SGIDs
102
- user.post_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
103
- user.role_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
104
-
105
- # Bulk assignment
106
- user.post_sgids = ["BAh7CEkiCG...", ...]
107
-
108
- # Individual manipulation
109
- user.add_post_sgid("BAh7CEkiCG...") # Add to collection
110
- user.remove_post_sgid("BAh7CEkiCG...") # Remove from collection
111
- ```
112
-
113
- ### Use Cases
114
-
115
- - Secure form submissions without exposing internal IDs
116
- - API responses with portable references
117
- - Caching and serialization
118
-
119
- ## Entity Scoping (associated_with)
120
-
121
- Query records associated with another record. Essential for multi-tenant apps.
122
-
123
- ### Basic Usage
124
-
125
- ```ruby
126
- class Comment < ResourceRecord
127
- belongs_to :post
128
- end
129
-
130
- # Find comments for a post
131
- Comment.associated_with(post)
132
- # => Comment.where(post: post)
133
- ```
134
-
135
- ### Association Detection
136
-
137
- Works with:
138
- - `belongs_to` - Uses WHERE clause (most efficient)
139
- - `has_one` - Uses JOIN + WHERE
140
- - `has_many` - Uses JOIN + WHERE
141
-
142
- ```ruby
143
- # Direct association (preferred)
144
- Comment.associated_with(post) # WHERE post_id = ?
145
-
146
- # Reverse association (less efficient, logs warning)
147
- Post.associated_with(comment) # JOIN comments WHERE comments.id = ?
148
- ```
149
-
150
- ### Custom Scopes
151
-
152
- For optimal performance, define custom scopes:
153
-
154
- ```ruby
155
- class Comment < ResourceRecord
156
- # Custom scope naming: associated_with_{model_name}
157
- scope :associated_with_user, ->(user) do
158
- joins(:post).where(posts: {user_id: user.id})
159
- end
160
- end
161
-
162
- # Automatically uses custom scope
163
- Comment.associated_with(user)
164
- ```
165
-
166
- ### Error Handling
167
-
168
- ```ruby
169
- # When no association exists
170
- UnrelatedModel.associated_with(user)
171
- # Raises: Could not resolve the association between 'UnrelatedModel' and 'User'
172
- #
173
- # Define:
174
- # 1. the associations between the models
175
- # 2. a named scope on UnrelatedModel e.g.
176
- #
177
- # scope :associated_with_user, ->(user) { do_something_here }
178
- ```
179
-
180
- ## URL Routing
181
-
182
- ### Default Behavior
183
-
184
- ```ruby
185
- user = User.find(1)
186
- user.to_param # => "1"
187
- ```
188
-
189
- ### Custom Path Parameters
190
-
191
- Use a stable, unique field instead of ID:
192
-
193
- ```ruby
194
- class User < ResourceRecord
195
- private
196
-
197
- def path_parameter(param_name)
198
- :username # Must be unique
199
- end
200
- end
201
-
202
- user = User.create(username: "john_doe")
203
- user.to_param # => "john_doe"
204
- # URLs: /users/john_doe
205
- ```
206
-
207
- ### Dynamic Path Parameters (SEO-friendly)
208
-
209
- Include ID prefix for uniqueness with human-readable suffix:
210
-
211
- ```ruby
212
- class Article < ResourceRecord
213
- private
214
-
215
- def dynamic_path_parameter(param_name)
216
- :title
217
- end
218
- end
219
-
220
- article = Article.create(id: 1, title: "My Great Article")
221
- article.to_param # => "1-my-great-article"
222
- # URLs: /articles/1-my-great-article
223
- ```
224
-
225
- ### Path Parameter Lookup
226
-
227
- ```ruby
228
- # Scope for finding by path parameter
229
- User.from_path_param("john_doe")
230
- Article.from_path_param("1-my-great-article") # Extracts ID
231
- ```
232
-
233
- ## Association Route Discovery
234
-
235
- ```ruby
236
- class User < ResourceRecord
237
- has_many :posts
238
- has_many :comments
239
- accepts_nested_attributes_for :posts
240
- end
241
-
242
- # Get has_many association names
243
- User.has_many_association_routes
244
- # => ["posts", "comments"]
245
-
246
- # Get nested attributes config
247
- User.all_nested_attributes_options
248
- # => {posts: {allow_destroy: false, update_only: false, macro: :has_many, class: Post}}
249
- ```
250
-
251
- ## Performance Tips
252
-
253
- ### Field Introspection
254
-
255
- ```ruby
256
- # Cached in production, fresh in development
257
- User.resource_field_names # First call queries, subsequent cached
258
- ```
259
-
260
- ### Association Queries
261
-
262
- ```ruby
263
- # Efficient: Direct belongs_to
264
- Comment.associated_with(post) # Simple WHERE
265
-
266
- # Less efficient: Reverse has_many (logs warning)
267
- Post.associated_with(comment) # JOIN required
268
-
269
- # Optimal: Custom scope when direct isn't possible
270
- scope :associated_with_user, ->(user) { where(user_id: user.id) }
271
- ```
272
-
273
- ### SGID Operations
274
-
275
- ```ruby
276
- # Efficient: Batch assignment
277
- user.post_sgids = sgid_array # Single operation
278
-
279
- # Inefficient: Individual adds
280
- sgid_array.each { |sgid| user.add_post_sgid(sgid) }
281
- ```
282
-
283
- ## Related Skills
284
-
285
- - `plutonium-model` - Model overview and structure
286
- - `plutonium-create-resource` - Scaffold generator
@@ -1,281 +0,0 @@
1
- ---
2
- name: plutonium-resource
3
- description: Overview of Plutonium resources - what they are and how the pieces fit together
4
- ---
5
-
6
- # Plutonium Resources
7
-
8
- A **resource** in Plutonium is the combination of four layers that work together to provide full CRUD functionality with minimal code.
9
-
10
- ## The Four Layers
11
-
12
- | Layer | File | Purpose |
13
- |-------|------|---------|
14
- | **Model** | `app/models/post.rb` | Data, validations, associations, business rules |
15
- | **Definition** | `app/definitions/post_definition.rb` | UI configuration - how fields render, actions, filters |
16
- | **Policy** | `app/policies/post_policy.rb` | Authorization - who can do what |
17
- | **Controller** | `app/controllers/posts_controller.rb` | Request handling - usually empty, inherits CRUD |
18
-
19
- ```
20
- ┌─────────────────────────────────────────────────────────────────┐
21
- │ Resource │
22
- ├─────────────────────────────────────────────────────────────────┤
23
- │ Model │ Definition │ Policy │ Controller │
24
- │ (WHAT it is) │ (HOW it looks) │ (WHO can act) │ (HOW it │
25
- │ │ │ │ responds) │
26
- ├─────────────────────────────────────────────────────────────────┤
27
- │ - attributes │ - field types │ - permissions │ - CRUD │
28
- │ - associations │ - inputs/forms │ - scoping │ - redirects│
29
- │ - validations │ - displays │ - attributes │ - params │
30
- │ - scopes │ - actions │ │ │
31
- │ - callbacks │ - filters │ │ │
32
- └─────────────────────────────────────────────────────────────────┘
33
- ```
34
-
35
- ## Creating Resources
36
-
37
- ### New Resources (from scratch)
38
-
39
- Use the scaffold generator to create all four layers at once:
40
-
41
- ```bash
42
- rails g pu:res:scaffold Post title:string content:text:required published:boolean
43
- ```
44
-
45
- This generates:
46
- - `app/models/post.rb` - Model with validations
47
- - `app/definitions/post_definition.rb` - Definition (empty, uses auto-detection)
48
- - `app/policies/post_policy.rb` - Policy with sensible defaults
49
- - `app/controllers/posts_controller.rb` - Controller (empty, inherits CRUD)
50
- - Migration file
51
-
52
- See `plutonium-create-resource` skill for full generator options.
53
-
54
- ### From Existing Models
55
-
56
- For existing Rails projects, you can convert models to Plutonium resources:
57
-
58
- 1. **Include the module** in your model:
59
-
60
- ```ruby
61
- class Post < ApplicationRecord
62
- include Plutonium::Resource::Record
63
- # Your existing code...
64
- end
65
- ```
66
-
67
- Or inherit from a base class that includes it:
68
-
69
- ```ruby
70
- class Post < ResourceRecord
71
- # Your existing code...
72
- end
73
- ```
74
-
75
- 2. **Generate the supporting files** (definition, policy, controller):
76
-
77
- ```bash
78
- rails g pu:res:scaffold Post --no-migration
79
- ```
80
-
81
- This creates definition, policy, and controller without touching your existing model.
82
-
83
- 3. **Connect to a portal**:
84
-
85
- ```bash
86
- rails g pu:res:conn Post --dest=admin_portal
87
- ```
88
-
89
- ## Connecting to Portals
90
-
91
- Resources must be connected to a portal to be accessible:
92
-
93
- ```bash
94
- rails g pu:res:conn Post --dest=admin_portal
95
- ```
96
-
97
- This:
98
- - Registers the resource in portal routes
99
- - Creates portal-specific controller
100
- - Creates portal-specific policy with attribute permissions
101
-
102
- See `plutonium-connect-resource` skill for details.
103
-
104
- ## Layer Responsibilities
105
-
106
- ### Model (Data Layer)
107
-
108
- ```ruby
109
- class Post < ResourceRecord
110
- belongs_to :author, class_name: "User"
111
- has_many :comments
112
-
113
- validates :title, presence: true
114
-
115
- scope :published, -> { where(published: true) }
116
- end
117
- ```
118
-
119
- The model handles:
120
- - Database schema and associations
121
- - Data validation
122
- - Business logic scopes
123
- - Callbacks
124
-
125
- **Skills:** `plutonium-model`, `plutonium-model-features`
126
-
127
- ### Definition (UI Layer)
128
-
129
- ```ruby
130
- class PostDefinition < ResourceDefinition
131
- # Override auto-detected field types
132
- input :content, as: :markdown
133
-
134
- # Add filters and scopes
135
- filter :published, with: Plutonium::Query::Filters::Boolean
136
- scope :published
137
-
138
- # Add actions
139
- action :publish, interaction: PublishPostInteraction
140
- end
141
- ```
142
-
143
- The definition handles:
144
- - Field type overrides (auto-detection handles most cases)
145
- - Form input customization
146
- - Display formatting
147
- - Search, filters, scopes, sorting
148
- - Actions (interactive operations)
149
-
150
- **Skills:** `plutonium-definition`, `plutonium-definition-fields`, `plutonium-definition-actions`, `plutonium-definition-query`
151
-
152
- ### Policy (Authorization Layer)
153
-
154
- ```ruby
155
- class PostPolicy < ResourcePolicy
156
- # Who can perform actions
157
- def create?
158
- user.present?
159
- end
160
-
161
- def read?
162
- true
163
- end
164
-
165
- def publish?
166
- user.admin? || record.author == user
167
- end
168
-
169
- # What records are visible
170
- relation_scope do |relation|
171
- return relation if user.admin?
172
- relation.where(author: user)
173
- end
174
-
175
- # What attributes are readable/writable
176
- def permitted_attributes_for_read
177
- %i[title content published author created_at]
178
- end
179
-
180
- def permitted_attributes_for_create
181
- %i[title content]
182
- end
183
- end
184
- ```
185
-
186
- The policy handles:
187
- - Action authorization (create?, update?, destroy?, custom actions)
188
- - Resource scoping (what records user can see)
189
- - Attribute permissions (read/write access per field)
190
-
191
- **Skill:** `plutonium-policy`
192
-
193
- ### Controller (Request Layer)
194
-
195
- ```ruby
196
- class PostsController < ::ResourceController
197
- # Empty - all CRUD actions inherited automatically
198
- end
199
- ```
200
-
201
- Controllers are usually empty because they inherit full CRUD functionality. Customize only when needed:
202
-
203
- ```ruby
204
- class PostsController < ::ResourceController
205
- private
206
-
207
- def preferred_action_after_submit
208
- "index" # Redirect to list instead of show
209
- end
210
- end
211
- ```
212
-
213
- The controller handles:
214
- - Request/response cycle
215
- - Redirect logic
216
- - Custom parameter processing
217
- - Non-standard authorization flows
218
-
219
- **Skill:** `plutonium-controller`
220
-
221
- ## Auto-Detection
222
-
223
- Plutonium automatically detects from your model:
224
- - All database columns with appropriate field types
225
- - Associations (belongs_to, has_one, has_many)
226
- - Attachments (Active Storage)
227
- - Enums
228
-
229
- **You only need to declare when overriding defaults.**
230
-
231
- ## Portal-Specific Customization
232
-
233
- Each portal can have its own definition that overrides the base:
234
-
235
- ```ruby
236
- # Base definition
237
- class PostDefinition < ResourceDefinition
238
- scope :published
239
- end
240
-
241
- # Admin portal sees more
242
- class AdminPortal::PostDefinition < ::PostDefinition
243
- scope :draft
244
- scope :pending_review
245
- action :feature, interaction: FeaturePostInteraction
246
- end
247
-
248
- # Public portal is restricted
249
- class PublicPortal::PostDefinition < ::PostDefinition
250
- # Only published scope, no actions
251
- end
252
- ```
253
-
254
- ## Workflow Summary
255
-
256
- 1. **Generate** - `rails g pu:res:scaffold Model attributes... --dest=main_app`
257
- 2. **Connect** - `rails g pu:res:conn Model --dest=portal_name`
258
- 3. **Customize** - Edit definition/policy as needed (model rarely needs changes)
259
- 4. **Override per portal** - Create portal-specific definitions when needed
260
-
261
- ## Related Skills
262
-
263
- - `plutonium-model` - Model structure and organization
264
- - `plutonium-model-features` - has_cents, associations, scopes, routes
265
- - `plutonium-definition` - Definition overview and structure
266
- - `plutonium-definition-fields` - Fields, inputs, displays, columns
267
- - `plutonium-definition-actions` - Actions and interactions
268
- - `plutonium-interaction` - Writing interaction classes
269
- - `plutonium-definition-query` - Search, filters, scopes, sorting
270
- - `plutonium-policy` - Authorization and permissions
271
- - `plutonium-controller` - Controller customization
272
- - `plutonium-views` - Custom pages, displays, tables using Phlex
273
- - `plutonium-forms` - Custom form templates and field builders
274
- - `plutonium-assets` - TailwindCSS and component theming
275
- - `plutonium-package` - Feature and portal packages
276
- - `plutonium-portal` - Portal configuration and entity scoping
277
- - `plutonium-nested-resources` - Parent/child routes and scoping
278
- - `plutonium-installation` - Setting up Plutonium
279
- - `plutonium-rodauth` - Authentication setup
280
- - `plutonium-create-resource` - Scaffold generator details
281
- - `plutonium-connect-resource` - Portal connection details