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.
- checksums.yaml +4 -4
- data/lib/generators/pu/core/assets/assets_generator.rb +2 -2
- data/lib/generators/pu/core/install/install_generator.rb +0 -3
- data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +10 -0
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +19 -0
- data/lib/generators/pu/pkg/app/templates/config/routes.rb.tt +5 -6
- data/lib/generators/pu/pkg/app/templates/lib/engine.rb.tt +0 -4
- data/lib/generators/pu/res/conn/conn_generator.rb +4 -4
- data/lib/plutonium/application/controller.rb +1 -1
- data/lib/plutonium/application/dynamic_controllers.rb +108 -0
- data/lib/plutonium/auth/rodauth.rb +1 -1
- data/lib/plutonium/concerns/resource_validatable.rb +34 -0
- data/lib/plutonium/config/overlayed_hash.rb +86 -0
- data/lib/plutonium/configuration.rb +138 -0
- data/lib/plutonium/core/autodiscovery/association_renderer_discoverer.rb +1 -1
- data/lib/plutonium/core/autodiscovery/input_discoverer.rb +1 -1
- data/lib/plutonium/core/autodiscovery/renderer_discoverer.rb +1 -1
- data/lib/plutonium/core/controllers/entity_scoping.rb +84 -26
- data/lib/plutonium/helpers/assets_helper.rb +73 -20
- data/lib/plutonium/pkg/app.rb +3 -115
- data/lib/plutonium/pkg/concerns/resource_validatable.rb +36 -0
- data/lib/plutonium/railtie.rb +53 -30
- data/lib/plutonium/reloader.rb +66 -24
- data/lib/plutonium/resource/controller.rb +1 -1
- data/lib/plutonium/resource_register.rb +83 -0
- data/lib/plutonium/routing/mapper_extensions.rb +127 -0
- data/lib/plutonium/routing/resource_registration.rb +16 -0
- data/lib/plutonium/routing/route_set_extensions.rb +132 -0
- data/lib/plutonium/smart_cache.rb +151 -0
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium.rb +41 -27
- data/sig/.keep +0 -0
- metadata +13 -4
- data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +0 -270
- data/sig/plutonium.rbs +0 -12
data/lib/plutonium/reloader.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "active_support/notifications"
|
4
4
|
|
5
5
|
module Plutonium
|
6
|
+
# Reloader class for Plutonium
|
7
|
+
#
|
8
|
+
# This class is responsible for managing the reloading of Plutonium components
|
9
|
+
# and related files during development.
|
6
10
|
class Reloader
|
7
11
|
class << self
|
12
|
+
# Start the reloader
|
13
|
+
#
|
14
|
+
# @return [void]
|
8
15
|
def start!
|
9
16
|
puts "=> [plutonium] starting reloader"
|
10
17
|
|
11
18
|
ActiveSupport::Notifications.instrument("plutonium.reloader.start") do
|
12
|
-
# Task code here
|
13
19
|
@listener&.stop
|
14
20
|
@listener = initialize_listener
|
15
21
|
end
|
@@ -17,34 +23,46 @@ module Plutonium
|
|
17
23
|
|
18
24
|
private
|
19
25
|
|
26
|
+
# Initialize the file listener
|
27
|
+
#
|
28
|
+
# @return [Listen::Listener, nil] the initialized listener or nil if no paths to watch
|
20
29
|
def initialize_listener
|
21
30
|
require "listen"
|
22
31
|
|
23
32
|
reload_paths = gather_reload_paths
|
24
|
-
return
|
33
|
+
return if reload_paths.empty?
|
25
34
|
|
26
|
-
|
35
|
+
Listen.to(*reload_paths, only: /\.rb$/) { |modified, added, removed|
|
27
36
|
handle_file_changes(modified, added, removed)
|
28
|
-
|
29
|
-
listener.start
|
30
|
-
listener
|
37
|
+
}.tap(&:start)
|
31
38
|
end
|
32
39
|
|
40
|
+
# Gather paths to be watched for changes
|
41
|
+
#
|
42
|
+
# @return [Array<String>] list of paths to watch
|
33
43
|
def gather_reload_paths
|
34
44
|
reload_paths = []
|
35
45
|
|
36
|
-
if Plutonium.development?
|
37
|
-
reload_paths
|
38
|
-
|
39
|
-
|
46
|
+
if Plutonium.configuration.development?
|
47
|
+
reload_paths.concat([
|
48
|
+
Plutonium.lib_root.to_s,
|
49
|
+
Plutonium.root.join("app", "views", "components").to_s,
|
50
|
+
Plutonium.root.join("config", "initializers").to_s
|
51
|
+
])
|
40
52
|
end
|
41
53
|
|
42
|
-
packages_dir = Rails.root.join("packages
|
54
|
+
packages_dir = Rails.root.join("packages").to_s
|
43
55
|
reload_paths << packages_dir if File.directory?(packages_dir)
|
44
56
|
|
45
57
|
reload_paths
|
46
58
|
end
|
47
59
|
|
60
|
+
# Handle file changes detected by the listener
|
61
|
+
#
|
62
|
+
# @param modified [Array<String>] list of modified files
|
63
|
+
# @param added [Array<String>] list of added files
|
64
|
+
# @param removed [Array<String>] list of removed files
|
65
|
+
# @return [void]
|
48
66
|
def handle_file_changes(modified, added, removed)
|
49
67
|
(modified + added).each do |file|
|
50
68
|
Plutonium.logger.debug "[plutonium] change detected: #{file}"
|
@@ -62,46 +80,70 @@ module Plutonium
|
|
62
80
|
end
|
63
81
|
end
|
64
82
|
|
83
|
+
# Check if the file is within the packages directory
|
84
|
+
#
|
85
|
+
# @param file [String] path to the file
|
86
|
+
# @return [Boolean] true if the file is within the packages directory
|
65
87
|
def file_starts_with_packages_dir?(file)
|
66
|
-
|
67
|
-
file.starts_with?(packages_dir)
|
88
|
+
file.start_with?(Rails.root.join("packages").to_s)
|
68
89
|
end
|
69
90
|
|
91
|
+
# Handle changes to files within the packages directory
|
92
|
+
#
|
93
|
+
# @param file [String] path to the changed file
|
94
|
+
# @param added [Array<String>] list of added files
|
95
|
+
# @return [void]
|
70
96
|
def handle_package_file_changes(file, added)
|
71
97
|
return if added.include?(file)
|
72
98
|
|
73
99
|
case File.basename(file)
|
74
100
|
when "engine.rb"
|
75
101
|
reload_engine_and_routes(file)
|
76
|
-
else
|
77
|
-
# Non-engine package files are reloaded by Rails automatically
|
78
102
|
end
|
79
103
|
end
|
80
104
|
|
105
|
+
# Reload engine and routes
|
106
|
+
#
|
107
|
+
# @param file [String] path to the engine file
|
108
|
+
# @return [void]
|
81
109
|
def reload_engine_and_routes(file)
|
82
110
|
Plutonium.logger.debug "[plutonium] reloading: engine+routes"
|
83
111
|
load file
|
84
112
|
Rails.application.reload_routes!
|
85
113
|
end
|
86
114
|
|
115
|
+
# Reload the framework and file
|
116
|
+
#
|
117
|
+
# @param file [String] path to the file
|
118
|
+
# @return [void]
|
87
119
|
def reload_framework_and_file(file)
|
88
|
-
# # Ensure that the file loads correctly before we do any reloading
|
89
|
-
# load file
|
90
|
-
|
91
120
|
Plutonium.logger.debug "[plutonium] reloading: app+framework"
|
92
121
|
Rails.application.reloader.reload!
|
93
122
|
Plutonium::ZEITWERK_LOADER.reload
|
94
|
-
|
123
|
+
reload_components
|
124
|
+
end
|
125
|
+
|
126
|
+
# Reload components
|
127
|
+
#
|
128
|
+
# @return [void]
|
129
|
+
def reload_components
|
95
130
|
Object.send(:remove_const, "PlutoniumUi")
|
96
131
|
load Plutonium.root.join("app", "views", "components", "base.rb")
|
97
|
-
# # Ensure files that do not contain constants are loaded again e.g. initializers
|
98
|
-
# load file
|
99
132
|
end
|
100
133
|
|
134
|
+
# Reload a single file
|
135
|
+
#
|
136
|
+
# @param file [String] path to the file
|
137
|
+
# @return [Boolean] true if the file was successfully loaded
|
101
138
|
def reload_file(file)
|
102
|
-
load
|
139
|
+
load(file)
|
103
140
|
end
|
104
141
|
|
142
|
+
# Log reload failure
|
143
|
+
#
|
144
|
+
# @param file [String] path to the file that failed to reload
|
145
|
+
# @param error [StandardError] the error that occurred during reloading
|
146
|
+
# @return [void]
|
105
147
|
def log_reload_failure(file, error)
|
106
148
|
Plutonium.logger.error "\n[plutonium] reloading failed\n\n#{error.message}\n"
|
107
149
|
end
|
@@ -123,7 +123,7 @@ module Plutonium
|
|
123
123
|
|
124
124
|
@current_parent ||= begin
|
125
125
|
parent_route_key = parent_route_param.to_s.gsub(/_id$/, "").to_sym
|
126
|
-
parent_class = current_engine.
|
126
|
+
parent_class = current_engine.resource_register.route_key_lookup[parent_route_key]
|
127
127
|
parent_scope = parent_class.from_path_param(params[parent_route_param])
|
128
128
|
parent_scope = parent_scope.associated_with(current_scoped_entity) if scoped_to_entity?
|
129
129
|
parent_scope.first!
|
@@ -0,0 +1,83 @@
|
|
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
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plutonium
|
4
|
+
module Routing
|
5
|
+
# MapperExtensions module provides additional functionality for route mapping in Plutonium applications.
|
6
|
+
#
|
7
|
+
# This module extends the functionality of Rails' routing mapper to support Plutonium-specific features,
|
8
|
+
# such as resource registration and custom route materialization.
|
9
|
+
#
|
10
|
+
# @example Usage in a Rails routes file
|
11
|
+
# Blorgh::Engine.routes.draw do
|
12
|
+
# register_resource SomeModel
|
13
|
+
# end
|
14
|
+
module MapperExtensions
|
15
|
+
# Registers a resource for routing and sets up associated routes.
|
16
|
+
#
|
17
|
+
# @param resource [Class] The resource class to be registered.
|
18
|
+
# @param options [Hash] Additional options for resource registration.
|
19
|
+
# @yield An optional block for additional resource configuration.
|
20
|
+
# @return [void]
|
21
|
+
def register_resource(resource, options = {}, &)
|
22
|
+
route_config = route_set.register_resource(resource, &)
|
23
|
+
define_resource_routes(route_config, resource)
|
24
|
+
resource_route_concern_names << route_config[:concern_name]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# @return [Array<Symbol>] Names of resource route concerns.
|
30
|
+
def resource_route_concern_names
|
31
|
+
@resource_route_concern_names ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets up shared concerns for interactive resource actions.
|
35
|
+
#
|
36
|
+
# @return [void]
|
37
|
+
def setup_shared_resource_concerns
|
38
|
+
concern :interactive_resource_actions do
|
39
|
+
define_member_interactive_actions
|
40
|
+
define_collection_interactive_actions
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Materializes all registered resource routes.
|
45
|
+
#
|
46
|
+
# @return [void]
|
47
|
+
def materialize_resource_routes
|
48
|
+
engine = route_set.engine
|
49
|
+
scope_params = determine_scope_params(engine)
|
50
|
+
|
51
|
+
scope scope_params[:name], scope_params[:options] do
|
52
|
+
concerns resource_route_concern_names.sort
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [ActionDispatch::Routing::RouteSet] The current route set.
|
57
|
+
def route_set
|
58
|
+
@set
|
59
|
+
end
|
60
|
+
|
61
|
+
# Defines routes for a registered resource.
|
62
|
+
#
|
63
|
+
# @param route_config [Hash] Configuration for the resource routes.
|
64
|
+
# @param resource [Class] The resource class.
|
65
|
+
# @return [void]
|
66
|
+
def define_resource_routes(route_config, resource)
|
67
|
+
concern route_config[:concern_name] do
|
68
|
+
resources route_config[:route_name], **route_config[:route_options] do
|
69
|
+
instance_exec(&route_config[:block]) if route_config[:block]
|
70
|
+
define_nested_resource_routes(resource)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Defines nested resource routes for a given resource.
|
76
|
+
#
|
77
|
+
# @param resource [Class] The parent resource class.
|
78
|
+
# @return [void]
|
79
|
+
def define_nested_resource_routes(resource)
|
80
|
+
nested_configs = route_set.resource_route_config_for(*resource.has_many_association_routes)
|
81
|
+
nested_configs.each do |nested_config|
|
82
|
+
resources nested_config[:route_name], **nested_config[:route_options] do
|
83
|
+
instance_exec(&nested_config[:block]) if nested_config[:block]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Defines member-level interactive actions.
|
89
|
+
#
|
90
|
+
# @return [void]
|
91
|
+
def define_member_interactive_actions
|
92
|
+
member do
|
93
|
+
get "record_actions/:interactive_action", action: :begin_interactive_resource_record_action,
|
94
|
+
as: :interactive_resource_record_action
|
95
|
+
post "record_actions/:interactive_action", action: :commit_interactive_resource_record_action
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Defines collection-level interactive actions.
|
100
|
+
#
|
101
|
+
# @return [void]
|
102
|
+
def define_collection_interactive_actions
|
103
|
+
collection do
|
104
|
+
get "collection_actions/:interactive_action", action: :begin_interactive_resource_collection_action,
|
105
|
+
as: :interactive_resource_collection_action
|
106
|
+
post "collection_actions/:interactive_action", action: :commit_interactive_resource_collection_action
|
107
|
+
|
108
|
+
get "recordless_actions/:interactive_action", action: :begin_interactive_resource_recordless_action,
|
109
|
+
as: :interactive_resource_recordless_action
|
110
|
+
post "recordless_actions/:interactive_action", action: :commit_interactive_resource_recordless_action
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Determines the scope parameters based on the engine configuration.
|
115
|
+
#
|
116
|
+
# @param engine [Class] The current engine.
|
117
|
+
# @return [Hash] Scope name and options.
|
118
|
+
def determine_scope_params(engine)
|
119
|
+
scoped_entity_param_key = engine.scoped_entity_param_key if engine.scoped_entity_strategy == :path
|
120
|
+
{
|
121
|
+
name: scoped_entity_param_key.present? ? ":#{scoped_entity_param_key}" : "",
|
122
|
+
options: scoped_entity_param_key.present? ? {as: scoped_entity_param_key} : {}
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plutonium
|
4
|
+
module Routing
|
5
|
+
# The ResourceRegistration module provides functionality for registering and managing resources
|
6
|
+
module ResourceRegistration
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
def resource_register
|
11
|
+
@resource_register ||= Plutonium::ResourceRegister.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plutonium
|
4
|
+
module Routing
|
5
|
+
# RouteSetExtensions module provides additional functionality for route management in Plutonium applications.
|
6
|
+
#
|
7
|
+
# This module extends the functionality of Rails' routing system to support Plutonium-specific features,
|
8
|
+
# such as resource registration and custom route drawing.
|
9
|
+
#
|
10
|
+
# @example Usage in a Rails application
|
11
|
+
# Blorgh::Engine.routes.draw do
|
12
|
+
# register_resource SomeModel
|
13
|
+
# end
|
14
|
+
module RouteSetExtensions
|
15
|
+
# Clears all registered resources and route configurations.
|
16
|
+
#
|
17
|
+
# This method should be called when you want to reset all registered resources
|
18
|
+
# and start with a clean slate for route definition.
|
19
|
+
#
|
20
|
+
# @return [void]
|
21
|
+
def clear!
|
22
|
+
resource_route_config_lookup.clear
|
23
|
+
engine.resource_register.clear
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
# Draws routes with additional Plutonium-specific setup and resource materialization.
|
28
|
+
#
|
29
|
+
# @param block [Proc] The block containing route definitions.
|
30
|
+
# @return [void]
|
31
|
+
# @yield Executes the given block in the context of route drawing.
|
32
|
+
def draw(&block)
|
33
|
+
if supported_engine?
|
34
|
+
ActiveSupport::Notifications.instrument("plutonium.resource_routes.draw", app: engine.to_s) do
|
35
|
+
super do
|
36
|
+
setup_shared_resource_concerns
|
37
|
+
instance_exec(&block)
|
38
|
+
materialize_resource_routes
|
39
|
+
end
|
40
|
+
end
|
41
|
+
else
|
42
|
+
super(&block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Registers a resource for routing.
|
47
|
+
#
|
48
|
+
# @param resource [Class] The resource class to be registered.
|
49
|
+
# @yield An optional block for additional resource configuration.
|
50
|
+
# @return [Hash] The configuration for the registered resource.
|
51
|
+
# @raise [ArgumentError] If the engine doesn't support Plutonium::Pkg::App.
|
52
|
+
def register_resource(resource, &)
|
53
|
+
validate_engine!
|
54
|
+
engine.resource_register.register(resource)
|
55
|
+
|
56
|
+
route_name = resource.model_name.plural
|
57
|
+
concern_name = :"#{route_name}_routes"
|
58
|
+
|
59
|
+
config = create_resource_config(resource, route_name, concern_name, &)
|
60
|
+
resource_route_config_lookup[route_name] = config
|
61
|
+
|
62
|
+
config
|
63
|
+
end
|
64
|
+
|
65
|
+
# Retrieves the route configuration for specified routes.
|
66
|
+
#
|
67
|
+
# @param routes [Array<Symbol>] The route names to fetch configurations for.
|
68
|
+
# @return [Array<Hash>] An array of route configurations.
|
69
|
+
def resource_route_config_for(*routes)
|
70
|
+
routes = Array(routes)
|
71
|
+
resource_route_config_lookup.slice(*routes).values
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the current engine for the routes.
|
75
|
+
#
|
76
|
+
# @return [Class] The engine class (Rails application or custom engine).
|
77
|
+
def engine
|
78
|
+
@engine ||= determine_engine
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# @return [Hash] A lookup table for resource route configurations.
|
84
|
+
def resource_route_config_lookup
|
85
|
+
@resource_route_config_lookup ||= {}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Validates that the current engine supports Plutonium features.
|
89
|
+
#
|
90
|
+
# @raise [ArgumentError] If the engine doesn't include Plutonium::Pkg::App.
|
91
|
+
# @return [void]
|
92
|
+
def validate_engine!
|
93
|
+
raise ArgumentError, "#{engine} must include Plutonium::Pkg::App to register resources" unless supported_engine?
|
94
|
+
end
|
95
|
+
|
96
|
+
# Checks if the current engine supports Plutonium features.
|
97
|
+
#
|
98
|
+
# @return [Boolean] True if the engine includes Plutonium::Pkg::App, false otherwise.
|
99
|
+
def supported_engine?
|
100
|
+
engine.include?(Plutonium::Pkg::App)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Determines the appropriate engine based on the current scope.
|
104
|
+
#
|
105
|
+
# @return [Class] The determined engine class.
|
106
|
+
def determine_engine
|
107
|
+
engine_module = default_scope&.fetch(:module)
|
108
|
+
engine_module.present? ? "#{engine_module.camelize}::Engine".constantize : Rails.application.class
|
109
|
+
end
|
110
|
+
|
111
|
+
# Creates a resource configuration hash.
|
112
|
+
#
|
113
|
+
# @param resource_name [String] The name of the resource.
|
114
|
+
# @param route_name [String] The pluralized name for routes.
|
115
|
+
# @param concern_name [Symbol] The name of the concern for this resource.
|
116
|
+
# @yield An optional block for additional resource configuration.
|
117
|
+
# @return [Hash] The complete resource configuration.
|
118
|
+
def create_resource_config(resource, route_name, concern_name, &block)
|
119
|
+
{
|
120
|
+
route_name: route_name,
|
121
|
+
concern_name: concern_name,
|
122
|
+
route_options: {
|
123
|
+
controller: resource.to_s.pluralize.underscore,
|
124
|
+
path: resource.model_name.collection,
|
125
|
+
concerns: %i[interactive_resource_actions]
|
126
|
+
},
|
127
|
+
block: block
|
128
|
+
}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,151 @@
|
|
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/lib/plutonium/version.rb
CHANGED