plutonium 0.14.1 → 0.15.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) 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/install/templates/app/controllers/plutonium_controller.rb.tt +2 -0
  19. data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +21 -1
  20. data/lib/generators/pu/core/install/templates/app/definitions/resource_definition.rb.tt +2 -0
  21. data/lib/generators/pu/core/install/templates/app/models/resource_record.rb.tt +0 -2
  22. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +1 -8
  23. data/lib/generators/pu/eject/shell/shell_generator.rb +2 -2
  24. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +1 -1
  25. data/lib/generators/pu/lib/plutonium_generators/concerns/logger.rb +1 -1
  26. data/lib/generators/pu/lib/plutonium_generators/generator.rb +5 -3
  27. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +26 -2
  28. data/lib/generators/pu/pkg/{feature/feature_generator.rb → package/package_generator.rb} +4 -4
  29. data/lib/generators/pu/pkg/{feature → package}/templates/app/controllers/resource_controller.rb.tt +0 -2
  30. data/lib/generators/pu/pkg/package/templates/app/definitions/resource_definition.rb.tt +4 -0
  31. data/lib/generators/pu/pkg/package/templates/app/query_objects/resource_query_object.rb.tt +4 -0
  32. data/lib/generators/pu/pkg/{app/app_generator.rb → portal/portal_generator.rb} +10 -8
  33. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/concerns/controller.rb.tt +3 -7
  34. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/dashboard_controller.rb.tt +1 -1
  35. data/lib/generators/pu/pkg/portal/templates/app/controllers/plutonium_controller.rb.tt +5 -0
  36. data/lib/generators/pu/pkg/{app/templates/app/controllers/controller.rb.tt → portal/templates/app/controllers/resource_controller.rb.tt} +1 -1
  37. data/lib/generators/pu/pkg/portal/templates/app/definitions/resource_definition.rb.tt +4 -0
  38. data/lib/generators/pu/pkg/{app → portal}/templates/app/views/package/dashboard/index.html.erb +2 -1
  39. data/lib/generators/pu/res/conn/conn_generator.rb +78 -3
  40. data/lib/generators/pu/res/conn/templates/app/controllers/resource_controller.rb.tt +1 -1
  41. data/lib/generators/pu/res/conn/templates/app/definitions/resource_definition.rb.tt +3 -0
  42. data/lib/generators/pu/res/conn/templates/app/policies/resource_policy.rb.tt +29 -1
  43. data/lib/generators/pu/res/conn/templates/app/presenters/resource_presenter.rb.tt +1 -1
  44. data/lib/generators/pu/res/conn/templates/app/query_objects/resource_query_object.rb.tt +1 -1
  45. data/lib/generators/pu/res/model/model_generator.rb +0 -7
  46. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -1
  47. data/lib/generators/pu/res/scaffold/scaffold_generator.rb +22 -4
  48. data/lib/generators/pu/res/scaffold/templates/controller.rb.tt +0 -1
  49. data/lib/generators/pu/res/scaffold/templates/definition.rb.tt +4 -0
  50. data/lib/generators/pu/res/scaffold/templates/policy.rb.tt +2 -2
  51. data/lib/generators/pu/rodauth/templates/app/controllers/rodauth_controller.rb.tt +1 -1
  52. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +270 -0
  53. data/lib/plutonium/action/README.md +0 -0
  54. data/lib/plutonium/action/base.rb +103 -0
  55. data/lib/plutonium/action/interactive.rb +117 -0
  56. data/lib/plutonium/action/route_options.rb +65 -0
  57. data/lib/plutonium/action/simple.rb +8 -0
  58. data/lib/plutonium/auth.rb +1 -1
  59. data/lib/plutonium/configuration.rb +0 -8
  60. data/lib/plutonium/core/actions/collection.rb +1 -1
  61. data/lib/plutonium/core/associations/renderers/factory.rb +3 -1
  62. data/lib/plutonium/core/controller.rb +110 -0
  63. data/lib/plutonium/core/controllers/authorizable.rb +12 -35
  64. data/lib/plutonium/core/controllers/bootable.rb +38 -7
  65. data/lib/plutonium/core/controllers/entity_scoping.rb +6 -2
  66. data/lib/plutonium/core/fields/renderers/association_renderer.rb +1 -1
  67. data/lib/plutonium/core/ui/collection.rb +1 -1
  68. data/lib/plutonium/core/ui/detail.rb +1 -1
  69. data/lib/plutonium/core/ui/form.rb +1 -1
  70. data/lib/plutonium/definition/actions.rb +50 -0
  71. data/lib/plutonium/definition/base.rb +92 -0
  72. data/lib/plutonium/definition/config_attr.rb +30 -0
  73. data/lib/plutonium/definition/defineable_props.rb +96 -0
  74. data/lib/plutonium/definition/search.rb +21 -0
  75. data/lib/plutonium/engine/validator.rb +30 -0
  76. data/lib/plutonium/engine.rb +25 -0
  77. data/lib/plutonium/helpers/form_helper.rb +1 -3
  78. data/lib/plutonium/interaction/README.md +369 -0
  79. data/lib/plutonium/interaction/base.rb +75 -0
  80. data/lib/plutonium/interaction/concerns/presentable.rb +61 -0
  81. data/lib/plutonium/interaction/concerns/workflow_dsl.rb +82 -0
  82. data/lib/plutonium/interaction/outcome.rb +129 -0
  83. data/lib/plutonium/interaction/response/base.rb +63 -0
  84. data/lib/plutonium/interaction/response/null.rb +33 -0
  85. data/lib/plutonium/interaction/response/redirect.rb +30 -0
  86. data/lib/plutonium/interaction/response/render.rb +28 -0
  87. data/lib/plutonium/lib/bit_flags.rb +70 -9
  88. data/lib/plutonium/{config → lib}/overlayed_hash.rb +1 -1
  89. data/lib/plutonium/lib/smart_cache.rb +171 -0
  90. data/lib/plutonium/models/has_cents.rb +170 -0
  91. data/lib/plutonium/{pkg/base.rb → package/engine.rb} +10 -2
  92. data/lib/plutonium/{application → portal}/controller.rb +3 -11
  93. data/lib/plutonium/{application → portal}/dynamic_controllers.rb +4 -4
  94. data/lib/plutonium/portal/engine.rb +15 -0
  95. data/lib/plutonium/railtie.rb +33 -1
  96. data/lib/plutonium/reloader.rb +5 -5
  97. data/lib/plutonium/resource/controller.rb +51 -34
  98. data/lib/plutonium/resource/controllers/authorizable.rb +128 -0
  99. data/lib/plutonium/{core → resource}/controllers/crud_actions.rb +23 -22
  100. data/lib/plutonium/resource/controllers/defineable.rb +26 -0
  101. data/lib/plutonium/{core → resource}/controllers/interactive_actions.rb +12 -12
  102. data/lib/plutonium/resource/controllers/presentable.rb +41 -0
  103. data/lib/plutonium/resource/controllers/queryable.rb +44 -0
  104. data/lib/plutonium/resource/definition.rb +6 -0
  105. data/lib/plutonium/resource/policy.rb +25 -13
  106. data/lib/plutonium/resource/query_object.rb +50 -51
  107. data/lib/plutonium/resource/record.rb +6 -89
  108. data/lib/plutonium/resource/register.rb +82 -0
  109. data/lib/plutonium/routing/mapper_extensions.rb +1 -1
  110. data/lib/plutonium/routing/resource_registration.rb +1 -1
  111. data/lib/plutonium/routing/route_set_extensions.rb +6 -18
  112. data/lib/plutonium/ui/action_button.rb +125 -0
  113. data/lib/plutonium/ui/breadcrumbs.rb +163 -0
  114. data/lib/plutonium/ui/component/base.rb +13 -0
  115. data/lib/plutonium/ui/component/behaviour.rb +38 -0
  116. data/lib/plutonium/ui/component/kit.rb +31 -0
  117. data/lib/plutonium/ui/component/methods.rb +54 -0
  118. data/lib/plutonium/ui/display/base.rb +25 -0
  119. data/lib/plutonium/ui/display/component/association.rb +26 -0
  120. data/lib/plutonium/ui/display/resource.rb +77 -0
  121. data/lib/plutonium/ui/display/theme.rb +27 -0
  122. data/lib/plutonium/ui/dyna_frame/content.rb +20 -0
  123. data/lib/plutonium/ui/empty_card.rb +20 -0
  124. data/lib/plutonium/ui/form/base.rb +37 -0
  125. data/lib/plutonium/ui/form/resource.rb +75 -0
  126. data/lib/plutonium/ui/form/theme.rb +42 -0
  127. data/lib/plutonium/ui/page/base.rb +112 -0
  128. data/lib/plutonium/ui/page/edit.rb +23 -0
  129. data/lib/plutonium/ui/page/index.rb +27 -0
  130. data/lib/plutonium/ui/page/new.rb +23 -0
  131. data/lib/plutonium/ui/page/show.rb +27 -0
  132. data/lib/plutonium/ui/page_header.rb +49 -0
  133. data/lib/plutonium/ui/table/base.rb +13 -0
  134. data/lib/plutonium/ui/table/components/pagy_info.rb +70 -0
  135. data/lib/plutonium/ui/table/components/pagy_page_info.rb +70 -0
  136. data/lib/plutonium/ui/table/components/pagy_pagination.rb +105 -0
  137. data/lib/plutonium/ui/table/components/scopes_bar.rb +136 -0
  138. data/lib/plutonium/ui/table/components/search_bar.rb +158 -0
  139. data/lib/plutonium/ui/table/display_theme.rb +21 -0
  140. data/lib/plutonium/ui/table/resource.rb +98 -0
  141. data/lib/plutonium/ui/table/theme.rb +35 -0
  142. data/lib/plutonium/ui.rb +9 -0
  143. data/lib/plutonium/version.rb +5 -1
  144. data/lib/plutonium.rb +14 -1
  145. data/package-lock.json +19 -22
  146. data/package.json +4 -4
  147. data/src/css/plutonium.css +15 -0
  148. data/tailwind.options.js +11 -3
  149. metadata +218 -81
  150. data/lib/generators/pu/core/install/templates/app/presenters/resource_presenter.rb.tt +0 -2
  151. data/lib/generators/pu/core/install/templates/app/query_objects/resource_query_object.rb.tt +0 -2
  152. data/lib/generators/pu/pkg/feature/templates/app/query_objects/resource_query_object.rb.tt +0 -4
  153. data/lib/plutonium/concerns/resource_validatable.rb +0 -34
  154. data/lib/plutonium/config.rb +0 -9
  155. data/lib/plutonium/core/controllers/base.rb +0 -101
  156. data/lib/plutonium/core/controllers/presentable.rb +0 -65
  157. data/lib/plutonium/core/controllers/queryable.rb +0 -28
  158. data/lib/plutonium/pkg/app.rb +0 -35
  159. data/lib/plutonium/pkg/concerns/resource_validatable.rb +0 -36
  160. data/lib/plutonium/pkg/feature.rb +0 -18
  161. data/lib/plutonium/policy/initializer.rb +0 -22
  162. data/lib/plutonium/policy/scope.rb +0 -19
  163. data/lib/plutonium/pundit/context.rb +0 -18
  164. data/lib/plutonium/pundit/policy_finder.rb +0 -25
  165. data/lib/plutonium/resource/policy_context.rb +0 -5
  166. data/lib/plutonium/resource_register.rb +0 -83
  167. data/lib/plutonium/smart_cache.rb +0 -151
  168. /data/app/views/{application → plutonium}/_flash.html.erb +0 -0
  169. /data/app/views/{application → plutonium}/_flash_alerts.html.erb +0 -0
  170. /data/app/views/{application → plutonium}/_flash_toasts.html.erb +0 -0
  171. /data/lib/generators/pu/pkg/{app/templates/app/views/package → package/templates}/.keep +0 -0
  172. /data/lib/generators/pu/pkg/{feature → package}/templates/app/interactions/resource_interaction.rb.tt +0 -0
  173. /data/lib/generators/pu/pkg/{feature → package}/templates/app/models/resource_record.rb.tt +0 -0
  174. /data/lib/generators/pu/pkg/{feature → package}/templates/app/policies/resource_policy.rb.tt +0 -0
  175. /data/lib/generators/pu/pkg/{feature → package}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  176. /data/lib/generators/pu/pkg/{feature → package}/templates/lib/engine.rb.tt +0 -0
  177. /data/lib/generators/pu/pkg/{app → portal}/templates/app/policies/resource_policy.rb.tt +0 -0
  178. /data/lib/generators/pu/pkg/{app → portal}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  179. /data/lib/generators/pu/pkg/{app → portal}/templates/app/query_objects/resource_query_object.rb.tt +0 -0
  180. /data/lib/generators/pu/pkg/{feature/templates → portal/templates/app/views/package}/.keep +0 -0
  181. /data/lib/generators/pu/pkg/{app → portal}/templates/config/routes.rb.tt +0 -0
  182. /data/lib/generators/pu/pkg/{app → portal}/templates/lib/engine.rb.tt +0 -0
@@ -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
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Definition
5
+ # Module for handling defineable properties in Plutonium definitions
6
+ #
7
+ # @example
8
+ # class MyDefinition
9
+ # include DefineableProperties
10
+ #
11
+ # defineable_property :field
12
+ # defineable_property :input
13
+ # defineable_property :filter
14
+ # defineable_property :scope
15
+ # defineable_property :sorter
16
+ # end
17
+ module DefineableProps
18
+ extend ActiveSupport::Concern
19
+
20
+ included do
21
+ class_attribute :_defineable_props_store, instance_accessor: false, instance_predicate: false, default: []
22
+ end
23
+
24
+ class_methods do
25
+ def defineable_props(*property_names)
26
+ property_names.each { |name| defineable_prop(name) }
27
+ end
28
+
29
+ # Defines a new property type for the class
30
+ #
31
+ # @param property_name [Symbol] The name of the property to define
32
+ # @return [void]
33
+ def defineable_prop(property_name)
34
+ property_plural = property_name.to_s.pluralize.to_sym
35
+ property_getter = :"defined_#{property_plural}"
36
+ self._defineable_props_store += [property_plural]
37
+
38
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
39
+ def self.#{property_name}(name, **options, &block)
40
+ #{property_getter}[name] = { options:, block:}.compact
41
+ end
42
+
43
+ def self.#{property_getter}
44
+ @#{property_getter} ||= {}
45
+ end
46
+
47
+ def #{property_name}(name, **options, &block)
48
+ instance_#{property_getter}[name] = { options:, block:}.compact
49
+ end
50
+
51
+ def #{property_getter}
52
+ @merged_#{property_getter} ||= begin
53
+ customize_#{property_plural}
54
+ merged = {}
55
+ self.class.#{property_getter}.each do |name, data|
56
+ merged[name] = {
57
+ options: data[:options].dup,
58
+ block: data[:block]
59
+ }.compact
60
+ end
61
+ instance_#{property_getter}.each do |name, data|
62
+ if merged.key?(name)
63
+ merged[name][:options].merge!(data[:options])
64
+ merged[name][:block] = data[:block] if data[:block]
65
+ else
66
+ merged[name] = data
67
+ end
68
+ # merged[name].compact!
69
+ end
70
+ merged
71
+ end
72
+ end
73
+
74
+ def customize_#{property_plural}
75
+ # Override in subclass to add or modify #{property_plural}
76
+ end
77
+
78
+ private
79
+
80
+ def instance_#{property_getter}
81
+ @instance_#{property_getter} ||= {}
82
+ end
83
+ RUBY
84
+ end
85
+
86
+ # Handles inheritance by duplicating class-level collections
87
+ def inherited(subclass)
88
+ super
89
+ _defineable_props_store.each do |property|
90
+ subclass.instance_variable_set(:"@defined_#{property}", instance_variable_get(:"@defined_#{property}")&.deep_dup || {})
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,21 @@
1
+ module Plutonium
2
+ module Definition
3
+ module Search
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :_search_definition, instance_accessor: false, instance_predicate: false
8
+ end
9
+
10
+ def search_definition
11
+ self.class._search_definition
12
+ end
13
+
14
+ class_methods do
15
+ def search(&block)
16
+ self._search_definition = block
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Engine
5
+ module Validator
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ # Validates that the current engine supports Plutonium features.
10
+ #
11
+ # @raise [ArgumentError] If the engine doesn't include Plutonium::Engine.
12
+ # @return [void]
13
+ def validate_engine!(engine)
14
+ return if supported_engine?(engine)
15
+
16
+ # TODO: make the error link to documentation on how to ensure that your engine is supported
17
+ raise ArgumentError, "#{engine} must include Plutonium::Engine to call register resources"
18
+ end
19
+
20
+ # Checks if the current engine supports Plutonium features.
21
+ #
22
+ # @return [Boolean] True if the engine includes Plutonium::Engine, false otherwise.
23
+ def supported_engine?(engine)
24
+ # TODO: fix constant being out of sync after reload during development
25
+ Plutonium.configuration.development? ? engine.respond_to?(:dom_id) : engine.include?(Plutonium::Engine)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Engine
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ attr_reader :scoped_entity_class, :scoped_entity_strategy, :scoped_entity_param_key
9
+
10
+ def scope_to_entity(entity_class, strategy: :path, param_key: nil)
11
+ @scoped_entity_class = entity_class
12
+ @scoped_entity_strategy = strategy
13
+ @scoped_entity_param_key = param_key || entity_class.model_name.singular_route_key.to_sym
14
+ end
15
+
16
+ def scoped_to_entity?
17
+ scoped_entity_class.present?
18
+ end
19
+
20
+ def dom_id
21
+ module_parent_name.underscore.dasherize
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,3 @@
1
- require "view_component/form"
2
-
3
1
  module Plutonium
4
2
  module Helpers
5
3
  module FormHelper
@@ -23,7 +21,7 @@ module Plutonium
23
21
 
24
22
  def token_tag(...)
25
23
  # needed to workaround https://github.com/tailwindlabs/tailwindcss/issues/3350
26
- super(...).sub(" />", " hidden />").html_safe
24
+ super.sub(" />", " hidden />").html_safe
27
25
  end
28
26
 
29
27
  private