plutonium 0.14.0 → 0.15.0.pre.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/README copy.md +1 -1
  3. data/README.md +1 -1
  4. data/app/assets/plutonium.css +1 -1
  5. data/app/views/{application → plutonium}/_resource_header.html copy.erb +1 -1
  6. data/app/views/{application → plutonium}/_resource_header.html.erb +1 -1
  7. data/app/views/{application → plutonium}/_resource_sidebar.html.erb +2 -0
  8. data/app/views/resource/_resource_details.html.erb +1 -36
  9. data/app/views/resource/_resource_form.html.erb +1 -5
  10. data/app/views/resource/_resource_table.html.erb +315 -85
  11. data/app/views/resource/edit.html.erb +1 -5
  12. data/app/views/resource/index.html.erb +1 -5
  13. data/app/views/resource/new.html.erb +1 -5
  14. data/app/views/resource/show.html.erb +1 -5
  15. data/config/initializers/pagy.rb +1 -0
  16. data/config/initializers/rabl.rb +27 -20
  17. data/gemfiles/rails_7.gemfile.lock +5 -1
  18. data/lib/generators/pu/core/assets/assets_generator.rb +2 -2
  19. data/lib/generators/pu/core/install/install_generator.rb +0 -3
  20. data/lib/generators/pu/core/install/templates/app/controllers/plutonium_controller.rb.tt +2 -0
  21. data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +21 -1
  22. data/lib/generators/pu/core/install/templates/app/definitions/resource_definition.rb.tt +2 -0
  23. data/lib/generators/pu/core/install/templates/app/models/resource_record.rb.tt +0 -2
  24. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +5 -2
  25. data/lib/generators/pu/eject/shell/shell_generator.rb +2 -2
  26. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +19 -0
  27. data/lib/generators/pu/lib/plutonium_generators/concerns/logger.rb +1 -1
  28. data/lib/generators/pu/lib/plutonium_generators/generator.rb +5 -3
  29. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +26 -2
  30. data/lib/generators/pu/pkg/{feature/feature_generator.rb → package/package_generator.rb} +4 -4
  31. data/lib/generators/pu/pkg/{feature → package}/templates/app/controllers/resource_controller.rb.tt +0 -2
  32. data/lib/generators/pu/pkg/package/templates/app/definitions/resource_definition.rb.tt +4 -0
  33. data/lib/generators/pu/pkg/package/templates/app/query_objects/resource_query_object.rb.tt +4 -0
  34. data/lib/generators/pu/pkg/{app/app_generator.rb → portal/portal_generator.rb} +10 -8
  35. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/concerns/controller.rb.tt +3 -7
  36. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/dashboard_controller.rb.tt +1 -1
  37. data/lib/generators/pu/pkg/portal/templates/app/controllers/plutonium_controller.rb.tt +5 -0
  38. data/lib/generators/pu/pkg/{app/templates/app/controllers/controller.rb.tt → portal/templates/app/controllers/resource_controller.rb.tt} +1 -1
  39. data/lib/generators/pu/pkg/portal/templates/app/definitions/resource_definition.rb.tt +4 -0
  40. data/lib/generators/pu/pkg/{app → portal}/templates/app/views/package/dashboard/index.html.erb +2 -1
  41. data/lib/generators/pu/res/conn/conn_generator.rb +78 -3
  42. data/lib/generators/pu/res/conn/templates/app/controllers/resource_controller.rb.tt +1 -1
  43. data/lib/generators/pu/res/conn/templates/app/definitions/resource_definition.rb.tt +3 -0
  44. data/lib/generators/pu/res/conn/templates/app/policies/resource_policy.rb.tt +29 -1
  45. data/lib/generators/pu/res/conn/templates/app/presenters/resource_presenter.rb.tt +1 -1
  46. data/lib/generators/pu/res/conn/templates/app/query_objects/resource_query_object.rb.tt +1 -1
  47. data/lib/generators/pu/res/model/model_generator.rb +0 -7
  48. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -1
  49. data/lib/generators/pu/res/scaffold/scaffold_generator.rb +22 -4
  50. data/lib/generators/pu/res/scaffold/templates/controller.rb.tt +0 -1
  51. data/lib/generators/pu/res/scaffold/templates/definition.rb.tt +4 -0
  52. data/lib/generators/pu/res/scaffold/templates/policy.rb.tt +2 -2
  53. data/lib/generators/pu/rodauth/templates/app/controllers/rodauth_controller.rb.tt +1 -1
  54. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +270 -0
  55. data/lib/plutonium/action/README.md +0 -0
  56. data/lib/plutonium/action/base.rb +103 -0
  57. data/lib/plutonium/action/interactive.rb +117 -0
  58. data/lib/plutonium/action/route_options.rb +65 -0
  59. data/lib/plutonium/action/simple.rb +8 -0
  60. data/lib/plutonium/auth.rb +1 -1
  61. data/lib/plutonium/configuration.rb +130 -0
  62. data/lib/plutonium/core/actions/collection.rb +1 -1
  63. data/lib/plutonium/core/associations/renderers/factory.rb +3 -1
  64. data/lib/plutonium/core/autodiscovery/association_renderer_discoverer.rb +1 -1
  65. data/lib/plutonium/core/autodiscovery/input_discoverer.rb +1 -1
  66. data/lib/plutonium/core/autodiscovery/renderer_discoverer.rb +1 -1
  67. data/lib/plutonium/core/controller.rb +110 -0
  68. data/lib/plutonium/core/controllers/authorizable.rb +12 -35
  69. data/lib/plutonium/core/controllers/bootable.rb +38 -7
  70. data/lib/plutonium/core/controllers/entity_scoping.rb +6 -2
  71. data/lib/plutonium/core/fields/renderers/association_renderer.rb +1 -1
  72. data/lib/plutonium/core/ui/collection.rb +1 -1
  73. data/lib/plutonium/core/ui/detail.rb +1 -1
  74. data/lib/plutonium/core/ui/form.rb +1 -1
  75. data/lib/plutonium/definition/actions.rb +50 -0
  76. data/lib/plutonium/definition/base.rb +92 -0
  77. data/lib/plutonium/definition/config_attr.rb +30 -0
  78. data/lib/plutonium/definition/defineable_props.rb +96 -0
  79. data/lib/plutonium/definition/search.rb +21 -0
  80. data/lib/plutonium/engine/validator.rb +30 -0
  81. data/lib/plutonium/engine.rb +25 -0
  82. data/lib/plutonium/helpers/assets_helper.rb +73 -20
  83. data/lib/plutonium/helpers/form_helper.rb +1 -3
  84. data/lib/plutonium/interaction/README.md +369 -0
  85. data/lib/plutonium/interaction/base.rb +75 -0
  86. data/lib/plutonium/interaction/concerns/presentable.rb +61 -0
  87. data/lib/plutonium/interaction/concerns/workflow_dsl.rb +82 -0
  88. data/lib/plutonium/interaction/outcome.rb +129 -0
  89. data/lib/plutonium/interaction/response/base.rb +63 -0
  90. data/lib/plutonium/interaction/response/null.rb +33 -0
  91. data/lib/plutonium/interaction/response/redirect.rb +30 -0
  92. data/lib/plutonium/interaction/response/render.rb +28 -0
  93. data/lib/plutonium/lib/bit_flags.rb +70 -9
  94. data/lib/plutonium/lib/overlayed_hash.rb +86 -0
  95. data/lib/plutonium/lib/smart_cache.rb +171 -0
  96. data/lib/plutonium/models/has_cents.rb +170 -0
  97. data/lib/plutonium/{pkg/base.rb → package/engine.rb} +10 -2
  98. data/lib/plutonium/{application → portal}/controller.rb +3 -11
  99. data/lib/plutonium/{application → portal}/dynamic_controllers.rb +4 -4
  100. data/lib/plutonium/portal/engine.rb +15 -0
  101. data/lib/plutonium/railtie.rb +35 -15
  102. data/lib/plutonium/reloader.rb +71 -29
  103. data/lib/plutonium/resource/controller.rb +51 -34
  104. data/lib/plutonium/resource/controllers/authorizable.rb +128 -0
  105. data/lib/plutonium/{core → resource}/controllers/crud_actions.rb +23 -22
  106. data/lib/plutonium/resource/controllers/defineable.rb +26 -0
  107. data/lib/plutonium/{core → resource}/controllers/interactive_actions.rb +12 -12
  108. data/lib/plutonium/resource/controllers/presentable.rb +41 -0
  109. data/lib/plutonium/resource/controllers/queryable.rb +44 -0
  110. data/lib/plutonium/resource/definition.rb +6 -0
  111. data/lib/plutonium/resource/policy.rb +25 -13
  112. data/lib/plutonium/resource/query_object.rb +50 -51
  113. data/lib/plutonium/resource/record.rb +6 -89
  114. data/lib/plutonium/resource/register.rb +82 -0
  115. data/lib/plutonium/routing/mapper_extensions.rb +1 -1
  116. data/lib/plutonium/routing/resource_registration.rb +1 -1
  117. data/lib/plutonium/routing/route_set_extensions.rb +6 -18
  118. data/lib/plutonium/ui/action_button.rb +125 -0
  119. data/lib/plutonium/ui/breadcrumbs.rb +163 -0
  120. data/lib/plutonium/ui/component/base.rb +13 -0
  121. data/lib/plutonium/ui/component/behaviour.rb +38 -0
  122. data/lib/plutonium/ui/component/kit.rb +31 -0
  123. data/lib/plutonium/ui/component/methods.rb +54 -0
  124. data/lib/plutonium/ui/display/base.rb +25 -0
  125. data/lib/plutonium/ui/display/component/association.rb +26 -0
  126. data/lib/plutonium/ui/display/resource.rb +77 -0
  127. data/lib/plutonium/ui/display/theme.rb +27 -0
  128. data/lib/plutonium/ui/dyna_frame/content.rb +20 -0
  129. data/lib/plutonium/ui/empty_card.rb +20 -0
  130. data/lib/plutonium/ui/form/base.rb +37 -0
  131. data/lib/plutonium/ui/form/resource.rb +75 -0
  132. data/lib/plutonium/ui/form/theme.rb +42 -0
  133. data/lib/plutonium/ui/page/base.rb +112 -0
  134. data/lib/plutonium/ui/page/edit.rb +23 -0
  135. data/lib/plutonium/ui/page/index.rb +27 -0
  136. data/lib/plutonium/ui/page/new.rb +23 -0
  137. data/lib/plutonium/ui/page/show.rb +27 -0
  138. data/lib/plutonium/ui/page_header.rb +49 -0
  139. data/lib/plutonium/ui/table/base.rb +13 -0
  140. data/lib/plutonium/ui/table/components/pagy_info.rb +70 -0
  141. data/lib/plutonium/ui/table/components/pagy_page_info.rb +70 -0
  142. data/lib/plutonium/ui/table/components/pagy_pagination.rb +105 -0
  143. data/lib/plutonium/ui/table/components/scopes_bar.rb +136 -0
  144. data/lib/plutonium/ui/table/components/search_bar.rb +158 -0
  145. data/lib/plutonium/ui/table/display_theme.rb +21 -0
  146. data/lib/plutonium/ui/table/resource.rb +98 -0
  147. data/lib/plutonium/ui/table/theme.rb +35 -0
  148. data/lib/plutonium/ui.rb +9 -0
  149. data/lib/plutonium/version.rb +5 -1
  150. data/lib/plutonium.rb +53 -26
  151. data/package-lock.json +19 -22
  152. data/package.json +4 -4
  153. data/sig/.keep +0 -0
  154. data/src/css/plutonium.css +15 -0
  155. data/tailwind.options.js +11 -3
  156. metadata +220 -81
  157. data/lib/generators/pu/core/install/templates/app/presenters/resource_presenter.rb.tt +0 -2
  158. data/lib/generators/pu/core/install/templates/app/query_objects/resource_query_object.rb.tt +0 -2
  159. data/lib/generators/pu/pkg/feature/templates/app/query_objects/resource_query_object.rb.tt +0 -4
  160. data/lib/plutonium/concerns/resource_validatable.rb +0 -34
  161. data/lib/plutonium/config.rb +0 -9
  162. data/lib/plutonium/core/controllers/base.rb +0 -101
  163. data/lib/plutonium/core/controllers/presentable.rb +0 -65
  164. data/lib/plutonium/core/controllers/queryable.rb +0 -28
  165. data/lib/plutonium/pkg/app.rb +0 -35
  166. data/lib/plutonium/pkg/concerns/resource_validatable.rb +0 -36
  167. data/lib/plutonium/pkg/feature.rb +0 -18
  168. data/lib/plutonium/policy/initializer.rb +0 -22
  169. data/lib/plutonium/policy/scope.rb +0 -19
  170. data/lib/plutonium/pundit/context.rb +0 -18
  171. data/lib/plutonium/pundit/policy_finder.rb +0 -25
  172. data/lib/plutonium/resource/policy_context.rb +0 -5
  173. data/lib/plutonium/resource_register.rb +0 -83
  174. data/lib/plutonium/smart_cache.rb +0 -151
  175. data/sig/plutonium.rbs +0 -12
  176. /data/app/views/{application → plutonium}/_flash.html.erb +0 -0
  177. /data/app/views/{application → plutonium}/_flash_alerts.html.erb +0 -0
  178. /data/app/views/{application → plutonium}/_flash_toasts.html.erb +0 -0
  179. /data/lib/generators/pu/pkg/{app/templates/app/views/package → package/templates}/.keep +0 -0
  180. /data/lib/generators/pu/pkg/{feature → package}/templates/app/interactions/resource_interaction.rb.tt +0 -0
  181. /data/lib/generators/pu/pkg/{feature → package}/templates/app/models/resource_record.rb.tt +0 -0
  182. /data/lib/generators/pu/pkg/{feature → package}/templates/app/policies/resource_policy.rb.tt +0 -0
  183. /data/lib/generators/pu/pkg/{feature → package}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  184. /data/lib/generators/pu/pkg/{feature → package}/templates/lib/engine.rb.tt +0 -0
  185. /data/lib/generators/pu/pkg/{app → portal}/templates/app/policies/resource_policy.rb.tt +0 -0
  186. /data/lib/generators/pu/pkg/{app → portal}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  187. /data/lib/generators/pu/pkg/{app → portal}/templates/app/query_objects/resource_query_object.rb.tt +0 -0
  188. /data/lib/generators/pu/pkg/{feature/templates → portal/templates/app/views/package}/.keep +0 -0
  189. /data/lib/generators/pu/pkg/{app → portal}/templates/config/routes.rb.tt +0 -0
  190. /data/lib/generators/pu/pkg/{app → portal}/templates/lib/engine.rb.tt +0 -0
@@ -1,101 +0,0 @@
1
- module Plutonium
2
- module Core
3
- module Controllers
4
- module Base
5
- extend ActiveSupport::Concern
6
- include Plutonium::Core::Controllers::Bootable
7
- include Plutonium::Core::Controllers::EntityScoping
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
- end
24
-
25
- private
26
-
27
- def set_page_title(page_title)
28
- @page_title = page_title
29
- end
30
-
31
- def make_page_title(title)
32
- [title.presence, helpers.application_name].compact.join(" | ")
33
- end
34
-
35
- #
36
- # Returns a dynamic list of args to be used with `url_for`, which considers the route namespace and nesting.
37
- # The current entity and parent record (for nested routes) are inserted appropriately, ensuring that generated URLs
38
- # obey the current routing.
39
- #
40
- # e.g., of route helpers that will be invoked given the output of this method
41
- #
42
- # - when invoked in a root route (/acme/dashboard/users)
43
- #
44
- # `resource_url_args_for User` => `entity_users_*`
45
- # `resource_url_args_for @user` => `entity_user_*`
46
- # `resource_url_args_for @user, action: :edit` => `edit_entity_user_*`
47
- # `resource_url_args_for @user, Post` => `entity_user_posts_*`
48
- #
49
- # - when invoked in a nested route (/acme/dashboard/users/1/post/1)
50
- #
51
- # `resource_url_args_for Post` => `entity_user_posts_*`
52
- # `resource_url_args_for @post` => `entity_user_post_*`
53
- # `resource_url_args_for @post, action: :edit` => `edit_entity_user_post_*`
54
- #
55
- # @param [Class, ApplicationRecord] *args arguments you would normally pass to `url_for`
56
- # @param [Symbol] action optional action to invoke, e.g., :new, :edit
57
- # @param [ApplicationRecord] parent the parent record for nested routes, if any
58
- # @param [Hash] kwargs additional keyword arguments to pass to `url_for`
59
- #
60
- # @return [Hash] args to pass to `url_for`
61
- #
62
- def resource_url_args_for(*args, action: nil, parent: nil, **kwargs)
63
- url_args = {**kwargs, action: action}.compact
64
-
65
- controller_chain = [current_package.to_s]
66
- [*args].compact.each_with_index do |element, index|
67
- if element.is_a?(Class)
68
- controller_chain << element.to_s.pluralize
69
- else
70
- controller_chain << element.class.to_s.pluralize
71
- if index == args.length - 1
72
- url_args[:id] = element.to_param
73
- url_args[:action] ||= :show
74
- else
75
- url_args[element.model_name.singular_route_key.to_sym] = element.to_param
76
- end
77
- end
78
- end
79
- url_args[:controller] = "/#{controller_chain.join("::").underscore}"
80
-
81
- url_args[:"#{parent.model_name.singular_route_key}_id"] = parent.to_param if parent.present?
82
- if scoped_to_entity? && scoped_entity_strategy == :path
83
- url_args[scoped_entity_param_key] = current_scoped_entity
84
- end
85
-
86
- url_args
87
- end
88
-
89
- def resource_url_for(...)
90
- send(current_package.name.underscore.to_sym).url_for(resource_url_args_for(...))
91
- end
92
-
93
- def root_path(*)
94
- return send(:"#{scoped_entity_param_key}_root_path", *) if scoped_to_entity? && scoped_entity_strategy == :path
95
-
96
- super(*)
97
- end
98
- end
99
- end
100
- end
101
- end
@@ -1,65 +0,0 @@
1
- module Plutonium
2
- module Core
3
- module Controllers
4
- module Presentable
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- helper_method :presentable_attributes, :present_associations?
9
- end
10
-
11
- private
12
-
13
- def resource_presenter(resource_class, resource_record)
14
- presenter_class = "#{resource_class}Presenter".constantize
15
- presenter_class.new resource_context, resource_record
16
- end
17
-
18
- def current_presenter
19
- @current_presenter ||= resource_presenter resource_class, @resource_record
20
- end
21
-
22
- def presentable_attributes
23
- @presentable_attributes ||= begin
24
- presentable_attributes = permitted_attributes
25
- presentable_attributes -= [scoped_entity_param_key, :"#{scoped_entity_param_key}_id"] if scoped_to_entity?
26
- presentable_attributes -= [parent_input_param, :"#{parent_input_param}_id"] if current_parent.present?
27
- presentable_attributes
28
- end
29
- end
30
-
31
- def build_collection
32
- Plutonium::Core::Ui::Collection.new(
33
- resource_class:,
34
- records: @resource_records,
35
- fields: current_presenter.defined_field_renderers_for(*presentable_attributes),
36
- actions: current_presenter.actions,
37
- pager: @pagy,
38
- search_object: @search_object
39
- )
40
- end
41
-
42
- def build_detail
43
- Plutonium::Core::Ui::Detail.new(
44
- resource_class:,
45
- record: resource_record,
46
- fields: current_presenter.defined_field_renderers_for(*presentable_attributes),
47
- associations: current_presenter.defined_association_renderers_for(*permitted_associations),
48
- actions: current_presenter.actions
49
- )
50
- end
51
-
52
- def build_form
53
- Plutonium::Core::Ui::Form.new(
54
- record: resource_record,
55
- inputs: current_presenter.defined_field_inputs_for(*presentable_attributes)
56
- )
57
- end
58
-
59
- def present_associations?
60
- current_parent.nil?
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,28 +0,0 @@
1
- using Plutonium::Refinements::ParameterRefinements
2
-
3
- module Plutonium
4
- module Core
5
- module Controllers
6
- module Queryable
7
- extend ActiveSupport::Concern
8
-
9
- included do
10
- helper_method :resource_query_params
11
- end
12
-
13
- def resource_query_object(resource_class, params)
14
- query_object_class = "#{resource_class}QueryObject".constantize
15
- query_object_class.new resource_context, params
16
- end
17
-
18
- def current_query_object
19
- @current_query_object ||= resource_query_object resource_class, resource_query_params
20
- end
21
-
22
- def resource_query_params
23
- (params[:q]&.nilify&.to_unsafe_h || {}).with_indifferent_access
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Plutonium
4
- module Pkg
5
- module App
6
- extend ActiveSupport::Concern
7
- include Base
8
-
9
- included do
10
- isolate_namespace to_s.deconstantize.constantize
11
- end
12
-
13
- class_methods do
14
- include Plutonium::Concerns::ResourceValidatable
15
- attr_reader :scoped_entity_class, :scoped_entity_strategy, :scoped_entity_param_key
16
-
17
- def scope_to_entity(entity_class, strategy: :path, param_key: nil)
18
- validate_resource! entity_class
19
-
20
- @scoped_entity_class = entity_class
21
- @scoped_entity_strategy = strategy
22
- @scoped_entity_param_key = param_key || entity_class.model_name.singular_route_key.to_sym
23
- end
24
-
25
- def scoped_to_entity?
26
- scoped_entity_class.present?
27
- end
28
-
29
- def dom_id
30
- module_parent_name.underscore.dasherize
31
- end
32
- end
33
- end
34
- end
35
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Plutonium
4
- module Pkg
5
- module Concerns
6
- # Provides methods for validating Plutonium resources
7
- module ResourceValidatable
8
- extend ActiveSupport::Concern
9
-
10
- # Custom error class for invalid resources
11
- class InvalidResourceError < StandardError; end
12
-
13
- private
14
-
15
- # Validates if a given resource is a valid Plutonium::Resource::Record
16
- #
17
- # @param resource [Object] The resource to validate
18
- # @raise [InvalidResourceError] If the resource is not valid
19
- # @return [void]
20
- def validate_resource!(resource)
21
- unless valid_resource?(resource)
22
- raise InvalidResourceError, "#{resource} is not a valid Plutonium::Resource::Record"
23
- end
24
- end
25
-
26
- # Checks if a given resource is a valid Plutonium::Resource::Record
27
- #
28
- # @param resource [Object] The resource to check
29
- # @return [Boolean] True if the resource is valid, false otherwise
30
- def valid_resource?(resource)
31
- resource.is_a?(Class) && resource.include?(Plutonium::Resource::Record)
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,18 +0,0 @@
1
- module Plutonium
2
- module Pkg
3
- module Feature
4
- extend ActiveSupport::Concern
5
- include Base
6
-
7
- included do
8
- initializer :append_migrations do |app|
9
- unless app.root.to_s.match root.to_s
10
- config.paths["db/migrate"].expanded.each do |expanded_path|
11
- app.config.paths["db/migrate"] << expanded_path
12
- end
13
- end
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,22 +0,0 @@
1
- require "pundit"
2
-
3
- module Plutonium
4
- module Policy
5
- module Initializer
6
- def initialize(context, record)
7
- authorize!(context)
8
-
9
- @context = context
10
- @record = record
11
- end
12
-
13
- private
14
-
15
- attr_reader :context, :record
16
-
17
- def authorize!(context)
18
- raise Pundit::NotAuthorizedError, "must be logged in" unless context&.user
19
- end
20
- end
21
- end
22
- end
@@ -1,19 +0,0 @@
1
- require "pundit"
2
-
3
- module Plutonium
4
- module Policy
5
- class Scope
6
- include Plutonium::Policy::Initializer
7
-
8
- def resolve
9
- scope = context.resource_context.resource_class.all
10
- if @context.resource_context.parent.present?
11
- scope = scope.associated_with(@context.resource_context.parent)
12
- elsif @context.resource_context.scope.present?
13
- scope = scope.associated_with(@context.resource_context.scope)
14
- end
15
- scope
16
- end
17
- end
18
- end
19
- end
@@ -1,18 +0,0 @@
1
- require "pundit"
2
-
3
- module Plutonium
4
- module Pundit
5
- class Context < ::Pundit::Context
6
- def initialize(*, package:, **)
7
- super(*, **)
8
- @package = package
9
- end
10
-
11
- private
12
-
13
- def policy_finder(record)
14
- PolicyFinder.new(record, package: @package)
15
- end
16
- end
17
- end
18
- end
@@ -1,25 +0,0 @@
1
- require "pundit"
2
-
3
- module Plutonium
4
- module Pundit
5
- class PolicyFinder < ::Pundit::PolicyFinder
6
- def initialize(*, package:)
7
- super(*)
8
- @package = package
9
- end
10
-
11
- attr_reader :package
12
-
13
- def policy
14
- policy_internal([package, object]) || policy_internal(object)
15
- end
16
-
17
- private
18
-
19
- def policy_internal(object)
20
- klass = find(object)
21
- klass.is_a?(String) ? klass.safe_constantize : klass
22
- end
23
- end
24
- end
25
- end
@@ -1,5 +0,0 @@
1
- module Plutonium
2
- module Resource
3
- PolicyContext = Data.define :user, :resource_context
4
- end
5
- end
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Plutonium
4
- # ResourceRegister manages the registration and lookup of resources.
5
- class ResourceRegister
6
- include Plutonium::SmartCache
7
- include Concerns::ResourceValidatable
8
-
9
- # Custom error class for frozen register operations
10
- class FrozenRegisterError < StandardError; end
11
-
12
- def initialize
13
- @resources = Set.new
14
- @frozen = false
15
- end
16
-
17
- # Registers a new resource with the register.
18
- #
19
- # @param resource [Class] The resource class to be registered.
20
- # @raise [Plutonium::Concerns::ResourceValidatable::InvalidResourceError] If the resource is not a valid Plutonium::Resource::Record.
21
- # @raise [FrozenRegisterError] If the register is frozen.
22
- # @return [void]
23
- def register(resource)
24
- raise FrozenRegisterError, "Cannot modify frozen resource register" if @frozen
25
-
26
- validate_resource!(resource)
27
- @resources.add(resource.to_s)
28
- end
29
-
30
- # Returns an array of all registered resource classes and freezes the register.
31
- #
32
- # @return [Array<Class>] An array of registered resource classes.
33
- def resources
34
- freeze
35
- @resources.map(&:constantize)
36
- end
37
- memoize_unless_reloading :resources
38
-
39
- # Returns a hash mapping route keys to their corresponding resource classes.
40
- # This method will freeze the register if it hasn't been frozen already.
41
- #
42
- # @return [Hash{Symbol => Class}] A hash where keys are route keys and values are resource classes.
43
- def route_key_lookup
44
- freeze
45
- resources.to_h do |resource|
46
- [resource.model_name.singular_route_key.to_sym, resource]
47
- end
48
- end
49
- memoize_unless_reloading :route_key_lookup
50
-
51
- # Clears all registered resources and invalidates the cache.
52
- #
53
- # @return [void]
54
- def clear
55
- @resources.clear
56
- @frozen = false
57
- invalidate_cache
58
- end
59
-
60
- # Checks if the register is frozen.
61
- #
62
- # @return [Boolean] True if the register is frozen, false otherwise.
63
- def frozen?
64
- @frozen
65
- end
66
-
67
- private
68
-
69
- # Freezes the register
70
- #
71
- # @return [Boolean] Always returns true
72
- def freeze
73
- @frozen ||= true
74
- end
75
-
76
- # Invalidates the memoization cache
77
- #
78
- # @return [void]
79
- def invalidate_cache
80
- flush_smart_cache
81
- end
82
- end
83
- end
@@ -1,151 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Plutonium
4
- # The SmartCache module provides flexible caching mechanisms for classes and objects,
5
- # allowing for both inline caching and method-level memoization.
6
- #
7
- # This module is designed to optimize performance by caching results
8
- # when class caching is enabled (typically in production),
9
- # while ensuring fresh results when caching is disabled (typically in development).
10
- #
11
- # This implementation is thread-safe.
12
- #
13
- # @example Including SmartCache in a class
14
- # class MyClass
15
- # include Plutonium::SmartCache
16
- #
17
- # def my_method(arg)
18
- # cache_unless_reloading("my_method_#{arg}") { expensive_operation(arg) }
19
- # end
20
- #
21
- # def another_method(arg)
22
- # # Method implementation
23
- # end
24
- # memoize_unless_reloading :another_method
25
- # end
26
- module SmartCache
27
- extend ActiveSupport::Concern
28
-
29
- included do
30
- class_attribute :_memoized_results, instance_writer: false, default: Concurrent::Map.new
31
- end
32
-
33
- # Caches the result of the given block unless class caching is disabled.
34
- #
35
- # @param cache_key [String] A unique key to identify the cached result
36
- # @yield The block whose result will be cached
37
- # @return [Object] The result of the block, either freshly computed or from cache
38
- #
39
- # @example Using cache_unless_reloading inline
40
- # def fetch_user_data(user_id)
41
- # cache_unless_reloading("user_data_#{user_id}") do
42
- # UserDataService.fetch(user_id)
43
- # end
44
- # end
45
- #
46
- # @note This method uses Rails.application.config.cache_classes
47
- # to determine whether to cache or not. When cache_classes is false
48
- # (typical in development), it will always yield to get a fresh result.
49
- # When true (typical in production), it will use the cache.
50
- def cache_unless_reloading(cache_key, &block)
51
- return yield unless should_cache?
52
-
53
- @cached_results ||= Concurrent::Map.new
54
- @cached_results.compute_if_absent(cache_key) { yield }
55
- end
56
-
57
- # Flushes the smart cache for the specified keys or all keys if none are specified.
58
- #
59
- # @param keys [Array<Symbol, String>, Symbol, String] The cache key(s) to flush
60
- # @return [void]
61
- #
62
- # @example Flushing specific cache keys
63
- # flush_smart_cache([:user_data, :product_list])
64
- #
65
- # @example Flushing all cache keys
66
- # flush_smart_cache
67
- #
68
- # @note This method clears both inline caches and memoized method results.
69
- def flush_smart_cache(keys = nil)
70
- keys = Array(keys).map(&:to_sym)
71
- if keys.present?
72
- @cached_results&.delete_if { |k, _| keys.include?(k.to_sym) }
73
- keys.each { |key| self.class._memoized_results.delete(key) }
74
- else
75
- @cached_results&.clear
76
- self.class._memoized_results.clear
77
- end
78
- end
79
-
80
- # Determines whether caching should be performed based on the current Rails configuration.
81
- #
82
- # @return [Boolean] true if caching should be performed, false otherwise
83
- # @note This method uses Rails.application.config.cache_classes to determine caching behavior.
84
- # When cache_classes is false (typical in development), it returns false.
85
- # When true (typical in production), it returns true.
86
- def should_cache?
87
- Rails.application.config.cache_classes
88
- end
89
-
90
- class_methods do
91
- # Memoizes the result of the specified method unless class caching is disabled.
92
- #
93
- # @param method_name [Symbol] The name of the method to memoize
94
- # @return [void]
95
- #
96
- # @example Memoizing a method
97
- # class User
98
- # include Plutonium::SmartCache
99
- #
100
- # def expensive_full_name_calculation
101
- # # Complex name calculation
102
- # end
103
- # memoize_unless_reloading :expensive_full_name_calculation
104
- # end
105
- #
106
- # @note This method uses Rails.application.config.cache_classes to determine
107
- # whether to memoize or not. When cache_classes is false (typical in development),
108
- # it will always call the original method. When true (typical in production),
109
- # it will use memoization, caching results for each unique set of arguments.
110
- def memoize_unless_reloading(method_name)
111
- original_method = instance_method(method_name)
112
- define_method(method_name) do |*args|
113
- if should_cache?
114
- cache = self.class._memoized_results[method_name] ||= Concurrent::Map.new
115
- cache.compute_if_absent(args.hash.to_s) { original_method.bind_call(self, *args) }
116
- else
117
- original_method.bind_call(self, *args)
118
- end
119
- end
120
- end
121
- end
122
- end
123
-
124
- # Configuration:
125
- # The caching behavior is controlled by the Rails configuration option config.cache_classes:
126
- #
127
- # - When false (typical in development):
128
- # - Classes are reloaded on each request.
129
- # - cache_unless_reloading always yields fresh results.
130
- # - memoize_unless_reloading always calls the original method.
131
- #
132
- # - When true (typical in production):
133
- # - Classes are cached.
134
- # - cache_unless_reloading uses cached results.
135
- # - memoize_unless_reloading uses memoized results, caching for each unique set of arguments.
136
- #
137
- # Best Practices:
138
- # - Use meaningful and unique cache keys to avoid collisions.
139
- # - Be mindful of memory usage, especially with large cached results.
140
- # - Consider cache expiration strategies for long-running processes.
141
- # - Use cache_unless_reloading for fine-grained control within methods.
142
- # - Use memoize_unless_reloading for entire methods, especially those with expensive computations.
143
- #
144
- # Thread Safety:
145
- # - This implementation is thread-safe.
146
- # - It uses Concurrent::Map from the concurrent-ruby gem for thread-safe caching.
147
- #
148
- # Testing:
149
- # - In your test environment, you may want to control caching behavior explicitly.
150
- # - You can mock or stub Rails.application.config.cache_classes or override should_cache? as needed in your tests.
151
- end
data/sig/plutonium.rbs DELETED
@@ -1,12 +0,0 @@
1
- # plutonium.rbs
2
- module Plutonium
3
- class Error < StandardError; end
4
-
5
- def self.root: () -> Pathname
6
- def self.lib_root: () -> Pathname
7
- def self.logger: () -> Logger
8
- def self.application_name: () -> String
9
- def self.application_name=: (String) -> void
10
- def self.development?: () -> bool
11
- def self.eager_load_rails!: () -> void
12
- end