plutonium 0.13.3 → 0.14.1

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/pu/core/assets/assets_generator.rb +2 -2
  3. data/lib/generators/pu/core/install/install_generator.rb +0 -3
  4. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +10 -0
  5. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +19 -0
  6. data/lib/generators/pu/pkg/app/templates/config/routes.rb.tt +5 -6
  7. data/lib/generators/pu/pkg/app/templates/lib/engine.rb.tt +0 -4
  8. data/lib/generators/pu/res/conn/conn_generator.rb +4 -4
  9. data/lib/plutonium/application/controller.rb +1 -1
  10. data/lib/plutonium/application/dynamic_controllers.rb +108 -0
  11. data/lib/plutonium/auth/rodauth.rb +1 -1
  12. data/lib/plutonium/concerns/resource_validatable.rb +34 -0
  13. data/lib/plutonium/config/overlayed_hash.rb +86 -0
  14. data/lib/plutonium/configuration.rb +138 -0
  15. data/lib/plutonium/core/autodiscovery/association_renderer_discoverer.rb +1 -1
  16. data/lib/plutonium/core/autodiscovery/input_discoverer.rb +1 -1
  17. data/lib/plutonium/core/autodiscovery/renderer_discoverer.rb +1 -1
  18. data/lib/plutonium/core/controllers/entity_scoping.rb +84 -26
  19. data/lib/plutonium/helpers/assets_helper.rb +73 -20
  20. data/lib/plutonium/pkg/app.rb +3 -115
  21. data/lib/plutonium/pkg/concerns/resource_validatable.rb +36 -0
  22. data/lib/plutonium/railtie.rb +53 -30
  23. data/lib/plutonium/reloader.rb +66 -24
  24. data/lib/plutonium/resource/controller.rb +1 -1
  25. data/lib/plutonium/resource_register.rb +83 -0
  26. data/lib/plutonium/routing/mapper_extensions.rb +127 -0
  27. data/lib/plutonium/routing/resource_registration.rb +16 -0
  28. data/lib/plutonium/routing/route_set_extensions.rb +132 -0
  29. data/lib/plutonium/smart_cache.rb +151 -0
  30. data/lib/plutonium/version.rb +1 -1
  31. data/lib/plutonium.rb +41 -27
  32. data/sig/.keep +0 -0
  33. metadata +13 -4
  34. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +0 -270
  35. data/sig/plutonium.rbs +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d53d01dfc30698f6bc1b3493db5509baab84b07fa49b4539ff18f6b033336982
4
- data.tar.gz: 45b8cf3a4d0f2b022ce5ad495d519724175bc62eda2c783851bf4b5d44e2e958
3
+ metadata.gz: fba393cb403832162cff127ae398c28751984ad8070292ea2e4bc0b01e726678
4
+ data.tar.gz: deeca5afea70265663e0343d2b23fb7f0522fcacbd8c6f79fe864b6b0b2fd7a7
5
5
  SHA512:
6
- metadata.gz: a59752cd033c146756b2a330f7e42bc7ae526570cc9851c26fec6daabbf5fe41794c0069b0a21f4c5a9ea4425f5ddb4a7d90d1df89097db700231bd119dcb9c0
7
- data.tar.gz: 1e64998368c580cd5a52a0c22c1e7df3a883e963a32a2f65b5e6f390a9b3e2d9cda19f568091a0c9cf2e0bea06f66ab459e25c7eb8672d86880aa83008b1c844
6
+ metadata.gz: aad819f43bb0a0707d02649fc169b9f4fa9e3bac860bb7fb01530d4f630a36e025eaea4f6e1502c94cb50b04c139735ad46eb9bef1de91d27ebfaf1e6461f589
7
+ data.tar.gz: 9c3bdb87605c5fa2339285b8a0ae6347016f40b72fbbfcdf93cadaf21281a0a1ce07dfb2630bc006a404e2265eefbc20dc238272c6e5091b8e6eaf871e5f892d
@@ -37,8 +37,8 @@ module Pu
37
37
  registerControllers(application)
38
38
  EOT
39
39
 
40
- environment "config.plutonium.assets.stylesheet = \"application\""
41
- environment "config.plutonium.assets.script = \"application\""
40
+ configure_plutonium "config.assets.stylesheet = \"application\""
41
+ configure_plutonium "config.assets.script = \"application\""
42
42
  end
43
43
  end
44
44
  end
@@ -31,9 +31,6 @@ module Pu
31
31
  def setup_app
32
32
  directory "config"
33
33
  directory "app"
34
-
35
- environment "# config.plutonium.assets.favicon = \"favicon.ico\""
36
- environment "# config.plutonium.assets.logo = \"logo.png\""
37
34
  end
38
35
 
39
36
  def eject_views
@@ -1,5 +1,15 @@
1
1
  # Configure plutonium
2
2
 
3
+ Plutonium.configure do |config|
4
+ config.load_defaults 1.0
5
+
6
+ # config.assets.logo = "logo.png"
7
+ # Configure plutonium above this
8
+ end
9
+
3
10
  Rails.application.config.to_prepare do
4
11
  # Register components here
12
+
13
+ # e.g
14
+ # Plutonium::Core::Fields::Renderers::Factory.map_type :mapped_collection, to: Fields::Renderers::MappedCollectionRenderer
5
15
  end
@@ -272,6 +272,25 @@ module PlutoniumGenerators
272
272
  end
273
273
  end
274
274
 
275
+ def configure_plutonium(data = nil, options = {})
276
+ data ||= yield if block_given?
277
+
278
+ log :configure_plutonium, data
279
+
280
+ in_root do
281
+ replace_existing = ->(file, data) do
282
+ gsub_file file, Regexp.new(".*#{data.split("=").first.strip}.*=.*\n"), data, verbose: false
283
+ end
284
+
285
+ data = optimize_indentation(data, 2)
286
+ file = "config/initializers/plutonium.rb"
287
+ replace_existing.call file, data
288
+ break if File.read(file).match? regexify_config(data)
289
+
290
+ inject_into_file file, data, before: /.*# Configure plutonium above this.*/, verbose: false
291
+ end
292
+ end
293
+
275
294
  #
276
295
  # Set a config in the application generator block
277
296
  # If the configuration exists already, it is updated
@@ -1,11 +1,10 @@
1
- <%= package_name %>::Engine.draw_custom_routes do
2
- # draw custom routes here
3
-
1
+ <%= package_name %>::Engine.routes.draw do
4
2
  root to: "dashboard#index"
5
- end
6
3
 
7
- # draw routes for registered resources
8
- <%= package_name %>::Engine.draw_resource_routes
4
+ # register resources above.
5
+
6
+ # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
7
+ end
9
8
 
10
9
  # mount our app
11
10
  Rails.application.routes.draw do
@@ -4,11 +4,7 @@ module <%= package_name %>
4
4
  # add concerns above.
5
5
 
6
6
  config.after_initialize do
7
- initialize_register!
8
-
9
7
  # add directives above.
10
-
11
- # register resources above.
12
8
  end
13
9
  end
14
10
  end
@@ -15,13 +15,13 @@ module Pu
15
15
 
16
16
  def start
17
17
  source_feature = select_feature msg: "Select source feature"
18
- source_module = (source_feature == "main_app") ? "ResourceRecord" : "#{source_feature.classify}::ResourceRecord"
18
+ source_module = (source_feature == "main_app") ? "ResourceRecord" : "#{source_feature.camelize}::ResourceRecord"
19
19
 
20
20
  Plutonium.eager_load_rails!
21
21
  available_resources = source_module.constantize.descendants.map(&:to_s).sort
22
22
  selected_resources = prompt.multi_select("Select resources", available_resources)
23
23
 
24
- @app_namespace = select_app.classify
24
+ @app_namespace = select_app.camelize
25
25
 
26
26
  selected_resources.each do |resource|
27
27
  @resource_class = resource
@@ -31,8 +31,8 @@ module Pu
31
31
  # template "app/presenters/resource_presenter.rb", "packages/#{package_namespace}/app/presenters/#{package_namespace}/#{resource.underscore}_presenter.rb"
32
32
  # template "app/query_objects/resource_query_object.rb", "packages/#{package_namespace}/app/query_objects/#{package_namespace}/#{resource.underscore}_query_object.rb"
33
33
 
34
- insert_into_file "packages/#{package_namespace}/lib/engine.rb",
35
- indent("register_resource ::#{resource}\n", 6),
34
+ insert_into_file "packages/#{package_namespace}/config/routes.rb",
35
+ indent("register_resource ::#{resource}\n", 2),
36
36
  before: /.*# register resources above.*/
37
37
  end
38
38
  rescue => e
@@ -36,7 +36,7 @@ module Plutonium
36
36
  # end
37
37
 
38
38
  def registered_resources
39
- current_engine.resource_register
39
+ current_engine.resource_register.resources
40
40
  end
41
41
  end
42
42
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Application
5
+ # DynamicControllers module provides functionality for dynamically creating controller classes
6
+ # when they are missing in the current module's namespace.
7
+ #
8
+ # @example Usage
9
+ # module MyApp
10
+ # include Plutonium::Application::DynamicControllers
11
+ # end
12
+ #
13
+ # # Now, MyApp::SomeController will be dynamically created if it doesn't exist,
14
+ # # inheriting from ::SomeController and including MyApp::Concerns::Controller
15
+ #
16
+ # @note This module is designed to be included in a parent module that represents
17
+ # a namespace for controllers.
18
+ module DynamicControllers
19
+ extend ActiveSupport::Concern
20
+
21
+ class_methods do
22
+ # Handles missing constant lookup, specifically for controller classes
23
+ #
24
+ # @param const_name [Symbol] The name of the missing constant
25
+ # @return [Class, nil] The dynamically created controller class if applicable, otherwise nil
26
+ # @raise [NameError] If the constant is not a controller and cannot be found
27
+ def const_missing(const_name)
28
+ if const_name.to_s.end_with?("Controller")
29
+ create_dynamic_controller(const_name)
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Creates a dynamic controller class
38
+ #
39
+ # @param const_name [Symbol] The name of the controller class to create
40
+ # @return [Class] The newly created controller class
41
+ # @raise [NameError] If the parent controller or concerns module cannot be found
42
+ def create_dynamic_controller(const_name)
43
+ parent_controller = "::#{const_name}".constantize
44
+ current_module = name
45
+ const_full_name = "#{current_module}::#{const_name}"
46
+
47
+ klass = Class.new(parent_controller) do
48
+ # YARD documentation for the dynamically created controller
49
+ # @!parse
50
+ # class DynamicController < ParentController
51
+ # include ParentModule::Concerns::Controller
52
+ # # Dynamically created controller for handling actions in the parent module
53
+ # #
54
+ # # This controller is created at runtime to handle requests within the parent namespace.
55
+ # # It inherits from the corresponding top-level controller (e.g., ::ClientsController for ParentModule::ClientsController).
56
+ # # It also includes ParentModule::Concerns::Controller.
57
+ # #
58
+ # # @note This class is created dynamically and may not have explicit method definitions.
59
+ # end
60
+ end
61
+
62
+ # Define the constant in the global namespace
63
+ define_nested_constant(const_full_name, klass)
64
+
65
+ # Include required modules
66
+ concerns_module = "#{current_module}::Concerns::Controller".constantize
67
+ route_helpers_module = "#{AdminApp}::Engine".constantize.routes.url_helpers
68
+ klass.include route_helpers_module
69
+ klass.include concerns_module
70
+
71
+ # # Run the load hooks to include necessary modules and configurations
72
+ # ActiveSupport.run_load_hooks(:action_controller, klass)
73
+
74
+ log_controller_creation(const_full_name, parent_controller)
75
+ const_full_name.constantize
76
+ rescue => e
77
+ Plutonium.logger.error "[plutonium] Failed to create dynamic controller: #{e.message}"
78
+ raise
79
+ end
80
+
81
+ # Defines a constant in the global namespace, handling nested modules
82
+ #
83
+ # @param const_full_name [String] The full module name
84
+ # @param value [Object] The value to assign to the constant
85
+ def define_nested_constant(const_full_name, value)
86
+ names = const_full_name.split("::")
87
+ const_name = names.pop
88
+
89
+ names.inject(Object) do |mod, name|
90
+ if mod.const_defined?(name)
91
+ mod.const_get(name)
92
+ else
93
+ mod.const_set(name, Module.new)
94
+ end
95
+ end.const_set(const_name, value)
96
+ end
97
+
98
+ # Logs the creation of a dynamic controller
99
+ #
100
+ # @param const_full_name [String] The full name of the created controller
101
+ # @param parent_controller [Class] The parent controller class
102
+ def log_controller_creation(const_full_name, parent_controller)
103
+ Plutonium.logger.info "[plutonium] Dynamically created #{const_full_name} < #{parent_controller}"
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -28,7 +28,7 @@ module Plutonium
28
28
  end
29
29
 
30
30
  define_singleton_method(:to_s) { "Plutonium::Auth::Rodauth(:#{name})" }
31
- define_singleton_method(:inspect) { "Plutonium::Auth::Rodautht(:#{name})" }
31
+ define_singleton_method(:inspect) { "Plutonium::Auth::Rodauth(:#{name})" }
32
32
  RUBY
33
33
  mod
34
34
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Concerns
5
+ # Provides methods for validating Plutonium resources
6
+ module ResourceValidatable
7
+ extend ActiveSupport::Concern
8
+
9
+ # Custom error class for invalid resources
10
+ class InvalidResourceError < StandardError; end
11
+
12
+ private
13
+
14
+ # Validates if a given resource is a valid Plutonium::Resource::Record
15
+ #
16
+ # @param resource [Object] The resource to validate
17
+ # @raise [InvalidResourceError] If the resource is not valid
18
+ # @return [void]
19
+ def validate_resource!(resource)
20
+ unless valid_resource?(resource)
21
+ raise InvalidResourceError, "#{resource} is not a valid Plutonium::Resource::Record"
22
+ end
23
+ end
24
+
25
+ # Checks if a given resource is a valid Plutonium::Resource::Record
26
+ #
27
+ # @param resource [Object] The resource to check
28
+ # @return [Boolean] True if the resource is valid, false otherwise
29
+ def valid_resource?(resource)
30
+ resource.is_a?(Class) && resource.include?(Plutonium::Resource::Record)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Config
5
+ # OverlayedHash provides a hash-like structure that overlays values on top of a base hash.
6
+ #
7
+ # This class allows you to create a new hash-like object that uses a base hash for default values,
8
+ # but can be modified without affecting the original base hash. When a key is accessed, it first
9
+ # checks if the key exists in the overlay; if not, it falls back to the base hash.
10
+ #
11
+ # @example Usage
12
+ # base = { a: 1, b: 2 }
13
+ # overlay = OverlayedHash.new(base)
14
+ # overlay[:b] = 3
15
+ # overlay[:c] = 4
16
+ #
17
+ # overlay[:a] # => 1 (from base)
18
+ # overlay[:b] # => 3 (from overlay)
19
+ # overlay[:c] # => 4 (from overlay)
20
+ # base[:b] # => 2 (unchanged)
21
+ class OverlayedHash
22
+ # Initialize a new OverlayedHash with a base hash.
23
+ #
24
+ # @param base [Hash] The base hash to use for fallback values.
25
+ def initialize(base)
26
+ @base = base
27
+ @overlay = {}
28
+ end
29
+
30
+ # Retrieve a value from the overlay hash or the base hash.
31
+ #
32
+ # @param key The key to look up.
33
+ # @return The value associated with the key, or nil if not found.
34
+ def [](key)
35
+ @overlay.key?(key) ? @overlay[key] : @base[key]
36
+ end
37
+
38
+ # Set a value in the overlay hash.
39
+ #
40
+ # @param key The key to set.
41
+ # @param value The value to associate with the key.
42
+ def []=(key, value)
43
+ @overlay[key] = value
44
+ end
45
+
46
+ # Check if a key exists in either the overlay or base hash.
47
+ #
48
+ # @param key The key to check for.
49
+ # @return [Boolean] true if the key exists, false otherwise.
50
+ def key?(key)
51
+ @overlay.key?(key) || @base.key?(key)
52
+ end
53
+
54
+ # Enumerate over all keys in both the overlay and base hash.
55
+ #
56
+ # @yield [key] Gives each key to the block.
57
+ # @return [Enumerator] If no block is given.
58
+ def each_key
59
+ return to_enum(:each_key) unless block_given?
60
+
61
+ keys.each { |key| yield key }
62
+ end
63
+
64
+ # Retrieve all keys from both the overlay and base hash.
65
+ #
66
+ # @return [Array] An array of all unique keys.
67
+ def keys
68
+ (@overlay.keys + @base.keys).uniq
69
+ end
70
+
71
+ # Retrieve all values, prioritizing the overlay over the base.
72
+ #
73
+ # @return [Array] An array of values corresponding to all keys.
74
+ def values
75
+ keys.map { |key| self[key] }
76
+ end
77
+
78
+ # Convert the OverlayedHash to a regular Hash.
79
+ #
80
+ # @return [Hash] A new Hash with all keys and values from the OverlayedHash.
81
+ def to_h
82
+ keys.each_with_object({}) { |key, hash| hash[key] = self[key] }
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ # Configuration class for Plutonium module
5
+ #
6
+ # @example
7
+ # Plutonium.configure do |config|
8
+ # config.load_defaults 1.0
9
+ # config.development = true
10
+ # config.cache_discovery = false
11
+ # config.enable_hotreload = true
12
+ # config.assets.logo = "custom_logo.png"
13
+ # end
14
+ class Configuration
15
+ # @return [Boolean] whether Plutonium is in development mode
16
+ attr_accessor :development
17
+
18
+ # @return [Boolean] whether to cache discovery
19
+ attr_accessor :cache_discovery
20
+
21
+ # @return [Boolean] whether to enable hot reloading
22
+ attr_accessor :enable_hotreload
23
+
24
+ # @return [AssetConfiguration] asset configuration
25
+ attr_reader :assets
26
+
27
+ # @return [Float] the current defaults version
28
+ attr_reader :defaults_version
29
+
30
+ # Map of version numbers to their default configurations
31
+ VERSION_DEFAULTS = {
32
+ 1.0 => proc do |config|
33
+ # No changes for 1.0 yet as it's the current base configuration
34
+ end
35
+ # Add more version configurations here as needed
36
+ # 1.1 => proc do |config|
37
+ # config.some_new_setting = true
38
+ # end
39
+ }.freeze
40
+
41
+ # Initialize a new Configuration instance
42
+ #
43
+ # @note This method sets initial values
44
+ def initialize
45
+ @defaults_version = nil
46
+ @assets = AssetConfiguration.new
47
+
48
+ @development = parse_boolean_env("PLUTONIUM_DEV")
49
+ @cache_discovery = !Rails.env.development?
50
+ @enable_hotreload = Rails.env.development?
51
+ end
52
+
53
+ # Load default configuration for a specific version
54
+ #
55
+ # @param version [Float] the version to load defaults for
56
+ # @return [void]
57
+ def load_defaults(version)
58
+ available_versions = VERSION_DEFAULTS.keys.sort
59
+ applicable_versions = available_versions.select { |v| v <= version }
60
+
61
+ if applicable_versions.empty?
62
+ raise "No applicable defaults found for version #{version}."
63
+ end
64
+
65
+ applicable_versions.each do |v|
66
+ VERSION_DEFAULTS[v].call(self)
67
+ end
68
+
69
+ @defaults_version = applicable_versions.last
70
+ end
71
+
72
+ # whether Plutonium is in development mode
73
+ #
74
+ # @return [Boolean]
75
+ def development?
76
+ @development
77
+ end
78
+
79
+ private
80
+
81
+ # Parse boolean environment variable
82
+ #
83
+ # @param env_var [String] name of the environment variable
84
+ # @return [Boolean] parsed boolean value
85
+ def parse_boolean_env(env_var)
86
+ ActiveModel::Type::Boolean.new.cast(ENV[env_var]).present?
87
+ end
88
+
89
+ # Asset configuration for Plutonium
90
+ class AssetConfiguration
91
+ # @return [String] path to logo file
92
+ attr_accessor :logo
93
+
94
+ # @return [String] path to favicon file
95
+ attr_accessor :favicon
96
+
97
+ # @return [String] path to stylesheet file
98
+ attr_accessor :stylesheet
99
+
100
+ # @return [String] path to JavaScript file
101
+ attr_accessor :script
102
+
103
+ # Initialize a new AssetConfiguration instance with default values
104
+ def initialize
105
+ @logo = "plutonium.png"
106
+ @favicon = "plutonium.ico"
107
+ @stylesheet = "plutonium.css"
108
+ @script = "plutonium.min.js"
109
+ end
110
+ end
111
+ end
112
+
113
+ class << self
114
+ # Get the current configuration
115
+ #
116
+ # @return [Configuration] current configuration instance
117
+ def configuration
118
+ @configuration ||= Configuration.new
119
+ end
120
+
121
+ # Configure Plutonium
122
+ #
123
+ # @yield [config] Configuration instance
124
+ # @yieldparam config [Configuration] current configuration instance
125
+ # @return [void]
126
+ def configure
127
+ yield(configuration)
128
+ end
129
+
130
+ # Load default configuration for a specific version
131
+ #
132
+ # @param version [Float] the version to load defaults for
133
+ # @return [void]
134
+ def load_defaults(version)
135
+ configuration.load_defaults(version)
136
+ end
137
+ end
138
+ end
@@ -14,7 +14,7 @@ module Plutonium
14
14
  # If cache_discovery is enabled, use the class level cache that persists
15
15
  # between requests, otherwise use the instance one.
16
16
  def autodiscovery_association_renderer_cache
17
- if Rails.application.config.plutonium.cache_discovery
17
+ if Plutonium.configuration.cache_discovery
18
18
  self.class.autodiscovery_association_renderer_cache
19
19
  else
20
20
  @autodiscovery_association_renderer_cache ||= {}
@@ -14,7 +14,7 @@ module Plutonium
14
14
  # If cache_discovery is enabled, use the class level cache that persists
15
15
  # between requests, otherwise use the instance one.
16
16
  def autodiscovery_input_cache
17
- if Rails.application.config.plutonium.cache_discovery
17
+ if Plutonium.configuration.cache_discovery
18
18
  self.class.autodiscovery_input_cache
19
19
  else
20
20
  @autodiscovery_input_cache ||= {}
@@ -14,7 +14,7 @@ module Plutonium
14
14
  # If cache_discovery is enabled, use the class level cache that persists
15
15
  # between requests, otherwise use the instance one.
16
16
  def autodiscovery_renderer_cache
17
- if Rails.application.config.plutonium.cache_discovery
17
+ if Plutonium.configuration.cache_discovery
18
18
  self.class.autodiscovery_renderer_cache
19
19
  else
20
20
  @autodiscovery_renderer_cache ||= {}