plutonium 0.14.0 → 0.15.0.pre.rc1

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