plutonium 0.34.1 → 0.35.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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/skill.md +53 -0
  3. data/.claude/skills/{assets → plutonium-assets}/SKILL.md +13 -8
  4. data/.claude/skills/{connect-resource → plutonium-connect-resource}/SKILL.md +1 -1
  5. data/.claude/skills/{controller → plutonium-controller}/SKILL.md +27 -13
  6. data/.claude/skills/{create-resource → plutonium-create-resource}/SKILL.md +1 -1
  7. data/.claude/skills/{definition → plutonium-definition}/SKILL.md +10 -10
  8. data/.claude/skills/{definition-actions → plutonium-definition-actions}/SKILL.md +34 -9
  9. data/.claude/skills/{definition-fields → plutonium-definition-fields}/SKILL.md +38 -10
  10. data/.claude/skills/plutonium-definition-query/SKILL.md +356 -0
  11. data/.claude/skills/{forms → plutonium-forms}/SKILL.md +6 -6
  12. data/.claude/skills/{installation → plutonium-installation}/SKILL.md +9 -9
  13. data/.claude/skills/{interaction → plutonium-interaction}/SKILL.md +20 -19
  14. data/.claude/skills/{model → plutonium-model}/SKILL.md +3 -3
  15. data/.claude/skills/{model-features → plutonium-model-features}/SKILL.md +3 -3
  16. data/.claude/skills/{nested-resources → plutonium-nested-resources}/SKILL.md +5 -5
  17. data/.claude/skills/{package → plutonium-package}/SKILL.md +7 -8
  18. data/.claude/skills/{policy → plutonium-policy}/SKILL.md +26 -4
  19. data/.claude/skills/{portal → plutonium-portal}/SKILL.md +33 -31
  20. data/.claude/skills/{resource → plutonium-resource}/SKILL.md +27 -27
  21. data/.claude/skills/{rodauth → plutonium-rodauth}/SKILL.md +5 -5
  22. data/.claude/skills/plutonium-theming/SKILL.md +424 -0
  23. data/.claude/skills/{views → plutonium-views}/SKILL.md +7 -7
  24. data/CHANGELOG.md +52 -0
  25. data/CLAUDE.md +215 -0
  26. data/CONTRIBUTING.md +72 -18
  27. data/README.md +100 -19
  28. data/app/assets/plutonium.css +1 -11
  29. data/app/assets/plutonium.js +1685 -1146
  30. data/app/assets/plutonium.js.map +4 -4
  31. data/app/assets/plutonium.min.js +70 -70
  32. data/app/assets/plutonium.min.js.map +4 -4
  33. data/app/views/resource/interactive_bulk_action.html.erb +1 -5
  34. data/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
  35. data/app/views/rodauth/_login_form.html.erb +15 -55
  36. data/app/views/rodauth/_login_form_footer.html.erb +2 -2
  37. data/app/views/rodauth/_password_visibility.html.erb +2 -8
  38. data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
  39. data/app/views/rodauth/change_login.html.erb +36 -19
  40. data/app/views/rodauth/change_password.html.erb +34 -10
  41. data/app/views/rodauth/close_account.html.erb +12 -4
  42. data/app/views/rodauth/confirm_password.html.erb +19 -17
  43. data/app/views/rodauth/create_account.html.erb +30 -109
  44. data/app/views/rodauth/email_auth.html.erb +1 -1
  45. data/app/views/rodauth/logout.html.erb +4 -4
  46. data/app/views/rodauth/otp_auth.html.erb +13 -4
  47. data/app/views/rodauth/otp_disable.html.erb +12 -4
  48. data/app/views/rodauth/otp_setup.html.erb +29 -12
  49. data/app/views/rodauth/otp_unlock.html.erb +19 -10
  50. data/app/views/rodauth/otp_unlock_not_available.html.erb +7 -7
  51. data/app/views/rodauth/recovery_auth.html.erb +12 -4
  52. data/app/views/rodauth/recovery_codes.html.erb +12 -4
  53. data/app/views/rodauth/remember.html.erb +7 -7
  54. data/app/views/rodauth/reset_password.html.erb +23 -7
  55. data/app/views/rodauth/reset_password_request.html.erb +14 -10
  56. data/app/views/rodauth/sms_auth.html.erb +13 -4
  57. data/app/views/rodauth/sms_confirm.html.erb +13 -4
  58. data/app/views/rodauth/sms_disable.html.erb +12 -4
  59. data/app/views/rodauth/sms_request.html.erb +1 -1
  60. data/app/views/rodauth/sms_setup.html.erb +23 -7
  61. data/app/views/rodauth/two_factor_auth.html.erb +2 -2
  62. data/app/views/rodauth/two_factor_disable.html.erb +12 -4
  63. data/app/views/rodauth/two_factor_manage.html.erb +7 -7
  64. data/app/views/rodauth/unlock_account.html.erb +13 -5
  65. data/app/views/rodauth/unlock_account_request.html.erb +2 -2
  66. data/app/views/rodauth/verify_account.html.erb +25 -7
  67. data/app/views/rodauth/verify_account_resend.html.erb +14 -10
  68. data/app/views/rodauth/verify_login_change.html.erb +1 -1
  69. data/app/views/rodauth/webauthn_auth.html.erb +1 -1
  70. data/app/views/rodauth/webauthn_remove.html.erb +18 -8
  71. data/app/views/rodauth/webauthn_setup.html.erb +12 -4
  72. data/docs/.vitepress/config.ts +15 -26
  73. data/docs/.vitepress/theme/custom.css +388 -29
  74. data/docs/getting-started/index.md +1 -1
  75. data/docs/getting-started/tutorial/02-first-resource.md +9 -0
  76. data/docs/getting-started/tutorial/06-nested-resources.md +2 -2
  77. data/docs/getting-started/tutorial/07-author-portal.md +191 -0
  78. data/docs/getting-started/tutorial/{07-customizing-ui.md → 08-customizing-ui.md} +7 -7
  79. data/docs/getting-started/tutorial/index.md +5 -2
  80. data/docs/guides/authorization.md +33 -0
  81. data/docs/guides/creating-packages.md +12 -16
  82. data/docs/guides/custom-actions.md +36 -0
  83. data/docs/guides/search-filtering.md +121 -42
  84. data/docs/guides/theming.md +232 -36
  85. data/docs/index.md +203 -57
  86. data/docs/public/og-image.png +0 -0
  87. data/docs/reference/controller/index.md +14 -16
  88. data/docs/reference/definition/actions.md +38 -3
  89. data/docs/reference/definition/fields.md +3 -3
  90. data/docs/reference/definition/index.md +2 -2
  91. data/docs/reference/generators/index.md +0 -1
  92. data/docs/reference/interaction/index.md +14 -10
  93. data/docs/reference/model/index.md +0 -1
  94. data/docs/reference/portal/index.md +13 -27
  95. data/gemfiles/rails_7.gemfile.lock +1 -1
  96. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  97. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  98. data/lib/generators/pu/pkg/portal/portal_generator.rb +0 -2
  99. data/lib/generators/pu/pkg/portal/templates/app/views/package/dashboard/index.html.erb +28 -72
  100. data/lib/plutonium/action/interactive.rb +2 -2
  101. data/lib/plutonium/core/controller.rb +2 -1
  102. data/lib/plutonium/definition/actions.rb +2 -2
  103. data/lib/plutonium/lib/deep_freezer.rb +3 -7
  104. data/lib/plutonium/query/filter.rb +14 -0
  105. data/lib/plutonium/query/filters/association.rb +49 -0
  106. data/lib/plutonium/query/filters/boolean.rb +35 -0
  107. data/lib/plutonium/query/filters/date.rb +97 -0
  108. data/lib/plutonium/query/filters/date_range.rb +58 -0
  109. data/lib/plutonium/query/filters/select.rb +55 -0
  110. data/lib/plutonium/resource/controllers/crud_actions.rb +24 -6
  111. data/lib/plutonium/resource/controllers/interactive_actions.rb +76 -58
  112. data/lib/plutonium/resource/controllers/queryable.rb +4 -2
  113. data/lib/plutonium/resource/query_object.rb +1 -1
  114. data/lib/plutonium/ui/action_button.rb +23 -65
  115. data/lib/plutonium/ui/actions_dropdown.rb +103 -0
  116. data/lib/plutonium/ui/block.rb +1 -1
  117. data/lib/plutonium/ui/breadcrumbs.rb +12 -19
  118. data/lib/plutonium/ui/color_mode_selector.rb +1 -1
  119. data/lib/plutonium/ui/component/kit.rb +6 -0
  120. data/lib/plutonium/ui/component_classes.rb +102 -0
  121. data/lib/plutonium/ui/display/base.rb +15 -0
  122. data/lib/plutonium/ui/display/components/attachment.rb +6 -5
  123. data/lib/plutonium/ui/display/components/boolean.rb +23 -0
  124. data/lib/plutonium/ui/display/components/color.rb +23 -0
  125. data/lib/plutonium/ui/display/resource.rb +1 -1
  126. data/lib/plutonium/ui/display/theme.rb +29 -15
  127. data/lib/plutonium/ui/empty_card.rb +3 -3
  128. data/lib/plutonium/ui/form/base.rb +20 -0
  129. data/lib/plutonium/ui/form/components/key_value_store.rb +11 -11
  130. data/lib/plutonium/ui/form/components/resource_select.rb +31 -0
  131. data/lib/plutonium/ui/form/components/secure_association.rb +1 -2
  132. data/lib/plutonium/ui/form/components/uppy.rb +5 -4
  133. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +4 -4
  134. data/lib/plutonium/ui/form/interaction.rb +17 -1
  135. data/lib/plutonium/ui/form/query.rb +133 -80
  136. data/lib/plutonium/ui/form/theme.rb +50 -35
  137. data/lib/plutonium/ui/frame_navigator_panel.rb +2 -2
  138. data/lib/plutonium/ui/layout/base.rb +1 -1
  139. data/lib/plutonium/ui/layout/header.rb +4 -7
  140. data/lib/plutonium/ui/layout/rodauth_layout.rb +7 -7
  141. data/lib/plutonium/ui/layout/sidebar.rb +1 -1
  142. data/lib/plutonium/ui/nav_grid_menu.rb +7 -6
  143. data/lib/plutonium/ui/nav_user.rb +9 -8
  144. data/lib/plutonium/ui/page/interactive_action.rb +5 -5
  145. data/lib/plutonium/ui/page_header.rb +29 -10
  146. data/lib/plutonium/ui/panel.rb +4 -4
  147. data/lib/plutonium/ui/sidebar_menu.rb +8 -8
  148. data/lib/plutonium/ui/skeleton_table.rb +7 -8
  149. data/lib/plutonium/ui/tab_list.rb +5 -5
  150. data/lib/plutonium/ui/table/base.rb +3 -0
  151. data/lib/plutonium/ui/table/components/attachment.rb +4 -3
  152. data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +82 -0
  153. data/lib/plutonium/ui/table/components/pagy_info.rb +2 -2
  154. data/lib/plutonium/ui/table/components/pagy_pagination.rb +13 -8
  155. data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +101 -0
  156. data/lib/plutonium/ui/table/components/scopes_bar.rb +2 -2
  157. data/lib/plutonium/ui/table/components/selection_column.rb +100 -0
  158. data/lib/plutonium/ui/table/display_theme.rb +6 -6
  159. data/lib/plutonium/ui/table/resource.rb +93 -52
  160. data/lib/plutonium/ui/table/theme.rb +28 -15
  161. data/lib/plutonium/version.rb +1 -1
  162. data/package.json +2 -2
  163. data/plutonium.gemspec +5 -4
  164. data/src/css/components.css +471 -0
  165. data/src/css/intl_tel_input.css +2 -2
  166. data/src/css/plutonium.css +2 -0
  167. data/src/css/tokens.css +149 -0
  168. data/src/js/controllers/bulk_actions_controller.js +109 -0
  169. data/src/js/controllers/filter_panel_controller.js +35 -0
  170. data/src/js/controllers/register_controllers.js +5 -1
  171. data/src/js/controllers/resource_drop_down_controller.js +25 -1
  172. data/src/js/controllers/slim_select_controller.js +6 -2
  173. data/src/js/turbo/turbo_actions.js +1 -1
  174. metadata +52 -39
  175. data/.claude/skills/definition-query/SKILL.md +0 -334
  176. data/docs/concepts/architecture.md +0 -226
  177. data/docs/concepts/auto-detection.md +0 -254
  178. data/docs/concepts/index.md +0 -61
  179. data/docs/concepts/packages-portals.md +0 -304
  180. data/docs/concepts/resources.md +0 -224
  181. data/docs/cookbook/blog.md +0 -411
  182. data/docs/cookbook/index.md +0 -289
  183. data/docs/cookbook/saas.md +0 -481
  184. data/docs/public/CLAUDE.md +0 -578
  185. data/lib/generators/pu/pkg/portal/templates/app/controllers/resource_controller.rb.tt +0 -5
@@ -1,578 +0,0 @@
1
- # Plutonium Framework Development Guide
2
-
3
- This guide helps AI agents understand and build Plutonium applications effectively. Plutonium is a Rails RAD framework that extends Rails conventions with application-level concepts.
4
-
5
- ## Quick Start
6
-
7
- ### New Application
8
- ```bash
9
- rails new app_name -a propshaft -j esbuild -c tailwind \
10
- -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
11
- ```
12
-
13
- ### Essential Commands
14
- ```bash
15
- # Setup auth
16
- rails generate pu:rodauth:install
17
- rails generate pu:rodauth:account user
18
-
19
- # Create feature + portal
20
- rails generate pu:pkg:package blog_management
21
- rails generate pu:pkg:portal admin_portal
22
-
23
- # Create complete resource (always specify --dest to avoid prompts)
24
- rails generate pu:res:scaffold Post user:belongs_to title:string content:text --dest=blog_management
25
- rails generate pu:res:scaffold User name:string email:string:uniq --dest=main_app # main app resource
26
-
27
- # Connect to portal
28
- rails generate pu:res:conn BlogManagement::Post --dest=admin_portal
29
- rails generate pu:res:conn User --dest=admin_portal # main app resource (no namespace)
30
- ```
31
-
32
- ### Start Building
33
- ```bash
34
- rails db:migrate
35
- bin/dev # Visit http://localhost:3000
36
- ```
37
-
38
- ## Core Concepts
39
-
40
- ### 1. Architecture
41
- - **Packages**: Modular organization using Rails engines
42
- - **Feature Packages**: Contain business logic (models, interactions, policies)
43
- - **Portal Packages**: Provide web interfaces with authentication
44
- - **Resources**: Complete CRUD entities with models, definitions, policies, controllers
45
- - **Entity Scoping**: Built-in multi-tenancy support
46
-
47
- ### 2. Key Components
48
- - **Models**: ActiveRecord with `ResourceRecord` base class for enhanced functionality
49
- - **Definitions**: Declarative UI configuration (how fields render)
50
- - **Policies**: Authorization control (what users can access)
51
- - **Interactions**: Business logic encapsulation (what actions do)
52
- - **Controllers**: Auto-generated CRUD with customization points
53
-
54
- ## Essential Generators
55
-
56
- ### Project Setup
57
- ```bash
58
- # New app with Plutonium template
59
- rails new app_name -a propshaft -j esbuild -c tailwind \
60
- -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
61
-
62
- # Add to existing app
63
- rails generate pu:core:install
64
- ```
65
-
66
- ### Authentication Setup (Optional)
67
-
68
- Authentication is optional in Plutonium. You can build apps without auth using `Plutonium::Auth::Public`. When you do need authentication:
69
-
70
- #### Basic Setup Workflow
71
- ```bash
72
- # 1. Install Rodauth infrastructure
73
- rails generate pu:rodauth:install
74
-
75
- # 2. Create account types as needed
76
- rails generate pu:rodauth:account user
77
-
78
- # 3. Run migrations
79
- rails db:migrate
80
-
81
- # 4. Connect auth to portals (see Portal Integration below)
82
- ```
83
-
84
- #### Account Type Options
85
- ```bash
86
- # Standard user accounts
87
- rails generate pu:rodauth:account user
88
-
89
- # Enhanced admin accounts with MFA and security features
90
- rails generate pu:rodauth:admin admin
91
-
92
- # Multi-tenant customer accounts with entity scoping
93
- rails generate pu:rodauth:customer Customer --entity=Organization
94
-
95
- # Custom feature set (specify exact features needed)
96
- rails generate pu:rodauth:account api_user \
97
- --no-defaults --login --logout --jwt --verify-account
98
- ```
99
-
100
- #### Portal Integration
101
- ```ruby
102
- # For authenticated portals - add to controller concerns:
103
- include Plutonium::Auth::Rodauth(:user) # Links to user auth
104
- include Plutonium::Auth::Rodauth(:admin) # Links to admin auth
105
-
106
- # For public portals - no authentication required:
107
- include Plutonium::Auth::Public
108
-
109
- # This provides: current_user, logout_url helpers in controllers/views
110
- ```
111
-
112
- #### Available Auth Features
113
- - **Core**: `login`, `logout`, `create_account`, `verify_account`, `reset_password`, `change_password`
114
- - **Security**: `remember`, `lockout`, `active_sessions`, `audit_logging`
115
- - **MFA**: `otp`, `recovery_codes`, `sms_codes`, `webauthn`
116
- - **Advanced**: `password_grace_period`, `single_session`, `jwt`, `internal_request`
117
-
118
- ### Package Generators
119
- ```bash
120
- # Create feature package for business logic
121
- rails generate pu:pkg:package blog_management
122
-
123
- # Create portal package for web interface
124
- rails generate pu:pkg:portal admin_dashboard
125
- ```
126
-
127
- ### Resource Generators (Most Important)
128
-
129
- **Always specify `--dest`** to avoid interactive prompts:
130
-
131
- ```bash
132
- # Main app resource (not in a package)
133
- rails generate pu:res:scaffold Post user:belongs_to title:string 'content:text?' --dest=main_app
134
-
135
- # Package resource
136
- rails generate pu:res:scaffold Post user:belongs_to title:string content:text 'published_at:datetime?' --dest=blogging
137
-
138
- # Referencing namespaced models in associations
139
- rails generate pu:res:scaffold Comment user:belongs_to blogging/post:belongs_to body:text --dest=comments
140
- rails generate pu:res:scaffold Order customer:belongs_to inventory/product:belongs_to quantity:integer --dest=commerce
141
-
142
- # Model only
143
- rails generate pu:res:model Article title:string body:text author:belongs_to --dest=blogging
144
- rails generate pu:res:model Review user:belongs_to inventory/product:belongs_to rating:integer --dest=reviews
145
-
146
- # CRITICAL: Use connection generator to expose resources in portals
147
- # Always use the generator - NEVER manually connect resources
148
-
149
- # Use full class name for namespaced resources
150
- rails generate pu:res:conn BlogManagement::Post BlogManagement::Comment --dest=admin_portal
151
-
152
- # Main app resources (not namespaced)
153
- rails generate pu:res:conn Post Comment --dest=admin_portal
154
-
155
- # The generator handles all routing, controller, and policy connections automatically
156
- ```
157
-
158
- ### Other Useful Generators
159
- ```bash
160
- # Entity for multi-tenancy
161
- rails generate pu:res:entity Organization --auth-account=Customer
162
-
163
- # Eject components for customization
164
- rails generate pu:eject:layout --dest=admin_portal
165
- rails generate pu:eject:shell --dest=admin_portal
166
-
167
- # Development tools
168
- rails generate pu:gem:annotated # Model annotations
169
- rails generate pu:gem:standard # Ruby Standard linter
170
- rails generate pu:gem:dotenv # Environment variables
171
- ```
172
-
173
- ## File Structure Patterns
174
-
175
- ### Feature Package Structure
176
- ```
177
- packages/blog_management/
178
- ├── lib/
179
- │ └── engine.rb # Package engine
180
- └── app/
181
- ├── models/blog_management/
182
- │ ├── post.rb # Business models
183
- │ └── resource_record.rb # Base class
184
- ├── policies/blog_management/
185
- │ ├── post_policy.rb # Authorization rules
186
- │ └── resource_policy.rb # Base policy
187
- ├── definitions/blog_management/
188
- │ ├── post_definition.rb # UI configuration
189
- │ └── resource_definition.rb # Base definition
190
- ├── interactions/blog_management/
191
- │ └── post_interactions/
192
- │ └── publish.rb # Business logic
193
- └── controllers/blog_management/
194
- └── posts_controller.rb # Optional custom controller
195
- ```
196
-
197
- ### Portal Package Structure
198
- ```
199
- packages/admin_portal/
200
- ├── lib/
201
- │ └── engine.rb # Portal engine
202
- ├── config/
203
- │ └── routes.rb # Portal routes
204
- └── app/
205
- ├── controllers/admin_portal/
206
- │ ├── concerns/controller.rb # Auth integration
207
- │ ├── dashboard_controller.rb # Dashboard
208
- │ ├── plutonium_controller.rb # Base controller
209
- │ └── resource_controller.rb # Resource base
210
- ├── policies/admin_portal/
211
- │ └── resource_policy.rb # Portal-specific policies
212
- └── definitions/admin_portal/
213
- └── resource_definition.rb # Portal-specific definitions
214
- ```
215
-
216
- ## Development Patterns
217
-
218
- ### 1. Resource Definition (UI Configuration)
219
-
220
- **CRITICAL**: Plutonium automatically detects all fields from your model - database columns, associations, and Active Storage attachments. Definitions should only contain customizations, not field declarations.
221
-
222
- ```ruby
223
- class PostDefinition < Plutonium::Resource::Definition
224
- # NO NEED to declare fields - they're auto-detected from the model
225
- # Only add customizations below when you want to change default behavior
226
-
227
- # Form inputs (only override when changing auto-detected behavior)
228
- input :content, as: :rich_text
229
- input :published_at, as: :date
230
- input :category, as: :select, choices: %w[Tech Business]
231
-
232
- # Display formatting (only override when changing auto-detected behavior)
233
- display :content, as: :markdown
234
- display :published_at, as: :datetime
235
-
236
- # Table columns (only override when changing auto-detected behavior)
237
- column :published_at, as: :datetime
238
-
239
- # Search functionality
240
- search do |scope, query|
241
- scope.where("title ILIKE ? OR content ILIKE ?", "%#{query}%", "%#{query}%")
242
- end
243
-
244
- # Filters (currently only Text filter available)
245
- filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
246
-
247
- # Scopes (named scopes defined on the model. they that appear as filter buttons)
248
- scope :published
249
- scope :drafts
250
-
251
- # Custom actions
252
- action :publish, interaction: PostInteractions::Publish
253
- action :archive, interaction: PostInteractions::Archive
254
- end
255
- ```
256
-
257
- ### 2. Policy Configuration (Authorization)
258
- ```ruby
259
- class PostPolicy < Plutonium::Resource::Policy
260
- # Basic permissions (required - secure by default)
261
- def create?
262
- user.present?
263
- end
264
-
265
- def update?
266
- record.user_id == user.id || user.admin?
267
- end
268
-
269
- def destroy?
270
- user.admin?
271
- end
272
-
273
- # Custom action permissions
274
- def publish?
275
- update? && record.published_at.nil?
276
- end
277
-
278
- # Attribute permissions (what fields are visible/editable)
279
- def permitted_attributes_for_read
280
- attrs = [:title, :content, :published_at, :created_at]
281
- attrs << :admin_notes if user.admin?
282
- attrs
283
- end
284
-
285
- def permitted_attributes_for_create
286
- [:title, :content, :user_id]
287
- end
288
-
289
- def permitted_attributes_for_update
290
- permitted_attributes_for_create
291
- end
292
-
293
- # Data scoping (what records are visible)
294
- relation_scope do |scope|
295
- scope = super(scope) # Important: call super for entity scoping
296
-
297
- if user.admin?
298
- scope
299
- else
300
- scope.where(user: user).or(scope.where(published: true))
301
- end
302
- end
303
- end
304
- ```
305
-
306
- ### 3. Interaction Implementation (Business Logic)
307
- ```ruby
308
- module PostInteractions
309
- class Publish < Plutonium::Resource::Interaction
310
- # Define what this interaction accepts
311
- attribute :resource, class: "Post"
312
- attribute :published_at, :datetime, default: -> { Time.current }
313
-
314
- # UI presentation
315
- presents label: "Publish Post",
316
- icon: Phlex::TablerIcons::Send,
317
- description: "Make this post public"
318
-
319
- # Validations
320
- validates :resource, presence: true
321
-
322
- private
323
-
324
- # Business logic
325
- def execute
326
- if resource.update(published_at: published_at, status: 'published')
327
- succeed(resource)
328
- .with_message("Post published successfully")
329
- .with_redirect_response(resource_url_for(resource))
330
- else
331
- failed(resource.errors)
332
- end
333
- end
334
- end
335
- end
336
- ```
337
-
338
- ### 4. Model Setup
339
- ```ruby
340
- class Post < BlogManagement::ResourceRecord
341
- # Associations
342
- belongs_to :user
343
- has_many :comments, dependent: :destroy
344
- has_one_attached :featured_image
345
-
346
- # Validations
347
- validates :title, presence: true
348
- validates :content, presence: true
349
-
350
- # Enums
351
- enum status: { draft: 0, published: 1, archived: 2 }
352
-
353
- # Scopes
354
- scope :published, -> { where.not(published_at: nil) }
355
- scope :recent, -> { order(created_at: :desc) }
356
-
357
- # Monetary fields (if needed)
358
- has_cents :price_cents
359
-
360
- # Custom path parameters (class methods, not instance methods)
361
- # path_parameter :username # Uses username in URLs
362
- # dynamic_path_parameter :title # Creates SEO URLs like "1-my-title"
363
-
364
- # Custom labeling (optional)
365
- def to_label
366
- title.presence || "Post ##{id}"
367
- end
368
- end
369
-
370
- # Example with cross-package associations
371
- class Comment < Comments::ResourceRecord
372
- belongs_to :user
373
- belongs_to :post, class_name: "Blogging::Post" # Cross-package reference
374
-
375
- validates :body, presence: true
376
- end
377
- ```
378
-
379
- ## Best Practices
380
-
381
- ### 1. File Organization
382
- - Use packages to organize related features
383
- - Keep business logic in feature packages
384
- - Use portal packages for different user interfaces
385
- - Follow namespacing conventions strictly
386
-
387
- ### 2. Security First
388
- - Always define explicit permissions in policies
389
- - Use relation_scope for data access control
390
- - Leverage entity scoping for multi-tenancy
391
- - Test authorization thoroughly
392
-
393
- ### 3. Generator Usage
394
- - Start with `pu:res:scaffold` for complete resources
395
- - Use `--dest=package_name` to specify target package
396
- - **CRITICAL**: Use `pu:res:conn` generator to connect resources to portals - never manually connect
397
- - Definitions only need overrides - auto-detection handles defaults
398
-
399
- ### 4. UI Customization
400
- - Policies control WHAT (authorization)
401
- - Definitions control HOW (presentation) - **fields are auto-detected, only add customizations**
402
- - Interactions control business logic
403
- - Trust auto-detection, customize only when needed
404
-
405
- ### 5. Common Field Types
406
- **Input Types**: `:string`, `:text`, `:rich_text`, `:email`, `:url`, `:tel`, `:password`, `:number`, `:boolean`, `:date`, `:datetime`, `:select`, `:file`, `:uppy`, `:association`
407
-
408
- **Display Types**: `:string`, `:text`, `:markdown`, `:email`, `:url`, `:boolean`, `:date`, `:datetime`, `:association`, `:attachment`
409
-
410
- **Action Options**: `category: :primary/:secondary/:danger`, `position: 10`, `record_action: true`, `collection_record_action: true`, `resource_action: true`, `bulk_action: true`, `confirmation: "message"`, `icon: Phlex::TablerIcons::IconName`
411
-
412
- ## Migration Tips
413
-
414
- ### Database Setup
415
- - Use standard Rails migration conventions
416
- - Always inline indexes and constraints in create_table blocks
417
- - Leverage Rails associations (`belongs_to`, `has_many`, etc.)
418
-
419
- ### Generator Field Syntax
420
-
421
- **IMPORTANT**: Quote fields containing `?` or `{}` to prevent shell expansion.
422
-
423
- **IMPORTANT**: Always specify `--dest` to avoid interactive prompts:
424
- - `--dest=main_app` for resources in the main application
425
- - `--dest=package_name` for resources in a feature package
426
-
427
- ```bash
428
- # Nullable fields
429
- 'name:string?' # null: true
430
- 'parent:belongs_to?' # null: true + optional: true in model
431
-
432
- # Decimal precision (only works for decimal type)
433
- 'price:decimal{10,2}' # precision: 10, scale: 2
434
- 'amount:decimal?{10,2}' # nullable with precision
435
-
436
- # For default values on other types (boolean, integer, etc.),
437
- # edit the migration manually after generation
438
-
439
- # Indexes
440
- email:string:index # Regular index
441
- email:string:uniq # Unique index
442
-
443
- # Cross-package references
444
- blogging/post:belongs_to # belongs_to :post, class_name: "Blogging::Post"
445
- ```
446
-
447
- ### Cross-Package Associations
448
- ```bash
449
- # Reference models from other packages using package/model syntax
450
- rails generate pu:res:scaffold Comment \
451
- user:belongs_to \
452
- blogging/post:belongs_to \
453
- 'parent:belongs_to?' \
454
- body:text \
455
- --dest=comments
456
-
457
- # Generates:
458
- # belongs_to :post, class_name: "Blogging::Post"
459
- # belongs_to :parent, optional: true
460
- ```
461
-
462
- ### Entity Scoping (Multi-Tenancy) Setup
463
-
464
- Plutonium provides powerful multi-tenancy through Entity Scoping, which automatically isolates data by tenant.
465
-
466
- #### 1. Configure Portal Engine
467
- ```ruby
468
- # In packages/admin_portal/lib/engine.rb
469
- config.after_initialize do
470
- scope_to_entity Organization, strategy: :path # URLs: /organizations/:organization_id/posts
471
-
472
- # Custom strategy (subdomain-based)
473
- scope_to_entity Organization, strategy: :current_organization # URLs: /posts on acme.app.com
474
-
475
- # Custom parameter name
476
- scope_to_entity Client, strategy: :path, param_key: :client_slug # URLs: /clients/:client_slug/posts
477
- end
478
- ```
479
-
480
- #### 2. Implement Custom Strategy Methods
481
- ```ruby
482
- # In packages/customer_portal/app/controllers/customer_portal/concerns/controller.rb
483
- private
484
-
485
- def current_organization
486
- @current_organization ||= begin
487
- organization = Organization.find_by!(subdomain: request.subdomain)
488
-
489
- # CRITICAL: Verify user has access to this organization
490
- unless current_user.organizations.include?(organization)
491
- raise ActionPolicy::Unauthorized, "Access denied to organization"
492
- end
493
-
494
- organization
495
- end
496
- rescue ActiveRecord::RecordNotFound
497
- redirect_to root_path, error: "Invalid organization subdomain"
498
- end
499
- ```
500
-
501
- #### 3. Model Association Setup
502
- ```ruby
503
- # Direct association (preferred)
504
- class Post < ApplicationRecord
505
- belongs_to :organization # Direct link
506
- end
507
-
508
- # Indirect association (automatic chain discovery)
509
- class Comment < ApplicationRecord
510
- belongs_to :post
511
- has_one :organization, through: :post # Chain: Comment -> Post -> Organization
512
- end
513
-
514
- # Custom scope for complex relationships
515
- class Invoice < ApplicationRecord
516
- belongs_to :customer
517
-
518
- scope :associated_with_organization, ->(organization) do
519
- joins(customer: :organization_memberships)
520
- .where(organization_memberships: { organization_id: organization.id })
521
- end
522
- end
523
- ```
524
-
525
- #### 4. Policy Integration
526
- ```ruby
527
- class PostPolicy < Plutonium::Resource::Policy
528
- authorize :entity_scope, allow_nil: true # Access to current tenant
529
-
530
- def update?
531
- # Ensure record belongs to current tenant AND user can edit
532
- record.organization == entity_scope && record.author == user
533
- end
534
-
535
- relation_scope do |relation|
536
- relation = super(relation) # Apply entity scoping first
537
-
538
- # Add additional tenant-aware filtering
539
- user.admin? ? relation : relation.where(published: true)
540
- end
541
- end
542
- ```
543
-
544
- ## Quick Reference Commands
545
-
546
- ```bash
547
- # Essential workflow (always use --dest to avoid prompts)
548
- rails generate pu:pkg:package feature_name # Create feature package
549
- rails generate pu:res:scaffold Resource --dest=main_app # Main app resource
550
- rails generate pu:res:scaffold Resource --dest=feature_name # Package resource
551
- rails generate pu:pkg:portal portal_name # Create portal
552
- rails generate pu:res:conn Feature::Resource --dest=portal # CRITICAL: Always use generator to connect
553
-
554
- # Authentication
555
- rails generate pu:rodauth:install # Install auth system
556
- rails generate pu:rodauth:account user # Create user account
557
- rails generate pu:rodauth:admin admin # Create admin account
558
-
559
- # Database
560
- rails db:migrate # Run migrations
561
- rails db:seed # Seed data
562
-
563
- # Development
564
- rails server # Start server
565
- rails console # Rails console
566
- rails runner "code" # Run code (prefer over console)
567
- ```
568
-
569
- ## Troubleshooting
570
-
571
- ### Common Issues
572
- - **Missing permissions**: Check policy methods return true
573
- - **Fields not showing**: Verify policy permits attributes
574
- - **Actions not visible**: Ensure action policy method exists
575
- - **Routing errors**: Check portal routes registration
576
- - **Package not loading**: Verify engine is properly configured
577
-
578
- This guide provides the foundation for building robust Plutonium applications with proper separation of concerns, security, and maintainability.
@@ -1,5 +0,0 @@
1
- module <%= package_name %>
2
- class ResourceController < ::ResourceController
3
- include <%= package_name %>::Concerns::Controller
4
- end
5
- end