plutonium 0.24.0 → 0.24.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38e9b4259eace82de069a8676149a0aee950555d3b8f9040e40d2f96d84408af
4
- data.tar.gz: 0cffa959583789f1450d2ba401ea9b1bfb78b0a348394a31e692682b4135648f
3
+ metadata.gz: cb793ad2d24a4aef3c7a5c46146260b019e6ab1af1e82a23c110379f96b51df6
4
+ data.tar.gz: 3926d88a01fa0a9ec45b2c072b0c8112e058082520fb4d2019f765a8cf2a97a3
5
5
  SHA512:
6
- metadata.gz: f6c4136e5b5d8fe44e790475b93df8d38a210f1b14f9e49913a8784af05b838475f14f2cf9596c9a012377e6fae78c439f04f034c7017c5984c9c95e58d37cd0
7
- data.tar.gz: 47183a92cd658a41d9d966fc803ef0a30635266e97e774e27115ae8122ef82568219b196e29388959078be8319f4b5bf538197a849ca0d75833385fa2ce77657
6
+ metadata.gz: 4bf56145b9fc26fe50d167bfdffd347488c4be2d7a32c985d698ca4c24e40fc98cfc26e3a3c5cf88fca7dbf8de934f4c4974e8d394b347918c1f1d962484ace6
7
+ data.tar.gz: dacc135a569a2c5ec92134e7cf2670948680622c529514e2ddb996318e4e120330081ea8fd122b6c4dd9e29e3e6c838dcb037ca0939f65e16a43c73f2f50851e
@@ -214,17 +214,26 @@ To create forms that dynamically show/hide inputs based on other form values, pa
214
214
 
215
215
  Plutonium offers three main approaches for rendering fields in a definition. Choose the one that best fits your needs for clarity, flexibility, and control.
216
216
 
217
+ **Quick Reference:**
218
+ - **`as: :symbol`** - Use built-in components for input and display (e.g., `:rich_text`, `:date`, `:markdown`)
219
+ - **`as: ComponentClass`** - Use custom component classes for input and display (new feature)
220
+ - **Block syntax** - Use for conditional logic and custom builder method calls
221
+ - **`as: :phlexi_tag`** - Advanced inline rendering with maximum flexibility (display only)
222
+
217
223
  #### 1. The `as:` Option (Recommended)
218
224
 
219
- The `as:` option is the simplest and most common way to specify a rendering component for an `input` or `display` declaration. It's ideal for using standard built-in components or overriding auto-detected types.
225
+ The `as:` option is the simplest and most common way to specify a rendering component for both `input` and `display` declarations. It's ideal for using standard built-in components or overriding auto-detected types.
226
+
227
+ **New Feature**: You can now pass a component class directly to the `as:` option for custom rendering in both input and display contexts.
220
228
 
221
229
  **Use When:**
222
- - Using standard or enhanced built-in components.
230
+ - Using standard or enhanced built-in components for input or display.
223
231
  - You want clean, readable code with minimal boilerplate.
224
- - Overriding an auto-detected type (e.g., `text` to `rich_text`).
232
+ - Overriding an auto-detected type (e.g., `text` to `rich_text` for input, or `text` to `markdown` for display).
233
+ - Using custom component classes for rendering.
225
234
 
226
235
  ::: code-group
227
- ```ruby [Input Fields]
236
+ ```ruby [Standard Input Fields]
228
237
  # Simple and concise overrides
229
238
  class PostDefinition < Plutonium::Resource::Definition
230
239
  input :content, as: :rich_text
@@ -235,7 +244,19 @@ class PostDefinition < Plutonium::Resource::Definition
235
244
  input :email, as: :email, placeholder: "Enter email"
236
245
  end
237
246
  ```
238
- ```ruby [Display Fields]
247
+ ```ruby [Custom Component Classes]
248
+ # Using custom component classes
249
+ class PostDefinition < Plutonium::Resource::Definition
250
+ # Pass component class to input
251
+ input :color_picker, as: ColorPickerComponent
252
+ input :custom_widget, as: MyCustomInputComponent
253
+
254
+ # Pass component class to display
255
+ display :status_badge, as: StatusBadgeComponent
256
+ display :chart, as: ChartComponent
257
+ end
258
+ ```
259
+ ```ruby [Standard Display Fields]
239
260
  # Simple and concise overrides
240
261
  class PostDefinition < Plutonium::Resource::Definition
241
262
  display :content, as: :markdown
@@ -252,25 +273,34 @@ end
252
273
 
253
274
  The block syntax offers more control over rendering, allowing for custom components, complex layouts, and conditional logic. The block receives a `field` object that you can use to render custom output.
254
275
 
276
+ **Important for Input Blocks**: When using blocks with `input` declarations, you can only use existing form builder methods (like `f.date_tag`, `f.text_tag`, etc.). You cannot return arbitrary components because the form system requires inputs to be registered internally.
277
+
255
278
  **Use When:**
256
- - Integrating custom-built Phlex or ViewComponent components.
257
- - Building complex layouts with multiple components or custom HTML.
279
+ - Integrating custom-built Phlex or ViewComponent components (for `display` only).
280
+ - Building complex layouts with multiple components or custom HTML (for `display` only).
258
281
  - You need conditional logic to determine which component to render.
282
+ - You need to call specific form builder methods with custom logic (for `input`).
259
283
 
260
284
  ::: code-group
261
285
  ```ruby [Custom Display Components]
262
- # Custom display component
286
+ # Custom display component - can return any component
263
287
  display :chart_data do |field|
264
288
  ChartComponent.new(data: field.value, type: :bar)
265
289
  end
266
290
  ```
267
- ```ruby [Custom Input Components]
268
- # Custom input component
269
- input :color do |field|
270
- ColorPickerComponent.new(field)
291
+ ```ruby [Custom Input with Builder Methods]
292
+ # Custom input - can only use form builder methods
293
+ input :birth_date do |f|
294
+ # Can use builder methods with custom logic
295
+ case object.age_category
296
+ when 'adult'
297
+ f.date_tag(min: 18.years.ago.to_date)
298
+ when 'minor'
299
+ f.date_tag(max: 18.years.ago.to_date)
300
+ else
301
+ f.date_tag
302
+ end
271
303
  end
272
- ```
273
-
274
304
  ```
275
305
  ```ruby [Conditional Rendering]
276
306
  # Conditional display based on value
data/docs/modules/form.md CHANGED
@@ -124,6 +124,36 @@ end
124
124
 
125
125
  Plutonium replaces standard form inputs with enhanced versions that provide a modern user experience.
126
126
 
127
+ ### Input Block Syntax
128
+
129
+ Input blocks provide additional flexibility for custom logic while ensuring proper form integration. **Important**: Input blocks can only use existing form builder methods (like `date_tag`, `text_tag`, etc.) because the form system requires inputs to be registered internally.
130
+
131
+ ::: code-group
132
+ ```ruby [Input Block Example]
133
+ # Valid: Using form builder methods with custom logic
134
+ input :birth_date do |f|
135
+ case object.age_category
136
+ when 'adult'
137
+ f.date_tag(min: 18.years.ago.to_date)
138
+ when 'minor'
139
+ f.date_tag(max: 18.years.ago.to_date)
140
+ else
141
+ f.date_tag
142
+ end
143
+ end
144
+ ```
145
+
146
+ ```ruby [Custom Component Classes]
147
+ # New: Pass component classes directly to as: option
148
+ # Works for both input and display
149
+ input :color_picker, as: ColorPickerComponent
150
+ input :custom_slider, as: RangeSliderComponent
151
+
152
+ display :status_badge, as: StatusBadgeComponent
153
+ display :progress_chart, as: ProgressChartComponent
154
+ ```
155
+ :::
156
+
127
157
  ### Markdown Editor (Easymde)
128
158
 
129
159
  For rich text content, Plutonium integrates a client-side markdown editor based on [EasyMDE](https://github.com/Ionaru/easy-markdown-editor). It's automatically used for `rich_text` fields (like ActionText) and provides a live preview.
@@ -121,6 +121,20 @@ class PostDefinition < Plutonium::Resource::Definition
121
121
  input :content, as: :rich_text
122
122
  input :category, as: :select, collection: %w[Tech Business Lifestyle]
123
123
 
124
+ # New: Custom component classes
125
+ input :color_picker, as: ColorPickerComponent
126
+ display :status_badge, as: StatusBadgeComponent
127
+
128
+ # Input blocks for custom logic (must use form builder methods)
129
+ input :birth_date do |f|
130
+ case object.age_category
131
+ when 'adult'
132
+ f.date_tag(min: 18.years.ago.to_date)
133
+ else
134
+ f.date_tag
135
+ end
136
+ end
137
+
124
138
  # Conditional inputs (for forms only)
125
139
  # Use for COSMETIC/STATE-BASED logic only, NOT authorization (use policies for that)
126
140
  input :debug_info, condition: -> { Rails.env.development? }
@@ -467,201 +481,72 @@ include Plutonium::Auth::Rodauth(:admin) # For admin authentication
467
481
  include Plutonium::Auth::Public # For public access
468
482
  ```
469
483
 
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
- ```
484
+ ## Routing & Custom Routes
480
485
 
481
- ### Database Associations for Scoping
486
+ ### Resource Registration
482
487
  ```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
488
+ # Basic resource registration
489
+ register_resource Post
490
+
491
+ # Custom routes with member/collection actions
492
+ register_resource Post do
493
+ member do
494
+ get :publish # /posts/1/publish
495
+ post :archive # /posts/1/archive
496
+ end
497
497
 
498
- scope :associated_with_organization, ->(organization) do
499
- joins(post: :author).where(users: { organization_id: organization.id })
498
+ collection do
499
+ get :search # /posts/search
500
500
  end
501
501
  end
502
502
  ```
503
503
 
504
- ## Query Objects & Filtering
504
+ ### Common Routing Errors
505
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`
506
+ #### "Unresolvable Association" Error
507
+ When adding custom routes inside `register_resource` blocks, Plutonium may interpret them as nested resources and fail with:
508
+ ```
509
+ RuntimeError (Could not resolve the association between 'Model' and 'Model')
510
+ ```
512
511
 
513
- ### Custom Query Objects
512
+ **Problem**: Routes like this create nested resource interpretation:
514
513
  ```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
- }
514
+ register_resource ::UniversalFlow::Definition do
515
+ get "editor", to: "universal_flow/definitions#editor"
532
516
  end
533
517
  ```
534
518
 
535
- ## UI Components & Theming
536
-
537
- ### Component Architecture
538
- Built on Phlex, components inherit from `Plutonium::UI::Component::Base`:
519
+ **Solutions**:
539
520
 
521
+ 1. **Add custom scope to model (Recommended)**:
540
522
  ```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
523
+ class UniversalFlow::Definition < ApplicationRecord
524
+ scope :associated_with_universal_flow_definition, ->(universal_flow_definition) {
525
+ all # or specific logic for your use case
526
+ }
553
527
  end
554
528
  ```
555
529
 
556
- ### Layout Customization
530
+ 2. **Use member/collection routes**:
557
531
  ```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
532
+ register_resource ::UniversalFlow::Definition do
533
+ member do
534
+ get :editor
565
535
  end
566
536
  end
567
537
  ```
568
538
 
569
- ## Configuration
570
-
571
- ### Main Configuration
539
+ 3. **Create separate controller**:
572
540
  ```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"
541
+ # In routes file
542
+ get 'universal_flow/editor/:id', to: 'universal_flow/editor#show'
543
+
544
+ # Create dedicated controller
545
+ class UniversalFlow::EditorController < CustomerPortal::ResourceController
546
+ def show
547
+ @definition = UniversalFlow::Definition.find(params[:id])
548
+ authorize_current! @definition
549
+ # ... custom logic
550
+ end
587
551
  end
588
552
  ```
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.
@@ -92,7 +92,11 @@ module Plutonium
92
92
  tag_attributes = display_options.except(:wrapper, :as, :condition)
93
93
  tag_block = display_definition[:block] || ->(f) {
94
94
  tag ||= f.inferred_field_component
95
- f.send(:"#{tag}_tag", **tag_attributes)
95
+ if tag.is_a?(Class)
96
+ f.send :create_component, tag, tag.name.demodulize.underscore.sub(/component$/, "").to_sym
97
+ else
98
+ f.send(:"#{tag}_tag", **tag_attributes)
99
+ end
96
100
  }
97
101
 
98
102
  wrapper_options = display_options[:wrapper] || {}
@@ -68,7 +68,11 @@ module Plutonium
68
68
  end
69
69
  tag_block = input_definition[:block] || ->(f) do
70
70
  tag ||= f.inferred_field_component
71
- f.send(:"#{tag}_tag", **tag_attributes)
71
+ if tag.is_a?(Class)
72
+ f.send :create_component, tag, tag.name.demodulize.underscore.sub(/component$/, "").to_sym
73
+ else
74
+ f.send(:"#{tag}_tag", **tag_attributes)
75
+ end
72
76
  end
73
77
 
74
78
  field_options = field_options.except(:as, :condition)
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.24.0"
2
+ VERSION = "0.24.1"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plutonium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.24.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-06-25 00:00:00.000000000 Z
11
+ date: 2025-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk