plutonium 0.42.0 → 0.43.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/.claude/skills/plutonium-controller/SKILL.md +38 -1
- data/.claude/skills/plutonium-definition/SKILL.md +14 -0
- data/.claude/skills/plutonium-forms/SKILL.md +16 -1
- data/.claude/skills/plutonium-profile/SKILL.md +276 -0
- data/.claude/skills/plutonium-views/SKILL.md +23 -1
- data/CHANGELOG.md +36 -0
- data/app/assets/plutonium.css +1 -1
- data/app/views/plutonium/_resource_header.html.erb +6 -27
- data/app/views/plutonium/_resource_sidebar.html.erb +1 -2
- data/app/views/resource/_resource_details.rabl +3 -2
- data/app/views/resource/index.rabl +3 -2
- data/app/views/resource/show.rabl +3 -2
- data/docs/guides/user-profile.md +322 -0
- data/docs/reference/controller/index.md +38 -1
- data/docs/reference/definition/index.md +16 -0
- data/docs/reference/views/forms.md +15 -0
- data/docs/reference/views/index.md +23 -1
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/assets/assets_generator.rb +12 -0
- data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +11 -0
- data/lib/generators/pu/core/typespec/templates/common.tsp.tt +95 -0
- data/lib/generators/pu/core/typespec/templates/main.tsp.tt +27 -0
- data/lib/generators/pu/core/typespec/templates/main_multi.tsp.tt +25 -0
- data/lib/generators/pu/core/typespec/templates/model.tsp.tt +226 -0
- data/lib/generators/pu/core/typespec/typespec_generator.rb +342 -0
- data/lib/generators/pu/invites/USAGE +0 -1
- data/lib/generators/pu/invites/install_generator.rb +62 -15
- data/lib/generators/pu/invites/templates/db/migrate/create_user_invites.rb.tt +2 -2
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +2 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/welcome_controller.rb.tt +1 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/models/invites/user_invite.rb.tt +5 -5
- data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/signup.html.erb.tt +4 -4
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +1 -1
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +29 -0
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +6 -23
- data/lib/generators/pu/pkg/portal/portal_generator.rb +5 -1
- data/lib/generators/pu/profile/USAGE +59 -0
- data/lib/generators/pu/profile/concerns/profile_arguments.rb +27 -0
- data/lib/generators/pu/profile/conn/USAGE +33 -0
- data/lib/generators/pu/profile/conn_generator.rb +167 -0
- data/lib/generators/pu/profile/install_generator.rb +119 -0
- data/lib/generators/pu/profile/setup/USAGE +42 -0
- data/lib/generators/pu/profile/setup_generator.rb +73 -0
- data/lib/generators/pu/rodauth/account_generator.rb +2 -4
- data/lib/generators/pu/rodauth/install_generator.rb +2 -2
- data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
- data/lib/generators/pu/saas/api_client_generator.rb +0 -2
- data/lib/generators/pu/saas/membership_generator.rb +68 -19
- data/lib/generators/pu/saas/setup_generator.rb +7 -2
- data/lib/generators/pu/saas/user_generator.rb +0 -2
- data/lib/plutonium/auth/rodauth.rb +8 -0
- data/lib/plutonium/core/controller.rb +7 -4
- data/lib/plutonium/core/controllers/authorizable.rb +5 -1
- data/lib/plutonium/definition/base.rb +7 -0
- data/lib/plutonium/helpers/display_helper.rb +6 -0
- data/lib/plutonium/profile/security_section.rb +118 -0
- data/lib/plutonium/resource/controller.rb +17 -7
- data/lib/plutonium/resource/controllers/interactive_actions.rb +11 -25
- data/lib/plutonium/resource/controllers/presentable.rb +46 -3
- data/lib/plutonium/resource/record/associated_with.rb +7 -1
- data/lib/plutonium/routing/mapper_extensions.rb +18 -18
- data/lib/plutonium/routing/route_set_extensions.rb +23 -2
- data/lib/plutonium/ui/breadcrumbs.rb +111 -131
- data/lib/plutonium/ui/dyna_frame/content.rb +12 -2
- data/lib/plutonium/ui/form/resource.rb +26 -19
- data/lib/plutonium/ui/page/base.rb +14 -14
- data/lib/plutonium/ui/table/components/selection_column.rb +6 -2
- data/lib/plutonium/ui/table/resource.rb +3 -2
- data/lib/plutonium/version.rb +1 -1
- data/package.json +1 -1
- metadata +17 -3
- data/lib/generators/pu/rodauth/concerns/gem_helpers.rb +0 -19
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plutonium
|
|
4
|
+
module Profile
|
|
5
|
+
# Renders security settings links based on enabled Rodauth features.
|
|
6
|
+
class SecuritySection < Plutonium::UI::Component::Base
|
|
7
|
+
FEATURES = {
|
|
8
|
+
change_password: {
|
|
9
|
+
label: "Change Password",
|
|
10
|
+
description: "Update your account password",
|
|
11
|
+
icon: Phlex::TablerIcons::Key,
|
|
12
|
+
path_method: :change_password_path
|
|
13
|
+
},
|
|
14
|
+
change_login: {
|
|
15
|
+
label: "Change Email",
|
|
16
|
+
description: "Update your email address",
|
|
17
|
+
icon: Phlex::TablerIcons::Mail,
|
|
18
|
+
path_method: :change_login_path
|
|
19
|
+
},
|
|
20
|
+
otp: {
|
|
21
|
+
label: "Two-Factor Authentication",
|
|
22
|
+
description: "Add an extra layer of security",
|
|
23
|
+
icon: Phlex::TablerIcons::DeviceMobile,
|
|
24
|
+
path_method: :otp_setup_path
|
|
25
|
+
},
|
|
26
|
+
recovery_codes: {
|
|
27
|
+
label: "Recovery Codes",
|
|
28
|
+
description: "View or regenerate backup codes",
|
|
29
|
+
icon: Phlex::TablerIcons::FileCode,
|
|
30
|
+
path_method: :recovery_codes_path
|
|
31
|
+
},
|
|
32
|
+
webauthn: {
|
|
33
|
+
label: "Security Keys",
|
|
34
|
+
description: "Manage passkeys and security keys",
|
|
35
|
+
icon: Phlex::TablerIcons::Fingerprint,
|
|
36
|
+
path_method: :webauthn_setup_path
|
|
37
|
+
},
|
|
38
|
+
active_sessions: {
|
|
39
|
+
label: "Active Sessions",
|
|
40
|
+
description: "View and manage your sessions",
|
|
41
|
+
icon: Phlex::TablerIcons::DevicesCheck,
|
|
42
|
+
path_method: :active_sessions_path
|
|
43
|
+
},
|
|
44
|
+
close_account: {
|
|
45
|
+
label: "Close Account",
|
|
46
|
+
description: "Permanently delete your account",
|
|
47
|
+
icon: Phlex::TablerIcons::Trash,
|
|
48
|
+
path_method: :close_account_path,
|
|
49
|
+
danger: true
|
|
50
|
+
}
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
def view_template
|
|
54
|
+
div(class: "mt-8") do
|
|
55
|
+
render_section_header
|
|
56
|
+
render_feature_links
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def render_section_header
|
|
63
|
+
div(class: "mb-4") do
|
|
64
|
+
h2(class: "text-lg font-semibold text-[var(--pu-text)]") { "Security Settings" }
|
|
65
|
+
p(class: "text-sm text-[var(--pu-text-muted)]") { "Manage your account security" }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def render_feature_links
|
|
70
|
+
div(
|
|
71
|
+
class: "bg-[var(--pu-card-bg)] border border-[var(--pu-card-border)] rounded-[var(--pu-radius-lg)] divide-y divide-[var(--pu-border)]",
|
|
72
|
+
style: "box-shadow: var(--pu-shadow-sm)"
|
|
73
|
+
) do
|
|
74
|
+
enabled_features.each do |feature, config|
|
|
75
|
+
render_feature_link(feature, config)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def render_feature_link(feature, config)
|
|
81
|
+
path = helpers.rodauth.send(config[:path_method])
|
|
82
|
+
danger = config[:danger]
|
|
83
|
+
|
|
84
|
+
a(
|
|
85
|
+
href: path,
|
|
86
|
+
class: tokens(
|
|
87
|
+
"flex items-center gap-4 p-4 hover:bg-[var(--pu-surface-alt)] transition-colors first:rounded-t-[var(--pu-radius-lg)] last:rounded-b-[var(--pu-radius-lg)]",
|
|
88
|
+
danger ? "text-[var(--pu-text-danger)]" : "text-[var(--pu-text)]"
|
|
89
|
+
)
|
|
90
|
+
) do
|
|
91
|
+
# Icon
|
|
92
|
+
div(class: "flex-shrink-0") do
|
|
93
|
+
render config[:icon].new(class: "w-5 h-5")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Content
|
|
97
|
+
div(class: "flex-grow") do
|
|
98
|
+
div(class: "font-medium") { config[:label] }
|
|
99
|
+
div(class: "text-sm text-[var(--pu-text-muted)]") { config[:description] }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Arrow
|
|
103
|
+
div(class: "flex-shrink-0 text-[var(--pu-text-muted)]") do
|
|
104
|
+
render Phlex::TablerIcons::ChevronRight.new(class: "w-5 h-5")
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def enabled_features
|
|
110
|
+
FEATURES.select { |feature, _config| feature_enabled?(feature) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def feature_enabled?(feature)
|
|
114
|
+
helpers.rodauth.features.include?(feature)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -104,6 +104,13 @@ module Plutonium
|
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
# Returns true if current resource is registered as a singular route
|
|
108
|
+
# (e.g., `resource :profile` vs `resources :users`)
|
|
109
|
+
# @return [Boolean]
|
|
110
|
+
def singular_resource_context?
|
|
111
|
+
current_resource_route_config&.[](:route_type) == :resource
|
|
112
|
+
end
|
|
113
|
+
|
|
107
114
|
# Extracts the association name from the current nested route
|
|
108
115
|
# e.g., for route /posts/:post_id/nested_comments, returns :comments
|
|
109
116
|
# @return [Symbol, nil] The association name
|
|
@@ -242,14 +249,17 @@ module Plutonium
|
|
|
242
249
|
# Overrides entity scoping parameters
|
|
243
250
|
# @param [Hash] input_params The input parameters
|
|
244
251
|
def override_entity_scoping_params(input_params)
|
|
245
|
-
|
|
246
|
-
if input_params.key?(scoped_entity_param_key) || resource_class.method_defined?(:"#{scoped_entity_param_key}=")
|
|
247
|
-
input_params[scoped_entity_param_key] = current_scoped_entity
|
|
248
|
-
end
|
|
252
|
+
return unless scoped_to_entity?
|
|
249
253
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
# Use the detected association if available, otherwise fall back to param_key
|
|
255
|
+
assoc_name = scoped_entity_association || scoped_entity_param_key
|
|
256
|
+
|
|
257
|
+
if input_params.key?(assoc_name) || resource_class.method_defined?(:"#{assoc_name}=")
|
|
258
|
+
input_params[assoc_name] = current_scoped_entity
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if input_params.key?(:"#{assoc_name}_id") || resource_class.method_defined?(:"#{assoc_name}_id=")
|
|
262
|
+
input_params[:"#{assoc_name}_id"] = current_scoped_entity.id
|
|
253
263
|
end
|
|
254
264
|
end
|
|
255
265
|
|
|
@@ -29,12 +29,7 @@ module Plutonium
|
|
|
29
29
|
# GET /resources/1/record_actions/:interactive_action
|
|
30
30
|
def interactive_record_action
|
|
31
31
|
build_interactive_record_action_interaction
|
|
32
|
-
|
|
33
|
-
if helpers.current_turbo_frame == "remote_modal"
|
|
34
|
-
render layout: false, formats: [:html]
|
|
35
|
-
else
|
|
36
|
-
render :interactive_record_action, formats: [:html]
|
|
37
|
-
end
|
|
32
|
+
render :interactive_record_action, layout: modal_layout, formats: [:html]
|
|
38
33
|
end
|
|
39
34
|
|
|
40
35
|
# POST /resources/1/record_actions/:interactive_action
|
|
@@ -67,7 +62,7 @@ module Plutonium
|
|
|
67
62
|
end
|
|
68
63
|
else
|
|
69
64
|
format.any(:html, :turbo_stream) do
|
|
70
|
-
render :interactive_record_action, formats: [:html], status: :unprocessable_content
|
|
65
|
+
render :interactive_record_action, layout: modal_layout, formats: [:html], status: :unprocessable_content
|
|
71
66
|
end
|
|
72
67
|
format.any do
|
|
73
68
|
@errors = @interaction.errors
|
|
@@ -82,16 +77,7 @@ module Plutonium
|
|
|
82
77
|
def interactive_resource_action
|
|
83
78
|
skip_verify_current_authorized_scope!
|
|
84
79
|
build_interactive_resource_action_interaction
|
|
85
|
-
|
|
86
|
-
respond_to do |format|
|
|
87
|
-
format.any(:html, :turbo_stream) do
|
|
88
|
-
if helpers.current_turbo_frame == "remote_modal"
|
|
89
|
-
render layout: false, formats: [:html]
|
|
90
|
-
else
|
|
91
|
-
render :interactive_resource_action, formats: [:html]
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
80
|
+
render :interactive_resource_action, layout: modal_layout, formats: [:html]
|
|
95
81
|
end
|
|
96
82
|
|
|
97
83
|
# POST /resources/resource_actions/:interactive_action
|
|
@@ -125,7 +111,7 @@ module Plutonium
|
|
|
125
111
|
end
|
|
126
112
|
else
|
|
127
113
|
format.any(:html, :turbo_stream) do
|
|
128
|
-
render :interactive_resource_action, formats: [:html], status: :unprocessable_content
|
|
114
|
+
render :interactive_resource_action, layout: modal_layout, formats: [:html], status: :unprocessable_content
|
|
129
115
|
end
|
|
130
116
|
format.any do
|
|
131
117
|
@errors = @interaction.errors
|
|
@@ -139,12 +125,7 @@ module Plutonium
|
|
|
139
125
|
# GET /resources/bulk_actions/:interactive_action?ids[]=1&ids[]=2
|
|
140
126
|
def interactive_bulk_action
|
|
141
127
|
build_interactive_bulk_action_interaction
|
|
142
|
-
|
|
143
|
-
if helpers.current_turbo_frame == "remote_modal"
|
|
144
|
-
render layout: false, formats: [:html]
|
|
145
|
-
else
|
|
146
|
-
render :interactive_bulk_action, formats: [:html]
|
|
147
|
-
end
|
|
128
|
+
render :interactive_bulk_action, layout: modal_layout, formats: [:html]
|
|
148
129
|
end
|
|
149
130
|
|
|
150
131
|
# POST /resources/bulk_actions/:interactive_action?ids[]=1&ids[]=2
|
|
@@ -177,7 +158,7 @@ module Plutonium
|
|
|
177
158
|
end
|
|
178
159
|
else
|
|
179
160
|
format.any(:html, :turbo_stream) do
|
|
180
|
-
render :interactive_bulk_action, formats: [:html], status: :unprocessable_content
|
|
161
|
+
render :interactive_bulk_action, layout: modal_layout, formats: [:html], status: :unprocessable_content
|
|
181
162
|
end
|
|
182
163
|
format.any do
|
|
183
164
|
@errors = @interaction.errors
|
|
@@ -190,6 +171,11 @@ module Plutonium
|
|
|
190
171
|
|
|
191
172
|
private
|
|
192
173
|
|
|
174
|
+
# Returns false for modal requests (skip layout), nil otherwise (use default layout)
|
|
175
|
+
def modal_layout
|
|
176
|
+
helpers.current_turbo_frame.present? ? false : nil
|
|
177
|
+
end
|
|
178
|
+
|
|
193
179
|
def current_interactive_action
|
|
194
180
|
@current_interactive_action = interactive_resource_actions[params[:interactive_action].to_sym]
|
|
195
181
|
end
|
|
@@ -17,7 +17,7 @@ module Plutonium
|
|
|
17
17
|
presentable_attributes -= [parent_input_param, :"#{parent_input_param}_id"]
|
|
18
18
|
end
|
|
19
19
|
if scoped_to_entity? && !present_scoped_entity?
|
|
20
|
-
presentable_attributes -=
|
|
20
|
+
presentable_attributes -= scoped_entity_field_names
|
|
21
21
|
end
|
|
22
22
|
presentable_attributes
|
|
23
23
|
end
|
|
@@ -33,11 +33,49 @@ module Plutonium
|
|
|
33
33
|
submittable_attributes -= [parent_input_param, :"#{parent_input_param}_id"]
|
|
34
34
|
end
|
|
35
35
|
if scoped_to_entity? && !submit_scoped_entity?
|
|
36
|
-
submittable_attributes -=
|
|
36
|
+
submittable_attributes -= scoped_entity_field_names
|
|
37
37
|
end
|
|
38
38
|
submittable_attributes
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
# Returns all field names related to the scoped entity.
|
|
42
|
+
# Finds associations by class to handle cases where param_key differs from association name.
|
|
43
|
+
def scoped_entity_field_names
|
|
44
|
+
field_names = [scoped_entity_param_key, :"#{scoped_entity_param_key}_id"]
|
|
45
|
+
|
|
46
|
+
assoc_name = scoped_entity_association
|
|
47
|
+
if assoc_name
|
|
48
|
+
field_names << assoc_name
|
|
49
|
+
field_names << :"#{assoc_name}_id"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
field_names.uniq
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the name of the belongs_to association pointing to the scoped entity class.
|
|
56
|
+
# Raises if multiple associations exist (ambiguous - user must configure manually).
|
|
57
|
+
# @return [Symbol, nil] the association name or nil if not found
|
|
58
|
+
def scoped_entity_association
|
|
59
|
+
return @scoped_entity_association if defined?(@scoped_entity_association)
|
|
60
|
+
|
|
61
|
+
matching_assocs = resource_class.reflect_on_all_associations(:belongs_to).select do |assoc|
|
|
62
|
+
assoc.klass.name == scoped_entity_class.name
|
|
63
|
+
rescue NameError
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if matching_assocs.size > 1
|
|
68
|
+
assoc_names = matching_assocs.map(&:name).join(", ")
|
|
69
|
+
raise <<~MSG.squish
|
|
70
|
+
#{resource_class} has multiple associations to #{scoped_entity_class}: #{assoc_names}.
|
|
71
|
+
Plutonium cannot auto-detect which one to use for entity scoping.
|
|
72
|
+
Override `scoped_entity_association` in your controller to specify the association.
|
|
73
|
+
MSG
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
@scoped_entity_association = matching_assocs.first&.name
|
|
77
|
+
end
|
|
78
|
+
|
|
41
79
|
def build_collection
|
|
42
80
|
current_definition.collection_class.new(@resource_records, resource_fields: presentable_attributes, resource_definition: current_definition)
|
|
43
81
|
end
|
|
@@ -47,7 +85,12 @@ module Plutonium
|
|
|
47
85
|
end
|
|
48
86
|
|
|
49
87
|
def build_form(record = resource_record!, action: action_name, form_action: nil, **)
|
|
50
|
-
form_options = {
|
|
88
|
+
form_options = {
|
|
89
|
+
resource_fields: submittable_attributes_for(action),
|
|
90
|
+
resource_definition: current_definition,
|
|
91
|
+
singular_resource: singular_resource_context?,
|
|
92
|
+
**
|
|
93
|
+
}
|
|
51
94
|
form_options[:action] = form_action unless form_action.nil?
|
|
52
95
|
current_definition.form_class.new(record, **form_options)
|
|
53
96
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# lib/plutonium/resource/associations.rb
|
|
4
3
|
module Plutonium
|
|
5
4
|
module Resource
|
|
6
5
|
module Record
|
|
@@ -9,6 +8,13 @@ module Plutonium
|
|
|
9
8
|
|
|
10
9
|
included do
|
|
11
10
|
scope :associated_with, ->(record) do
|
|
11
|
+
# If scoping to same class, just match by ID (e.g., Team scoped to Team)
|
|
12
|
+
# Compare by name to handle Rails class reloading (different object_id after reload)
|
|
13
|
+
if klass.name == record.class.name
|
|
14
|
+
pk = klass.primary_key
|
|
15
|
+
return where(pk => record.public_send(pk))
|
|
16
|
+
end
|
|
17
|
+
|
|
12
18
|
named_scope = :"associated_with_#{record.model_name.singular}"
|
|
13
19
|
return send(named_scope, record) if respond_to?(named_scope)
|
|
14
20
|
|
|
@@ -44,16 +44,28 @@ module Plutonium
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# Draws routes wrapped in entity scope when using path-based entity scoping.
|
|
48
|
+
#
|
|
49
|
+
# @param scope_params [Hash, nil] Scope params from RouteSetExtensions, or nil if no scoping
|
|
50
|
+
# @param block [Proc] The block containing route definitions.
|
|
51
|
+
# @return [void]
|
|
52
|
+
def draw_routes_with_entity_scope(scope_params, &block)
|
|
53
|
+
if scope_params
|
|
54
|
+
scope scope_params[:name], **scope_params[:options] do
|
|
55
|
+
instance_exec(&block)
|
|
56
|
+
materialize_resource_routes
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
instance_exec(&block)
|
|
60
|
+
materialize_resource_routes
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
47
64
|
# Materializes all registered resource routes.
|
|
48
65
|
#
|
|
49
66
|
# @return [void]
|
|
50
67
|
def materialize_resource_routes
|
|
51
|
-
|
|
52
|
-
scope_params = determine_scope_params(engine)
|
|
53
|
-
|
|
54
|
-
scope scope_params[:name], scope_params[:options] do
|
|
55
|
-
concerns resource_route_concern_names.sort
|
|
56
|
-
end
|
|
68
|
+
concerns resource_route_concern_names.sort
|
|
57
69
|
end
|
|
58
70
|
|
|
59
71
|
# @return [ActionDispatch::Routing::RouteSet] The current route set.
|
|
@@ -149,18 +161,6 @@ module Plutonium
|
|
|
149
161
|
as: :commit_interactive_resource_action
|
|
150
162
|
end
|
|
151
163
|
end
|
|
152
|
-
|
|
153
|
-
# Determines the scope parameters based on the engine configuration.
|
|
154
|
-
#
|
|
155
|
-
# @param engine [Class] The current engine.
|
|
156
|
-
# @return [Hash] Scope name and options.
|
|
157
|
-
def determine_scope_params(engine)
|
|
158
|
-
scoped_entity_param_key = engine.scoped_entity_param_key if engine.scoped_entity_strategy == :path
|
|
159
|
-
{
|
|
160
|
-
name: scoped_entity_param_key.present? ? ":#{scoped_entity_param_key}" : "",
|
|
161
|
-
options: scoped_entity_param_key.present? ? {as: scoped_entity_param_key} : {}
|
|
162
|
-
}
|
|
163
|
-
end
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
end
|
|
@@ -34,11 +34,11 @@ module Plutonium
|
|
|
34
34
|
# @yield Executes the given block in the context of route drawing.
|
|
35
35
|
def draw(&block)
|
|
36
36
|
if self.class.supported_engine?(engine)
|
|
37
|
+
scope_params = entity_scope_params_for_path_strategy
|
|
37
38
|
ActiveSupport::Notifications.instrument("plutonium.resource_routes.draw", app: engine.to_s) do
|
|
38
39
|
super do
|
|
39
40
|
setup_shared_resource_concerns
|
|
40
|
-
|
|
41
|
-
materialize_resource_routes
|
|
41
|
+
draw_routes_with_entity_scope(scope_params, &block)
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
else
|
|
@@ -46,6 +46,19 @@ module Plutonium
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# Determines entity scope parameters for path-based scoping.
|
|
50
|
+
#
|
|
51
|
+
# @return [Hash, nil] Scope params if path-based scoping is enabled, nil otherwise
|
|
52
|
+
def entity_scope_params_for_path_strategy
|
|
53
|
+
return nil unless engine.scoped_entity_strategy == :path
|
|
54
|
+
|
|
55
|
+
param_key = engine.scoped_entity_param_key
|
|
56
|
+
{
|
|
57
|
+
name: ":#{param_key}",
|
|
58
|
+
options: {as: param_key}
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
49
62
|
# Registers a resource for routing.
|
|
50
63
|
#
|
|
51
64
|
# @param resource [Class] The resource class to be registered.
|
|
@@ -75,6 +88,14 @@ module Plutonium
|
|
|
75
88
|
resource_route_config_lookup.slice(*routes).values
|
|
76
89
|
end
|
|
77
90
|
|
|
91
|
+
# Checks if a resource is registered as a singular route.
|
|
92
|
+
#
|
|
93
|
+
# @param route_key [String] The route key (e.g., "users" or "users/profiles")
|
|
94
|
+
# @return [Boolean] true if the resource is a singular route, false otherwise
|
|
95
|
+
def singular_resource_route?(route_key)
|
|
96
|
+
resource_route_config_for(route_key)[0]&.[](:route_type) == :resource
|
|
97
|
+
end
|
|
98
|
+
|
|
78
99
|
# Returns the current engine for the routes.
|
|
79
100
|
#
|
|
80
101
|
# @return [Class] The engine class (Rails application or custom engine).
|