plutonium 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -4
  3. data/app/views/resource/index.html.erb +1 -1
  4. data/brakeman.ignore +0 -23
  5. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +1 -2
  6. data/lib/generators/pu/resource/model/templates/model.rb.tt +11 -11
  7. data/lib/generators/pu/rodauth/templates/app/models/account.rb.tt +3 -7
  8. data/lib/plutonium/core/app_controller.rb +2 -2
  9. data/lib/plutonium/core/controllers/authorizable.rb +8 -1
  10. data/lib/plutonium/core/controllers/crud_actions.rb +11 -13
  11. data/lib/plutonium/core/controllers/interactive_actions.rb +1 -0
  12. data/lib/plutonium/core/controllers/presentable.rb +4 -3
  13. data/lib/plutonium/core/fields/inputs/attachment_input.rb +24 -0
  14. data/lib/plutonium/core/fields/inputs/{basic_input.rb → base.rb} +4 -2
  15. data/lib/plutonium/core/fields/inputs/{belongs_to_input.rb → belongs_to_association_input.rb} +1 -1
  16. data/lib/plutonium/core/fields/inputs/factory.rb +7 -25
  17. data/lib/plutonium/core/fields/inputs/{has_many_input.rb → has_many_association_input.rb} +1 -1
  18. data/lib/plutonium/core/fields/inputs/{association_input.rb → simple_form_association_input.rb} +1 -1
  19. data/lib/plutonium/core/fields/inputs/simple_form_input.rb +11 -0
  20. data/lib/plutonium/core/fields/inputs.rb +6 -4
  21. data/lib/plutonium/core/fields/renderers/attachment_renderer.rb +28 -0
  22. data/lib/plutonium/core/fields/renderers/factory.rb +4 -18
  23. data/lib/plutonium/core/fields/renderers.rb +3 -2
  24. data/lib/plutonium/core/ui/collection.rb +2 -2
  25. data/lib/plutonium/helpers/attachment_helper.rb +22 -11
  26. data/lib/plutonium/helpers/form_helper.rb +2 -4
  27. data/lib/plutonium/policy/scope.rb +5 -5
  28. data/lib/plutonium/railtie.rb +12 -0
  29. data/lib/plutonium/reactor/core.rb +2 -0
  30. data/lib/plutonium/reactor/policy_context.rb +5 -0
  31. data/lib/plutonium/reactor/resource_context.rb +1 -11
  32. data/lib/plutonium/reactor/resource_controller.rb +28 -5
  33. data/lib/plutonium/reactor/resource_policy.rb +2 -2
  34. data/lib/plutonium/reactor/resource_presenter.rb +7 -2
  35. data/lib/plutonium/reactor/resource_record.rb +97 -35
  36. data/lib/plutonium/reactor.rb +1 -0
  37. data/lib/plutonium/simple_form_components/attachment_component.rb +9 -3
  38. data/lib/plutonium/simple_form_components/input_group_component.rb +1 -0
  39. data/lib/plutonium/version.rb +1 -1
  40. data/lib/plutonium.rb +1 -0
  41. metadata +11 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b91e22fa0430a2ca94704f472ac5ae688c50140c82bec3ef8db7a0d9571418f4
4
- data.tar.gz: 76c32ee40e384bdef1de3aeda5dbece07841a7898260beffb3a3841e8c0f0c27
3
+ metadata.gz: c8fb31a8eb4837634d28b3b0f602b06494991cdcdc02fc600888d0fecf8c497e
4
+ data.tar.gz: 4c62d88f641d1890785cac1f6bf483b78e334fd2646bf91b21b3a5a53bd20b2c
5
5
  SHA512:
6
- metadata.gz: 5f78a1c9dd3978b92dd9e33ca8c594f24895dd6a18ac25563cd50a1a85bc222199ed830d574c55409e99c76c90a37104f9cf5daa9a024dfc86ad49ec43a3dd48
7
- data.tar.gz: 7df604d36bbc6191068299d91cf141f5c08bb671513a3853146cc90646c262a56e26fb0fd73316491758a3e56e73f0e22718382dbc3d96efb4d186b45587e78e
6
+ metadata.gz: cdc10f0c4d1f23a8fe565968d674d41fd8f9ee0aa43b6a1007ac8bca40897d1f0ee48189230b6aa742d1df53c7055e57eee960a29f265cebeb18160d6b8b73c3
7
+ data.tar.gz: 745dfeb89b822d2ba1181050ad3a78de32084361a19664b2f8dc8ab3bbdbff87e4eebae40cec02ebeaed1b966654edab646d6b1570a29eb64147b557ff8d75d8
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Plutonium: Nuclear Powered Rails Application Development
1
+ # Plutonium: Take Your Rails Productivity Nuclear🚀!
2
2
 
3
- **Plutonium** transforms the way you build applications with Rails, offering a powerful toolkit for rapid application development.
3
+ **Plutonium** picks up where Rails left off, introducing application level concepts and tooling, transforming the way you build applications with Rails.
4
4
  It's a culmination of lessons learned from years of developing nearly identical applications and is designed to save you from the drudgery of re-implementation.
5
5
 
6
6
  **Why Choose Plutonium?**
@@ -8,14 +8,14 @@ It's a culmination of lessons learned from years of developing nearly identical
8
8
  - **Efficiency by Design:** Plutonium is built for developers who demand efficiency without compromise. It automates 90% of your application needs while giving you the flexibility to tailor the remaining 10% to your specific requirements.
9
9
  - **Comprehensive Features:** From authentication and authorization to CRUD operations, and beyond, Plutonium covers a wide array of functionalities out of the box:
10
10
  - Authentication & Authorization
11
- - Complete CRUD operations with advanced features: customizable tables, forms, pagination, actions, search, filtering, and nested resources.
11
+ - Complete CRUD operations with advanced features: customizable inputs, fields, tables, forms, pagination, actions, search, filtering, and nested resources.
12
12
  - Modular architecture leveraging Rails engines for improved packaging and namespacing.
13
13
  - Time-saving generators for boilerplate tasks.
14
14
  - **Omakase with a Twist:** Inspired by Rails' omakase philosophy, Plutonium delivers a convention-based approach but doesn't box you in. It's seamlessly integrated into your project, allowing you to write your application as you would with vanilla Rails but with powerful extensions.
15
15
  - **MVC and Beyond:** Plutonium adopts the MVC pattern, enhanced with modern web technologies like [hotwire](TODO), to deliver an interactive and robust user experience. It emphasizes progressive enhancement, ensuring a smooth development process and end-user experience.
16
16
  - **Rails Harmony:** A Plutonium app is a Rails app at its core. It respects and builds upon Rails' conventions, making it intuitive for Rails developers. If you know Rails, learning Plutonium requires only a few new concepts.
17
17
  - **Effortless Customization:** Plutonium is designed for easy customization to meet your unique requirements. Whether adjusting the functionality of entire resource groups or fine-tuning individual elements, our accessible low-level APIs and the familiar Rails conventions offer unparalleled flexibility. This ensures that any modifications you need to make can be implemented swiftly and smoothly, reducing complexity and enhancing your development experience.
18
- - **Community-Driven Dependencies:** Plutonium stands on the shoulders of giants, integrating with well-established gems known for their robustness and flexibility, including:
18
+ - **Community-Driven Dependencies:** Plutonium stands on the shoulders of giants, integrating with well-established gems chosen for their robustness and flexibility, including:
19
19
  - [ActiveInteraction](https://github.com/AaronLasseigne/active_interaction) for business logic
20
20
  - [Pagy](https://github.com/ddnexus/pagy) for pagination
21
21
  - [Pundit](https://github.com/varvet/pundit) for authorization
@@ -4,7 +4,7 @@
4
4
  <%=
5
5
  render partial: 'toolbar_search_input', locals: {
6
6
  search_object: @collection.search_object,
7
- search_field: nil
7
+ search_field: @collection.search_field
8
8
  }
9
9
  %>
10
10
  <%=
data/brakeman.ignore CHANGED
@@ -22,29 +22,6 @@
22
22
  ],
23
23
  "note": "this is tested and confirmed to be a false flag"
24
24
  },
25
- {
26
- "warning_type": "Mass Assignment",
27
- "warning_code": 70,
28
- "fingerprint": "873ee0d868e06a32e8ff387a38ddb8c6183a419813d5c20122fa9c3a887f4e54",
29
- "check_name": "MassAssignment",
30
- "message": "Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys",
31
- "file": "lib/plutonium/reactor/resource_controller.rb",
32
- "line": 58,
33
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
34
- "code": "params.require(resource_param_key).permit!",
35
- "render_path": null,
36
- "location": {
37
- "type": "method",
38
- "class": "Plutonium::Reactor::ResourceController",
39
- "method": "resource_params"
40
- },
41
- "user_input": null,
42
- "confidence": "Medium",
43
- "cwe_id": [
44
- 915
45
- ],
46
- "note": "we manually filter params"
47
- }
48
25
  ],
49
26
  "updated": "2024-02-18 00:37:39 +0000",
50
27
  "brakeman_version": "6.1.2"
@@ -1,2 +1 @@
1
- # get the ball rolling
2
- Plutonium::Reactor::Core.achieve_criticality!
1
+ # Configure plutonium here
@@ -25,17 +25,6 @@ class <%= class_name %> < <%= [feature_package_name, "ResourceRecord"].join "::"
25
25
  <% end -%>
26
26
  # add attachments above.
27
27
 
28
- <% attributes.select(&:rich_text?).each do |attribute| -%>
29
- has_rich_text :<%= attribute.name %>
30
- <% end -%>
31
- <% attributes.select(&:token?).each do |attribute| -%>
32
- has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %>
33
- <% end -%>
34
- <% if attributes.any?(&:password_digest?) -%>
35
- has_secure_password
36
- <% end -%>
37
- # add misc attribute macros above.
38
-
39
28
  # add scopes above.
40
29
 
41
30
  <% attributes.select(&:required?).each do |attribute| -%>
@@ -48,6 +37,17 @@ class <%= class_name %> < <%= [feature_package_name, "ResourceRecord"].join "::"
48
37
 
49
38
  # add delegations above.
50
39
 
40
+ <% attributes.select(&:rich_text?).each do |attribute| -%>
41
+ has_rich_text :<%= attribute.name %>
42
+ <% end -%>
43
+ <% attributes.select(&:token?).each do |attribute| -%>
44
+ has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %>
45
+ <% end -%>
46
+ <% if attributes.any?(&:password_digest?) -%>
47
+ has_secure_password
48
+ <% end -%>
49
+ # add misc attribute macros above.
50
+
51
51
  # add methods above.
52
52
  end
53
53
  <% end -%>
@@ -13,13 +13,6 @@ class <%= table_prefix.camelize %> < ApplicationRecord
13
13
 
14
14
  # add attachments above.
15
15
 
16
- <% if ActiveRecord.version >= Gem::Version.new("7.0") -%>
17
- enum :status, unverified: 1, verified: 2, closed: 3
18
- <% else -%>
19
- enum status: { unverified: 1, verified: 2, closed: 3 }
20
- <% end -%>
21
- # add misc attribute macros above.
22
-
23
16
  # add scopes above.
24
17
 
25
18
  validates :email, presence: true
@@ -29,6 +22,9 @@ class <%= table_prefix.camelize %> < ApplicationRecord
29
22
 
30
23
  # add delegations above.
31
24
 
25
+ enum :status, unverified: 1, verified: 2, closed: 3
26
+ # add misc attribute macros above.
27
+
32
28
  # add methods above.
33
29
  end
34
30
  <% else -%>
@@ -11,9 +11,9 @@ module Plutonium
11
11
 
12
12
  private
13
13
 
14
- def resource_presenter(resource_class)
14
+ def resource_presenter(resource_class, resource_record)
15
15
  presenter_class = "#{current_package}::#{resource_class}Presenter".constantize
16
- presenter_class.new resource_context
16
+ presenter_class.new resource_context, resource_record
17
17
  end
18
18
 
19
19
  def policy_namespace(scope)
@@ -20,8 +20,15 @@ module Plutonium
20
20
  raise NotImplementedError, "policy_namespace"
21
21
  end
22
22
 
23
+ def policy_context
24
+ Plutonium::Reactor::PolicyContext.new(
25
+ user: current_user,
26
+ resource_context: resource_context
27
+ )
28
+ end
29
+
23
30
  def pundit_user
24
- resource_context
31
+ policy_context
25
32
  end
26
33
 
27
34
  def policy(scope)
@@ -29,24 +29,12 @@ module Plutonium
29
29
  authorize resource_class
30
30
 
31
31
  @resource_record = resource_class.new
32
- # set params if they have been passed
33
- resource_record.attributes = params[resource_param_key].present? ? resource_params : {}
32
+ maybe_apply_submitted_resource_params!
34
33
  @form = build_form
35
34
 
36
35
  render :new
37
36
  end
38
37
 
39
- # GET /resources/1/edit
40
- def edit
41
- authorize resource_record
42
-
43
- # set params if they have been passed
44
- resource_record.attributes = params[resource_param_key].present? ? resource_params : {}
45
- @form = build_form
46
-
47
- render :edit
48
- end
49
-
50
38
  # POST /resources(.{format})
51
39
  def create
52
40
  authorize resource_class
@@ -73,6 +61,16 @@ module Plutonium
73
61
  end
74
62
  end
75
63
 
64
+ # GET /resources/1/edit
65
+ def edit
66
+ authorize resource_record
67
+
68
+ maybe_apply_submitted_resource_params!
69
+ @form = build_form
70
+
71
+ render :edit
72
+ end
73
+
76
74
  # PATCH/PUT /resources/1(.{format})
77
75
  def update
78
76
  authorize resource_record
@@ -202,6 +202,7 @@ module Plutonium
202
202
  end
203
203
 
204
204
  def interaction_params
205
+ # active interaction handles filtering so no need for strong params
205
206
  (params[:interaction] || {}).except(:resource, :resources)
206
207
  end
207
208
  end
@@ -10,12 +10,12 @@ module Plutonium
10
10
 
11
11
  private
12
12
 
13
- def resource_presenter(resource_class)
13
+ def resource_presenter(resource_class, resource_record)
14
14
  raise NotImplementedError, "#{self.class}#resource_presenter"
15
15
  end
16
16
 
17
17
  def current_presenter
18
- resource_presenter resource_class
18
+ resource_presenter resource_class, @resource_record
19
19
  end
20
20
 
21
21
  def presentable_attributes
@@ -34,7 +34,8 @@ module Plutonium
34
34
  fields: current_presenter.defined_renderers_for(presentable_attributes),
35
35
  actions: current_presenter.actions,
36
36
  pagination: @pagy,
37
- search_object: @ransack
37
+ search_object: @ransack,
38
+ search_field: current_presenter.search_field
38
39
  )
39
40
  end
40
41
 
@@ -0,0 +1,24 @@
1
+ module Plutonium
2
+ module Core
3
+ module Fields
4
+ module Inputs
5
+ class AttachmentInput < SimpleFormInput
6
+ attr_reader :reflection
7
+
8
+ def initialize(name, reflection:, **user_options)
9
+ @reflection = reflection
10
+ super(name, **user_options)
11
+ end
12
+
13
+ private
14
+
15
+ def input_options
16
+ options = {attachment: true} # enable the attachment component
17
+ options[:input_html] = {multiple: true} if reflection.macro == :has_many_attached
18
+ options
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -2,7 +2,7 @@ module Plutonium
2
2
  module Core
3
3
  module Fields
4
4
  module Inputs
5
- class BasicInput
5
+ class Base
6
6
  attr_reader :name, :user_options
7
7
 
8
8
  def initialize(name, **user_options)
@@ -10,7 +10,9 @@ module Plutonium
10
10
  @user_options = user_options
11
11
  end
12
12
 
13
- def render(f, record) = f.input name, **options
13
+ def render(f, record)
14
+ raise NotImplementedError, "#{self.class}#render"
15
+ end
14
16
 
15
17
  def collect(params)
16
18
  # Handles multi parameter attributes
@@ -2,7 +2,7 @@ module Plutonium
2
2
  module Core
3
3
  module Fields
4
4
  module Inputs
5
- class BelongsToInput < AssociationInput
5
+ class BelongsToAssociationInput < SimpleFormAssociationInput
6
6
  private
7
7
 
8
8
  def param
@@ -8,40 +8,22 @@ module Plutonium
8
8
  extend ::SimpleForm::MapType
9
9
 
10
10
  map_type :has_one, to: Plutonium::Core::Fields::Inputs::NoopInput
11
- map_type :belongs_to, to: Plutonium::Core::Fields::Inputs::BelongsToInput
12
- map_type :has_many, to: Plutonium::Core::Fields::Inputs::HasManyInput
11
+ map_type :belongs_to, to: Plutonium::Core::Fields::Inputs::BelongsToAssociationInput
12
+ map_type :has_many, to: Plutonium::Core::Fields::Inputs::HasManyAssociationInput
13
+ map_type :attachment, to: Plutonium::Core::Fields::Inputs::AttachmentInput
13
14
 
14
15
  def self.build(name, type:, **)
15
- mapping = mappings[type] || Plutonium::Core::Fields::Inputs::BasicInput
16
+ mapping = mappings[type] || Plutonium::Core::Fields::Inputs::SimpleFormInput
16
17
  mapping.new(name, **)
17
-
18
- # type ||= :slim_select if options.key? :collection
19
-
20
- # case type
21
- # when :belongs_to
22
- # Plutonium::Core::Fields::Inputs::BelongsToInput.new(name, **)
23
- # when :has_one
24
- # Plutonium::Core::Fields::Inputs::NoopInput.new
25
- # when :has_many
26
- # Plutonium::Core::Fields::Inputs::HasManyInput.new(name, **)
27
- # else
28
- # Plutonium::Core::Fields::Inputs::BasicInput.new(name, **)
29
- # end
30
18
  end
31
19
 
32
20
  def self.for_resource_attribute(resource_class, attr_name, **options)
33
21
  type = nil
34
22
 
35
- if resource_class.respond_to? :reflect_on_association
36
- attachment = resource_class.reflect_on_association(:"#{attr_name}_attachment") || \
37
- resource_class.reflect_on_association(:"#{attr_name}_attachments")
38
- association = resource_class.reflect_on_association(attr_name)
39
- end
40
-
41
- if attachment.present?
23
+ if (attachment = resource_class.try(:reflect_on_attachment, attr_name))
42
24
  type = :attachment
43
- options[:multiple] = (attachment.macro == :has_many) unless options.key?(:multiple)
44
- elsif association.present?
25
+ options[:reflection] = attachment
26
+ elsif (association = resource_class.try(:reflect_on_association, attr_name))
45
27
  type = association.macro
46
28
  options[:reflection] = association
47
29
  elsif (column = resource_class.try(:column_for_attribute, attr_name))
@@ -2,7 +2,7 @@ module Plutonium
2
2
  module Core
3
3
  module Fields
4
4
  module Inputs
5
- class HasManyInput < AssociationInput
5
+ class HasManyAssociationInput < SimpleFormAssociationInput
6
6
  private
7
7
 
8
8
  def param
@@ -2,7 +2,7 @@ module Plutonium
2
2
  module Core
3
3
  module Fields
4
4
  module Inputs
5
- class AssociationInput < BasicInput
5
+ class SimpleFormAssociationInput < Base
6
6
  attr_reader :reflection
7
7
 
8
8
  def initialize(name, reflection:, **user_options)
@@ -0,0 +1,11 @@
1
+ module Plutonium
2
+ module Core
3
+ module Fields
4
+ module Inputs
5
+ class SimpleFormInput < Base
6
+ def render(f, record) = f.input name, **options
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -5,12 +5,14 @@ module Plutonium
5
5
  extend ActiveSupport::Autoload
6
6
 
7
7
  eager_autoload do
8
+ autoload :AttachmentInput
9
+ autoload :Base
10
+ autoload :BelongsToAssociationInput
8
11
  autoload :Factory
9
- autoload :AssociationInput
10
- autoload :BasicInput
11
- autoload :BelongsToInput
12
- autoload :HasManyInput
12
+ autoload :HasManyAssociationInput
13
13
  autoload :NoopInput
14
+ autoload :SimpleFormAssociationInput
15
+ autoload :SimpleFormInput
14
16
  end
15
17
  end
16
18
  end
@@ -0,0 +1,28 @@
1
+ module Plutonium
2
+ module Core
3
+ module Fields
4
+ module Renderers
5
+ class AttachmentRenderer < BasicRenderer
6
+ attr_reader :reflection
7
+
8
+ def initialize(name, reflection:, **user_options)
9
+ @reflection = reflection
10
+ super(name, **user_options)
11
+ end
12
+
13
+ def render(view_context, record)
14
+ view_context.attachment_preview record.send(name), **options
15
+ end
16
+
17
+ private
18
+
19
+ def renderer_options
20
+ {
21
+ caption: true
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -8,35 +8,21 @@ module Plutonium
8
8
  extend ::SimpleForm::MapType
9
9
 
10
10
  map_type :belongs_to, :has_one, :has_many, to: Plutonium::Core::Fields::Renderers::AssociationRenderer
11
+ map_type :attachment, to: Plutonium::Core::Fields::Renderers::AttachmentRenderer
11
12
 
12
13
  def self.build(name, type:, **)
13
14
  mapping = mappings[type] || Plutonium::Core::Fields::Renderers::BasicRenderer
14
15
  mapping.new(name, **)
15
-
16
- # type ||= :slim_select if options.key? :collection
17
-
18
- # case type
19
- # when :belongs_to, :has_one, :has_many
20
- # Plutonium::Core::Fields::Renderers::AssociationRenderer.new(name, **)
21
- # else
22
- # Plutonium::Core::Fields::Renderers::BasicRenderer.new(name, **)
23
- # end
24
16
  end
25
17
 
26
18
  def self.for_resource_attribute(resource_class, attr_name, **options)
27
19
  type = nil
28
20
  options[:label] ||= resource_class.human_attribute_name(attr_name)
29
21
 
30
- if resource_class.respond_to? :reflect_on_association
31
- attachment = resource_class.reflect_on_association(:"#{attr_name}_attachment") || \
32
- resource_class.reflect_on_association(:"#{attr_name}_attachments")
33
- association = resource_class.reflect_on_association(attr_name)
34
- end
35
-
36
- if attachment.present?
22
+ if (attachment = resource_class.try(:reflect_on_attachment, attr_name))
37
23
  type = :attachment
38
- options[:multiple] = (attachment.macro == :has_many) unless options.key?(:multiple)
39
- elsif association.present?
24
+ options[:reflection] = attachment
25
+ elsif (association = resource_class.try(:reflect_on_association, attr_name))
40
26
  type = association.macro
41
27
  options[:reflection] = association
42
28
  elsif (column = resource_class.try(:column_for_attribute, attr_name))
@@ -5,9 +5,10 @@ module Plutonium
5
5
  extend ActiveSupport::Autoload
6
6
 
7
7
  eager_autoload do
8
- autoload :Factory
9
- autoload :BasicRenderer
10
8
  autoload :AssociationRenderer
9
+ autoload :AttachmentRenderer
10
+ autoload :BasicRenderer
11
+ autoload :Factory
11
12
  end
12
13
  end
13
14
  end
@@ -1,10 +1,10 @@
1
1
  module Plutonium
2
2
  module Core
3
3
  module UI
4
- Collection = Data.define :resource_class, :records, :fields, :actions, :pagination, :search_object do
4
+ Collection = Data.define :resource_class, :records, :fields, :actions, :pagination, :search_object, :search_field do
5
5
  def initialize(
6
6
  resource_class:, records: [], fields: {}, actions: Plutonium::Core::Actions::Collection.new,
7
- pagination: nil, search_object: nil
7
+ pagination: nil, search_object: nil, search_field: nil
8
8
  )
9
9
  super
10
10
  end
@@ -6,23 +6,23 @@ module Plutonium
6
6
  tag.div class: [options[:identity_class], "attachment-preview-container d-flex flex-wrap gap-1 my-1"],
7
7
  data: {controller: "attachment-preview-container"} do
8
8
  Array(attachments).each do |attachment|
9
- next unless attachment.file.present?
9
+ next unless attachment.url.present?
10
10
 
11
11
  concat begin
12
12
  tag.div class: [options[:identity_class], "attachment-preview d-inline-block text-center"],
13
- title: attachment.file.original_filename,
13
+ title: attachment.filename,
14
14
  data: {
15
15
  controller: "attachment-preview",
16
- attachment_preview_mime_type_value: attachment.file.mime_type,
17
- attachment_preview_thumbnail_url_value: attachment.thumbnail_url
16
+ attachment_preview_mime_type_value: attachment.content_type,
17
+ attachment_preview_thumbnail_url_value: _attachment_thumbnail_url(attachment)
18
18
  } do
19
19
  tag.figure class: "figure my-1", style: "width: 160px;" do
20
20
  concat attachment_preview_thumnail(attachment)
21
21
  concat begin
22
22
  tag.figcaption class: "figure-caption text-truncate" do
23
23
  if options[:caption]
24
- caption = options[:caption].is_a?(String) ? options[:caption] : attachment.file.original_filename
25
- concat link_to(caption, attachment.file_url, class: "text-decoration-none", target: :blank)
24
+ caption = options[:caption].is_a?(String) ? options[:caption] : attachment.filename
25
+ concat link_to(caption, attachment.url, class: "text-decoration-none", target: :blank)
26
26
  end
27
27
 
28
28
  if block_given?
@@ -40,23 +40,34 @@ module Plutonium
40
40
  end
41
41
 
42
42
  def attachment_preview_thumnail(attachment)
43
- return unless attachment.file.present?
43
+ return unless attachment.url.present?
44
44
 
45
45
  # Any changes made here must be reflected in attachment_input_controller#buildPreviewTemplate
46
46
 
47
47
  tag.div class: "d-inline-block img-thumbnail", data: {attachment_preview_target: "thumbnail"} do
48
- link_body = if attachment.representable?
49
- image_tag attachment.thumbnail_url, style: "width:100%; height:100%; object-fit: contain;"
48
+ thumbnail_url = _attachment_thumbnail_url(attachment)
49
+ link_body = if thumbnail_url
50
+ image_tag thumbnail_url, style: "width:100%; height:100%; object-fit: contain;"
50
51
  else
51
- attachment.file.extension.to_s
52
+ _attachment_extension(attachment)
52
53
  end
53
54
 
54
- link_to link_body, attachment.file_url, style: "width:150px; height:150px; line-height: 150px;",
55
+ link_to link_body, attachment.url, style: "width:150px; height:150px; line-height: 150px;",
55
56
  class: "d-block text-decoration-none user-select-none fs-5 font-monospace text-body-secondary",
56
57
  target: :blank,
57
58
  data: {attachment_preview_target: "thumbnailLink"}
58
59
  end
59
60
  end
61
+
62
+ private
63
+
64
+ def _attachment_thumbnail_url(attachment)
65
+ attachment.url if attachment.representable?
66
+ end
67
+
68
+ def _attachment_extension(attachment)
69
+ attachment.try(:extension) || File.extname(attachment.filename.to_s)
70
+ end
60
71
  end
61
72
  end
62
73
  end
@@ -3,10 +3,6 @@ module Plutonium
3
3
  module FormHelper
4
4
  include ActionView::Helpers::FormHelper
5
5
 
6
- #
7
- # Override the original form_for helper to disable turbo forms by default if not
8
- # explicitly opted into
9
- #
10
6
  def resource_form_for(record, options = {}, &block)
11
7
  turbo_frame = options.key?(:turbo_frame) ? options[:turbo_frame] : "_top"
12
8
  options = {
@@ -17,6 +13,8 @@ module Plutonium
17
13
  }
18
14
  }.deep_merge! options
19
15
 
16
+ # record = adapt_route_args(record) unless record.is_a?(Array) || options.key?(:url)
17
+
20
18
  simple_form_for(record, options, &block)
21
19
  end
22
20
  end
@@ -6,11 +6,11 @@ module Plutonium
6
6
  include Plutonium::Policy::Initializer
7
7
 
8
8
  def resolve
9
- scope = context.resource_class.all
10
- if @context.parent.present?
11
- scope = scope.associated_with(@context.parent)
12
- elsif @context.scope.present?
13
- scope = scope.associated_with(@context.scope)
9
+ scope = context.resource_context.resource_class.all
10
+ if @context.resource_context.parent.present?
11
+ scope = scope.associated_with(@context.resource_context.parent)
12
+ elsif @context.resource_context.scope.present?
13
+ scope = scope.associated_with(@context.resource_context.scope)
14
14
  end
15
15
  scope
16
16
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ class Railtie < Rails::Railtie
5
+ # add railties here
6
+
7
+ initializer "plutonium.achieve_criticality" do
8
+ # get the ball rolling
9
+ Plutonium::Reactor::Core.achieve_criticality!
10
+ end
11
+ end
12
+ end
@@ -50,6 +50,8 @@ module Plutonium
50
50
  else
51
51
  load file
52
52
  end
53
+ rescue => e
54
+ Rails.logger.error "\npu.hotreloader: failed to reload #{file}\n\n#{e}\n"
53
55
  end
54
56
  end
55
57
  listener.start
@@ -0,0 +1,5 @@
1
+ module Plutonium
2
+ module Reactor
3
+ PolicyContext = Data.define :user, :resource_context
4
+ end
5
+ end
@@ -1,15 +1,5 @@
1
1
  module Plutonium
2
2
  module Reactor
3
- class ResourceContext
4
- attr_reader :user, :resource_record, :resource_class, :parent, :scope
5
-
6
- def initialize(user:, resource_record:, resource_class:, parent: nil, scope: nil)
7
- @user = user
8
- @resource_record = resource_record
9
- @resource_class = resource_class || resource_record&.class
10
- @parent = parent
11
- @scope = scope
12
- end
13
- end
3
+ ResourceContext = Data.define :resource_class, :parent, :scope
14
4
  end
15
5
  end
@@ -36,6 +36,12 @@ module Plutonium
36
36
  before_action :set_page_title
37
37
  before_action :set_sidebar_menu
38
38
 
39
+ before_action do
40
+ return unless defined?(ActiveStorage)
41
+
42
+ ActiveStorage::Current.url_options = {protocol: request.protocol, host: request.host, port: request.port}
43
+ end
44
+
39
45
  # https://github.com/ddnexus/pagy/blob/master/docs/extras/headers.md#headers
40
46
  after_action { pagy_headers_merge(@pagy) if @pagy }
41
47
 
@@ -52,10 +58,15 @@ module Plutonium
52
58
  end
53
59
  helper_method :resource_record
54
60
 
61
+ def submitted_resource_params
62
+ @submitted_resource_params ||= begin
63
+ strong_parameters = resource_class.strong_parameters_for(*permitted_attributes)
64
+ params.require(resource_param_key).permit(*strong_parameters).nilify.to_h
65
+ end
66
+ end
67
+
55
68
  def resource_params
56
- # Example of documenting an ignore in the source code
57
- # NOTE: Brakeman warning ignored for MassAssignment because inputs are filtered manually
58
- input_params = params.require(resource_param_key).permit!.nilify.to_h
69
+ input_params = submitted_resource_params.dup
59
70
 
60
71
  # Override any entity scoping params
61
72
  input_params[scoped_entity_param_key] = current_scoped_entity if scoped_to_entity?
@@ -64,6 +75,7 @@ module Plutonium
64
75
  input_params[parent_input_param] = current_parent if current_parent.present?
65
76
  input_params[:"#{parent_input_param}_id"] = current_parent.id if current_parent.present?
66
77
 
78
+ # additionally filter our input_params through our inputs
67
79
  current_presenter.defined_inputs_for(permitted_attributes)
68
80
  .values.map { |input| input.collect input_params }
69
81
  .reduce(:merge)
@@ -74,6 +86,19 @@ module Plutonium
74
86
  end
75
87
  helper_method :resource_param_key
76
88
 
89
+ # sets params on submitted_resource_params if they have been passed
90
+ def maybe_apply_submitted_resource_params!
91
+ # this is useful in dynamic forms as we can read the resource record to determine how to define our inputs
92
+ # we need to ensure that this is being called from get because
93
+ # it is potentially unsafe since we don't apply the input filter. see #resource_params
94
+ # would have been nice to be able to, but we can't until we have the presenter, and the presenter
95
+ # requires the resource_record for our dynamic forms
96
+ # is this perfect? no. but it works.
97
+ raise "🚨🚨🚨 this should be called from actions that are not persisting this data" unless request.method == "GET"
98
+
99
+ resource_record.attributes = submitted_resource_params if params[resource_param_key].present?
100
+ end
101
+
77
102
  # Layout
78
103
 
79
104
  def set_page_title
@@ -90,9 +115,7 @@ module Plutonium
90
115
 
91
116
  def resource_context
92
117
  Plutonium::Reactor::ResourceContext.new(
93
- user: current_user,
94
118
  resource_class:,
95
- resource_record: @resource_record,
96
119
  parent: current_parent,
97
120
  scope: scoped_to_entity? ? current_scoped_entity : nil
98
121
  )
@@ -83,7 +83,7 @@ module Plutonium
83
83
  def autodetect_fields_for(method_name)
84
84
  maybe_warn_autodetect_usage method_name
85
85
 
86
- context.resource_class.resource_field_names
86
+ context.resource_context.resource_class.resource_field_names
87
87
  end
88
88
 
89
89
  def maybe_warn_autodetect_usage(method)
@@ -95,7 +95,7 @@ module Plutonium
95
95
  Resource field auto-detection: #{self.class}##{method}
96
96
 
97
97
  Auto-detected resource fields result in security holes and will fail outside of development.
98
- Override #{context.resource_class}Policy or #{self.class} with your own ##{method} method.
98
+ Override #{context.resource_context.resource_class}Policy or #{self.class} with your own ##{method} method.
99
99
 
100
100
  🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
101
101
  )
@@ -4,17 +4,22 @@ module Plutonium
4
4
  include Plutonium::Core::Definers::FieldDefiner
5
5
  include Plutonium::Core::Definers::ActionDefiner
6
6
 
7
- def initialize(context)
7
+ def initialize(context, resource_record)
8
8
  @context = context
9
+ @resource_record = resource_record
9
10
 
10
11
  define_standard_actions
11
12
  define_actions
12
13
  define_fields
13
14
  end
14
15
 
16
+ def search_field
17
+ nil
18
+ end
19
+
15
20
  private
16
21
 
17
- attr_reader :context
22
+ attr_reader :context, :resource_record
18
23
 
19
24
  def define_fields
20
25
  # override this in child presenters for custom field definitions
@@ -10,21 +10,23 @@ module Plutonium
10
10
  named_scope = :"associated_with_#{record.model_name.singular}"
11
11
  return send(named_scope, record) if respond_to?(named_scope)
12
12
 
13
+ # TOD: add suppport for polymorphic associations
14
+
13
15
  # TODO: add logging
14
16
  if (own_association = reflect_on_all_associations.find { |assoc| assoc.klass == record.class })
15
17
  case own_association.macro
16
18
  when :has_one
17
19
  joins(own_association.name).where({
18
- own_association.name.to_sym => {
20
+ own_association.name => {
19
21
  record.class.primary_key => record.id
20
22
  }
21
23
  })
22
24
  when :belongs_to
23
25
  where(own_association.name => record)
24
26
  when :has_many
25
- joins(own_association.name).where(own_association.klass.table_name.to_sym => record)
27
+ joins(own_association.name).where(own_association.klass.table_name => record)
26
28
  else
27
- raise Net::HTTPNotImplemented, "associated_with->##{own_association.macro}"
29
+ raise NotImplementedError, "associated_with->##{own_association.macro}"
28
30
  end
29
31
  elsif (record_association = record.class.reflect_on_all_associations.find { |assoc| assoc.klass == klass })
30
32
  # TODO: add a warning here about a potentially poor performing query
@@ -33,37 +35,66 @@ module Plutonium
33
35
  raise "Could not resolve the association between '#{klass.name}' and '#{record.class.name}'\n\n" \
34
36
  "Define\n" \
35
37
  " 1. the associations between the models\n" \
36
- " 2. a named scope e.g.\n\n" \
38
+ " 2. a named scope on #{klass.name} e.g.\n\n" \
37
39
  "scope :#{named_scope}, ->(#{record.model_name.singular}) { do_something_here }"
38
40
  end
39
41
  end
40
42
  end
41
43
 
42
44
  module ClassMethods
43
- # Path parameters
45
+ def resource_field_names
46
+ @resource_field_names ||= belongs_to_association_field_names +
47
+ has_one_attached_field_names + has_one_association_field_names +
48
+ has_many_attached_field_names + has_many_association_field_names +
49
+ content_column_field_names
50
+ end
44
51
 
45
- def path_parameter(param_name)
46
- param_name = param_name.to_sym
52
+ def belongs_to_association_field_names
53
+ @belongs_to_association_field_names ||= reflect_on_all_associations(:belongs_to).map(&:name)
54
+ end
47
55
 
48
- scope :from_path_param, ->(param) { where(param_name => param) }
56
+ def has_one_association_field_names
57
+ @has_one_association_field_names ||= reflect_on_all_associations(:has_one)
58
+ .map { |assoc| /_attachment$|_blob$/.match?(assoc.name) ? nil : assoc.name }
59
+ .compact
60
+ end
49
61
 
50
- define_method :to_param do
51
- return nil unless persisted?
62
+ def has_many_association_field_names
63
+ @has_many_association_field_names ||= reflect_on_all_associations(:has_many)
64
+ .map { |assoc| /_attachments$|_blobs$/.match?(assoc.name) ? nil : assoc.name }
65
+ .compact
66
+ end
52
67
 
53
- send param_name
54
- end
68
+ def has_one_attached_field_names
69
+ @has_one_attached_field_names ||= reflect_on_all_attachments
70
+ .map { |a| (a.macro == :has_one_attached) ? a.name : nil }
71
+ .compact
55
72
  end
56
73
 
57
- def dynamic_path_parameter(param_name)
58
- param_name = param_name.to_sym
74
+ def has_many_attached_field_names
75
+ @has_many_attached_field_names ||= reflect_on_all_attachments
76
+ .map { |a| (a.macro == :has_many_attached) ? a.name : nil }
77
+ .compact
78
+ end
59
79
 
60
- scope :from_path_param, ->(param) { where(id: param.split("-").first) }
80
+ def content_column_field_names
81
+ @content_column_field_names ||= content_columns.map { |col| col.name.to_sym }
82
+ end
61
83
 
62
- define_method :to_param do
63
- return nil unless persisted?
84
+ def has_many_association_routes
85
+ @has_many_association_routes ||= reflect_on_all_associations(:has_many).map { |assoc| assoc.klass.model_name.plural }
86
+ end
64
87
 
65
- "#{id}-#{send(param_name)}".parameterize
66
- end
88
+ #
89
+ # Returns the strong parameters definition for the given attribute names
90
+ #
91
+ # @param [Array] *attributes Attribute names
92
+ #
93
+ # @return [Array] A strong parameters compatible array e.g.
94
+ # [:title, :body, {:images=>[]}, {:docs=>[]}]
95
+ #
96
+ def strong_parameters_for(*attributes)
97
+ strong_parameters_definition.slice(*attributes).map { |key, value| value.nil? ? key : {key => value} }
67
98
  end
68
99
 
69
100
  # Ransack
@@ -84,29 +115,60 @@ module Plutonium
84
115
  []
85
116
  end
86
117
 
87
- def resource_field_names
88
- @resource_field_names ||= belongs_to_association_field_names + has_one_association_field_names +
89
- has_many_association_field_names + content_column_field_names
90
- end
118
+ private
91
119
 
92
- def belongs_to_association_field_names
93
- @belongs_to_association_field_names ||= reflect_on_all_associations(:belongs_to).map { |assoc| assoc.name.to_sym }
94
- end
120
+ def strong_parameters_definition
121
+ # @strong_parameters ||= begin
122
+ @strong_parameters = begin
123
+ content_column_parameters = content_column_field_names.map do |name|
124
+ column = columns_hash[name.to_s]
95
125
 
96
- def has_one_association_field_names
97
- @has_one_association_field_names ||= reflect_on_all_associations(:has_one).map { |assoc| assoc.name.to_sym }
98
- end
126
+ type = nil
127
+ type = [] if column&.try(:array?)
128
+ type = {} if [:json, :jsonb].include?(column&.type)
99
129
 
100
- def has_many_association_field_names
101
- @has_many_association_field_names ||= reflect_on_all_associations(:has_many).map { |assoc| assoc.name.to_sym }
130
+ [name, type]
131
+ end
132
+ parameters = content_column_parameters.to_h
133
+
134
+ # TODO: add nested support
135
+
136
+ parameters.merge! belongs_to_association_field_names.map { |name| [name, nil] }.to_h
137
+ parameters.merge! has_one_association_field_names.map { |name| [name, nil] }.to_h
138
+ parameters.merge! has_one_attached_field_names.map { |name| [name, nil] }.to_h
139
+
140
+ parameters.merge! has_many_association_field_names.map { |name| [name, []] }.to_h
141
+ parameters.merge! has_many_attached_field_names.map { |name| [name, []] }.to_h
142
+
143
+ # e.g. {:title=>nil, :body=>nil, :state=>nil, :created_at=>nil, :updated_at=>nil, :image=>nil, :images=>[]}
144
+ parameters
145
+ end
102
146
  end
103
147
 
104
- def content_column_field_names
105
- @content_column_field_names ||= content_columns.map { |col| col.name.to_sym }
148
+ # Path parameters
149
+
150
+ def path_parameter(param_name)
151
+ param_name = param_name.to_sym
152
+
153
+ scope :from_path_param, ->(param) { where(param_name => param) }
154
+
155
+ define_method :to_param do
156
+ return nil unless persisted?
157
+
158
+ send param_name
159
+ end
106
160
  end
107
161
 
108
- def has_many_association_routes
109
- @has_many_association_routes ||= reflect_on_all_associations(:has_many).map { |assoc| assoc.klass.model_name.plural }
162
+ def dynamic_path_parameter(param_name)
163
+ param_name = param_name.to_sym
164
+
165
+ scope :from_path_param, ->(param) { where(id: param.split("-").first) }
166
+
167
+ define_method :to_param do
168
+ return nil unless persisted?
169
+
170
+ "#{id}-#{send(param_name)}".parameterize
171
+ end
110
172
  end
111
173
  end
112
174
 
@@ -3,6 +3,7 @@ module Plutonium
3
3
  extend ActiveSupport::Autoload
4
4
 
5
5
  autoload :Core
6
+ autoload :PolicyContext
6
7
  autoload :ResourceContext
7
8
  autoload :ResourceController
8
9
  autoload :ResourceInteraction
@@ -14,7 +14,7 @@ module Plutonium
14
14
  next if value.nil?
15
15
 
16
16
  template.concat begin
17
- template.display_attachment_value value, identity_class: input_class, caption: true do |attachment|
17
+ template.display_attachment_value value, identity_class: input_class, caption: caption? do |attachment|
18
18
  [
19
19
  # These hidden fields allow us to preserve the already uploaded files.
20
20
  # For has_one_attached, it overrides the hidden field previously added
@@ -25,7 +25,8 @@ module Plutonium
25
25
  @builder.hidden_field(attribute_name, multiple: multiple?, value: attachment.signed_id),
26
26
 
27
27
  # By removing this component, the hidden field is gone, which allows us to remove the files from active storage
28
- template.content_tag(:p, class: "text-danger m-0", role: :button, data: {action: "click->attachment-preview#remove"}) do
28
+ template.content_tag(:p, class: "text-danger m-0", role: :button,
29
+ data: {action: "click->attachment-preview#remove"}) do
29
30
  template.content_tag :span, " Delete", class: "bi bi-trash"
30
31
  end
31
32
  ]
@@ -41,7 +42,11 @@ module Plutonium
41
42
  end
42
43
 
43
44
  def multiple?
44
- options[:multiple]
45
+ options[:input_html] && options[:input_html][:multiple]
46
+ end
47
+
48
+ def caption?
49
+ options.key?(:caption) ? options[:caption] : true
45
50
  end
46
51
 
47
52
  def maybe_setup_direct_uploads
@@ -74,4 +79,5 @@ module Plutonium
74
79
  end
75
80
  end
76
81
  end
82
+ # Register with simple_form
77
83
  SimpleForm.include_component(Plutonium::SimpleForm::AttachmentComponent)
@@ -11,4 +11,5 @@ module Plutonium
11
11
  end
12
12
  end
13
13
  end
14
+ # Register with simple_form
14
15
  SimpleForm.include_component(Plutonium::SimpleForm::InputGroupComponent)
@@ -1,3 +1,3 @@
1
1
  module Plutonium
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/plutonium.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "active_support"
2
2
  require_relative "plutonium/version"
3
+ require_relative "plutonium/railtie"
3
4
 
4
5
  module Plutonium
5
6
  # require_relative "active_model/validations/array_validator"
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.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-18 00:00:00.000000000 Z
11
+ date: 2024-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -428,14 +428,17 @@ files:
428
428
  - lib/plutonium/core/definers/renderer_definer.rb
429
429
  - lib/plutonium/core/fields.rb
430
430
  - lib/plutonium/core/fields/inputs.rb
431
- - lib/plutonium/core/fields/inputs/association_input.rb
432
- - lib/plutonium/core/fields/inputs/basic_input.rb
433
- - lib/plutonium/core/fields/inputs/belongs_to_input.rb
431
+ - lib/plutonium/core/fields/inputs/attachment_input.rb
432
+ - lib/plutonium/core/fields/inputs/base.rb
433
+ - lib/plutonium/core/fields/inputs/belongs_to_association_input.rb
434
434
  - lib/plutonium/core/fields/inputs/factory.rb
435
- - lib/plutonium/core/fields/inputs/has_many_input.rb
435
+ - lib/plutonium/core/fields/inputs/has_many_association_input.rb
436
436
  - lib/plutonium/core/fields/inputs/noop_input.rb
437
+ - lib/plutonium/core/fields/inputs/simple_form_association_input.rb
438
+ - lib/plutonium/core/fields/inputs/simple_form_input.rb
437
439
  - lib/plutonium/core/fields/renderers.rb
438
440
  - lib/plutonium/core/fields/renderers/association_renderer.rb
441
+ - lib/plutonium/core/fields/renderers/attachment_renderer.rb
439
442
  - lib/plutonium/core/fields/renderers/basic_renderer.rb
440
443
  - lib/plutonium/core/fields/renderers/factory.rb
441
444
  - lib/plutonium/core/ui.rb
@@ -469,8 +472,10 @@ files:
469
472
  - lib/plutonium/policy/scope.rb
470
473
  - lib/plutonium/preserved__/field.rb
471
474
  - lib/plutonium/preserved__/input.rb
475
+ - lib/plutonium/railtie.rb
472
476
  - lib/plutonium/reactor.rb
473
477
  - lib/plutonium/reactor/core.rb
478
+ - lib/plutonium/reactor/policy_context.rb
474
479
  - lib/plutonium/reactor/resource_context.rb
475
480
  - lib/plutonium/reactor/resource_controller.rb
476
481
  - lib/plutonium/reactor/resource_interaction.rb