plutonium 0.18.5 → 0.18.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.js +1 -1
  3. data/app/assets/plutonium.js.map +2 -2
  4. data/app/assets/plutonium.min.js +1 -1
  5. data/app/assets/plutonium.min.js.map +2 -2
  6. data/app/views/resource/interactive_bulk_action.html.erb +1 -1
  7. data/app/views/resource/interactive_resource_action.html.erb +1 -1
  8. data/lib/generators/pu/eject/layout/layout_generator.rb +1 -2
  9. data/lib/generators/pu/eject/shell/shell_generator.rb +1 -3
  10. data/lib/generators/pu/lib/plutonium_generators/concerns/logger.rb +4 -0
  11. data/lib/generators/pu/lib/plutonium_generators/concerns/package_selector.rb +80 -0
  12. data/lib/generators/pu/lib/plutonium_generators/concerns/resource_selector.rb +48 -0
  13. data/lib/generators/pu/lib/plutonium_generators/generator.rb +2 -42
  14. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +1 -4
  15. data/lib/generators/pu/res/conn/conn_generator.rb +9 -21
  16. data/lib/generators/pu/res/scaffold/scaffold_generator.rb +10 -5
  17. data/lib/plutonium/models/has_cents.rb +4 -2
  18. data/lib/plutonium/resource/controller.rb +3 -3
  19. data/lib/plutonium/resource/controllers/authorizable.rb +1 -1
  20. data/lib/plutonium/resource/controllers/crud_actions.rb +17 -17
  21. data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
  22. data/lib/plutonium/resource/controllers/presentable.rb +2 -2
  23. data/lib/plutonium/resource/record/associated_with.rb +83 -0
  24. data/lib/plutonium/resource/record/associations.rb +92 -0
  25. data/lib/plutonium/resource/record/field_names.rb +83 -0
  26. data/lib/plutonium/resource/record/labeling.rb +19 -0
  27. data/lib/plutonium/resource/record/routes.rb +66 -0
  28. data/lib/plutonium/resource/record.rb +6 -258
  29. data/lib/plutonium/ui/breadcrumbs.rb +4 -4
  30. data/lib/plutonium/ui/component/methods.rb +4 -12
  31. data/lib/plutonium/ui/form/base.rb +22 -10
  32. data/lib/plutonium/ui/form/components/secure_association.rb +112 -0
  33. data/lib/plutonium/ui/form/components/secure_polymorphic_association.rb +54 -0
  34. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +0 -1
  35. data/lib/plutonium/ui/form/theme.rb +12 -1
  36. data/lib/plutonium/ui/page/show.rb +1 -1
  37. data/lib/plutonium/ui/page_header.rb +1 -1
  38. data/lib/plutonium/version.rb +1 -1
  39. data/package-lock.json +2 -2
  40. data/package.json +1 -1
  41. data/src/js/controllers/slim_select_controller.js +3 -1
  42. metadata +11 -4
  43. data/lib/plutonium/ui/form/components/belongs_to.rb +0 -66
  44. data/lib/plutonium/ui/form/components/has_many.rb +0 -66
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Form
6
+ module Components
7
+ class SecureAssociation < Phlexi::Form::Components::AssociationBase
8
+ include Plutonium::UI::Component::Methods
9
+
10
+ def view_template
11
+ div(class: "flex space-x-1") do
12
+ super
13
+ render_add_button
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ delegate :association_reflection, to: :field
20
+
21
+ def render_add_button
22
+ return if @add_action == false || add_url.nil?
23
+
24
+ a(
25
+ href: add_url,
26
+ class:
27
+ "bg-gray-100 dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-500 hover:bg-gray-200 border border-gray-300 rounded-lg p-3 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none dark:text-white"
28
+ ) do
29
+ render Phlex::TablerIcons::Plus.new(class: "w-3 h-3")
30
+ end
31
+ end
32
+
33
+ def add_url
34
+ @add_url ||= begin
35
+ return unless @skip_authorization || allowed_to?(:create?, association_reflection.klass)
36
+
37
+ url = @add_action || resource_url_for(association_reflection.klass, action: :new, parent: nil)
38
+ return unless url
39
+
40
+ uri = URI(url)
41
+ uri.query = URI.encode_www_form({return_to: request.original_url})
42
+ uri.to_s
43
+ end
44
+ end
45
+
46
+ def choices
47
+ @choices ||= begin
48
+ collection = if (user_choices = attributes.delete(:choices))
49
+ user_choices
50
+ elsif @skip_authorization
51
+ choices_from_association(association_reflection.klass)
52
+ else
53
+ authorized_resource_scope(association_reflection.klass, relation: choices_from_association(association_reflection.klass))
54
+ end
55
+ build_choice_mapper(collection)
56
+ end
57
+ end
58
+
59
+ def build_attributes
60
+ build_association_attributes
61
+ super
62
+ end
63
+
64
+ def build_association_attributes
65
+ @skip_authorization = attributes.delete(:skip_authorization)
66
+ @add_action = attributes.delete(:add_action)
67
+
68
+ attributes.fetch(:value_method) { attributes[:value_method] = :to_signed_global_id }
69
+
70
+ case association_reflection.macro
71
+ when :belongs_to, :has_one
72
+ build_singluar_association_attributes
73
+ when :has_many, :has_and_belongs_to_many
74
+ build_collection_association_attributes
75
+ end
76
+ end
77
+
78
+ def build_singluar_association_attributes
79
+ attributes.fetch(:input_param) { attributes[:input_param] = :"#{association_reflection.name}_sgid" }
80
+ end
81
+
82
+ def build_collection_association_attributes
83
+ attributes.fetch(:input_param) { attributes[:input_param] = :"#{association_reflection.name.to_s.singularize}_sgids" }
84
+ attributes[:multiple] = true
85
+ end
86
+
87
+ def normalize_simple_input(input_value)
88
+ @signed_global_ids ||= choices.values.map { |choice| SignedGlobalID.parse(choice) }
89
+ ([SignedGlobalID.parse(input_value.presence)].compact & @signed_global_ids)[0]
90
+ end
91
+
92
+ def selected?(option)
93
+ case association_reflection.macro
94
+ when :belongs_to, :has_one
95
+ singular_field_value == SignedGlobalID.parse(option)
96
+ when :has_many, :has_and_belongs_to_many
97
+ collection_field_value.any? { |item| item == SignedGlobalID.parse(option) }
98
+ end
99
+ end
100
+
101
+ def singular_field_value
102
+ @singular_field_value ||= field.object.send :"#{association_reflection.name}_sgid"
103
+ end
104
+
105
+ def collection_field_value
106
+ @collection_field_value ||= field.object.send :"#{association_reflection.name.to_s.singularize}_sgids"
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Form
6
+ module Components
7
+ class SecurePolymorphicAssociation < SecureAssociation
8
+ protected
9
+
10
+ def build_attributes
11
+ attributes.fetch(:group_method) { attributes[:group_method] = :last }
12
+ super
13
+ end
14
+
15
+ def choices
16
+ @choices ||= begin
17
+ Plutonium.eager_load_rails!
18
+ collection = if (user_choices = attributes.delete(:choices))
19
+ user_choices
20
+ else
21
+ associated_classes.map { |klass|
22
+ [
23
+ klass.model_name.human.pluralize,
24
+ @skip_authorization ? choices_from_association(klass) : authorized_resource_scope(klass, relation: choices_from_association(klass))
25
+ ]
26
+ }.to_h
27
+ end
28
+ build_choice_mapper(collection)
29
+ end
30
+ end
31
+
32
+ def associated_classes
33
+ Plutonium.eager_load_rails!
34
+
35
+ associated_classes = []
36
+ ActiveRecord::Base.descendants.each do |model_klass|
37
+ next if !model_klass.table_exists? || model_klass.abstract_class?
38
+
39
+ (model_klass.reflect_on_all_associations(:has_many) + model_klass.reflect_on_all_associations(:has_one)).each do |association|
40
+ if association.options[:as] == association_reflection.name
41
+ associated_classes << model_klass
42
+ end
43
+ end
44
+ end
45
+ associated_classes
46
+ end
47
+
48
+ def render_add_button
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -91,7 +91,6 @@ module Plutonium
91
91
  # @param [Symbol] name The name of the nested resource field
92
92
  # @raise [ArgumentError] if the nested input definition is missing required configuration
93
93
  def render_nested_resource_field(name)
94
- # debugger if $extracting_input
95
94
  context = NestedFieldContext.new(
96
95
  name: name,
97
96
  definition: build_nested_fields_definition(name),
@@ -51,7 +51,18 @@ module Plutonium
51
51
  uppy: :file,
52
52
  valid_uppy: :valid_file,
53
53
  invalid_uppy: :invalid_file,
54
- neutral_uppy: :neutral_file
54
+ neutral_uppy: :neutral_file,
55
+
56
+ association: :select,
57
+ valid_association: :valid_select,
58
+ invalid_association: :invalid_select,
59
+ neutral_association: :neutral_select,
60
+
61
+ polymorpic_association: :association,
62
+ valid_polymorpic_association: :valid_association,
63
+ invalid_polymorpic_association: :invalid_association,
64
+ neutral_polymorpic_association: :neutral_association
65
+
55
66
  })
56
67
  end
57
68
  end
@@ -7,7 +7,7 @@ module Plutonium
7
7
  private
8
8
 
9
9
  def page_title
10
- current_definition.show_page_title || super || display_name_of(resource_record)
10
+ current_definition.show_page_title || super || display_name_of(resource_record!)
11
11
  end
12
12
 
13
13
  def page_description
@@ -40,7 +40,7 @@ module Plutonium
40
40
 
41
41
  def render_actions
42
42
  @actions.each do |action|
43
- url = resource_url_for(resource_record || resource_class, *action.route_options.url_args, **action.route_options.url_options)
43
+ url = resource_url_for(resource_record? || resource_class, *action.route_options.url_args, **action.route_options.url_options)
44
44
  ActionButton(action, url:)
45
45
  end
46
46
  end
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.18.5"
2
+ VERSION = "0.18.7"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@radioactive-labs/plutonium",
9
- "version": "0.3.1",
9
+ "version": "0.3.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@hotwired/stimulus": "^3.2.2",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Core assets for the Plutonium gem",
5
5
  "type": "module",
6
6
  "main": "src/js/core.js",
@@ -16,6 +16,8 @@ export default class extends Controller {
16
16
 
17
17
  reconnect() {
18
18
  this.disconnect()
19
- this.connect()
19
+ // dispatch this on the next frame.
20
+ // there's some funny issue where my elements get removed from the DOM
21
+ setTimeout(() => this.connect(), 10)
20
22
  }
21
23
  }
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.18.5
4
+ version: 0.18.7
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-01-01 00:00:00.000000000 Z
11
+ date: 2025-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -603,6 +603,8 @@ files:
603
603
  - lib/generators/pu/lib/plutonium_generators/concerns/actions.rb
604
604
  - lib/generators/pu/lib/plutonium_generators/concerns/config.rb
605
605
  - lib/generators/pu/lib/plutonium_generators/concerns/logger.rb
606
+ - lib/generators/pu/lib/plutonium_generators/concerns/package_selector.rb
607
+ - lib/generators/pu/lib/plutonium_generators/concerns/resource_selector.rb
606
608
  - lib/generators/pu/lib/plutonium_generators/concerns/serializer.rb
607
609
  - lib/generators/pu/lib/plutonium_generators/generator.rb
608
610
  - lib/generators/pu/lib/plutonium_generators/installer.rb
@@ -786,6 +788,11 @@ files:
786
788
  - lib/plutonium/resource/policy.rb
787
789
  - lib/plutonium/resource/query_object.rb
788
790
  - lib/plutonium/resource/record.rb
791
+ - lib/plutonium/resource/record/associated_with.rb
792
+ - lib/plutonium/resource/record/associations.rb
793
+ - lib/plutonium/resource/record/field_names.rb
794
+ - lib/plutonium/resource/record/labeling.rb
795
+ - lib/plutonium/resource/record/routes.rb
789
796
  - lib/plutonium/resource/register.rb
790
797
  - lib/plutonium/rodauth/controller_methods.rb
791
798
  - lib/plutonium/routing/mapper_extensions.rb
@@ -813,11 +820,11 @@ files:
813
820
  - lib/plutonium/ui/dyna_frame/host.rb
814
821
  - lib/plutonium/ui/empty_card.rb
815
822
  - lib/plutonium/ui/form/base.rb
816
- - lib/plutonium/ui/form/components/belongs_to.rb
817
823
  - lib/plutonium/ui/form/components/easymde.rb
818
824
  - lib/plutonium/ui/form/components/flatpickr.rb
819
- - lib/plutonium/ui/form/components/has_many.rb
820
825
  - lib/plutonium/ui/form/components/intl_tel_input.rb
826
+ - lib/plutonium/ui/form/components/secure_association.rb
827
+ - lib/plutonium/ui/form/components/secure_polymorphic_association.rb
821
828
  - lib/plutonium/ui/form/components/uppy.rb
822
829
  - lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb
823
830
  - lib/plutonium/ui/form/interaction.rb
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Plutonium
4
- module UI
5
- module Form
6
- module Components
7
- class BelongsTo < Phlexi::Form::Components::BelongsTo
8
- include Plutonium::UI::Component::Methods
9
-
10
- def view_template
11
- div(class: "flex space-x-1") do
12
- super
13
- render_add_button
14
- end
15
- end
16
-
17
- private
18
-
19
- def add_url
20
- @add_url ||= begin
21
- return unless @skip_authorization || allowed_to?(:create?, association_reflection.klass)
22
-
23
- url = @add_action || resource_url_for(association_reflection.klass, action: :new, parent: nil)
24
- return unless url
25
-
26
- uri = URI(url)
27
- uri.query = URI.encode_www_form({return_to: request.original_url})
28
- uri.to_s
29
- end
30
- end
31
-
32
- def render_add_button
33
- return if @add_action == false || add_url.nil?
34
-
35
- a(
36
- href: add_url,
37
- class:
38
- "bg-gray-100 dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-500 hover:bg-gray-200 border border-gray-300 rounded-lg p-3 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none dark:text-white"
39
- ) do
40
- render Phlex::TablerIcons::Plus.new(class: "w-3 h-3")
41
- end
42
- end
43
-
44
- def choices
45
- @choices ||= begin
46
- collection = if @provided_choices || @skip_authorization
47
- @choice_collection
48
- else
49
- authorized_resource_scope(association_reflection.klass, relation: @choice_collection)
50
- end
51
- Phlexi::Form::ChoicesMapper.new(collection, label_method: @label_method, value_method: @value_method)
52
- end
53
- end
54
-
55
- def build_attributes
56
- @provided_choices = !attributes[:choices].nil?
57
- @skip_authorization = attributes.delete(:skip_authorization)
58
- @add_action = attributes.delete(:add_action)
59
-
60
- super
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Plutonium
4
- module UI
5
- module Form
6
- module Components
7
- class HasMany < Phlexi::Form::Components::HasMany
8
- include Plutonium::UI::Component::Methods
9
-
10
- def view_template
11
- div(class: "flex space-x-1") do
12
- super
13
- render_add_button
14
- end
15
- end
16
-
17
- private
18
-
19
- def add_url
20
- @add_url ||= begin
21
- return unless @skip_authorization || allowed_to?(:create?, association_reflection.klass)
22
-
23
- url = @add_action || resource_url_for(association_reflection.klass, action: :new, parent: nil)
24
- return unless url
25
-
26
- uri = URI(url)
27
- uri.query = URI.encode_www_form({return_to: request.original_url})
28
- uri.to_s
29
- end
30
- end
31
-
32
- def render_add_button
33
- return if @add_action == false || add_url.nil?
34
-
35
- a(
36
- href: add_url,
37
- class:
38
- "bg-gray-100 dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-500 hover:bg-gray-200 border border-gray-300 rounded-lg p-3 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none dark:text-white"
39
- ) do
40
- render Phlex::TablerIcons::Plus.new(class: "w-3 h-3")
41
- end
42
- end
43
-
44
- def choices
45
- @choices ||= begin
46
- collection = if @provided_choices || @skip_authorization
47
- @choice_collection
48
- else
49
- authorized_resource_scope(association_reflection.klass, relation: @choice_collection)
50
- end
51
- Phlexi::Form::ChoicesMapper.new(collection, label_method: @label_method, value_method: @value_method)
52
- end
53
- end
54
-
55
- def build_attributes
56
- @provided_choices = !attributes[:choices].nil?
57
- @skip_authorization = attributes.delete(:skip_authorization)
58
- @add_action = attributes.delete(:add_action)
59
-
60
- super
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end