plutonium 0.13.3 → 0.14.1

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