plutonium 0.14.0 → 0.15.0.pre.rc1

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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/README copy.md +1 -1
  3. data/README.md +1 -1
  4. data/app/assets/plutonium.css +1 -1
  5. data/app/views/{application → plutonium}/_resource_header.html copy.erb +1 -1
  6. data/app/views/{application → plutonium}/_resource_header.html.erb +1 -1
  7. data/app/views/{application → plutonium}/_resource_sidebar.html.erb +2 -0
  8. data/app/views/resource/_resource_details.html.erb +1 -36
  9. data/app/views/resource/_resource_form.html.erb +1 -5
  10. data/app/views/resource/_resource_table.html.erb +315 -85
  11. data/app/views/resource/edit.html.erb +1 -5
  12. data/app/views/resource/index.html.erb +1 -5
  13. data/app/views/resource/new.html.erb +1 -5
  14. data/app/views/resource/show.html.erb +1 -5
  15. data/config/initializers/pagy.rb +1 -0
  16. data/config/initializers/rabl.rb +27 -20
  17. data/gemfiles/rails_7.gemfile.lock +5 -1
  18. data/lib/generators/pu/core/assets/assets_generator.rb +2 -2
  19. data/lib/generators/pu/core/install/install_generator.rb +0 -3
  20. data/lib/generators/pu/core/install/templates/app/controllers/plutonium_controller.rb.tt +2 -0
  21. data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +21 -1
  22. data/lib/generators/pu/core/install/templates/app/definitions/resource_definition.rb.tt +2 -0
  23. data/lib/generators/pu/core/install/templates/app/models/resource_record.rb.tt +0 -2
  24. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +5 -2
  25. data/lib/generators/pu/eject/shell/shell_generator.rb +2 -2
  26. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +19 -0
  27. data/lib/generators/pu/lib/plutonium_generators/concerns/logger.rb +1 -1
  28. data/lib/generators/pu/lib/plutonium_generators/generator.rb +5 -3
  29. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +26 -2
  30. data/lib/generators/pu/pkg/{feature/feature_generator.rb → package/package_generator.rb} +4 -4
  31. data/lib/generators/pu/pkg/{feature → package}/templates/app/controllers/resource_controller.rb.tt +0 -2
  32. data/lib/generators/pu/pkg/package/templates/app/definitions/resource_definition.rb.tt +4 -0
  33. data/lib/generators/pu/pkg/package/templates/app/query_objects/resource_query_object.rb.tt +4 -0
  34. data/lib/generators/pu/pkg/{app/app_generator.rb → portal/portal_generator.rb} +10 -8
  35. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/concerns/controller.rb.tt +3 -7
  36. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/dashboard_controller.rb.tt +1 -1
  37. data/lib/generators/pu/pkg/portal/templates/app/controllers/plutonium_controller.rb.tt +5 -0
  38. data/lib/generators/pu/pkg/{app/templates/app/controllers/controller.rb.tt → portal/templates/app/controllers/resource_controller.rb.tt} +1 -1
  39. data/lib/generators/pu/pkg/portal/templates/app/definitions/resource_definition.rb.tt +4 -0
  40. data/lib/generators/pu/pkg/{app → portal}/templates/app/views/package/dashboard/index.html.erb +2 -1
  41. data/lib/generators/pu/res/conn/conn_generator.rb +78 -3
  42. data/lib/generators/pu/res/conn/templates/app/controllers/resource_controller.rb.tt +1 -1
  43. data/lib/generators/pu/res/conn/templates/app/definitions/resource_definition.rb.tt +3 -0
  44. data/lib/generators/pu/res/conn/templates/app/policies/resource_policy.rb.tt +29 -1
  45. data/lib/generators/pu/res/conn/templates/app/presenters/resource_presenter.rb.tt +1 -1
  46. data/lib/generators/pu/res/conn/templates/app/query_objects/resource_query_object.rb.tt +1 -1
  47. data/lib/generators/pu/res/model/model_generator.rb +0 -7
  48. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -1
  49. data/lib/generators/pu/res/scaffold/scaffold_generator.rb +22 -4
  50. data/lib/generators/pu/res/scaffold/templates/controller.rb.tt +0 -1
  51. data/lib/generators/pu/res/scaffold/templates/definition.rb.tt +4 -0
  52. data/lib/generators/pu/res/scaffold/templates/policy.rb.tt +2 -2
  53. data/lib/generators/pu/rodauth/templates/app/controllers/rodauth_controller.rb.tt +1 -1
  54. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +270 -0
  55. data/lib/plutonium/action/README.md +0 -0
  56. data/lib/plutonium/action/base.rb +103 -0
  57. data/lib/plutonium/action/interactive.rb +117 -0
  58. data/lib/plutonium/action/route_options.rb +65 -0
  59. data/lib/plutonium/action/simple.rb +8 -0
  60. data/lib/plutonium/auth.rb +1 -1
  61. data/lib/plutonium/configuration.rb +130 -0
  62. data/lib/plutonium/core/actions/collection.rb +1 -1
  63. data/lib/plutonium/core/associations/renderers/factory.rb +3 -1
  64. data/lib/plutonium/core/autodiscovery/association_renderer_discoverer.rb +1 -1
  65. data/lib/plutonium/core/autodiscovery/input_discoverer.rb +1 -1
  66. data/lib/plutonium/core/autodiscovery/renderer_discoverer.rb +1 -1
  67. data/lib/plutonium/core/controller.rb +110 -0
  68. data/lib/plutonium/core/controllers/authorizable.rb +12 -35
  69. data/lib/plutonium/core/controllers/bootable.rb +38 -7
  70. data/lib/plutonium/core/controllers/entity_scoping.rb +6 -2
  71. data/lib/plutonium/core/fields/renderers/association_renderer.rb +1 -1
  72. data/lib/plutonium/core/ui/collection.rb +1 -1
  73. data/lib/plutonium/core/ui/detail.rb +1 -1
  74. data/lib/plutonium/core/ui/form.rb +1 -1
  75. data/lib/plutonium/definition/actions.rb +50 -0
  76. data/lib/plutonium/definition/base.rb +92 -0
  77. data/lib/plutonium/definition/config_attr.rb +30 -0
  78. data/lib/plutonium/definition/defineable_props.rb +96 -0
  79. data/lib/plutonium/definition/search.rb +21 -0
  80. data/lib/plutonium/engine/validator.rb +30 -0
  81. data/lib/plutonium/engine.rb +25 -0
  82. data/lib/plutonium/helpers/assets_helper.rb +73 -20
  83. data/lib/plutonium/helpers/form_helper.rb +1 -3
  84. data/lib/plutonium/interaction/README.md +369 -0
  85. data/lib/plutonium/interaction/base.rb +75 -0
  86. data/lib/plutonium/interaction/concerns/presentable.rb +61 -0
  87. data/lib/plutonium/interaction/concerns/workflow_dsl.rb +82 -0
  88. data/lib/plutonium/interaction/outcome.rb +129 -0
  89. data/lib/plutonium/interaction/response/base.rb +63 -0
  90. data/lib/plutonium/interaction/response/null.rb +33 -0
  91. data/lib/plutonium/interaction/response/redirect.rb +30 -0
  92. data/lib/plutonium/interaction/response/render.rb +28 -0
  93. data/lib/plutonium/lib/bit_flags.rb +70 -9
  94. data/lib/plutonium/lib/overlayed_hash.rb +86 -0
  95. data/lib/plutonium/lib/smart_cache.rb +171 -0
  96. data/lib/plutonium/models/has_cents.rb +170 -0
  97. data/lib/plutonium/{pkg/base.rb → package/engine.rb} +10 -2
  98. data/lib/plutonium/{application → portal}/controller.rb +3 -11
  99. data/lib/plutonium/{application → portal}/dynamic_controllers.rb +4 -4
  100. data/lib/plutonium/portal/engine.rb +15 -0
  101. data/lib/plutonium/railtie.rb +35 -15
  102. data/lib/plutonium/reloader.rb +71 -29
  103. data/lib/plutonium/resource/controller.rb +51 -34
  104. data/lib/plutonium/resource/controllers/authorizable.rb +128 -0
  105. data/lib/plutonium/{core → resource}/controllers/crud_actions.rb +23 -22
  106. data/lib/plutonium/resource/controllers/defineable.rb +26 -0
  107. data/lib/plutonium/{core → resource}/controllers/interactive_actions.rb +12 -12
  108. data/lib/plutonium/resource/controllers/presentable.rb +41 -0
  109. data/lib/plutonium/resource/controllers/queryable.rb +44 -0
  110. data/lib/plutonium/resource/definition.rb +6 -0
  111. data/lib/plutonium/resource/policy.rb +25 -13
  112. data/lib/plutonium/resource/query_object.rb +50 -51
  113. data/lib/plutonium/resource/record.rb +6 -89
  114. data/lib/plutonium/resource/register.rb +82 -0
  115. data/lib/plutonium/routing/mapper_extensions.rb +1 -1
  116. data/lib/plutonium/routing/resource_registration.rb +1 -1
  117. data/lib/plutonium/routing/route_set_extensions.rb +6 -18
  118. data/lib/plutonium/ui/action_button.rb +125 -0
  119. data/lib/plutonium/ui/breadcrumbs.rb +163 -0
  120. data/lib/plutonium/ui/component/base.rb +13 -0
  121. data/lib/plutonium/ui/component/behaviour.rb +38 -0
  122. data/lib/plutonium/ui/component/kit.rb +31 -0
  123. data/lib/plutonium/ui/component/methods.rb +54 -0
  124. data/lib/plutonium/ui/display/base.rb +25 -0
  125. data/lib/plutonium/ui/display/component/association.rb +26 -0
  126. data/lib/plutonium/ui/display/resource.rb +77 -0
  127. data/lib/plutonium/ui/display/theme.rb +27 -0
  128. data/lib/plutonium/ui/dyna_frame/content.rb +20 -0
  129. data/lib/plutonium/ui/empty_card.rb +20 -0
  130. data/lib/plutonium/ui/form/base.rb +37 -0
  131. data/lib/plutonium/ui/form/resource.rb +75 -0
  132. data/lib/plutonium/ui/form/theme.rb +42 -0
  133. data/lib/plutonium/ui/page/base.rb +112 -0
  134. data/lib/plutonium/ui/page/edit.rb +23 -0
  135. data/lib/plutonium/ui/page/index.rb +27 -0
  136. data/lib/plutonium/ui/page/new.rb +23 -0
  137. data/lib/plutonium/ui/page/show.rb +27 -0
  138. data/lib/plutonium/ui/page_header.rb +49 -0
  139. data/lib/plutonium/ui/table/base.rb +13 -0
  140. data/lib/plutonium/ui/table/components/pagy_info.rb +70 -0
  141. data/lib/plutonium/ui/table/components/pagy_page_info.rb +70 -0
  142. data/lib/plutonium/ui/table/components/pagy_pagination.rb +105 -0
  143. data/lib/plutonium/ui/table/components/scopes_bar.rb +136 -0
  144. data/lib/plutonium/ui/table/components/search_bar.rb +158 -0
  145. data/lib/plutonium/ui/table/display_theme.rb +21 -0
  146. data/lib/plutonium/ui/table/resource.rb +98 -0
  147. data/lib/plutonium/ui/table/theme.rb +35 -0
  148. data/lib/plutonium/ui.rb +9 -0
  149. data/lib/plutonium/version.rb +5 -1
  150. data/lib/plutonium.rb +53 -26
  151. data/package-lock.json +19 -22
  152. data/package.json +4 -4
  153. data/sig/.keep +0 -0
  154. data/src/css/plutonium.css +15 -0
  155. data/tailwind.options.js +11 -3
  156. metadata +220 -81
  157. data/lib/generators/pu/core/install/templates/app/presenters/resource_presenter.rb.tt +0 -2
  158. data/lib/generators/pu/core/install/templates/app/query_objects/resource_query_object.rb.tt +0 -2
  159. data/lib/generators/pu/pkg/feature/templates/app/query_objects/resource_query_object.rb.tt +0 -4
  160. data/lib/plutonium/concerns/resource_validatable.rb +0 -34
  161. data/lib/plutonium/config.rb +0 -9
  162. data/lib/plutonium/core/controllers/base.rb +0 -101
  163. data/lib/plutonium/core/controllers/presentable.rb +0 -65
  164. data/lib/plutonium/core/controllers/queryable.rb +0 -28
  165. data/lib/plutonium/pkg/app.rb +0 -35
  166. data/lib/plutonium/pkg/concerns/resource_validatable.rb +0 -36
  167. data/lib/plutonium/pkg/feature.rb +0 -18
  168. data/lib/plutonium/policy/initializer.rb +0 -22
  169. data/lib/plutonium/policy/scope.rb +0 -19
  170. data/lib/plutonium/pundit/context.rb +0 -18
  171. data/lib/plutonium/pundit/policy_finder.rb +0 -25
  172. data/lib/plutonium/resource/policy_context.rb +0 -5
  173. data/lib/plutonium/resource_register.rb +0 -83
  174. data/lib/plutonium/smart_cache.rb +0 -151
  175. data/sig/plutonium.rbs +0 -12
  176. /data/app/views/{application → plutonium}/_flash.html.erb +0 -0
  177. /data/app/views/{application → plutonium}/_flash_alerts.html.erb +0 -0
  178. /data/app/views/{application → plutonium}/_flash_toasts.html.erb +0 -0
  179. /data/lib/generators/pu/pkg/{app/templates/app/views/package → package/templates}/.keep +0 -0
  180. /data/lib/generators/pu/pkg/{feature → package}/templates/app/interactions/resource_interaction.rb.tt +0 -0
  181. /data/lib/generators/pu/pkg/{feature → package}/templates/app/models/resource_record.rb.tt +0 -0
  182. /data/lib/generators/pu/pkg/{feature → package}/templates/app/policies/resource_policy.rb.tt +0 -0
  183. /data/lib/generators/pu/pkg/{feature → package}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  184. /data/lib/generators/pu/pkg/{feature → package}/templates/lib/engine.rb.tt +0 -0
  185. /data/lib/generators/pu/pkg/{app → portal}/templates/app/policies/resource_policy.rb.tt +0 -0
  186. /data/lib/generators/pu/pkg/{app → portal}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  187. /data/lib/generators/pu/pkg/{app → portal}/templates/app/query_objects/resource_query_object.rb.tt +0 -0
  188. /data/lib/generators/pu/pkg/{feature/templates → portal/templates/app/views/package}/.keep +0 -0
  189. /data/lib/generators/pu/pkg/{app → portal}/templates/config/routes.rb.tt +0 -0
  190. /data/lib/generators/pu/pkg/{app → portal}/templates/lib/engine.rb.tt +0 -0
@@ -1,15 +1,21 @@
1
- require "active_support/notifications"
1
+ # frozen_string_literal: true
2
2
 
3
- # Be sure to restart your server when you modify this file.
3
+ require "active_support/notifications"
4
4
 
5
5
  module Plutonium
6
+ # Reloader class for Plutonium
7
+ #
8
+ # This class is responsible for managing the reloading of Plutonium components
9
+ # and related files during development.
6
10
  class Reloader
7
11
  class << self
12
+ # Start the reloader
13
+ #
14
+ # @return [void]
8
15
  def start!
9
16
  puts "=> [plutonium] starting reloader"
10
17
 
11
18
  ActiveSupport::Notifications.instrument("plutonium.reloader.start") do
12
- # Task code here
13
19
  @listener&.stop
14
20
  @listener = initialize_listener
15
21
  end
@@ -17,37 +23,49 @@ module Plutonium
17
23
 
18
24
  private
19
25
 
26
+ # Initialize the file listener
27
+ #
28
+ # @return [Listen::Listener, nil] the initialized listener or nil if no paths to watch
20
29
  def initialize_listener
21
30
  require "listen"
22
31
 
23
32
  reload_paths = gather_reload_paths
24
- return unless reload_paths.any?
33
+ return if reload_paths.empty?
25
34
 
26
- listener = Listen.to(*reload_paths, only: /\.rb$/) do |modified, added, removed|
35
+ Listen.to(*reload_paths, only: /\.rb$/) { |modified, added, removed|
27
36
  handle_file_changes(modified, added, removed)
28
- end
29
- listener.start
30
- listener
37
+ }.tap(&:start)
31
38
  end
32
39
 
40
+ # Gather paths to be watched for changes
41
+ #
42
+ # @return [Array<String>] list of paths to watch
33
43
  def gather_reload_paths
34
44
  reload_paths = []
35
45
 
36
- if Plutonium.development?
37
- reload_paths << Plutonium.lib_root.to_s
38
- reload_paths << Plutonium.root.join("app", "views", "components").to_s
39
- reload_paths << Plutonium.root.join("config", "initializers").to_s
46
+ if Plutonium.configuration.development?
47
+ reload_paths.concat([
48
+ Plutonium.lib_root.to_s,
49
+ Plutonium.root.join("app", "views", "components").to_s,
50
+ Plutonium.root.join("config", "initializers").to_s
51
+ ])
40
52
  end
41
53
 
42
- packages_dir = Rails.root.join("packages/").to_s
54
+ packages_dir = Rails.root.join("packages").to_s
43
55
  reload_paths << packages_dir if File.directory?(packages_dir)
44
56
 
45
57
  reload_paths
46
58
  end
47
59
 
60
+ # Handle file changes detected by the listener
61
+ #
62
+ # @param modified [Array<String>] list of modified files
63
+ # @param added [Array<String>] list of added files
64
+ # @param removed [Array<String>] list of removed files
65
+ # @return [void]
48
66
  def handle_file_changes(modified, added, removed)
49
67
  (modified + added).each do |file|
50
- Plutonium.logger.debug "[plutonium] change detected: #{file}"
68
+ Plutonium.logger.debug { "[plutonium] change detected: #{file}" }
51
69
 
52
70
  if file == __FILE__
53
71
  reload_file(file)
@@ -62,48 +80,72 @@ module Plutonium
62
80
  end
63
81
  end
64
82
 
83
+ # Check if the file is within the packages directory
84
+ #
85
+ # @param file [String] path to the file
86
+ # @return [Boolean] true if the file is within the packages directory
65
87
  def file_starts_with_packages_dir?(file)
66
- packages_dir = Rails.root.join("packages/").to_s
67
- file.starts_with?(packages_dir)
88
+ file.start_with?(Rails.root.join("packages").to_s)
68
89
  end
69
90
 
91
+ # Handle changes to files within the packages directory
92
+ #
93
+ # @param file [String] path to the changed file
94
+ # @param added [Array<String>] list of added files
95
+ # @return [void]
70
96
  def handle_package_file_changes(file, added)
71
97
  return if added.include?(file)
72
98
 
73
99
  case File.basename(file)
74
100
  when "engine.rb"
75
101
  reload_engine_and_routes(file)
76
- else
77
- # Non-engine package files are reloaded by Rails automatically
78
102
  end
79
103
  end
80
104
 
105
+ # Reload engine and routes
106
+ #
107
+ # @param file [String] path to the engine file
108
+ # @return [void]
81
109
  def reload_engine_and_routes(file)
82
- Plutonium.logger.debug "[plutonium] reloading: engine+routes"
110
+ Plutonium.logger.debug { "[plutonium] reloading: engine+routes" }
83
111
  load file
84
112
  Rails.application.reload_routes!
85
113
  end
86
114
 
115
+ # Reload the framework and file
116
+ #
117
+ # @param file [String] path to the file
118
+ # @return [void]
87
119
  def reload_framework_and_file(file)
88
- # # Ensure that the file loads correctly before we do any reloading
89
- # load file
90
-
91
- Plutonium.logger.debug "[plutonium] reloading: app+framework"
120
+ Plutonium.logger.debug { "[plutonium] reloading: app+framework" }
92
121
  Rails.application.reloader.reload!
93
- Plutonium::ZEITWERK_LOADER.reload
94
- # reload components
122
+ Plutonium::Loader.reload
123
+ reload_components
124
+ end
125
+
126
+ # Reload components
127
+ #
128
+ # @return [void]
129
+ def reload_components
95
130
  Object.send(:remove_const, "PlutoniumUi")
96
131
  load Plutonium.root.join("app", "views", "components", "base.rb")
97
- # # Ensure files that do not contain constants are loaded again e.g. initializers
98
- # load file
99
132
  end
100
133
 
134
+ # Reload a single file
135
+ #
136
+ # @param file [String] path to the file
137
+ # @return [Boolean] true if the file was successfully loaded
101
138
  def reload_file(file)
102
- load file
139
+ load(file)
103
140
  end
104
141
 
142
+ # Log reload failure
143
+ #
144
+ # @param file [String] path to the file that failed to reload
145
+ # @param error [StandardError] the error that occurred during reloading
146
+ # @return [void]
105
147
  def log_reload_failure(file, error)
106
- Plutonium.logger.error "\n[plutonium] reloading failed\n\n#{error.message}\n"
148
+ Plutonium.logger.error { "\n[plutonium] reloading failed\n\n#{error.message}\n" }
107
149
  end
108
150
  end
109
151
  end
@@ -10,16 +10,15 @@ module Plutonium
10
10
  module Controller
11
11
  extend ActiveSupport::Concern
12
12
  include Pagy::Backend
13
- include Plutonium::Core::Controllers::Base
14
- include Plutonium::Core::Controllers::Authorizable
15
- include Plutonium::Core::Controllers::Presentable
16
- include Plutonium::Core::Controllers::Queryable
17
- include Plutonium::Core::Controllers::CrudActions
18
- include Plutonium::Core::Controllers::InteractiveActions
13
+ include Plutonium::Core::Controller
14
+ include Plutonium::Resource::Controllers::Defineable
15
+ include Plutonium::Resource::Controllers::Authorizable
16
+ include Plutonium::Resource::Controllers::Presentable
17
+ include Plutonium::Resource::Controllers::Queryable
18
+ include Plutonium::Resource::Controllers::CrudActions
19
+ include Plutonium::Resource::Controllers::InteractiveActions
19
20
 
20
21
  included do
21
- class_attribute :resource_class, instance_writer: false, instance_predicate: false
22
-
23
22
  # https://github.com/ddnexus/pagy/blob/master/docs/extras/headers.md#headers
24
23
  after_action { pagy_headers_merge(@pagy) if @pagy }
25
24
 
@@ -27,49 +26,55 @@ module Plutonium
27
26
  end
28
27
 
29
28
  class_methods do
29
+ include Plutonium::Lib::SmartCache
30
+
30
31
  # Sets the resource class for the controller
31
- # @param [Class] resource_class The resource class
32
+ # @param [ActiveRecord::Base] resource_class The resource class
32
33
  def controller_for(resource_class)
33
- self.resource_class = resource_class
34
+ @resource_class = resource_class
34
35
  end
36
+
37
+ # Gets the resource class for the controller
38
+ # @return [ActiveRecord::Base] The resource class
39
+ def resource_class
40
+ return @resource_class if @resource_class.present?
41
+
42
+ name.to_s.gsub(/^#{current_package}::/, "").gsub(/Controller$/, "").classify.constantize
43
+ rescue NameError
44
+ raise NameError, "Failed to determine the resource class. Please call `controller_for(MyResource)` in #{name}."
45
+ end
46
+ memoize_unless_reloading :resource_class
35
47
  end
36
48
 
37
49
  private
38
50
 
39
- # Creates a policy context
40
- # @return [Plutonium::Resource::PolicyContext] The policy context
41
- def policy_context
42
- Plutonium::Resource::PolicyContext.new(
43
- user: current_user,
44
- resource_context: resource_context
45
- )
51
+ def resource_class
52
+ self.class.resource_class
46
53
  end
47
54
 
48
55
  # Returns the resource record based on path parameters
49
56
  # @return [ActiveRecord::Base, nil] The resource record
50
57
  def resource_record
51
- @resource_record ||= policy_scope(resource_class).from_path_param(params[:id]).first! if params[:id].present?
58
+ @resource_record ||= current_authorized_scope.from_path_param(params[:id]).first! if params[:id].present?
52
59
  @resource_record
53
60
  end
54
61
 
55
62
  # Returns the submitted resource parameters
56
63
  # @return [Hash] The submitted resource parameters
57
64
  def submitted_resource_params
58
- @submitted_resource_params ||= begin
59
- strong_parameters = resource_class.strong_parameters_for(*permitted_attributes)
60
- params.require(resource_param_key).permit(*strong_parameters).nilify.to_h
61
- end
65
+ @submitted_resource_params ||= build_form(resource_class.new).extract_input(params)[resource_param_key.to_sym]
62
66
  end
63
67
 
64
68
  # Returns the resource parameters, including scoped and parent parameters
65
69
  # @return [Hash] The resource parameters
66
70
  def resource_params
67
- input_params = submitted_resource_params.dup
71
+ @resource_params ||= begin
72
+ input_params = submitted_resource_params.dup
73
+ override_entity_scoping_params(input_params)
74
+ override_parent_params(input_params)
68
75
 
69
- override_entity_scoping_params(input_params)
70
- override_parent_params(input_params)
71
-
72
- current_presenter.defined_field_inputs_for(*permitted_attributes).collect_all(input_params)
76
+ input_params
77
+ end
73
78
  end
74
79
 
75
80
  # Returns the resource parameter key
@@ -93,10 +98,20 @@ module Plutonium
93
98
  # @param [ActiveRecord::Base] resource_record The resource record
94
99
  # @return [Object] The resource presenter
95
100
  def resource_presenter(resource_class, resource_record)
96
- presenter_class = "#{current_package}::#{resource_class}Presenter".constantize
101
+ presenter_class = [current_package, "#{resource_class}Presenter"].compact.join("::").constantize
97
102
  presenter_class.new resource_context, resource_record
98
103
  rescue NameError
99
- super(resource_class, resource_record)
104
+ super
105
+ end
106
+
107
+ # Creates a resource definition
108
+ # @param [Class] resource_class The resource class
109
+ # @return [Object] The resource definition
110
+ def resource_definition(resource_class)
111
+ definition_class = [current_package, "#{resource_class}Definition"].compact.join("::").constantize
112
+ definition_class.new
113
+ rescue NameError
114
+ super
100
115
  end
101
116
 
102
117
  # Creates a resource query object
@@ -104,10 +119,10 @@ module Plutonium
104
119
  # @param [ActionController::Parameters] params The request parameters
105
120
  # @return [Object] The resource query object
106
121
  def resource_query_object(resource_class, params)
107
- query_object_class = "#{current_package}::#{resource_class}QueryObject".constantize
122
+ query_object_class = [current_package, "#{resource_class}QueryObject"].compact.join("::").constantize
108
123
  query_object_class.new resource_context, params
109
124
  rescue NameError
110
- super(resource_class, params)
125
+ super
111
126
  end
112
127
 
113
128
  # Applies submitted resource params if they have been passed
@@ -126,7 +141,9 @@ module Plutonium
126
141
  parent_class = current_engine.resource_register.route_key_lookup[parent_route_key]
127
142
  parent_scope = parent_class.from_path_param(params[parent_route_param])
128
143
  parent_scope = parent_scope.associated_with(current_scoped_entity) if scoped_to_entity?
129
- parent_scope.first!
144
+ current_parent = parent_scope.first!
145
+ authorize! current_parent, to: :read?
146
+ current_parent
130
147
  end
131
148
  end
132
149
 
@@ -173,9 +190,9 @@ module Plutonium
173
190
  # @param [Array] args The URL arguments
174
191
  # @param [Hash] kwargs The keyword arguments
175
192
  # @return [Array] The URL arguments
176
- def resource_url_args_for(*args, **kwargs)
193
+ def resource_url_args_for(*, **kwargs)
177
194
  kwargs[:parent] = current_parent unless kwargs.key?(:parent)
178
- super(*args, **kwargs)
195
+ super
179
196
  end
180
197
  end
181
198
  end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Resource
5
+ module Controllers
6
+ # The Authorizable module provides authorization functionality for controllers,
7
+ # specifically for the current resource being handled by the controller.
8
+ # It integrates with ActionPolicy to enforce authorization checks and scoping.
9
+ #
10
+ # @example Including the module in a controller
11
+ # class MyController < ApplicationController
12
+ # include Plutonium::Resource::Controllers::Authorizable
13
+ # end
14
+ #
15
+ # @note This module assumes the existence of methods like `resource_record`,
16
+ # `resource_class`, `current_parent`, and `entity_scope_for_authorize`.
17
+ #
18
+ # @see ActionPolicy
19
+ module Authorizable
20
+ extend ActiveSupport::Concern
21
+
22
+ # Custom exception for missing authorize_current call
23
+ class ActionMissingAuthorizeCurrent < ActionPolicy::UnauthorizedAction; end
24
+
25
+ # Custom exception for missing current_authorized_scope call
26
+ class ActionMissingCurrentAuthorizedScope < ActionPolicy::UnauthorizedAction; end
27
+
28
+ included do
29
+ verify_authorized
30
+ after_action :verify_authorize_current
31
+ after_action :verify_current_authorized_scope, except: %i[new create]
32
+
33
+ helper_method :current_policy, :permitted_attributes
34
+
35
+ attr_writer :authorize_current_count
36
+ attr_writer :current_authorized_scope_count
37
+
38
+ protected :authorize_current_count=, :authorize_current_count
39
+ protected :current_authorized_scope_count=, :current_authorized_scope_count
40
+ end
41
+
42
+ private
43
+
44
+ # Verifies that authorize_current has been called
45
+ #
46
+ # @raise [ActionMissingAuthorizeCurrent] if authorize_current hasn't been called
47
+ def verify_authorize_current
48
+ return if verify_authorized_skipped
49
+
50
+ raise ActionMissingAuthorizeCurrent.new(controller_path, action_name) if authorize_current_count.zero?
51
+ end
52
+
53
+ # Verifies that current_authorized_scope has been called
54
+ #
55
+ # @raise [ActionMissingCurrentAuthorizedScope] if current_authorized_scope hasn't been called
56
+ def verify_current_authorized_scope
57
+ return if verify_authorized_skipped
58
+
59
+ raise ActionMissingCurrentAuthorizedScope.new(controller_path, action_name) if current_authorized_scope_count.zero?
60
+ end
61
+
62
+ # @return [Integer] the number of times authorize_current has been called
63
+ def authorize_current_count
64
+ @authorize_current_count ||= 0
65
+ end
66
+
67
+ # @return [Integer] the number of times current_authorized_scope has been called
68
+ def current_authorized_scope_count
69
+ @current_authorized_scope_count ||= 0
70
+ end
71
+
72
+ # Returns the policy for the current resource
73
+ #
74
+ # @return [ActionPolicy::Base] the policy for the current resource
75
+ def current_policy
76
+ @current_policy ||= policy_for(record: current_policy_subject, context: current_policy_context)
77
+ end
78
+
79
+ # Returns the authorized scope for the current resource
80
+ #
81
+ # @return [ActiveRecord::Relation] the authorized scope for the current resource
82
+ def current_authorized_scope
83
+ self.current_authorized_scope_count += 1
84
+ authorized_scope(resource_class.all, context: current_policy_context)
85
+ end
86
+
87
+ # Sets the policy context scope value to the current parent if available
88
+ #
89
+ # @return [Hash] default context for the current resource's policy
90
+ def current_policy_context
91
+ {scope: current_parent || entity_scope_for_authorize}
92
+ end
93
+
94
+ # Authorizes the current action for the given record of the current resource
95
+ #
96
+ # @param record [Object] the record to authorize
97
+ # @param options [Hash] additional options for authorization
98
+ # @raise [ActionPolicy::Unauthorized] if the action is not authorized
99
+ def authorize_current!(record, **options)
100
+ options[:context] = (options[:context] || {}).deep_merge(current_policy_context)
101
+ authorize!(record, **options)
102
+ self.authorize_current_count += 1
103
+ end
104
+
105
+ # Returns the list of permitted attributes for the current action on the current resource
106
+ #
107
+ # @return [Array<Symbol>] the list of permitted attributes for the current action
108
+ def permitted_attributes
109
+ @permitted_attributes ||= current_policy.send_with_report(:"permitted_attributes_for_#{action_name}")
110
+ end
111
+
112
+ # Returns the list of permitted associations for the current resource
113
+ #
114
+ # @return [Array<Symbol>] the list of permitted associations
115
+ def permitted_associations
116
+ @permitted_associations ||= current_policy.send_with_report(:permitted_associations)
117
+ end
118
+
119
+ # Returns the subject for the current resource's policy
120
+ #
121
+ # @return [Object] the subject for the policy (either resource_record or resource_class)
122
+ def current_policy_subject
123
+ resource_record || resource_class
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- module Core
2
+ module Resource
3
3
  module Controllers
4
4
  module CrudActions
5
5
  extend ActiveSupport::Concern
@@ -10,44 +10,40 @@ module Plutonium
10
10
 
11
11
  # GET /resources(.{format})
12
12
  def index
13
- authorize resource_class
13
+ authorize_current! resource_class
14
14
  set_page_title resource_class.model_name.human.pluralize.titleize
15
15
 
16
16
  @search_object = current_query_object
17
- base_query = policy_scope(resource_class)
17
+ base_query = current_authorized_scope
18
18
  base_query = @search_object.apply(base_query)
19
- base_query = base_query.public_send(params[:scope].to_sym) if params[:scope].present?
19
+ # base_query = base_query.public_send(params[:scope].to_sym) if params[:scope].present?
20
20
  @pagy, @resource_records = pagy base_query
21
- @collection = build_collection
22
21
 
23
22
  render :index
24
23
  end
25
24
 
26
25
  # GET /resources/1(.{format})
27
26
  def show
28
- authorize resource_record
27
+ authorize_current! resource_record
29
28
  set_page_title resource_record.to_label.titleize
30
29
 
31
- @detail = build_detail
32
-
33
30
  render :show
34
31
  end
35
32
 
36
33
  # GET /resources/new
37
34
  def new
38
- authorize resource_class
35
+ authorize_current! resource_class
39
36
  set_page_title "Create #{resource_class.model_name.human.titleize}"
40
37
 
41
38
  @resource_record = resource_class.new
42
39
  maybe_apply_submitted_resource_params!
43
- @form = build_form
44
40
 
45
41
  render :new
46
42
  end
47
43
 
48
44
  # POST /resources(.{format})
49
45
  def create
50
- authorize resource_class
46
+ authorize_current! resource_class
51
47
  set_page_title "Create #{resource_class.model_name.human.titleize}"
52
48
 
53
49
  @resource_record = resource_class.new resource_params
@@ -61,7 +57,6 @@ module Plutonium
61
57
  format.any { render :show, status: :created, location: redirect_url_after_submit }
62
58
  else
63
59
  format.html do
64
- @form = build_form
65
60
  render :new, status: :unprocessable_entity
66
61
  end
67
62
  format.any do
@@ -74,18 +69,17 @@ module Plutonium
74
69
 
75
70
  # GET /resources/1/edit
76
71
  def edit
77
- authorize resource_record
72
+ authorize_current! resource_record
78
73
  set_page_title "Update #{resource_record.to_label.titleize}"
79
74
 
80
75
  maybe_apply_submitted_resource_params!
81
- @form = build_form
82
76
 
83
77
  render :edit
84
78
  end
85
79
 
86
80
  # PATCH/PUT /resources/1(.{format})
87
81
  def update
88
- authorize resource_record
82
+ authorize_current! resource_record
89
83
  set_page_title "Update #{resource_record.to_label.titleize}"
90
84
 
91
85
  respond_to do |format|
@@ -97,7 +91,6 @@ module Plutonium
97
91
  format.any { render :show, status: :ok, location: redirect_url_after_submit }
98
92
  else
99
93
  format.html do
100
- @form = build_form
101
94
  render :edit, status: :unprocessable_entity
102
95
  end
103
96
  format.any do
@@ -110,13 +103,13 @@ module Plutonium
110
103
 
111
104
  # DELETE /resources/1(.{format})
112
105
  def destroy
113
- authorize resource_record
106
+ authorize_current! resource_record
114
107
 
115
108
  respond_to do |format|
116
109
  resource_record.destroy
117
110
 
118
111
  format.html do
119
- redirect_to resource_url_for(resource_class),
112
+ redirect_to redirect_url_after_destroy,
120
113
  notice: "#{resource_class.model_name.human} was successfully deleted."
121
114
  end
122
115
  format.json { head :no_content }
@@ -143,13 +136,13 @@ module Plutonium
143
136
 
144
137
  url = case preferred_action_after_submit
145
138
  when "show"
146
- resource_url_for(resource_record) if current_policy.show?
139
+ resource_url_for(resource_record) if current_policy.allowed_to? :show?
147
140
  when "edit"
148
- resource_url_for(resource_record, action: :edit) if current_policy.edit?
141
+ resource_url_for(resource_record, action: :edit) if current_policy.allowed_to? :edit?
149
142
  when "new"
150
- resource_url_for(resource_class, action: :new) if current_policy.new?
143
+ resource_url_for(resource_class, action: :new) if current_policy.allowed_to? :new?
151
144
  when "index"
152
- resource_url_for(resource_class) if current_policy.index?
145
+ resource_url_for(resource_class) if current_policy.allowed_to? :index?
153
146
  else
154
147
  # ensure we have a valid value
155
148
  session[:action_after_submit_preference] = "show"
@@ -157,6 +150,14 @@ module Plutonium
157
150
  url || resource_url_for(resource_record)
158
151
  end
159
152
 
153
+ def redirect_url_after_destroy
154
+ if (return_to = url_from(params[:return_to]))
155
+ return return_to
156
+ end
157
+
158
+ resource_url_for(resource_class)
159
+ end
160
+
160
161
  def preferred_action_after_submit
161
162
  @preferred_action_after_submit = begin
162
163
  if %w[new edit show index].include? params[:commit]
@@ -0,0 +1,26 @@
1
+ using Plutonium::Refinements::ParameterRefinements
2
+
3
+ module Plutonium
4
+ module Resource
5
+ module Controllers
6
+ module Defineable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ helper_method :current_definition, :resource_definition
11
+ end
12
+
13
+ private
14
+
15
+ def resource_definition(resource_class)
16
+ definition_class = "#{resource_class}Definition".constantize
17
+ definition_class.new
18
+ end
19
+
20
+ def current_definition
21
+ @current_definition ||= resource_definition resource_class
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end