plutonium 0.18.5 → 0.18.7

Sign up to get free protection for your applications and to get access to all the features.
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