plutonium 0.14.0 → 0.15.0.pre.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/README copy.md +1 -1
  3. data/README.md +1 -1
  4. data/app/assets/plutonium.css +1 -1
  5. data/app/views/{application → plutonium}/_resource_header.html copy.erb +1 -1
  6. data/app/views/{application → plutonium}/_resource_header.html.erb +1 -1
  7. data/app/views/{application → plutonium}/_resource_sidebar.html.erb +2 -0
  8. data/app/views/resource/_resource_details.html.erb +1 -36
  9. data/app/views/resource/_resource_form.html.erb +1 -5
  10. data/app/views/resource/_resource_table.html.erb +315 -85
  11. data/app/views/resource/edit.html.erb +1 -5
  12. data/app/views/resource/index.html.erb +1 -5
  13. data/app/views/resource/new.html.erb +1 -5
  14. data/app/views/resource/show.html.erb +1 -5
  15. data/config/initializers/pagy.rb +1 -0
  16. data/config/initializers/rabl.rb +27 -20
  17. data/gemfiles/rails_7.gemfile.lock +5 -1
  18. data/lib/generators/pu/core/assets/assets_generator.rb +2 -2
  19. data/lib/generators/pu/core/install/install_generator.rb +0 -3
  20. data/lib/generators/pu/core/install/templates/app/controllers/plutonium_controller.rb.tt +2 -0
  21. data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +21 -1
  22. data/lib/generators/pu/core/install/templates/app/definitions/resource_definition.rb.tt +2 -0
  23. data/lib/generators/pu/core/install/templates/app/models/resource_record.rb.tt +0 -2
  24. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +5 -2
  25. data/lib/generators/pu/eject/shell/shell_generator.rb +2 -2
  26. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +19 -0
  27. data/lib/generators/pu/lib/plutonium_generators/concerns/logger.rb +1 -1
  28. data/lib/generators/pu/lib/plutonium_generators/generator.rb +5 -3
  29. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +26 -2
  30. data/lib/generators/pu/pkg/{feature/feature_generator.rb → package/package_generator.rb} +4 -4
  31. data/lib/generators/pu/pkg/{feature → package}/templates/app/controllers/resource_controller.rb.tt +0 -2
  32. data/lib/generators/pu/pkg/package/templates/app/definitions/resource_definition.rb.tt +4 -0
  33. data/lib/generators/pu/pkg/package/templates/app/query_objects/resource_query_object.rb.tt +4 -0
  34. data/lib/generators/pu/pkg/{app/app_generator.rb → portal/portal_generator.rb} +10 -8
  35. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/concerns/controller.rb.tt +3 -7
  36. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/dashboard_controller.rb.tt +1 -1
  37. data/lib/generators/pu/pkg/portal/templates/app/controllers/plutonium_controller.rb.tt +5 -0
  38. data/lib/generators/pu/pkg/{app/templates/app/controllers/controller.rb.tt → portal/templates/app/controllers/resource_controller.rb.tt} +1 -1
  39. data/lib/generators/pu/pkg/portal/templates/app/definitions/resource_definition.rb.tt +4 -0
  40. data/lib/generators/pu/pkg/{app → portal}/templates/app/views/package/dashboard/index.html.erb +2 -1
  41. data/lib/generators/pu/res/conn/conn_generator.rb +78 -3
  42. data/lib/generators/pu/res/conn/templates/app/controllers/resource_controller.rb.tt +1 -1
  43. data/lib/generators/pu/res/conn/templates/app/definitions/resource_definition.rb.tt +3 -0
  44. data/lib/generators/pu/res/conn/templates/app/policies/resource_policy.rb.tt +29 -1
  45. data/lib/generators/pu/res/conn/templates/app/presenters/resource_presenter.rb.tt +1 -1
  46. data/lib/generators/pu/res/conn/templates/app/query_objects/resource_query_object.rb.tt +1 -1
  47. data/lib/generators/pu/res/model/model_generator.rb +0 -7
  48. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -1
  49. data/lib/generators/pu/res/scaffold/scaffold_generator.rb +22 -4
  50. data/lib/generators/pu/res/scaffold/templates/controller.rb.tt +0 -1
  51. data/lib/generators/pu/res/scaffold/templates/definition.rb.tt +4 -0
  52. data/lib/generators/pu/res/scaffold/templates/policy.rb.tt +2 -2
  53. data/lib/generators/pu/rodauth/templates/app/controllers/rodauth_controller.rb.tt +1 -1
  54. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +270 -0
  55. data/lib/plutonium/action/README.md +0 -0
  56. data/lib/plutonium/action/base.rb +103 -0
  57. data/lib/plutonium/action/interactive.rb +117 -0
  58. data/lib/plutonium/action/route_options.rb +65 -0
  59. data/lib/plutonium/action/simple.rb +8 -0
  60. data/lib/plutonium/auth.rb +1 -1
  61. data/lib/plutonium/configuration.rb +130 -0
  62. data/lib/plutonium/core/actions/collection.rb +1 -1
  63. data/lib/plutonium/core/associations/renderers/factory.rb +3 -1
  64. data/lib/plutonium/core/autodiscovery/association_renderer_discoverer.rb +1 -1
  65. data/lib/plutonium/core/autodiscovery/input_discoverer.rb +1 -1
  66. data/lib/plutonium/core/autodiscovery/renderer_discoverer.rb +1 -1
  67. data/lib/plutonium/core/controller.rb +110 -0
  68. data/lib/plutonium/core/controllers/authorizable.rb +12 -35
  69. data/lib/plutonium/core/controllers/bootable.rb +38 -7
  70. data/lib/plutonium/core/controllers/entity_scoping.rb +6 -2
  71. data/lib/plutonium/core/fields/renderers/association_renderer.rb +1 -1
  72. data/lib/plutonium/core/ui/collection.rb +1 -1
  73. data/lib/plutonium/core/ui/detail.rb +1 -1
  74. data/lib/plutonium/core/ui/form.rb +1 -1
  75. data/lib/plutonium/definition/actions.rb +50 -0
  76. data/lib/plutonium/definition/base.rb +92 -0
  77. data/lib/plutonium/definition/config_attr.rb +30 -0
  78. data/lib/plutonium/definition/defineable_props.rb +96 -0
  79. data/lib/plutonium/definition/search.rb +21 -0
  80. data/lib/plutonium/engine/validator.rb +30 -0
  81. data/lib/plutonium/engine.rb +25 -0
  82. data/lib/plutonium/helpers/assets_helper.rb +73 -20
  83. data/lib/plutonium/helpers/form_helper.rb +1 -3
  84. data/lib/plutonium/interaction/README.md +369 -0
  85. data/lib/plutonium/interaction/base.rb +75 -0
  86. data/lib/plutonium/interaction/concerns/presentable.rb +61 -0
  87. data/lib/plutonium/interaction/concerns/workflow_dsl.rb +82 -0
  88. data/lib/plutonium/interaction/outcome.rb +129 -0
  89. data/lib/plutonium/interaction/response/base.rb +63 -0
  90. data/lib/plutonium/interaction/response/null.rb +33 -0
  91. data/lib/plutonium/interaction/response/redirect.rb +30 -0
  92. data/lib/plutonium/interaction/response/render.rb +28 -0
  93. data/lib/plutonium/lib/bit_flags.rb +70 -9
  94. data/lib/plutonium/lib/overlayed_hash.rb +86 -0
  95. data/lib/plutonium/lib/smart_cache.rb +171 -0
  96. data/lib/plutonium/models/has_cents.rb +170 -0
  97. data/lib/plutonium/{pkg/base.rb → package/engine.rb} +10 -2
  98. data/lib/plutonium/{application → portal}/controller.rb +3 -11
  99. data/lib/plutonium/{application → portal}/dynamic_controllers.rb +4 -4
  100. data/lib/plutonium/portal/engine.rb +15 -0
  101. data/lib/plutonium/railtie.rb +35 -15
  102. data/lib/plutonium/reloader.rb +71 -29
  103. data/lib/plutonium/resource/controller.rb +51 -34
  104. data/lib/plutonium/resource/controllers/authorizable.rb +128 -0
  105. data/lib/plutonium/{core → resource}/controllers/crud_actions.rb +23 -22
  106. data/lib/plutonium/resource/controllers/defineable.rb +26 -0
  107. data/lib/plutonium/{core → resource}/controllers/interactive_actions.rb +12 -12
  108. data/lib/plutonium/resource/controllers/presentable.rb +41 -0
  109. data/lib/plutonium/resource/controllers/queryable.rb +44 -0
  110. data/lib/plutonium/resource/definition.rb +6 -0
  111. data/lib/plutonium/resource/policy.rb +25 -13
  112. data/lib/plutonium/resource/query_object.rb +50 -51
  113. data/lib/plutonium/resource/record.rb +6 -89
  114. data/lib/plutonium/resource/register.rb +82 -0
  115. data/lib/plutonium/routing/mapper_extensions.rb +1 -1
  116. data/lib/plutonium/routing/resource_registration.rb +1 -1
  117. data/lib/plutonium/routing/route_set_extensions.rb +6 -18
  118. data/lib/plutonium/ui/action_button.rb +125 -0
  119. data/lib/plutonium/ui/breadcrumbs.rb +163 -0
  120. data/lib/plutonium/ui/component/base.rb +13 -0
  121. data/lib/plutonium/ui/component/behaviour.rb +38 -0
  122. data/lib/plutonium/ui/component/kit.rb +31 -0
  123. data/lib/plutonium/ui/component/methods.rb +54 -0
  124. data/lib/plutonium/ui/display/base.rb +25 -0
  125. data/lib/plutonium/ui/display/component/association.rb +26 -0
  126. data/lib/plutonium/ui/display/resource.rb +77 -0
  127. data/lib/plutonium/ui/display/theme.rb +27 -0
  128. data/lib/plutonium/ui/dyna_frame/content.rb +20 -0
  129. data/lib/plutonium/ui/empty_card.rb +20 -0
  130. data/lib/plutonium/ui/form/base.rb +37 -0
  131. data/lib/plutonium/ui/form/resource.rb +75 -0
  132. data/lib/plutonium/ui/form/theme.rb +42 -0
  133. data/lib/plutonium/ui/page/base.rb +112 -0
  134. data/lib/plutonium/ui/page/edit.rb +23 -0
  135. data/lib/plutonium/ui/page/index.rb +27 -0
  136. data/lib/plutonium/ui/page/new.rb +23 -0
  137. data/lib/plutonium/ui/page/show.rb +27 -0
  138. data/lib/plutonium/ui/page_header.rb +49 -0
  139. data/lib/plutonium/ui/table/base.rb +13 -0
  140. data/lib/plutonium/ui/table/components/pagy_info.rb +70 -0
  141. data/lib/plutonium/ui/table/components/pagy_page_info.rb +70 -0
  142. data/lib/plutonium/ui/table/components/pagy_pagination.rb +105 -0
  143. data/lib/plutonium/ui/table/components/scopes_bar.rb +136 -0
  144. data/lib/plutonium/ui/table/components/search_bar.rb +158 -0
  145. data/lib/plutonium/ui/table/display_theme.rb +21 -0
  146. data/lib/plutonium/ui/table/resource.rb +98 -0
  147. data/lib/plutonium/ui/table/theme.rb +35 -0
  148. data/lib/plutonium/ui.rb +9 -0
  149. data/lib/plutonium/version.rb +5 -1
  150. data/lib/plutonium.rb +53 -26
  151. data/package-lock.json +19 -22
  152. data/package.json +4 -4
  153. data/sig/.keep +0 -0
  154. data/src/css/plutonium.css +15 -0
  155. data/tailwind.options.js +11 -3
  156. metadata +220 -81
  157. data/lib/generators/pu/core/install/templates/app/presenters/resource_presenter.rb.tt +0 -2
  158. data/lib/generators/pu/core/install/templates/app/query_objects/resource_query_object.rb.tt +0 -2
  159. data/lib/generators/pu/pkg/feature/templates/app/query_objects/resource_query_object.rb.tt +0 -4
  160. data/lib/plutonium/concerns/resource_validatable.rb +0 -34
  161. data/lib/plutonium/config.rb +0 -9
  162. data/lib/plutonium/core/controllers/base.rb +0 -101
  163. data/lib/plutonium/core/controllers/presentable.rb +0 -65
  164. data/lib/plutonium/core/controllers/queryable.rb +0 -28
  165. data/lib/plutonium/pkg/app.rb +0 -35
  166. data/lib/plutonium/pkg/concerns/resource_validatable.rb +0 -36
  167. data/lib/plutonium/pkg/feature.rb +0 -18
  168. data/lib/plutonium/policy/initializer.rb +0 -22
  169. data/lib/plutonium/policy/scope.rb +0 -19
  170. data/lib/plutonium/pundit/context.rb +0 -18
  171. data/lib/plutonium/pundit/policy_finder.rb +0 -25
  172. data/lib/plutonium/resource/policy_context.rb +0 -5
  173. data/lib/plutonium/resource_register.rb +0 -83
  174. data/lib/plutonium/smart_cache.rb +0 -151
  175. data/sig/plutonium.rbs +0 -12
  176. /data/app/views/{application → plutonium}/_flash.html.erb +0 -0
  177. /data/app/views/{application → plutonium}/_flash_alerts.html.erb +0 -0
  178. /data/app/views/{application → plutonium}/_flash_toasts.html.erb +0 -0
  179. /data/lib/generators/pu/pkg/{app/templates/app/views/package → package/templates}/.keep +0 -0
  180. /data/lib/generators/pu/pkg/{feature → package}/templates/app/interactions/resource_interaction.rb.tt +0 -0
  181. /data/lib/generators/pu/pkg/{feature → package}/templates/app/models/resource_record.rb.tt +0 -0
  182. /data/lib/generators/pu/pkg/{feature → package}/templates/app/policies/resource_policy.rb.tt +0 -0
  183. /data/lib/generators/pu/pkg/{feature → package}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  184. /data/lib/generators/pu/pkg/{feature → package}/templates/lib/engine.rb.tt +0 -0
  185. /data/lib/generators/pu/pkg/{app → portal}/templates/app/policies/resource_policy.rb.tt +0 -0
  186. /data/lib/generators/pu/pkg/{app → portal}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  187. /data/lib/generators/pu/pkg/{app → portal}/templates/app/query_objects/resource_query_object.rb.tt +0 -0
  188. /data/lib/generators/pu/pkg/{feature/templates → portal/templates/app/views/package}/.keep +0 -0
  189. /data/lib/generators/pu/pkg/{app → portal}/templates/config/routes.rb.tt +0 -0
  190. /data/lib/generators/pu/pkg/{app → portal}/templates/lib/engine.rb.tt +0 -0
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Lib
5
+ # The SmartCache module provides flexible caching mechanisms for classes and objects,
6
+ # allowing for both inline caching and method-level memoization.
7
+ #
8
+ # This module is designed to optimize performance by caching results
9
+ # when class caching is enabled (typically in production),
10
+ # while ensuring fresh results when caching is disabled (typically in development).
11
+ #
12
+ # This implementation is thread-safe.
13
+ #
14
+ # @example Including SmartCache in a class
15
+ # class MyClass
16
+ # include Plutonium::Lib::SmartCache
17
+ #
18
+ # def my_method(arg)
19
+ # cache_unless_reloading("my_method_#{arg}") { expensive_operation(arg) }
20
+ # end
21
+ #
22
+ # def another_method(arg)
23
+ # # Method implementation
24
+ # end
25
+ # memoize_unless_reloading :another_method
26
+ # end
27
+ module SmartCache
28
+ extend ::ActiveSupport::Concern
29
+
30
+ # Class-level thread-local storage for force caching (useful for testing)
31
+ thread_mattr_accessor :force_caching
32
+
33
+ included do
34
+ mattr_accessor :_smart_cache, instance_writer: false, default: Concurrent::Map.new
35
+ end
36
+
37
+ private
38
+
39
+ # Caches the result of the given block unless class caching is disabled.
40
+ #
41
+ # @param cache_key [String] A unique key to identify the cached result
42
+ # @yield The block whose result will be cached
43
+ # @return [Object] The result of the block, either freshly computed or from cache
44
+ #
45
+ # @example Using cache_unless_reloading inline
46
+ # def fetch_user_data(user_id)
47
+ # cache_unless_reloading("user_data_#{user_id}") do
48
+ # UserDataService.fetch(user_id)
49
+ # end
50
+ # end
51
+ def cache_unless_reloading(cache_key, &block)
52
+ return yield unless should_smart_cache?
53
+
54
+ self.class._smart_cache.compute_if_absent(cache_key.to_sym) { 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
+ def flush_smart_cache(keys = nil)
68
+ self.class.flush_smart_cache(keys)
69
+ end
70
+
71
+ # Determines whether caching should be performed based on the current Rails configuration
72
+ # or the force_caching flag.
73
+ #
74
+ # @return [Boolean] true if caching should be performed, false otherwise
75
+ def should_smart_cache?
76
+ Plutonium::Lib::SmartCache.force_caching.nil? ? Rails.application.config.cache_classes : Plutonium::Lib::SmartCache.force_caching
77
+ end
78
+
79
+ class_methods do
80
+ # Memoizes the result of the specified method unless class caching is disabled.
81
+ #
82
+ # @param method_name [Symbol] The name of the method to memoize
83
+ # @return [void]
84
+ #
85
+ # @example Memoizing a method
86
+ # class User
87
+ # include Plutonium::Lib::SmartCache
88
+ #
89
+ # def expensive_full_name_calculation
90
+ # # Complex name calculation
91
+ # end
92
+ # memoize_unless_reloading :expensive_full_name_calculation
93
+ # end
94
+ def memoize_unless_reloading(method_name)
95
+ return if method_defined?(:"#{method_name}_without_memoization")
96
+
97
+ original_method = instance_method(method_name)
98
+ alias_method :"#{method_name}_without_memoization", method_name
99
+
100
+ define_method(method_name) do |*args|
101
+ if should_smart_cache?
102
+ cache = self.class._smart_cache.compute_if_absent(method_name.to_sym) { Concurrent::Map.new }
103
+ if args.empty?
104
+ cache.compute_if_absent(:no_args) { original_method.bind_call(self) }
105
+ else
106
+ cache.compute_if_absent(args.hash.to_s) { original_method.bind_call(self, *args) }
107
+ end
108
+ else
109
+ original_method.bind_call(self, *args)
110
+ end
111
+ end
112
+ end
113
+
114
+ # Flushes the smart cache for the specified keys or all keys if none are specified.
115
+ #
116
+ # @param keys [Array<Symbol, String>, Symbol, String] The cache key(s) to flush
117
+ # @return [void]
118
+ #
119
+ # @example Flushing specific cache keys
120
+ # flush_smart_cache([:user_data, :product_list])
121
+ #
122
+ # @example Flushing all cache keys
123
+ # flush_smart_cache
124
+ def flush_smart_cache(keys = nil)
125
+ keys = Array(keys).map(&:to_sym)
126
+ if keys.present?
127
+ keys.each { |key| _smart_cache.delete(key) }
128
+ else
129
+ _smart_cache.clear
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ # Configuration:
136
+ # The caching behavior is controlled by the Rails configuration option config.cache_classes
137
+ # or can be overridden using the SmartCache.force_caching flag:
138
+ #
139
+ # - When false (typical in development):
140
+ # - Classes are reloaded on each request.
141
+ # - cache_unless_reloading always yields fresh results.
142
+ # - memoize_unless_reloading always calls the original method.
143
+ #
144
+ # - When true (typical in production):
145
+ # - Classes are cached.
146
+ # - cache_unless_reloading uses cached results.
147
+ # - memoize_unless_reloading uses memoized results, caching for each unique set of arguments.
148
+ #
149
+ # Best Practices:
150
+ # - Use meaningful and unique cache keys to avoid collisions.
151
+ # - Be mindful of memory usage, especially with large cached results.
152
+ # - Consider cache expiration strategies for long-running processes.
153
+ # - Use cache_unless_reloading for fine-grained control within methods.
154
+ # - Use memoize_unless_reloading for entire methods, especially those with expensive computations.
155
+ #
156
+ # Thread Safety:
157
+ # - This implementation is thread-safe.
158
+ # - It uses Concurrent::Map from the concurrent-ruby gem for thread-safe caching.
159
+ #
160
+ # Testing:
161
+ # - In your test environment, you can control caching behavior explicitly using SmartCache.force_caching.
162
+ # - Example:
163
+ # describe "MyClass" do
164
+ # before { Plutonium::Lib::SmartCache.force_caching = true }
165
+ # after { Plutonium::Lib::SmartCache.force_caching = nil }
166
+ # it "caches results" do
167
+ # # Test caching behavior
168
+ # end
169
+ # end
170
+ end
171
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Models
5
+ # HasCents module provides functionality to handle monetary values stored as cents
6
+ # and expose them as decimal values. It also ensures that validations applied to
7
+ # the cents attribute are inherited by the decimal attribute.
8
+ #
9
+ # @example Usage
10
+ # class Product < ApplicationRecord
11
+ # include Plutonium::Models::HasCents
12
+ #
13
+ # has_cents :price_cents
14
+ # has_cents :cost_cents, name: :wholesale_price, rate: 1000
15
+ # has_cents :quantity_cents, name: :quantity, rate: 1
16
+ # has_cents :total_cents, suffix: "value"
17
+ #
18
+ # validates :price_cents, numericality: { greater_than_or_equal_to: 0 }
19
+ # end
20
+ #
21
+ # @example Basic Usage
22
+ # product = Product.new(price: 10.99)
23
+ #
24
+ # product.price_cents #=> 1099
25
+ # product.price #=> 10.99
26
+ #
27
+ # product.wholesale_price = 5.5
28
+ # product.cost_cents #=> 5500
29
+ #
30
+ # product.quantity = 3
31
+ # product.quantity_cents #=> 3
32
+ #
33
+ # @example Truncation
34
+ # product.price = 10.991
35
+ # product.price_cents #=> 1099
36
+ #
37
+ # product.price = 10.995
38
+ # product.price_cents #=> 1099
39
+ #
40
+ # product.price = 10.999
41
+ # product.price_cents #=> 1099
42
+ #
43
+ # product.total_value = 100.50
44
+ # product.total_cents #=> 10050
45
+ #
46
+ # @example Validation Inheritance
47
+ # product = Product.new(price: -10.99)
48
+ # product.valid? #=> false
49
+ # product.errors[:price_cents] #=> ["must be greater than or equal to 0"]
50
+ # product.errors[:price] #=> ["is invalid"]
51
+ #
52
+ # @example Reflection
53
+ # Product.has_cents_attributes
54
+ # #=> {
55
+ # # price_cents: { name: :price, rate: 100 },
56
+ # # cost_cents: { name: :wholesale_price, rate: 1000 },
57
+ # # quantity_cents: { name: :quantity, rate: 1 },
58
+ # # total_cents: { name: :total_value, rate: 100 }
59
+ # # }
60
+ #
61
+ # Product.has_cents_attribute?(:price_cents) #=> true
62
+ # Product.has_cents_attribute?(:name) #=> false
63
+ # Product.has_cents_attributes[:cost_cents] #=> {name: :wholesale_price, rate: 1000}
64
+ #
65
+ # @note This module automatically handles validation propagation. If a validation error
66
+ # is applied to the cents attribute, the decimal attribute will be marked as invalid.
67
+ #
68
+ # @note The module uses BigDecimal for internal calculations to ensure precision
69
+ # in monetary operations.
70
+ #
71
+ # @see ClassMethods#has_cents for details on setting up attributes
72
+ module HasCents
73
+ extend ActiveSupport::Concern
74
+
75
+ included do
76
+ class_attribute :has_cents_attributes, instance_writer: false, default: {}
77
+ end
78
+
79
+ module ClassMethods
80
+ # # Inherit validations from cents attribute to decimal attribute
81
+ # def validate(*args, &block)
82
+ # options = args.extract_options!
83
+ # Array(options[:attributes]).each do |attribute|
84
+ # attribute = attribute.to_sym
85
+ # if has_cents_attribute?(attribute)
86
+ # decimal_attribute = has_cents_attributes[attribute][:name]
87
+ # options[:attributes] += [decimal_attribute]
88
+ # args = args.map do |validator|
89
+ # if validator.respond_to?(:attributes)
90
+ # validator.instance_variable_set(:@attributes, validator.attributes + [decimal_attribute])
91
+ # _validators[decimal_attribute] << validator
92
+ # validator
93
+ # end
94
+ # end
95
+ # end
96
+ # end
97
+
98
+ # super(*args, options, &block)
99
+ # end
100
+ # Defines getter and setter methods for a monetary value stored as cents,
101
+ # and ensures validations are applied to both cents and decimal attributes.
102
+ #
103
+ # @param cents_name [Symbol] The name of the attribute storing the cents value.
104
+ # @param name [Symbol, nil] The name for the generated methods. If nil, it's derived from cents_name.
105
+ # @param rate [Integer] The conversion rate from the decimal value to cents (default: 100).
106
+ # This represents how many cents are in one unit of the decimal value.
107
+ # For example:
108
+ # - rate: 100 for dollars/cents (1 dollar = 100 cents)
109
+ # - rate: 1000 for dollars/mils (1 dollar = 1000 mils)
110
+ # - rate: 1 for a whole number representation
111
+ # @param suffix [String] The suffix to append to the cents_name if name is not provided (default: "amount").
112
+ #
113
+ # @example Standard currency (dollars and cents)
114
+ # has_cents :price_cents
115
+ #
116
+ # @example Custom rate for a different currency division
117
+ # has_cents :amount_cents, name: :cost, rate: 1000
118
+ #
119
+ # @example Whole number storage without decimal places
120
+ # has_cents :quantity_cents, name: :quantity, rate: 1
121
+ #
122
+ # @example Using custom suffix
123
+ # has_cents :total_cents, suffix: "value"
124
+ def has_cents(cents_name, name: nil, rate: 100, suffix: "amount")
125
+ cents_name = cents_name.to_sym
126
+ name ||= cents_name.to_s.gsub(/_cents$/, "")
127
+ name = name.to_sym
128
+ name = (name == cents_name) ? :"#{cents_name}_#{suffix}" : name
129
+
130
+ self.has_cents_attributes = has_cents_attributes.merge(
131
+ cents_name => {name: name, rate: rate}
132
+ )
133
+
134
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
135
+ # Getter method for the decimal representation of the cents value.
136
+ #
137
+ # @return [BigDecimal, nil] The decimal value or nil if cents_name is not present.
138
+ def #{name}
139
+ #{cents_name}.to_d / #{rate} if #{cents_name}.present?
140
+ end
141
+
142
+ # Setter method for the decimal representation of the cents value.
143
+ #
144
+ # @param value [Numeric, nil] The decimal value to be set.
145
+ def #{name}=(value)
146
+ self.#{cents_name} = if value.present?
147
+ (BigDecimal(value.to_s) * #{rate}).to_i
148
+ end
149
+ end
150
+
151
+ # Mark decimal field as invalid if cents field is not valid
152
+ after_validation do
153
+ next unless errors[#{cents_name.inspect}].present?
154
+
155
+ errors.add(#{name.inspect}, :invalid)
156
+ end
157
+ RUBY
158
+ end
159
+
160
+ # Checks if a given attribute is defined with has_cents
161
+ #
162
+ # @param attribute [Symbol] The attribute to check
163
+ # @return [Boolean] true if the attribute is defined with has_cents, false otherwise
164
+ def has_cents_attribute?(attribute)
165
+ has_cents_attributes.key?(attribute.to_sym)
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -1,6 +1,6 @@
1
1
  module Plutonium
2
- module Pkg
3
- module Base
2
+ module Package
3
+ module Engine
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
@@ -16,6 +16,14 @@ module Plutonium
16
16
  end
17
17
  add_view_paths_initializer.instance_variable_set(:@block, ->(app) {})
18
18
  end
19
+
20
+ initializer :append_migrations do |app|
21
+ unless app.root.to_s.match root.to_s
22
+ config.paths["db/migrate"].expanded.each do |expanded_path|
23
+ app.config.paths["db/migrate"] << expanded_path
24
+ end
25
+ end
26
+ end
19
27
  end
20
28
  end
21
29
  end
@@ -1,14 +1,10 @@
1
1
  module Plutonium
2
- module Application
2
+ module Portal
3
3
  module Controller
4
4
  extend ActiveSupport::Concern
5
- include Plutonium::Core::Controllers::Base
5
+ include Plutonium::Core::Controller
6
6
 
7
- included do
8
- helper_method :registered_resources
9
- end
10
-
11
- private
7
+ # private
12
8
 
13
9
  # # Menu Builder
14
10
  # def build_namespace_node(namespaces, resource, parent)
@@ -34,10 +30,6 @@ module Plutonium
34
30
  # def build_sidebar_menu
35
31
  # build_namespace_tree(current_engine.resource_register)
36
32
  # end
37
-
38
- def registered_resources
39
- current_engine.resource_register.resources
40
- end
41
33
  end
42
34
  end
43
35
  end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plutonium
4
- module Application
4
+ module Portal
5
5
  # DynamicControllers module provides functionality for dynamically creating controller classes
6
6
  # when they are missing in the current module's namespace.
7
7
  #
8
8
  # @example Usage
9
9
  # module MyApp
10
- # include Plutonium::Application::DynamicControllers
10
+ # include Plutonium::Portal::DynamicControllers
11
11
  # end
12
12
  #
13
13
  # # Now, MyApp::SomeController will be dynamically created if it doesn't exist,
@@ -74,7 +74,7 @@ module Plutonium
74
74
  log_controller_creation(const_full_name, parent_controller)
75
75
  const_full_name.constantize
76
76
  rescue => e
77
- Plutonium.logger.error "[plutonium] Failed to create dynamic controller: #{e.message}"
77
+ Plutonium.logger.error { "[plutonium] Failed to create dynamic controller: #{e.message}" }
78
78
  raise
79
79
  end
80
80
 
@@ -100,7 +100,7 @@ module Plutonium
100
100
  # @param const_full_name [String] The full name of the created controller
101
101
  # @param parent_controller [Class] The parent controller class
102
102
  def log_controller_creation(const_full_name, parent_controller)
103
- Plutonium.logger.info "[plutonium] Dynamically created #{const_full_name} < #{parent_controller}"
103
+ Plutonium.logger.info { "[plutonium] Dynamically created #{const_full_name} < #{parent_controller}" }
104
104
  end
105
105
  end
106
106
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Portal
5
+ module Engine
6
+ extend ActiveSupport::Concern
7
+ include Plutonium::Engine
8
+ include Plutonium::Package::Engine
9
+
10
+ included do
11
+ isolate_namespace to_s.deconstantize.constantize
12
+ end
13
+ end
14
+ end
15
+ end
@@ -8,18 +8,6 @@ module Plutonium
8
8
  # This Railtie sets up configurations, initializers, and tasks for Plutonium
9
9
  # to work seamlessly within a Rails environment.
10
10
  class Railtie < Rails::Railtie
11
- # Configuration options for Plutonium
12
- config.plutonium = ActiveSupport::OrderedOptions.new
13
- config.plutonium.cache_discovery = !Rails.env.development?
14
- config.plutonium.enable_hotreload = Rails.env.development?
15
-
16
- # Asset configuration
17
- config.plutonium.assets = ActiveSupport::OrderedOptions.new
18
- config.plutonium.assets.logo = "plutonium.png"
19
- config.plutonium.assets.favicon = "plutonium.ico"
20
- config.plutonium.assets.stylesheet = "plutonium.css"
21
- config.plutonium.assets.script = "plutonium.min.js"
22
-
23
11
  # Assets to be precompiled
24
12
  #
25
13
  # If you don't want to precompile Plutonium's assets (eg. because you're using webpack),
@@ -33,6 +21,14 @@ module Plutonium
33
21
  plutonium.css plutonium.png plutonium.ico
34
22
  ].freeze
35
23
 
24
+ initializer "plutonium.base" do
25
+ Rails.application.class.include Plutonium::Engine
26
+ end
27
+
28
+ initializer "plutonium.deprecator" do |app|
29
+ app.deprecators[:plutonium] = Plutonium.deprecator
30
+ end
31
+
36
32
  initializer "plutonium.assets" do
37
33
  setup_asset_pipeline if Rails.application.config.respond_to?(:assets)
38
34
  end
@@ -46,7 +42,7 @@ module Plutonium
46
42
  end
47
43
 
48
44
  initializer "plutonium.asset_server" do
49
- setup_development_asset_server if Plutonium.development?
45
+ setup_development_asset_server if Plutonium.configuration.development?
50
46
  end
51
47
 
52
48
  initializer "plutonium.view_components_capture_compat" do
@@ -57,13 +53,22 @@ module Plutonium
57
53
  extend_action_dispatch
58
54
  end
59
55
 
56
+ initializer "plutonium.active_record_extensions" do
57
+ extend_active_record
58
+ end
59
+
60
+ initializer "plutonium.phlexi_themes" do
61
+ setup_phlexi_themes
62
+ end
63
+
60
64
  rake_tasks do
61
65
  load "tasks/create_rodauth_admin.rake"
62
66
  end
63
67
 
64
68
  config.after_initialize do
65
- Plutonium::Reloader.start! if Rails.application.config.plutonium.enable_hotreload
66
- Plutonium::ZEITWERK_LOADER.eager_load if Rails.env.production?
69
+ Plutonium::Reloader.start! if Plutonium.configuration.enable_hotreload
70
+ Plutonium::Loader.eager_load if Rails.env.production?
71
+ ActionPolicy::PerThreadCache.enabled = !Rails.env.local?
67
72
  end
68
73
 
69
74
  private
@@ -95,10 +100,25 @@ module Plutonium
95
100
  )
96
101
  end
97
102
 
103
+ def setup_phlexi_themes
104
+ Rails.application.config.to_prepare do
105
+ Phlexi::Form::Theme.instance = Plutonium::UI::Form::Theme.instance
106
+ Phlexi::Display::Theme.instance = Plutonium::UI::Display::Theme.instance
107
+ Phlexi::Table::Theme.instance = Plutonium::UI::Table::Theme.instance
108
+ Phlexi::Table::DisplayTheme.instance = Plutonium::UI::Table::DisplayTheme.instance
109
+ end
110
+ end
111
+
98
112
  def extend_action_dispatch
99
113
  ActionDispatch::Routing::Mapper.prepend Plutonium::Routing::MapperExtensions
100
114
  ActionDispatch::Routing::RouteSet.prepend Plutonium::Routing::RouteSetExtensions
101
115
  Rails::Engine.include Plutonium::Routing::ResourceRegistration
102
116
  end
117
+
118
+ def extend_active_record
119
+ ActiveSupport.on_load(:active_record) do
120
+ include Plutonium::Resource::Record
121
+ end
122
+ end
103
123
  end
104
124
  end