plutonium 0.23.4 → 0.23.5

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.css +2 -2
  3. data/config/initializers/sqlite_json_alias.rb +1 -1
  4. data/docs/.vitepress/config.ts +60 -19
  5. data/docs/guide/cursor-rules.md +75 -0
  6. data/docs/guide/deep-dive/authorization.md +189 -0
  7. data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
  8. data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
  9. data/docs/guide/index.md +28 -0
  10. data/docs/guide/introduction/02-core-concepts.md +440 -0
  11. data/docs/guide/tutorial/01-project-setup.md +75 -0
  12. data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
  13. data/docs/guide/tutorial/03-defining-resources.md +90 -0
  14. data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
  15. data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
  16. data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
  17. data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
  18. data/docs/index.md +24 -31
  19. data/docs/modules/action.md +190 -0
  20. data/docs/modules/authentication.md +236 -0
  21. data/docs/modules/configuration.md +599 -0
  22. data/docs/modules/controller.md +398 -0
  23. data/docs/modules/core.md +316 -0
  24. data/docs/modules/definition.md +876 -0
  25. data/docs/modules/display.md +759 -0
  26. data/docs/modules/form.md +605 -0
  27. data/docs/modules/generator.md +288 -0
  28. data/docs/modules/index.md +167 -0
  29. data/docs/modules/interaction.md +470 -0
  30. data/docs/modules/package.md +151 -0
  31. data/docs/modules/policy.md +176 -0
  32. data/docs/modules/portal.md +710 -0
  33. data/docs/modules/query.md +287 -0
  34. data/docs/modules/resource_record.md +618 -0
  35. data/docs/modules/routing.md +641 -0
  36. data/docs/modules/table.md +293 -0
  37. data/docs/modules/ui.md +631 -0
  38. data/docs/public/plutonium.mdc +667 -0
  39. data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
  40. data/lib/plutonium/ui/display/resource.rb +7 -2
  41. data/lib/plutonium/ui/table/resource.rb +8 -3
  42. data/lib/plutonium/version.rb +1 -1
  43. metadata +36 -9
  44. data/docs/guide/getting-started/authorization.md +0 -296
  45. data/docs/guide/getting-started/core-concepts.md +0 -432
  46. data/docs/guide/getting-started/index.md +0 -21
  47. data/docs/guide/tutorial.md +0 -401
  48. /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -0,0 +1,667 @@
1
+ ---
2
+ description: Expert Plutonium framework guidelines for AI-assisted Rails development with auto-detection, resource architecture, component-based UI, and RAD patterns
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+ You are an expert Ruby on Rails developer specializing in the Plutonium framework. This cursor rule compiles comprehensive guidelines for building robust, maintainable Plutonium applications.
7
+
8
+ # Plutonium Framework Development Guidelines
9
+
10
+ ## Framework Overview
11
+
12
+ Plutonium is a Rapid Application Development (RAD) toolkit that extends Rails with powerful conventions, patterns, and tools. It provides:
13
+ - Resource-oriented architecture with declarative definitions
14
+ - Modular packaging system (Feature Packages and Portal Packages)
15
+ - Built-in authentication, authorization, and multi-tenancy
16
+ - Component-based UI system built on Phlex
17
+ - Business logic encapsulation through Interactions
18
+ - Query objects for filtering and searching
19
+
20
+ ## Project Structure & Setup
21
+
22
+ ### Initial Setup
23
+ - Use Ruby 3.2.2+ and Rails 7.1+
24
+ - Create new apps with: `rails new app_name -a propshaft -j esbuild -c tailwind -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb`
25
+ - For existing apps: `bin/rails app:template LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb`
26
+ - Run `rails generate pu:core:install` after adding the gem
27
+
28
+ ### Directory Structure
29
+ ```
30
+ app/
31
+ ├── controllers/
32
+ │ ├── plutonium_controller.rb # Base controller
33
+ │ └── resource_controller.rb # Base for resources
34
+ ├── definitions/ # Resource definitions
35
+ ├── interactions/ # Business logic
36
+ ├── models/
37
+ └── policies/ # Authorization policies
38
+ config/
39
+ ├── initializers/plutonium.rb # Main configuration
40
+ └── packages.rb # Package registration
41
+ packages/ # Modular features
42
+ ```
43
+
44
+ ## Resource Architecture
45
+
46
+ ### Resource Components
47
+ Every resource consists of 4 core components:
48
+ 1. **Model** - ActiveRecord model with `include Plutonium::Resource::Record`
49
+ 2. **Definition** - Declarative UI and behavior configuration
50
+ 3. **Policy** - Authorization and access control
51
+ 4. **Controller** - Auto-generated CRUD operations
52
+
53
+ ### Creating Resources
54
+ ```bash
55
+ # Scaffold a complete resource
56
+ rails generate pu:res:scaffold Post user:belongs_to title:string content:text 'published_at:datetime?'
57
+
58
+ # Individual components
59
+ rails generate pu:res:model Post title:string content:text
60
+ rails generate pu:res:definition Post
61
+ rails generate pu:res:policy Post
62
+ ```
63
+
64
+ ### Resource Models
65
+ ```ruby
66
+ class Post < ApplicationRecord
67
+ include Plutonium::Resource::Record
68
+
69
+ belongs_to :user
70
+ has_many :comments, dependent: :destroy
71
+
72
+ validates :title, :content, presence: true
73
+
74
+ scope :published, -> { where.not(published_at: nil) }
75
+ scope :drafts, -> { where(published_at: nil) }
76
+ end
77
+ ```
78
+
79
+ ### Resource Definitions
80
+ ```ruby
81
+ class PostDefinition < Plutonium::Resource::Definition
82
+ # Field declarations are OPTIONAL - all attributes are auto-detected from the model
83
+ # You only need to declare fields when overriding auto-detected behavior
84
+
85
+ # These would be auto-detected from your Post model:
86
+ # - :title (string column) → :string field
87
+ # - :content (text column) → :text field
88
+ # - :published_at (datetime column) → :datetime field
89
+ # - :author (belongs_to association) → :association field
90
+
91
+ # Only declare fields when you want to override defaults:
92
+ field :content, as: :rich_text # Override text → rich_text
93
+ field :status, as: :select, collection: %w[draft published archived]
94
+
95
+ # Display customization (show/index pages)
96
+ display :title, as: :string
97
+ display :content, as: :markdown
98
+ display :status, as: :string
99
+ display :author, as: :association
100
+
101
+ # Conditional displays (for table columns and show pages)
102
+ # Use for COSMETIC/STATE-BASED logic only, NOT authorization (use policies for that)
103
+ display :debug_info, condition: -> { Rails.env.development? }
104
+ display :internal_notes, condition: -> { object.status == 'draft' }
105
+ display :parent_category, condition: -> { current_parent.present? }
106
+
107
+ # Custom display with block (for complex rendering)
108
+ display :custom_field do |field|
109
+ # Return component instances directly in definition blocks (no 'render' needed)
110
+ CustomComponent.new(value: field.value)
111
+ end
112
+
113
+ # Advanced custom display with phlexi_tag (use sparingly)
114
+ display :status, as: :phlexi_tag, with: ->(value, attrs) {
115
+ # SGML methods available here because proc is evaluated in rendering context
116
+ span(class: "badge badge-#{value}") { value.humanize }
117
+ }
118
+
119
+ # Input configuration (new/edit forms)
120
+ input :title, placeholder: "Enter post title" # No need for as: :string (auto-detected)
121
+ input :content, as: :rich_text
122
+ input :category, as: :select, collection: %w[Tech Business Lifestyle]
123
+
124
+ # Conditional inputs (for forms only)
125
+ # Use for COSMETIC/STATE-BASED logic only, NOT authorization (use policies for that)
126
+ input :debug_info, condition: -> { Rails.env.development? }
127
+ input :internal_notes, condition: -> { object.status == 'draft' }
128
+ input :parent_category, condition: -> { current_parent.present? }
129
+ input :team_projects, condition: -> { current_user.team_lead? }
130
+
131
+ # Search functionality
132
+ search do |scope, query|
133
+ scope.where("title ILIKE ? OR content ILIKE ?", "%#{query}%", "%#{query}%")
134
+ end
135
+
136
+ # Filters
137
+ filter :published, with: Plutonium::Query::Filters::Text, predicate: :eq
138
+ filter :category, with: Plutonium::Query::Filters::Text, predicate: :contains
139
+ filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
140
+
141
+ # Scopes (appear as buttons)
142
+ scope :published
143
+ scope :drafts
144
+ scope :recent, -> { where('created_at > ?', 1.week.ago) }
145
+
146
+ # Custom actions
147
+ action :publish, interaction: PublishPostInteraction, icon: Phlex::TablerIcons::Send
148
+ action :archive, interaction: ArchivePostInteraction, color: :warning
149
+
150
+ # Page customization
151
+ index_page_title "All Posts"
152
+ show_page_title "Post Details"
153
+ end
154
+ ```
155
+
156
+ ### Available Field Types (Auto-Detected from Model)
157
+ - Text: `:string`, `:text`, `:rich_text`, `:markdown`
158
+ - Numeric: `:number`, `:integer`, `:decimal`
159
+ - Boolean: `:boolean`
160
+ - Date/Time: `:date`, `:datetime`, `:time`
161
+ - Selection: `:select`, `:radio_buttons`, `:check_boxes`
162
+ - Files: `:file` (with `multiple: true` for multiple files)
163
+ - Associations: `:association`
164
+ - Special: `:hidden`, `:email`, `:url`, `:color`
165
+
166
+ ### Conditional Rendering Guidelines
167
+
168
+ **IMPORTANT**: Use conditions for COSMETIC/STATE-BASED logic only, NOT for authorization.
169
+
170
+ #### ✅ Appropriate Uses
171
+ - **Environment-based**: Show debug info in development only
172
+ - **Object state-based**: Show fields based on record status
173
+ - **Context-based**: Show fields based on parent presence
174
+ - **Dynamic behavior**: Show dependent fields conditionally
175
+
176
+ ```ruby
177
+ # Environment-based visibility
178
+ display :debug_info, condition: -> { Rails.env.development? }
179
+
180
+ # Object state-based visibility
181
+ display :approval_date, condition: -> { object.approved? }
182
+ input :rejection_reason, condition: -> { object.rejected? }
183
+
184
+ # Parent context-based visibility
185
+ display :parent_category, condition: -> { current_parent.present? }
186
+
187
+ # Dynamic form behavior
188
+ input :end_date, condition: -> { object.start_date.present? }
189
+ ```
190
+
191
+ #### ❌ Inappropriate Uses
192
+ Authorization logic belongs in policies, not conditions:
193
+
194
+ ```ruby
195
+ # DON'T use conditions for role-based authorization
196
+ display :admin_notes, condition: -> { current_user&.admin? } # Use policy instead
197
+ input :sensitive_data, condition: -> { current_user&.can_edit_sensitive? } # Use policy instead
198
+ ```
199
+
200
+ ### Available Configuration Options
201
+
202
+ #### Field Options
203
+ ```ruby
204
+ field :name, as: :string, class: "custom-class", wrapper: {class: "field-wrapper"}
205
+ ```
206
+
207
+ #### Input Options
208
+ ```ruby
209
+ input :title,
210
+ as: :string,
211
+ placeholder: "Enter title",
212
+ required: true,
213
+ class: "custom-input",
214
+ wrapper: {class: "input-wrapper"},
215
+ data: {controller: "custom"},
216
+ condition: -> { object.status == 'draft' } # Cosmetic condition only
217
+ ```
218
+
219
+ #### Display Options
220
+ ```ruby
221
+ display :content,
222
+ as: :markdown,
223
+ class: "prose",
224
+ wrapper: {class: "content-wrapper"},
225
+ condition: -> { Rails.env.development? } # Cosmetic condition only
226
+ ```
227
+
228
+ #### Collection Options (for selects)
229
+ ```ruby
230
+ input :category, as: :select, collection: %w[Tech Business Lifestyle]
231
+ input :author, as: :select, collection: -> { User.active.pluck(:name, :id) }
232
+
233
+ # Collection procs are executed in form rendering context with access to:
234
+ # current_user, current_parent, object, request, params, and helpers
235
+ input :team_members, as: :select, collection: -> {
236
+ current_user.organization.users.active.pluck(:name, :id)
237
+ }
238
+ input :related_posts, as: :select, collection: -> {
239
+ Post.where.not(id: object.id).published.pluck(:title, :id) if object.persisted?
240
+ }
241
+ ```
242
+
243
+ #### File Upload Options
244
+ ```ruby
245
+ input :avatar, as: :file, multiple: false
246
+ input :documents, as: :file, multiple: true
247
+ # Note: Advanced file options like allowed_file_types and max_file_size
248
+ # are not currently supported by the framework
249
+ ```
250
+
251
+ ### Resource Policies
252
+
253
+ Policies control authorization and data access. They define who can perform what actions and what data users can see.
254
+
255
+ ```ruby
256
+ class PostPolicy < Plutonium::Resource::Policy
257
+ # CRUD permissions
258
+ def index?
259
+ true # Everyone can view list
260
+ end
261
+
262
+ def show?
263
+ record.published? || record.author == user || user.admin?
264
+ end
265
+
266
+ def create?
267
+ user.present?
268
+ end
269
+
270
+ def update?
271
+ record.author == user || user.admin?
272
+ end
273
+
274
+ def destroy?
275
+ user.admin?
276
+ end
277
+
278
+ # Custom action permissions
279
+ def publish?
280
+ update? && record.draft?
281
+ end
282
+
283
+ # Attribute permissions - control which fields are accessible
284
+ def permitted_attributes_for_read
285
+ attrs = [:title, :content, :category, :published_at]
286
+ attrs << :internal_notes if user.admin?
287
+ attrs
288
+ end
289
+
290
+ def permitted_attributes_for_create
291
+ [:title, :content, :category]
292
+ end
293
+
294
+ def permitted_attributes_for_update
295
+ attrs = permitted_attributes_for_create
296
+ attrs << :slug if user.admin?
297
+ attrs
298
+ end
299
+
300
+ # Collection scoping - filter what records users can see
301
+ relation_scope do |relation|
302
+ relation = super(relation) # Apply entity scoping first
303
+
304
+ if user.admin?
305
+ relation
306
+ else
307
+ # Users see only their posts or published posts
308
+ relation.where(author: user).or(relation.where(published: true))
309
+ end
310
+ end
311
+
312
+ # Association permissions
313
+ def permitted_associations
314
+ [:comments, :author, :tags]
315
+ end
316
+ end
317
+ ```
318
+
319
+ ## Business Logic with Interactions
320
+
321
+ ### Interaction Structure
322
+ ```ruby
323
+ class PublishPostInteraction < Plutonium::Resource::Interaction
324
+ # Metadata for UI
325
+ presents label: "Publish Post",
326
+ icon: Phlex::TablerIcons::Send,
327
+ description: "Make this post visible to readers"
328
+
329
+ # Attributes (form inputs)
330
+ attribute :resource, class: Post
331
+ attribute :publish_date, :datetime, default: -> { Time.current }
332
+ attribute :notify_subscribers, :boolean, default: true
333
+
334
+ # Validations
335
+ validates :resource, presence: true
336
+ validates :publish_date, presence: true
337
+
338
+ private
339
+
340
+ def execute
341
+ # Business logic
342
+ resource.transaction do
343
+ resource.update!(
344
+ published_at: publish_date,
345
+ status: 'published'
346
+ )
347
+
348
+ # Side effects
349
+ NotifySubscribersJob.perform_later(resource) if notify_subscribers
350
+
351
+ # Return successful outcome
352
+ succeed(resource)
353
+ .with_message("Post published successfully!")
354
+ .with_redirect_response(resource_path(resource))
355
+ end
356
+ rescue => error
357
+ # Return failure outcome
358
+ failed(error.message)
359
+ end
360
+ end
361
+ ```
362
+
363
+ ### Interaction Types (Auto-detected)
364
+ - **Record Actions**: Have `attribute :resource` - work on single records
365
+ - **Bulk Actions**: Have `attribute :resources` - work on multiple records
366
+ - **Resource Actions**: Have neither - work at resource level (imports, etc.)
367
+
368
+ ### Interaction Execution Patterns
369
+ - **Immediate**: Only `resource`/`resources` attribute → executes on click
370
+ - **Modal**: Additional attributes → shows form modal first
371
+
372
+ ## Package Architecture
373
+
374
+ ### Feature Packages
375
+ Contain business logic and domain models:
376
+ ```bash
377
+ rails generate pu:pkg:package blogging
378
+ ```
379
+
380
+ Structure:
381
+ ```
382
+ packages/blogging/
383
+ ├── app/
384
+ │ ├── models/blogging/
385
+ │ ├── definitions/blogging/
386
+ │ ├── policies/blogging/
387
+ │ ├── interactions/blogging/
388
+ │ └── controllers/blogging/
389
+ └── lib/engine.rb
390
+ ```
391
+
392
+ ### Portal Packages
393
+ Provide web interfaces with routing and authentication:
394
+ ```bash
395
+ rails generate pu:pkg:portal admin
396
+ ```
397
+
398
+ Portal Engine:
399
+ ```ruby
400
+ module AdminPortal
401
+ class Engine < ::Rails::Engine
402
+ include Plutonium::Portal::Engine
403
+
404
+ # Multi-tenancy
405
+ scope_to_entity Organization, strategy: :path
406
+ end
407
+ end
408
+ ```
409
+
410
+ Portal Routes:
411
+ ```ruby
412
+ AdminPortal::Engine.routes.draw do
413
+ register_resource Blog::Post
414
+ register_resource Blog::Comment
415
+ register_resource User
416
+ end
417
+ ```
418
+
419
+ Portal Controller:
420
+ ```ruby
421
+ module AdminPortal
422
+ module Concerns
423
+ module Controller
424
+ extend ActiveSupport::Concern
425
+ include Plutonium::Portal::Controller
426
+ include Plutonium::Auth::Rodauth(:admin)
427
+
428
+ included do
429
+ before_action :ensure_admin_access
430
+ layout "admin_portal"
431
+ end
432
+
433
+ private
434
+
435
+ def ensure_admin_access
436
+ redirect_to root_path unless current_user&.admin?
437
+ end
438
+ end
439
+ end
440
+ end
441
+ ```
442
+
443
+ ## Authentication & Authorization
444
+
445
+ ### Rodauth Setup
446
+ ```bash
447
+ # Install Rodauth
448
+ rails generate pu:rodauth:install
449
+
450
+ # Create user account
451
+ rails generate pu:rodauth:account user
452
+
453
+ # Create admin account with MFA
454
+ rails generate pu:rodauth:account admin --no-defaults \
455
+ --login --logout --remember --lockout \
456
+ --create-account --verify-account --close-account \
457
+ --change-password --reset-password --reset-password-notify \
458
+ --active-sessions --password-grace-period --otp \
459
+ --recovery-codes --audit-logging --internal-request
460
+ ```
461
+
462
+ ### Authentication Integration
463
+ ```ruby
464
+ # In portal controller concerns
465
+ include Plutonium::Auth::Rodauth(:user) # For user authentication
466
+ include Plutonium::Auth::Rodauth(:admin) # For admin authentication
467
+ include Plutonium::Auth::Public # For public access
468
+ ```
469
+
470
+ ## Multi-Tenancy & Entity Scoping
471
+
472
+ ### Path-Based Scoping
473
+ ```ruby
474
+ # Portal engine
475
+ scope_to_entity Organization, strategy: :path
476
+
477
+ # Generates routes like: /organizations/:organization_id/posts
478
+ # Automatically scopes all queries to the organization
479
+ ```
480
+
481
+ ### Database Associations for Scoping
482
+ ```ruby
483
+ # Direct association (auto-detected)
484
+ class Post < ApplicationRecord
485
+ belongs_to :organization
486
+ end
487
+
488
+ # Indirect association (auto-detected)
489
+ class Post < ApplicationRecord
490
+ belongs_to :author, class_name: 'User'
491
+ has_one :organization, through: :author
492
+ end
493
+
494
+ # Custom scope (manual)
495
+ class Comment < ApplicationRecord
496
+ belongs_to :post
497
+
498
+ scope :associated_with_organization, ->(organization) do
499
+ joins(post: :author).where(users: { organization_id: organization.id })
500
+ end
501
+ end
502
+ ```
503
+
504
+ ## Query Objects & Filtering
505
+
506
+ ### Automatic Query Handling
507
+ Controllers automatically handle:
508
+ - Search: `?q[search]=rails`
509
+ - Filters: `?q[published]=true`
510
+ - Sorting: `?q[sort_fields][]=created_at&q[sort_directions][created_at]=desc`
511
+ - Scopes: `?q[scope]=published`
512
+
513
+ ### Custom Query Objects
514
+ ```ruby
515
+ query_object = Plutonium::Resource::QueryObject.new(Post, params[:q] || {}, request.path) do |query|
516
+ # Custom search
517
+ query.define_search proc { |scope, search:|
518
+ scope.joins(:author, :tags)
519
+ .where("posts.title ILIKE :search OR users.name ILIKE :search", search: "%#{search}%")
520
+ .distinct
521
+ }
522
+
523
+ # Custom filter
524
+ query.define_filter :date_range, proc { |scope, start_date:, end_date:|
525
+ scope.where(created_at: start_date.beginning_of_day..end_date.end_of_day)
526
+ }
527
+
528
+ # Custom sorter
529
+ query.define_sorter :author_name, proc { |scope, direction:|
530
+ scope.joins(:author).order("users.name #{direction}")
531
+ }
532
+ end
533
+ ```
534
+
535
+ ## UI Components & Theming
536
+
537
+ ### Component Architecture
538
+ Built on Phlex, components inherit from `Plutonium::UI::Component::Base`:
539
+
540
+ ```ruby
541
+ class CustomComponent < Plutonium::UI::Component::Base
542
+ def initialize(title:, content: nil)
543
+ @title = title
544
+ @content = content
545
+ end
546
+
547
+ def view_template
548
+ div(class: "custom-component") do
549
+ h2(class: "text-xl font-bold") { @title }
550
+ div(class: "content") { @content } if @content
551
+ end
552
+ end
553
+ end
554
+ ```
555
+
556
+ ### Layout Customization
557
+ ```ruby
558
+ # Custom page classes in definitions
559
+ class PostDefinition < Plutonium::Resource::Definition
560
+ class ShowPage < Plutonium::UI::Page::Show
561
+ def render_content
562
+ # Custom show page rendering
563
+ super
564
+ end
565
+ end
566
+ end
567
+ ```
568
+
569
+ ## Configuration
570
+
571
+ ### Main Configuration
572
+ ```ruby
573
+ # config/initializers/plutonium.rb
574
+ Plutonium.configure do |config|
575
+ config.load_defaults 1.0
576
+
577
+ # Development settings
578
+ config.development = Rails.env.development?
579
+ config.cache_discovery = !Rails.env.development?
580
+ config.enable_hotreload = Rails.env.development?
581
+
582
+ # Assets
583
+ config.assets.logo = "custom_logo.png"
584
+ config.assets.stylesheet = "custom_theme.css"
585
+ config.assets.script = "custom_plutonium.js"
586
+ config.assets.favicon = "custom_favicon.ico"
587
+ end
588
+ ```
589
+
590
+ ## Generator Commands
591
+
592
+ ### Core Setup
593
+ ```bash
594
+ # Install Plutonium in existing app
595
+ bin/rails app:template LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb
596
+ rails generate pu:core:install
597
+
598
+ # Install authentication
599
+ rails generate pu:rodauth:install
600
+ rails generate pu:rodauth:account user
601
+ ```
602
+
603
+ ### Package Creation
604
+ ```bash
605
+ # Create feature package
606
+ rails generate pu:pkg:package blogging
607
+
608
+ # Create portal package
609
+ rails generate pu:pkg:portal admin
610
+ ```
611
+
612
+ ### Resource Management
613
+ ```bash
614
+ # Full resource scaffold
615
+ rails generate pu:res:scaffold Post user:belongs_to title:string content:text
616
+
617
+ # Individual resource components
618
+ rails generate pu:res:model Post title:string content:text
619
+ rails generate pu:res:definition Post
620
+ rails generate pu:res:policy Post
621
+ ```
622
+
623
+ ## Best Practices
624
+
625
+ ### Resource Design
626
+ 1. Keep models focused on data and basic validations
627
+ 2. Use definitions for UI configuration, not business logic
628
+ 3. Implement complex business logic in interactions
629
+ 4. Use policies for all authorization logic
630
+ 5. Leverage scopes for common queries
631
+
632
+ ### Package Organization
633
+ 1. Create feature packages for domain logic
634
+ 2. Use portal packages for different user interfaces
635
+ 3. Keep packages focused and cohesive
636
+ 4. Namespace everything properly to avoid conflicts
637
+
638
+ ### Security
639
+ 1. Always define explicit permissions in policies
640
+ 2. Use secure defaults (deny by default)
641
+ 3. Implement proper entity scoping for multi-tenancy
642
+ 4. Validate all user inputs in interactions
643
+ 5. Use CSRF protection and implement rate limiting
644
+
645
+ ### Performance
646
+ 1. Use `includes` and `joins` in relation scopes
647
+ 2. Add database indexes for filtered and sorted fields
648
+ 3. Implement caching where appropriate
649
+ 4. Use background jobs for heavy operations
650
+ 5. Monitor N+1 queries with tools like Prosopite
651
+
652
+ ### Code Organization
653
+ 1. Follow Rails naming conventions
654
+ 2. Keep controllers thin - use interactions for business logic
655
+ 3. Use consistent patterns across resources
656
+ 4. Write comprehensive tests for policies and interactions
657
+ 5. Document complex business logic
658
+
659
+ ### Development Workflow
660
+ 1. Start with resource scaffolding for rapid prototyping
661
+ 2. Customize definitions for UI requirements
662
+ 3. Implement business logic through interactions
663
+ 4. Add proper authorization with policies
664
+ 5. Create appropriate packages for organization
665
+ 6. Set up portals for different user types
666
+
667
+ This comprehensive guide should enable you to build robust, maintainable Plutonium applications following the framework's conventions and best practices.
@@ -49,11 +49,6 @@ module Pu
49
49
  registerControllers(application)
50
50
  EOT
51
51
 
52
- # For older versions tailwindcss, specifically v3
53
- gsub_file "app/assets/stylesheets/application.tailwind.css", %r{@tailwind\s+base;\s*@tailwind\s+components;\s*@tailwind\s+utilities;\s*} do
54
- "@import \"tailwindcss\";\n@config '../../../tailwind.config.js';\n"
55
- end
56
-
57
52
  insert_into_file "app/assets/stylesheets/application.tailwind.css", <<~EOT, after: /@import "tailwindcss";\n/
58
53
  @config '../../../tailwind.config.js';
59
54
  EOT
@@ -83,8 +83,13 @@ module Plutonium
83
83
  display_definition = resource_definition.defined_displays[name] || {}
84
84
  display_options = display_definition[:options] || {}
85
85
 
86
+ # Check for conditional rendering
87
+ condition = display_options[:condition] || field_options[:condition]
88
+ conditionally_hidden = condition && !instance_exec(&condition)
89
+ return if conditionally_hidden
90
+
86
91
  tag = display_options[:as] || field_options[:as]
87
- tag_attributes = display_options.except(:wrapper, :as)
92
+ tag_attributes = display_options.except(:wrapper, :as, :condition)
88
93
  tag_block = display_definition[:block] || ->(f) {
89
94
  tag ||= f.inferred_field_component
90
95
  f.send(:"#{tag}_tag", **tag_attributes)
@@ -92,7 +97,7 @@ module Plutonium
92
97
 
93
98
  wrapper_options = display_options[:wrapper] || {}
94
99
 
95
- field_options = field_options.except(:as)
100
+ field_options = field_options.except(:as, :condition)
96
101
  render field(name, **field_options).wrapped(**wrapper_options) do |f|
97
102
  render instance_exec(f, &tag_block)
98
103
  end