plutonium 0.13.2 → 0.14.0
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/app/views/components/breadcrumbs/breadcrumbs_component.html.erb +1 -1
- 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/core/controllers/entity_scoping.rb +84 -26
- data/lib/plutonium/pkg/app.rb +3 -115
- data/lib/plutonium/pkg/concerns/resource_validatable.rb +36 -0
- data/lib/plutonium/railtie.rb +57 -22
- 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
- metadata +10 -3
- data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +0 -270
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a68eccc72cf54155fa9d9959a22b60d13ea434a4faf34221ec0314e5ab74124
|
4
|
+
data.tar.gz: 99c748e56b3b07eb7361f67887137430eef9cb35a3e0b3bd04c1de831ae68b04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ada4ff77eddc1daa695bd4ff96542f5f72b378ab7826c99f256faa089182c9084edd5db2433d044d6270df5714097e9e46add301631446ead9c1ae034ead0b9
|
7
|
+
data.tar.gz: e600eb51acf5873891b4c38b4541e1220c4e62f19eb74b9194c68203e73e0178d3e6ef74ee9437daf9dd27ced14b93ebe2807d3f4338b160e3de26dddd21691b
|
@@ -59,7 +59,7 @@
|
|
59
59
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
|
60
60
|
</svg>
|
61
61
|
<span class="ms-1 text-sm font-medium text-gray-500 md:ms-2 dark:text-gray-200">
|
62
|
-
<%= resource.persisted? ? display_name_of(resource) :
|
62
|
+
<%= resource.persisted? ? display_name_of(resource) : "Create" %>
|
63
63
|
</span>
|
64
64
|
</li>
|
65
65
|
<% end %>
|
@@ -1,11 +1,10 @@
|
|
1
|
-
<%= package_name %>::Engine.
|
2
|
-
# draw custom routes here
|
3
|
-
|
1
|
+
<%= package_name %>::Engine.routes.draw do
|
4
2
|
root to: "dashboard#index"
|
5
|
-
end
|
6
3
|
|
7
|
-
#
|
8
|
-
|
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
|
@@ -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.
|
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.
|
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}/
|
35
|
-
indent("register_resource ::#{resource}\n",
|
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
|
@@ -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::
|
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
|
@@ -1,6 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Plutonium
|
2
4
|
module Core
|
3
5
|
module Controllers
|
6
|
+
# EntityScoping module provides functionality for scoping controllers to specific entities.
|
7
|
+
#
|
8
|
+
# This module is designed to be included in controllers that need to operate within the context
|
9
|
+
# of a specific entity, such as a user's organization or a project.
|
10
|
+
#
|
11
|
+
# @example Usage in a controller
|
12
|
+
# class MyController < ApplicationController
|
13
|
+
# include Plutonium::Core::Controllers::EntityScoping
|
14
|
+
# end
|
4
15
|
module EntityScoping
|
5
16
|
extend ActiveSupport::Concern
|
6
17
|
|
@@ -9,70 +20,117 @@ module Plutonium
|
|
9
20
|
helper_method :current_scoped_entity
|
10
21
|
end
|
11
22
|
|
12
|
-
|
13
|
-
|
23
|
+
# Checks if the current engine is scoped to an entity.
|
24
|
+
#
|
25
|
+
# @return [Boolean] true if scoped to an entity, false otherwise
|
14
26
|
def scoped_to_entity?
|
15
27
|
current_engine.scoped_to_entity?
|
16
28
|
end
|
17
29
|
|
30
|
+
# Returns the strategy used for entity scoping.
|
31
|
+
#
|
32
|
+
# @return [Symbol] the scoping strategy
|
18
33
|
def scoped_entity_strategy
|
19
34
|
current_engine.scoped_entity_strategy
|
20
35
|
end
|
21
36
|
|
22
|
-
|
23
|
-
|
37
|
+
# Returns the parameter key used for entity scoping.
|
38
|
+
#
|
39
|
+
# @return [Symbol] the parameter key
|
40
|
+
# @raise [NotImplementedError] if not scoped to an entity
|
41
|
+
def scoped_entity_param_key
|
42
|
+
ensure_legal_entity_scoping_method_access!(__method__)
|
43
|
+
current_engine.scoped_entity_param_key
|
44
|
+
end
|
24
45
|
|
46
|
+
# Returns the class of the scoped entity.
|
47
|
+
#
|
48
|
+
# @return [Class] the scoped entity class
|
49
|
+
# @raise [NotImplementedError] if not scoped to an entity
|
50
|
+
def scoped_entity_class
|
51
|
+
ensure_legal_entity_scoping_method_access!(__method__)
|
25
52
|
current_engine.scoped_entity_class
|
26
53
|
end
|
27
54
|
|
28
|
-
|
29
|
-
ensure_legal_entity_scoping_method_access! :scoped_entity_param_key
|
30
|
-
|
31
|
-
current_engine.scoped_entity_param_key
|
32
|
-
end
|
55
|
+
private
|
33
56
|
|
57
|
+
# Returns the session key used to store the scoped entity.
|
58
|
+
#
|
59
|
+
# @return [Symbol] the session key
|
60
|
+
# @raise [NotImplementedError] if not scoped to an entity
|
34
61
|
def scoped_entity_session_key
|
35
|
-
ensure_legal_entity_scoping_method_access!
|
36
|
-
|
62
|
+
ensure_legal_entity_scoping_method_access!(__method__)
|
37
63
|
:"#{current_package.name.underscore}__scoped_entity_id"
|
38
64
|
end
|
39
65
|
|
66
|
+
# Returns the current scoped entity for the request.
|
67
|
+
#
|
68
|
+
# @return [ActiveRecord::Base, nil] the current scoped entity or nil if not found
|
69
|
+
# @raise [NotImplementedError] if not scoped to an entity or strategy is unknown
|
40
70
|
def current_scoped_entity
|
41
|
-
ensure_legal_entity_scoping_method_access!
|
42
|
-
|
71
|
+
ensure_legal_entity_scoping_method_access!(__method__)
|
43
72
|
return unless current_user.present?
|
44
73
|
|
45
|
-
@current_scoped_entity ||=
|
74
|
+
@current_scoped_entity ||= fetch_current_scoped_entity
|
75
|
+
end
|
76
|
+
|
77
|
+
# Fetches the current scoped entity based on the scoping strategy.
|
78
|
+
#
|
79
|
+
# @return [ActiveRecord::Base, nil] the current scoped entity or nil if not found
|
80
|
+
# @raise [NotImplementedError] if the scoping strategy is unknown
|
81
|
+
def fetch_current_scoped_entity
|
82
|
+
case scoped_entity_strategy
|
46
83
|
when :path
|
47
|
-
|
48
|
-
.associated_with(current_user)
|
49
|
-
.from_path_param(request.path_parameters[scoped_entity_param_key])
|
50
|
-
.first! # Raise NotFound if user does not have access to the entity or it does not exist
|
84
|
+
fetch_entity_from_path
|
51
85
|
when Symbol
|
52
|
-
send
|
86
|
+
send(scoped_entity_strategy)
|
53
87
|
else
|
54
|
-
raise NotImplementedError, "
|
88
|
+
raise NotImplementedError, "Unknown scoped entity strategy: #{scoped_entity_strategy.inspect}"
|
55
89
|
end
|
56
90
|
end
|
57
91
|
|
92
|
+
# Fetches the scoped entity from the path parameters.
|
93
|
+
#
|
94
|
+
# @return [ActiveRecord::Base] the scoped entity
|
95
|
+
# @raise [ActiveRecord::RecordNotFound] if the entity is not found or the user doesn't have access
|
96
|
+
def fetch_entity_from_path
|
97
|
+
scoped_entity_class
|
98
|
+
.associated_with(current_user)
|
99
|
+
.from_path_param(request.path_parameters[scoped_entity_param_key])
|
100
|
+
.first!
|
101
|
+
end
|
102
|
+
|
103
|
+
# Remembers the current scoped entity in the session.
|
104
|
+
#
|
105
|
+
# @return [void]
|
58
106
|
def remember_scoped_entity
|
59
107
|
return unless scoped_to_entity?
|
60
108
|
|
61
109
|
session[scoped_entity_session_key] = current_scoped_entity.to_global_id.to_s
|
62
110
|
end
|
63
111
|
|
112
|
+
# Retrieves the remembered scoped entity from the session.
|
113
|
+
#
|
114
|
+
# @return [ActiveRecord::Base, nil] the remembered scoped entity or nil if not found
|
115
|
+
# @raise [NotImplementedError] if not scoped to an entity
|
64
116
|
def remembered_scoped_entity
|
65
|
-
ensure_legal_entity_scoping_method_access!
|
66
|
-
|
67
|
-
@remembered_scoped_entity ||= GlobalID::Locator.locate session[scoped_entity_session_key]
|
117
|
+
ensure_legal_entity_scoping_method_access!(__method__)
|
118
|
+
@remembered_scoped_entity ||= GlobalID::Locator.locate(session[scoped_entity_session_key])
|
68
119
|
end
|
69
120
|
|
121
|
+
# Ensures that the method call is legal within the current scoping context.
|
122
|
+
#
|
123
|
+
# @param method [Symbol] the method being called
|
124
|
+
# @raise [NotImplementedError] if not scoped to an entity
|
70
125
|
def ensure_legal_entity_scoping_method_access!(method)
|
71
126
|
return if scoped_to_entity?
|
72
127
|
|
73
|
-
raise NotImplementedError,
|
74
|
-
|
75
|
-
|
128
|
+
raise NotImplementedError, <<~ERROR_MESSAGE
|
129
|
+
This request is not scoped to an entity.
|
130
|
+
|
131
|
+
Add the `scope_to_entity YourEntityRecord` directive in #{current_engine}
|
132
|
+
or implement #{self.class}##{method}
|
133
|
+
ERROR_MESSAGE
|
76
134
|
end
|
77
135
|
end
|
78
136
|
end
|
data/lib/plutonium/pkg/app.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Plutonium
|
4
4
|
module Pkg
|
@@ -11,10 +11,11 @@ module Plutonium
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class_methods do
|
14
|
+
include Plutonium::Concerns::ResourceValidatable
|
14
15
|
attr_reader :scoped_entity_class, :scoped_entity_strategy, :scoped_entity_param_key
|
15
16
|
|
16
17
|
def scope_to_entity(entity_class, strategy: :path, param_key: nil)
|
17
|
-
|
18
|
+
validate_resource! entity_class
|
18
19
|
|
19
20
|
@scoped_entity_class = entity_class
|
20
21
|
@scoped_entity_strategy = strategy
|
@@ -25,122 +26,9 @@ module Plutonium
|
|
25
26
|
scoped_entity_class.present?
|
26
27
|
end
|
27
28
|
|
28
|
-
def initialize_register!
|
29
|
-
# this exists solely to support hot reloads
|
30
|
-
# if the user has modified the register especially if they removed a registration, we have no way of telling
|
31
|
-
# so instead we start over
|
32
|
-
@resource_register = []
|
33
|
-
end
|
34
|
-
|
35
|
-
def register_resource(resource)
|
36
|
-
@resource_register.append resource
|
37
|
-
end
|
38
|
-
|
39
|
-
def resource_register
|
40
|
-
@resource_register || []
|
41
|
-
end
|
42
|
-
|
43
|
-
def registered_resource_route_key_lookup
|
44
|
-
@registered_resource_route_key_lookup = resource_register.map { |resource|
|
45
|
-
[resource.model_name.singular_route_key.to_sym, resource]
|
46
|
-
}.to_h
|
47
|
-
end
|
48
|
-
|
49
|
-
def draw_custom_routes(&block)
|
50
|
-
@custom_routes_block = block
|
51
|
-
end
|
52
|
-
|
53
|
-
def draw_resource_routes
|
54
|
-
ActiveSupport::Notifications.instrument("plutonium.app.draw_resource_routes", app: self.class.module_parent.to_s) do
|
55
|
-
draw_resource_routes_internal
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
29
|
def dom_id
|
60
30
|
module_parent_name.underscore.dasherize
|
61
31
|
end
|
62
|
-
|
63
|
-
private
|
64
|
-
|
65
|
-
def draw_resource_routes_internal
|
66
|
-
custom_routes_block = @custom_routes_block
|
67
|
-
registered_resources = resource_register
|
68
|
-
scoped_entity_param_key = self.scoped_entity_param_key if scoped_entity_strategy == :path
|
69
|
-
routes.draw do
|
70
|
-
shared_resource_concerns = [:interactive_resource_actions] # TODO: make this a config parameter
|
71
|
-
concern :interactive_resource_actions do
|
72
|
-
# these concerns power the interactive actions feature
|
73
|
-
member do
|
74
|
-
get "record_actions/:interactive_action", action: :begin_interactive_resource_record_action,
|
75
|
-
as: :interactive_resource_record_action
|
76
|
-
post "record_actions/:interactive_action", action: :commit_interactive_resource_record_action
|
77
|
-
end
|
78
|
-
|
79
|
-
collection do
|
80
|
-
get "collection_actions/:interactive_action", action: :begin_interactive_resource_collection_action,
|
81
|
-
as: :interactive_resource_collection_action
|
82
|
-
post "collection_actions/:interactive_action", action: :commit_interactive_resource_collection_action
|
83
|
-
|
84
|
-
get "recordless_actions/:interactive_action", action: :begin_interactive_resource_recordless_action,
|
85
|
-
as: :interactive_resource_recordless_action
|
86
|
-
post "recordless_actions/:interactive_action", action: :commit_interactive_resource_recordless_action
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
resource_route_names = []
|
91
|
-
resource_route_opts_lookup = {}
|
92
|
-
# for each of our registered resources, we are registering the routes required
|
93
|
-
registered_resources.each do |resource|
|
94
|
-
resource_name = resource.to_s # Deeply::Namespaced::ResourceModel
|
95
|
-
resource_controller = resource_name.pluralize.underscore # deeply/namespaced/resource_models
|
96
|
-
resource_route = resource.model_name.plural # deeply_namespaced_resource_models
|
97
|
-
resource_route_name = :"#{resource_route}_routes" # deeply_namespaced_resource_models_routes
|
98
|
-
|
99
|
-
resource_route_opts = {}
|
100
|
-
# rails is not smart enough to infer Deeply::Namespaced::ResourceModelsController from deeply_namespaced_resource_models
|
101
|
-
# since we are heavy on namespaces, we choose to be explicit to guarantee there is no confusion
|
102
|
-
resource_route_opts[:controller] = resource_controller
|
103
|
-
# using collection for path is much nicer than the alternative
|
104
|
-
# e.g. deeply/namespaced/resource_models vs deeply_namespaced_resource_models
|
105
|
-
resource_route_opts[:path] = resource.model_name.collection
|
106
|
-
resource_route_opts_lookup[resource_route] = resource_route_opts
|
107
|
-
|
108
|
-
# defining our resources with concerns allows us to defer materializing till later,
|
109
|
-
# ensuring that resource_route_opts_lookup is populated
|
110
|
-
concern resource_route_name do
|
111
|
-
resources resource_route, **resource_route_opts, concerns: shared_resource_concerns do
|
112
|
-
nested_resources_route_opts = resource_route_opts_lookup.slice(*resource.has_many_association_routes)
|
113
|
-
nested_resources_route_opts.each do |nested_resource_route, nested_resource_route_opts|
|
114
|
-
resources nested_resource_route, **nested_resource_route_opts, concerns: shared_resource_concerns
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
resource_route_names << resource_route_name
|
119
|
-
end
|
120
|
-
|
121
|
-
# materialize our routes using a scope
|
122
|
-
# if the app is scoped to an entity, ensure that the expected route param and url helper prefix are specified.
|
123
|
-
|
124
|
-
# path => /:entity/deeply/namespaced/resource_models/:deeply_namespaced_resource_model_id/
|
125
|
-
# helper => entity_deeply_namespaced_resource_models_path
|
126
|
-
scope_name = scoped_entity_param_key.present? ? ":#{scoped_entity_param_key}" : ""
|
127
|
-
|
128
|
-
# path => /deeply/namespaced/resource_models/:deeply_namespaced_resource_model_id/
|
129
|
-
# helper => deeply_namespaced_resource_models_path
|
130
|
-
scope_options = scoped_entity_param_key.present? ? {as: scoped_entity_param_key} : {}
|
131
|
-
|
132
|
-
scope scope_name, scope_options do
|
133
|
-
instance_exec(&custom_routes_block) if custom_routes_block.present?
|
134
|
-
# we have to reverse sort our resource routes in order to prevent routing conflicts
|
135
|
-
# e.g. /blogs/1 and blogs/comments cause an issue if Blog is registered before Blogs::Comment
|
136
|
-
# attempting to load blogs/comments routes to blogs/:id which fails with a 404 since BlogsController
|
137
|
-
# essentially performs a Blog.find('comments')
|
138
|
-
# since the route names for these 2 will be 'blogs' and 'blog_comments',
|
139
|
-
# reverse sorting ensures that blog_comments is registered first, preventing the issue described above
|
140
|
-
concerns resource_route_names.sort
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
32
|
end
|
145
33
|
end
|
146
34
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plutonium
|
4
|
+
module Pkg
|
5
|
+
module Concerns
|
6
|
+
# Provides methods for validating Plutonium resources
|
7
|
+
module ResourceValidatable
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
# Custom error class for invalid resources
|
11
|
+
class InvalidResourceError < StandardError; end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Validates if a given resource is a valid Plutonium::Resource::Record
|
16
|
+
#
|
17
|
+
# @param resource [Object] The resource to validate
|
18
|
+
# @raise [InvalidResourceError] If the resource is not valid
|
19
|
+
# @return [void]
|
20
|
+
def validate_resource!(resource)
|
21
|
+
unless valid_resource?(resource)
|
22
|
+
raise InvalidResourceError, "#{resource} is not a valid Plutonium::Resource::Record"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Checks if a given resource is a valid Plutonium::Resource::Record
|
27
|
+
#
|
28
|
+
# @param resource [Object] The resource to check
|
29
|
+
# @return [Boolean] True if the resource is valid, false otherwise
|
30
|
+
def valid_resource?(resource)
|
31
|
+
resource.is_a?(Class) && resource.include?(Plutonium::Resource::Record)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|