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
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ # Configuration class for Plutonium module
5
+ #
6
+ # @example
7
+ # Plutonium.configure do |config|
8
+ # config.load_defaults 1.0
9
+ # config.development = true
10
+ # config.cache_discovery = false
11
+ # config.enable_hotreload = true
12
+ # config.assets.logo = "custom_logo.png"
13
+ # end
14
+ class Configuration
15
+ # @return [Boolean] whether Plutonium is in development mode
16
+ attr_accessor :development
17
+
18
+ # @return [Boolean] whether to cache discovery
19
+ attr_accessor :cache_discovery
20
+
21
+ # @return [Boolean] whether to enable hot reloading
22
+ attr_accessor :enable_hotreload
23
+
24
+ # @return [AssetConfiguration] asset configuration
25
+ attr_reader :assets
26
+
27
+ # @return [Float] the current defaults version
28
+ attr_reader :defaults_version
29
+
30
+ # Map of version numbers to their default configurations
31
+ VERSION_DEFAULTS = {
32
+ 1.0 => proc do |config|
33
+ # No changes for 1.0 yet as it's the current base configuration
34
+ end
35
+ # Add more version configurations here as needed
36
+ # 1.1 => proc do |config|
37
+ # config.some_new_setting = true
38
+ # end
39
+ }.freeze
40
+
41
+ # Initialize a new Configuration instance
42
+ #
43
+ # @note This method sets initial values
44
+ def initialize
45
+ @defaults_version = nil
46
+ @assets = AssetConfiguration.new
47
+
48
+ @development = parse_boolean_env("PLUTONIUM_DEV")
49
+ @cache_discovery = !Rails.env.development?
50
+ @enable_hotreload = Rails.env.development?
51
+ end
52
+
53
+ # Load default configuration for a specific version
54
+ #
55
+ # @param version [Float] the version to load defaults for
56
+ # @return [void]
57
+ def load_defaults(version)
58
+ available_versions = VERSION_DEFAULTS.keys.sort
59
+ applicable_versions = available_versions.select { |v| v <= version }
60
+
61
+ if applicable_versions.empty?
62
+ raise "No applicable defaults found for version #{version}."
63
+ end
64
+
65
+ applicable_versions.each do |v|
66
+ VERSION_DEFAULTS[v].call(self)
67
+ end
68
+
69
+ @defaults_version = applicable_versions.last
70
+ end
71
+
72
+ # whether Plutonium is in development mode
73
+ #
74
+ # @return [Boolean]
75
+ def development?
76
+ @development
77
+ end
78
+
79
+ private
80
+
81
+ # Parse boolean environment variable
82
+ #
83
+ # @param env_var [String] name of the environment variable
84
+ # @return [Boolean] parsed boolean value
85
+ def parse_boolean_env(env_var)
86
+ ActiveModel::Type::Boolean.new.cast(ENV[env_var]).present?
87
+ end
88
+
89
+ # Asset configuration for Plutonium
90
+ class AssetConfiguration
91
+ # @return [String] path to logo file
92
+ attr_accessor :logo
93
+
94
+ # @return [String] path to favicon file
95
+ attr_accessor :favicon
96
+
97
+ # @return [String] path to stylesheet file
98
+ attr_accessor :stylesheet
99
+
100
+ # @return [String] path to JavaScript file
101
+ attr_accessor :script
102
+
103
+ # Initialize a new AssetConfiguration instance with default values
104
+ def initialize
105
+ @logo = "plutonium.png"
106
+ @favicon = "plutonium.ico"
107
+ @stylesheet = "plutonium.css"
108
+ @script = "plutonium.min.js"
109
+ end
110
+ end
111
+ end
112
+
113
+ class << self
114
+ # Get the current configuration
115
+ #
116
+ # @return [Configuration] current configuration instance
117
+ def configuration
118
+ @configuration ||= Configuration.new
119
+ end
120
+
121
+ # Configure Plutonium
122
+ #
123
+ # @yield [config] Configuration instance
124
+ # @yieldparam config [Configuration] current configuration instance
125
+ # @return [void]
126
+ def configure
127
+ yield(configuration)
128
+ end
129
+ end
130
+ end
@@ -9,7 +9,7 @@ module Plutonium
9
9
  end
10
10
 
11
11
  def permitted_for(policy)
12
- Collection.new(@collection.select { |name, action| policy.send_with_report :"#{action.name}?" })
12
+ Collection.new(@collection.select { |name, action| policy.allowed_to? :"#{action.name}?" })
13
13
  end
14
14
 
15
15
  def collection_actions
@@ -19,7 +19,9 @@ module Plutonium
19
19
  def self.for_resource_association(resource_class, attr_name, **options)
20
20
  association = resource_class.try(:reflect_on_association, attr_name)
21
21
  raise ArgumentError, "#{attr_name} is not a valid association of #{resource_class}" unless association.present?
22
- raise ArgumentError, "#{association.klass} does is not a resource record" unless association.klass.include?(Plutonium::Resource::Record)
22
+ # # TODO: fix constant being out of sync after reload during development
23
+ # valid_resource_record = Plutonium.configuration.development? ? association.klass.respond_to?(:resource_field_names) : association.klass.include?(Plutonium: :Resource: :Record)
24
+ # raise ArgumentError, "#{association.klass} is not a resource record" unless valid_resource_record
23
25
 
24
26
  type = association.macro
25
27
  raise NotImplementedError, "#{macro} associations are currently not supported." unless type == :has_many
@@ -14,7 +14,7 @@ module Plutonium
14
14
  # If cache_discovery is enabled, use the class level cache that persists
15
15
  # between requests, otherwise use the instance one.
16
16
  def autodiscovery_association_renderer_cache
17
- if Rails.application.config.plutonium.cache_discovery
17
+ if Plutonium.configuration.cache_discovery
18
18
  self.class.autodiscovery_association_renderer_cache
19
19
  else
20
20
  @autodiscovery_association_renderer_cache ||= {}
@@ -14,7 +14,7 @@ module Plutonium
14
14
  # If cache_discovery is enabled, use the class level cache that persists
15
15
  # between requests, otherwise use the instance one.
16
16
  def autodiscovery_input_cache
17
- if Rails.application.config.plutonium.cache_discovery
17
+ if Plutonium.configuration.cache_discovery
18
18
  self.class.autodiscovery_input_cache
19
19
  else
20
20
  @autodiscovery_input_cache ||= {}
@@ -14,7 +14,7 @@ module Plutonium
14
14
  # If cache_discovery is enabled, use the class level cache that persists
15
15
  # between requests, otherwise use the instance one.
16
16
  def autodiscovery_renderer_cache
17
- if Rails.application.config.plutonium.cache_discovery
17
+ if Plutonium.configuration.cache_discovery
18
18
  self.class.autodiscovery_renderer_cache
19
19
  else
20
20
  @autodiscovery_renderer_cache ||= {}
@@ -0,0 +1,110 @@
1
+ module Plutonium
2
+ module Core
3
+ module Controller
4
+ extend ActiveSupport::Concern
5
+ include Plutonium::Core::Controllers::Bootable
6
+ include Plutonium::Core::Controllers::EntityScoping
7
+ include Plutonium::Core::Controllers::Authorizable
8
+
9
+ included do
10
+ add_flash_types :success, :warning, :error
11
+
12
+ before_action do
13
+ next unless defined?(ActiveStorage)
14
+
15
+ ActiveStorage::Current.url_options = {protocol: request.protocol, host: request.host, port: request.port}
16
+ end
17
+
18
+ helper Plutonium::Helpers
19
+ helper_method :make_page_title, :resource_url_for, :resource_url_args_for, :root_path
20
+
21
+ append_view_path File.expand_path("app/views", Plutonium.root)
22
+ layout -> { turbo_frame_request? ? false : "resource" }
23
+ helper_method :registered_resources
24
+ end
25
+
26
+ private
27
+
28
+ def set_page_title(page_title)
29
+ @page_title = page_title
30
+ end
31
+
32
+ def make_page_title(title)
33
+ [title.presence, helpers.application_name].compact.join(" | ")
34
+ end
35
+
36
+ #
37
+ # Returns a dynamic list of args to be used with `url_for`, which considers the route namespace and nesting.
38
+ # The current entity and parent record (for nested routes) are inserted appropriately, ensuring that generated URLs
39
+ # obey the current routing.
40
+ #
41
+ # e.g., of route helpers that will be invoked given the output of this method
42
+ #
43
+ # - when invoked in a root route (/acme/dashboard/users)
44
+ #
45
+ # `resource_url_args_for User` => `entity_users_*`
46
+ # `resource_url_args_for @user` => `entity_user_*`
47
+ # `resource_url_args_for @user, action: :edit` => `edit_entity_user_*`
48
+ # `resource_url_args_for @user, Post` => `entity_user_posts_*`
49
+ #
50
+ # - when invoked in a nested route (/acme/dashboard/users/1/post/1)
51
+ #
52
+ # `resource_url_args_for Post` => `entity_user_posts_*`
53
+ # `resource_url_args_for @post` => `entity_user_post_*`
54
+ # `resource_url_args_for @post, action: :edit` => `edit_entity_user_post_*`
55
+ #
56
+ # @param [Class, ApplicationRecord] *args arguments you would normally pass to `url_for`
57
+ # @param [Symbol] action optional action to invoke, e.g., :new, :edit
58
+ # @param [ApplicationRecord] parent the parent record for nested routes, if any
59
+ # @param [Hash] kwargs additional keyword arguments to pass to `url_for`
60
+ #
61
+ # @return [Hash] args to pass to `url_for`
62
+ #
63
+ def resource_url_args_for(*args, action: nil, parent: nil, **kwargs)
64
+ url_args = {**kwargs, action: action}.compact
65
+
66
+ controller_chain = [current_package&.to_s].compact
67
+ [*args].compact.each_with_index do |element, index|
68
+ if element.is_a?(Class)
69
+ controller_chain << element.to_s.pluralize
70
+ else
71
+ controller_chain << element.class.to_s.pluralize
72
+ if index == args.length - 1
73
+ url_args[:id] = element.to_param
74
+ url_args[:action] ||= :show
75
+ else
76
+ url_args[element.model_name.singular_route_key.to_sym] = element.to_param
77
+ end
78
+ end
79
+ end
80
+ url_args[:controller] = "/#{controller_chain.join("::").underscore}"
81
+
82
+ url_args[:"#{parent.model_name.singular_route_key}_id"] = parent.to_param if parent.present?
83
+ if scoped_to_entity? && scoped_entity_strategy == :path
84
+ url_args[scoped_entity_param_key] = current_scoped_entity
85
+ end
86
+
87
+ url_args
88
+ end
89
+
90
+ def resource_url_for(...)
91
+ args = resource_url_args_for(...)
92
+ if current_package.present?
93
+ send(current_package.name.underscore.to_sym).url_for(args)
94
+ else
95
+ url_for(args)
96
+ end
97
+ end
98
+
99
+ def root_path(*)
100
+ return send(:"#{scoped_entity_param_key}_root_path", *) if scoped_to_entity? && scoped_entity_strategy == :path
101
+
102
+ super(*)
103
+ end
104
+
105
+ def registered_resources
106
+ current_engine.resource_register.resources
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,56 +1,33 @@
1
- require "pundit"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Plutonium
4
4
  module Core
5
5
  module Controllers
6
6
  module Authorizable
7
7
  extend ActiveSupport::Concern
8
- include ::Pundit::Authorization
8
+ include ActionPolicy::Controller
9
9
 
10
10
  included do
11
- after_action :verify_authorized
12
- after_action :verify_policy_scoped, except: %i[new create]
11
+ authorize :user, through: :current_user
12
+ authorize :scope, through: :entity_scope_for_authorize
13
13
 
14
- helper_method :current_policy, :permitted_attributes
14
+ helper_method :policy_for, :authorized_resource_scope
15
15
  end
16
16
 
17
17
  private
18
18
 
19
- def policy_context
20
- raise NotImplementedError, "policy_context"
21
- end
22
-
23
- def pundit_user
24
- policy_context
25
- end
19
+ def authorized_resource_scope(resource, **options)
20
+ raise ArgumentError("Expected resource to be a class inheriting ActiveRecord::Base") unless resource.instance_of?(Class) && resource < ActiveRecord::Base
26
21
 
27
- # @return [Plutonium::Pundit::Context] a new instance of {Plutonium::Pundit::Context} with the current user and package
28
- def pundit
29
- @pundit ||= Plutonium::Pundit::Context.new(
30
- package: current_package.to_s.underscore.to_sym,
31
- user: pundit_user,
32
- policy_cache: ::Pundit::CacheStore::LegacyStore.new(policies)
33
- )
34
- end
22
+ options[:with] ||= ActionPolicy.lookup(resource, namespace: authorization_namespace)
23
+ resource = resource.all
35
24
 
36
- def permitted_attributes
37
- @permitted_attributes ||= current_policy.send_with_report :"permitted_attributes_for_#{action_name}"
25
+ authorized_scope(resource, **options)
38
26
  end
39
27
 
40
- def permitted_associations
41
- @permitted_associations ||= current_policy.permitted_associations
28
+ def entity_scope_for_authorize
29
+ scoped_to_entity? ? current_scoped_entity : nil
42
30
  end
43
-
44
- def current_policy
45
- @current_policy ||= begin
46
- policy_subject = resource_record || resource_class
47
- policy(policy_subject)
48
- end
49
- end
50
-
51
- # def parent_policy
52
- # @parent_policy ||= policy(current_parent) if current_parent.present?
53
- # end
54
31
  end
55
32
  end
56
33
  end
@@ -3,21 +3,52 @@ module Plutonium
3
3
  module Controllers
4
4
  module Bootable
5
5
  extend ActiveSupport::Concern
6
+ include Plutonium::Engine::Validator
6
7
 
7
8
  included do
8
- class_attribute :current_package, instance_writer: false, instance_predicate: false
9
- class_attribute :current_engine, instance_writer: false, instance_predicate: false
9
+ helper_method :current_package, :current_engine
10
+ end
11
+
12
+ def current_package
13
+ self.class.current_package
14
+ end
10
15
 
11
- helper_method :current_engine, :current_package
16
+ def current_engine
17
+ self.class.current_engine
12
18
  end
13
19
 
14
20
  class_methods do
15
- def boot(package)
16
- self.current_package = package
17
- self.current_engine = "#{package}::Engine".constantize
21
+ include Plutonium::Lib::SmartCache
22
+
23
+ def inherited(subclass)
24
+ super
25
+
26
+ subclass.boot
27
+ end
28
+
29
+ def boot(package = nil)
30
+ if package.present?
31
+ Plutonium.deprecator.warn(
32
+ "Calling boot with an argument is deprecated and no longer has an effect.",
33
+ caller_locations(1)
34
+ )
35
+ end
36
+
37
+ if current_engine != Rails.application.class
38
+ prepend_view_path current_engine.paths["app/views"].first
39
+ end
40
+ end
41
+
42
+ def current_package
43
+ (current_engine == Rails.application.class) ? nil : current_engine.module_parent
44
+ end
45
+ memoize_unless_reloading :current_package
18
46
 
19
- prepend_view_path current_engine.paths["app/views"].first
47
+ def current_engine
48
+ potential_package = module_parents[-2]
49
+ potential_package.nil? ? Rails.application.class : ("#{potential_package}::Engine".safe_constantize || Rails.application.class)
20
50
  end
51
+ memoize_unless_reloading :current_engine
21
52
  end
22
53
  end
23
54
  end
@@ -60,7 +60,7 @@ module Plutonium
60
60
  # @raise [NotImplementedError] if not scoped to an entity
61
61
  def scoped_entity_session_key
62
62
  ensure_legal_entity_scoping_method_access!(__method__)
63
- :"#{current_package.name.underscore}__scoped_entity_id"
63
+ [current_package&.name&.underscore, "scoped_entity_id"].compact.join("__").to_sym
64
64
  end
65
65
 
66
66
  # Returns the current scoped entity for the request.
@@ -69,6 +69,8 @@ module Plutonium
69
69
  # @raise [NotImplementedError] if not scoped to an entity or strategy is unknown
70
70
  def current_scoped_entity
71
71
  ensure_legal_entity_scoping_method_access!(__method__)
72
+ # this method might be invoked even when not authenticated.
73
+ # so let's guard against that.
72
74
  return unless current_user.present?
73
75
 
74
76
  @current_scoped_entity ||= fetch_current_scoped_entity
@@ -79,7 +81,7 @@ module Plutonium
79
81
  # @return [ActiveRecord::Base, nil] the current scoped entity or nil if not found
80
82
  # @raise [NotImplementedError] if the scoping strategy is unknown
81
83
  def fetch_current_scoped_entity
82
- case scoped_entity_strategy
84
+ scoped_entity = case scoped_entity_strategy
83
85
  when :path
84
86
  fetch_entity_from_path
85
87
  when Symbol
@@ -87,6 +89,8 @@ module Plutonium
87
89
  else
88
90
  raise NotImplementedError, "Unknown scoped entity strategy: #{scoped_entity_strategy.inspect}"
89
91
  end
92
+ authorize! scoped_entity, to: :read?
93
+ scoped_entity
90
94
  end
91
95
 
92
96
  # Fetches the scoped entity from the path parameters.
@@ -18,7 +18,7 @@ module Plutonium
18
18
 
19
19
  def renderer_options
20
20
  {
21
- helper: value.class.include?(Plutonium::Resource::Record) ? :display_association_value : :display_name_of
21
+ # helper: value.class.include?(Plutonium: :Resource: :Record) ? :display_association_value : :display_name_of
22
22
  }
23
23
  end
24
24
  end
@@ -2,7 +2,7 @@ require "dry-initializer"
2
2
 
3
3
  module Plutonium
4
4
  module Core
5
- module Ui
5
+ module UI
6
6
  class Collection
7
7
  extend Dry::Initializer
8
8
 
@@ -1,6 +1,6 @@
1
1
  module Plutonium
2
2
  module Core
3
- module Ui
3
+ module UI
4
4
  class Detail
5
5
  extend Dry::Initializer
6
6
 
@@ -1,6 +1,6 @@
1
1
  module Plutonium
2
2
  module Core
3
- module Ui
3
+ module UI
4
4
  class Form
5
5
  extend Dry::Initializer
6
6
 
@@ -0,0 +1,50 @@
1
+ module Plutonium
2
+ module Definition
3
+ module Actions
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ defineable_prop :action
8
+
9
+ def self.action(name, interaction: nil, **)
10
+ defined_actions[name] = if interaction
11
+ Plutonium::Action::Interactive::Factory.create(name, interaction:, **)
12
+ else
13
+ Plutonium::Action::Simple.new(name, **)
14
+ end
15
+ end
16
+
17
+ def action(name, **)
18
+ instance_defined_actions[name] = Plutonium::Action::Simple.new(name, **)
19
+ end
20
+
21
+ def defined_actions
22
+ @merged_defined_actions ||= begin
23
+ customize_actions
24
+ merged = self.class.defined_actions.merge(instance_defined_actions)
25
+ merged.sort_by { |k, v| v.position }.to_h
26
+ end
27
+ end
28
+
29
+ # standard CRUD actions
30
+
31
+ action(:new, route_options: {action: :new},
32
+ resource_action: true, category: :primary,
33
+ icon: Phlex::TablerIcons::Plus, position: 10)
34
+
35
+ action(:show, route_options: {action: :show},
36
+ collection_record_action: true,
37
+ icon: Phlex::TablerIcons::Eye, position: 10)
38
+
39
+ action(:edit, route_options: {action: :edit},
40
+ record_action: true, collection_record_action: true,
41
+ icon: Phlex::TablerIcons::Edit, position: 20)
42
+
43
+ action(:destroy, route_options: {method: :delete},
44
+ record_action: true, collection_record_action: true, category: :danger,
45
+ icon: Phlex::TablerIcons::Trash, position: 100,
46
+ confirmation: "Are you sure?", turbo_frame: "_top")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Definition
5
+ # Base class for Plutonium definitions
6
+ #
7
+ # @abstract Subclass and override {#customize_fields}, {#customize_inputs},
8
+ # {#customize_filters}, {#customize_scopes}, and {#customize_sorters}
9
+ # to implement custom behavior.
10
+ #
11
+ # @example
12
+ # class MyDefinition < Plutonium::Definition::Base
13
+ # field :name, as: :string
14
+ # input :email, as: :email
15
+ # filter :status, type: :select, collection: %w[active inactive]
16
+ # scope :active, default: true
17
+ # sorter :created_at
18
+ #
19
+ # def customize_fields
20
+ # field :custom_field, as: :integer
21
+ # end
22
+ # end
23
+ #
24
+ # @note This class is not thread-safe. Ensure proper synchronization
25
+ # if used in a multi-threaded environment.
26
+ class Base
27
+ include DefineableProps
28
+ include ConfigAttr
29
+ include Actions
30
+ include Search
31
+
32
+ class IndexPage < Plutonium::UI::Page::Index; end
33
+
34
+ class NewPage < Plutonium::UI::Page::New; end
35
+
36
+ class ShowPage < Plutonium::UI::Page::Show; end
37
+
38
+ class EditPage < Plutonium::UI::Page::Edit; end
39
+
40
+ class Form < Plutonium::UI::Form::Resource; end
41
+
42
+ class Table < Plutonium::UI::Table::Resource; end
43
+
44
+ class Display < Plutonium::UI::Display::Resource; end
45
+
46
+ # fields
47
+ defineable_props :field, :input, :display, :column
48
+
49
+ # queries
50
+ defineable_props :filter, :scope, :sort
51
+
52
+ # pages
53
+ config_attr \
54
+ :index_page_title, :index_page_description,
55
+ :show_page_title, :show_page_description,
56
+ :new_page_title, :new_page_description,
57
+ :edit_page_title, :edit_page_description
58
+
59
+ def initialize
60
+ super
61
+ end
62
+
63
+ def index_page_class
64
+ self.class::IndexPage
65
+ end
66
+
67
+ def new_page_class
68
+ self.class::NewPage
69
+ end
70
+
71
+ def show_page_class
72
+ self.class::ShowPage
73
+ end
74
+
75
+ def edit_page_class
76
+ self.class::EditPage
77
+ end
78
+
79
+ def form_class
80
+ self.class::Form
81
+ end
82
+
83
+ def collection_class
84
+ self.class::Table
85
+ end
86
+
87
+ def detail_class
88
+ self.class::Display
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,30 @@
1
+ module Plutonium
2
+ module Definition
3
+ module ConfigAttr
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def config_attr(*names)
8
+ names.each do |name|
9
+ define_singleton_method(name) do |value = :__this_is_so_nilly__|
10
+ if value == :__this_is_so_nilly__
11
+ # Getter behavior
12
+ # if singleton_class.instance_variable_defined?(:"@#{name}")
13
+ singleton_class.instance_variable_get(:"@#{name}")
14
+ # end
15
+ else
16
+ # Setter behavior
17
+ singleton_class.instance_variable_set(:"@#{name}", value)
18
+ end
19
+ end
20
+
21
+ # Instance-level method
22
+ define_method(name) do
23
+ self.class.send(name)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end