plutonium 0.18.5 → 0.18.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/plutonium.js +1 -1
- data/app/assets/plutonium.js.map +2 -2
- data/app/assets/plutonium.min.js +1 -1
- data/app/assets/plutonium.min.js.map +2 -2
- data/app/views/resource/interactive_bulk_action.html.erb +1 -1
- data/app/views/resource/interactive_resource_action.html.erb +1 -1
- data/lib/generators/pu/eject/layout/layout_generator.rb +1 -2
- data/lib/generators/pu/eject/shell/shell_generator.rb +1 -3
- data/lib/generators/pu/lib/plutonium_generators/concerns/logger.rb +4 -0
- data/lib/generators/pu/lib/plutonium_generators/concerns/package_selector.rb +80 -0
- data/lib/generators/pu/lib/plutonium_generators/concerns/resource_selector.rb +48 -0
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +2 -42
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +1 -4
- data/lib/generators/pu/res/conn/conn_generator.rb +9 -21
- data/lib/generators/pu/res/scaffold/scaffold_generator.rb +10 -5
- data/lib/plutonium/resource/controller.rb +3 -3
- data/lib/plutonium/resource/controllers/authorizable.rb +1 -1
- data/lib/plutonium/resource/controllers/crud_actions.rb +17 -17
- data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
- data/lib/plutonium/resource/controllers/presentable.rb +2 -2
- data/lib/plutonium/resource/record/associated_with.rb +83 -0
- data/lib/plutonium/resource/record/associations.rb +92 -0
- data/lib/plutonium/resource/record/field_names.rb +83 -0
- data/lib/plutonium/resource/record/labeling.rb +19 -0
- data/lib/plutonium/resource/record/routes.rb +66 -0
- data/lib/plutonium/resource/record.rb +6 -258
- data/lib/plutonium/ui/breadcrumbs.rb +4 -4
- data/lib/plutonium/ui/component/methods.rb +4 -12
- data/lib/plutonium/ui/form/base.rb +22 -10
- data/lib/plutonium/ui/form/components/secure_association.rb +112 -0
- data/lib/plutonium/ui/form/components/secure_polymorphic_association.rb +54 -0
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +0 -1
- data/lib/plutonium/ui/form/theme.rb +12 -1
- data/lib/plutonium/ui/page/show.rb +1 -1
- data/lib/plutonium/ui/page_header.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/package-lock.json +2 -2
- data/package.json +1 -1
- data/src/js/controllers/slim_select_controller.js +3 -1
- metadata +11 -4
- data/lib/plutonium/ui/form/components/belongs_to.rb +0 -66
- data/lib/plutonium/ui/form/components/has_many.rb +0 -66
@@ -1,4 +1,4 @@
|
|
1
|
-
<%= render_component :breadcrumbs, resource_class:, parent: current_parent, resource: resource_record %>
|
1
|
+
<%= render_component :breadcrumbs, resource_class:, parent: current_parent, resource: resource_record! %>
|
2
2
|
|
3
3
|
<%= render_component :dyna_frame_content do %>
|
4
4
|
<%= render "interactive_action_form", interactive_action: current_interactive_action %>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<%= render_component :breadcrumbs, resource_class:, parent: current_parent, resource: resource_record %>
|
1
|
+
<%= render_component :breadcrumbs, resource_class:, parent: current_parent, resource: resource_record! %>
|
2
2
|
|
3
3
|
<%= render_component :dyna_frame_content do %>
|
4
4
|
<%= render "interactive_action_form", interactive_action: current_interactive_action %>
|
@@ -11,7 +11,6 @@ module Pu
|
|
11
11
|
|
12
12
|
desc "Eject layout views into your own project"
|
13
13
|
|
14
|
-
class_option :dest, type: :string
|
15
14
|
class_option :rodauth, type: :boolean
|
16
15
|
|
17
16
|
def start
|
@@ -28,7 +27,7 @@ module Pu
|
|
28
27
|
private
|
29
28
|
|
30
29
|
def destination_portal
|
31
|
-
|
30
|
+
portal_option(:dest, prompt: "Select destination portal")
|
32
31
|
end
|
33
32
|
|
34
33
|
def copy_file(source_path, destination_path)
|
@@ -11,8 +11,6 @@ module Pu
|
|
11
11
|
|
12
12
|
desc "Eject layout shell (i.e header, sidebar) into your own project"
|
13
13
|
|
14
|
-
class_option :dest, type: :string
|
15
|
-
|
16
14
|
def start
|
17
15
|
destination_dir = (destination_portal == "main_app") ? "app/views/" : "packages/#{destination_portal}/app/views"
|
18
16
|
[
|
@@ -28,7 +26,7 @@ module Pu
|
|
28
26
|
private
|
29
27
|
|
30
28
|
def destination_portal
|
31
|
-
|
29
|
+
portal_option(:dest, prompt: "Select destination portal")
|
32
30
|
end
|
33
31
|
|
34
32
|
def copy_file(source_path, destination_path)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlutoniumGenerators
|
4
|
+
module Concerns
|
5
|
+
module PackageSelector
|
6
|
+
def self.included(base)
|
7
|
+
base.send :class_option, :src, type: :string, desc: "The source package if applicable"
|
8
|
+
base.send :class_option, :dest, type: :string, desc: "The destination package if applicable"
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def reserved_packages
|
14
|
+
%w[core reactor app main plutonium pluton8 plutonate]
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_package_name(package_name)
|
18
|
+
package_name = package_name.underscore
|
19
|
+
error("Package name is reserved\n\n#{reserved_packages.join "\n"}") if reserved_packages.include?(package_name)
|
20
|
+
error("Package name cannot end in `_app` or `_portal`") if /(_app|_portal)$/i.match?(package_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def available_packages
|
24
|
+
@available_packages ||= begin
|
25
|
+
packages = Dir["packages/*"].map { |dir| dir.gsub "packages/", "" }
|
26
|
+
packages - reserved_packages
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def available_portals
|
31
|
+
@available_portals ||= ["main_app"] + available_packages.select { |pkg| pkg.ends_with?("_app") || pkg.ends_with?("_portal") }.sort
|
32
|
+
end
|
33
|
+
|
34
|
+
def available_features
|
35
|
+
@available_features ||= ["main_app"] + available_packages.select { |pkg| !(pkg.ends_with?("_app") || pkg.ends_with?("_portal")) }.sort
|
36
|
+
end
|
37
|
+
|
38
|
+
def select_package(selected_package = nil, msg: "Select package", pkgs: nil)
|
39
|
+
pkgs ||= available_packages
|
40
|
+
if pkgs.include?(selected_package)
|
41
|
+
selected_package
|
42
|
+
else
|
43
|
+
prompt.select(msg, pkgs)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def select_feature(selected_package = nil, msg: "Select feature")
|
48
|
+
select_package(selected_package, msg: msg, pkgs: available_features)
|
49
|
+
end
|
50
|
+
|
51
|
+
def feature_option(name, prompt: nil, option_key: nil)
|
52
|
+
# Get stored value or command line option
|
53
|
+
ivar = :"@#{name}_feature_option"
|
54
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
55
|
+
|
56
|
+
# Validate option or prompt user
|
57
|
+
option_key ||= name
|
58
|
+
value = select_feature(options[option_key], msg: prompt || "Select #{name} feature")
|
59
|
+
instance_variable_set(ivar, value)
|
60
|
+
value
|
61
|
+
end
|
62
|
+
|
63
|
+
def select_portal(selected_package = nil, msg: "Select portal")
|
64
|
+
select_package(selected_package, msg: msg, pkgs: available_portals)
|
65
|
+
end
|
66
|
+
|
67
|
+
def portal_option(name, prompt: nil, option_key: nil)
|
68
|
+
# Get stored value or command line option
|
69
|
+
ivar = :"@#{name}_portal_option"
|
70
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
71
|
+
|
72
|
+
# Validate option or prompt user
|
73
|
+
option_key ||= name
|
74
|
+
value = select_portal(options[option_key], msg: prompt || "Select #{name} portal")
|
75
|
+
instance_variable_set(ivar, value)
|
76
|
+
value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlutoniumGenerators
|
4
|
+
module Concerns
|
5
|
+
module ResourceSelector
|
6
|
+
def self.included(base)
|
7
|
+
# base.send :class_option, :resources, type: :array, desc: "List of resource model names if applicable"
|
8
|
+
base.send :argument, :resources, type: :array, optional: true, default: [],
|
9
|
+
desc: "List of model names if applicable"
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def available_resources(source_module)
|
15
|
+
Plutonium.eager_load_rails!
|
16
|
+
|
17
|
+
source_module.constantize.descendants.reject do |resource_klass|
|
18
|
+
next true if resource_klass.abstract_class?
|
19
|
+
next true if source_module == "ApplicationRecord" &&
|
20
|
+
resource_klass.ancestors.any? { |ancestor| ancestor.to_s.end_with?("::ResourceRecord") }
|
21
|
+
end.map(&:to_s).sort
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_resources(source_module, prompt: "Select resources")
|
25
|
+
resources = available_resources(source_module)
|
26
|
+
error "No resources found" if resources.blank?
|
27
|
+
|
28
|
+
self.prompt.multi_select(prompt, resources)
|
29
|
+
end
|
30
|
+
|
31
|
+
def resources_selection(prompt: nil)
|
32
|
+
ivar = :@resources_selection
|
33
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
34
|
+
|
35
|
+
# Convert comma-separated string to array if from command line
|
36
|
+
value = resources.map(&:classify)
|
37
|
+
if value.empty?
|
38
|
+
source_feature = feature_option :src, prompt: "Select source feature"
|
39
|
+
source_module = (source_feature == "main_app") ? "ApplicationRecord" : "#{source_feature.camelize}::ResourceRecord"
|
40
|
+
value = select_resources(source_module, prompt: prompt || "Select #{source_module} resources")
|
41
|
+
end
|
42
|
+
|
43
|
+
instance_variable_set(ivar, value)
|
44
|
+
value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -14,51 +14,11 @@ module PlutoniumGenerators
|
|
14
14
|
base.send :class_option, :interactive, type: :boolean, desc: "Show prompts. Default: true"
|
15
15
|
base.send :class_option, :bundle, type: :boolean, desc: "Run bundle after setup. Default: true"
|
16
16
|
base.send :class_option, :lint, type: :boolean, desc: "Run linter after generation. Default: false"
|
17
|
-
end
|
18
|
-
|
19
|
-
protected
|
20
|
-
|
21
|
-
def reserved_packages
|
22
|
-
%w[core reactor app main plutonium pluton8 plutonate]
|
23
|
-
end
|
24
|
-
|
25
|
-
def validate_package_name(package_name)
|
26
|
-
package_name = package_name.underscore
|
27
|
-
error("Package name is reserved\n\n#{reserved_packages.join "\n"}") if reserved_packages.include?(package_name)
|
28
|
-
error("Package name cannot end in `_app` or `_portal`") if /(_app|_portal)$/i.match?(package_name)
|
29
|
-
end
|
30
|
-
|
31
|
-
def available_packages
|
32
|
-
@available_packages ||= begin
|
33
|
-
packages = Dir["packages/*"].map { |dir| dir.gsub "packages/", "" }
|
34
|
-
packages - reserved_packages
|
35
|
-
end
|
36
|
-
end
|
37
17
|
|
38
|
-
|
39
|
-
@available_apps ||= ["main_app"] + available_packages.select { |pkg| pkg.ends_with?("_app") || pkg.ends_with?("_portal") }.sort
|
18
|
+
base.include Concerns::PackageSelector
|
40
19
|
end
|
41
20
|
|
42
|
-
|
43
|
-
@available_features ||= ["main_app"] + available_packages.select { |pkg| !(pkg.ends_with?("_app") || pkg.ends_with?("_portal")) }.sort
|
44
|
-
end
|
45
|
-
|
46
|
-
def select_package(selected_package = nil, msg: "Select package", pkgs: nil)
|
47
|
-
pkgs ||= available_packages
|
48
|
-
if pkgs.include?(selected_package)
|
49
|
-
selected_package
|
50
|
-
else
|
51
|
-
prompt.select(msg, pkgs)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def select_portal(selected_package = nil, msg: "Select portal")
|
56
|
-
select_package(selected_package, msg: msg, pkgs: available_apps)
|
57
|
-
end
|
58
|
-
|
59
|
-
def select_feature(selected_package = nil, msg: "Select feature")
|
60
|
-
select_package(selected_package, msg: msg, pkgs: available_features)
|
61
|
-
end
|
21
|
+
protected
|
62
22
|
|
63
23
|
# ####################
|
64
24
|
|
@@ -13,8 +13,6 @@ module PlutoniumGenerators
|
|
13
13
|
remove_task :create_module_file
|
14
14
|
# remove_task :check_class_collision
|
15
15
|
|
16
|
-
class_option :dest, type: :string
|
17
|
-
|
18
16
|
# def check_class_collision # :doc:
|
19
17
|
# class_collisions "#{options[:prefix]}#{name}#{options[:suffix]}"
|
20
18
|
# end
|
@@ -41,7 +39,6 @@ module PlutoniumGenerators
|
|
41
39
|
def name
|
42
40
|
@pu_name ||= begin
|
43
41
|
@original_name = @name
|
44
|
-
@selected_destination_feature = select_feature selected_destination_feature, msg: "Select destination feature"
|
45
42
|
@name = [main_app? ? nil : selected_destination_feature.underscore, super.singularize.underscore].compact.join "/"
|
46
43
|
set_destination_root!
|
47
44
|
@name
|
@@ -57,7 +54,7 @@ module PlutoniumGenerators
|
|
57
54
|
end
|
58
55
|
|
59
56
|
def selected_destination_feature
|
60
|
-
|
57
|
+
feature_option :dest, prompt: "Select destination feature"
|
61
58
|
end
|
62
59
|
|
63
60
|
def set_destination_root!
|
@@ -6,26 +6,20 @@ module Pu
|
|
6
6
|
module Res
|
7
7
|
class ConnGenerator < Rails::Generators::Base
|
8
8
|
include PlutoniumGenerators::Generator
|
9
|
+
include PlutoniumGenerators::Concerns::ResourceSelector
|
9
10
|
|
10
11
|
source_root File.expand_path("templates", __dir__)
|
11
12
|
|
12
|
-
desc
|
13
|
+
desc(
|
14
|
+
"Create a connection between a resource and a portal\n\n" \
|
15
|
+
"e.g. rails g pu:res:conn todo --dest=dashboard_portal"
|
16
|
+
)
|
13
17
|
|
14
18
|
# argument :name
|
15
19
|
|
16
20
|
def start
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
Plutonium.eager_load_rails!
|
21
|
-
available_resources = source_module.constantize.descendants.reject do |model|
|
22
|
-
next true if model.abstract_class?
|
23
|
-
next true if source_module == "ApplicationRecord" && model.ancestors.any? { |ancestor| ancestor.to_s.end_with?("::ResourceRecord") }
|
24
|
-
end.map(&:to_s).sort
|
25
|
-
error "No resources found" if available_resources.blank?
|
26
|
-
selected_resources = prompt.multi_select("Select resources", available_resources)
|
27
|
-
|
28
|
-
@app_namespace = select_portal.camelize
|
21
|
+
selected_resources = resources_selection
|
22
|
+
@app_namespace = portal_option(:dest, prompt: "Select destination portal").camelize
|
29
23
|
|
30
24
|
selected_resources.each do |resource|
|
31
25
|
@resource_class = resource
|
@@ -97,13 +91,7 @@ module Pu
|
|
97
91
|
|
98
92
|
def attributes
|
99
93
|
resource_klass = resource_class.constantize
|
100
|
-
unwanted_attrs = [
|
101
|
-
resource_klass.primary_key.to_sym, # primary_key
|
102
|
-
:created_at, :updated_at # timestamps
|
103
|
-
]
|
104
94
|
resource_klass.content_columns.filter_map { |col|
|
105
|
-
next if unwanted_attrs.include? col.name.to_sym
|
106
|
-
|
107
95
|
PlutoniumGenerators::ModelGeneratorBase::GeneratedAttribute.parse resource_class, "#{col.name}:#{col.type}"
|
108
96
|
}
|
109
97
|
rescue ActiveRecord::StatementInvalid
|
@@ -116,11 +104,11 @@ module Pu
|
|
116
104
|
end
|
117
105
|
|
118
106
|
def policy_attributes_for_create
|
119
|
-
default_policy_attributes
|
107
|
+
default_policy_attributes - [:created_at, :updated_at]
|
120
108
|
end
|
121
109
|
|
122
110
|
def policy_attributes_for_read
|
123
|
-
default_policy_attributes
|
111
|
+
default_policy_attributes
|
124
112
|
end
|
125
113
|
end
|
126
114
|
end
|
@@ -4,6 +4,7 @@ require_relative "../../lib/plutonium_generators"
|
|
4
4
|
|
5
5
|
module Pu
|
6
6
|
module Res
|
7
|
+
# run `rails g pu:res:scaffold existing_resource` without any arguments to import an existing resource
|
7
8
|
class ScaffoldGenerator < PlutoniumGenerators::ModelGeneratorBase
|
8
9
|
include PlutoniumGenerators::Generator
|
9
10
|
|
@@ -17,9 +18,13 @@ module Pu
|
|
17
18
|
return unless options[:model]
|
18
19
|
|
19
20
|
model_class = class_name.safe_constantize
|
20
|
-
if model_class.present?
|
21
|
-
|
22
|
-
|
21
|
+
if model_class.present?
|
22
|
+
if attributes.empty?
|
23
|
+
attributes_str = model_class.content_columns.map { |col| "#{col.name}:#{col.type}" }
|
24
|
+
self.attributes = parse_attributes_internal!(attributes_str)
|
25
|
+
else
|
26
|
+
warn("Overwriting existing resource. You can leave out the attributes to import an existing resource.")
|
27
|
+
end
|
23
28
|
end
|
24
29
|
end
|
25
30
|
|
@@ -56,11 +61,11 @@ module Pu
|
|
56
61
|
end
|
57
62
|
|
58
63
|
def policy_attributes_for_create
|
59
|
-
default_policy_attributes
|
64
|
+
default_policy_attributes - [:created_at, :updated_at]
|
60
65
|
end
|
61
66
|
|
62
67
|
def policy_attributes_for_read
|
63
|
-
default_policy_attributes
|
68
|
+
default_policy_attributes
|
64
69
|
end
|
65
70
|
end
|
66
71
|
end
|
@@ -19,7 +19,7 @@ module Plutonium
|
|
19
19
|
# https://github.com/ddnexus/pagy/blob/master/docs/extras/headers.md#headers
|
20
20
|
after_action { pagy_headers_merge(@pagy) if @pagy }
|
21
21
|
|
22
|
-
helper_method :current_parent, :resource_record
|
22
|
+
helper_method :current_parent, :resource_record!, :resource_record?, :resource_param_key, :resource_class
|
23
23
|
end
|
24
24
|
|
25
25
|
class_methods do
|
@@ -62,7 +62,7 @@ module Plutonium
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
def resource_record
|
65
|
+
def resource_record!
|
66
66
|
@resource_record ||= resource_record_relation.first!
|
67
67
|
end
|
68
68
|
|
@@ -117,7 +117,7 @@ module Plutonium
|
|
117
117
|
# Applies submitted resource params if they have been passed
|
118
118
|
def maybe_apply_submitted_resource_params!
|
119
119
|
ensure_get_request
|
120
|
-
resource_record
|
120
|
+
resource_record!.attributes = submitted_resource_params if params[resource_param_key]
|
121
121
|
end
|
122
122
|
|
123
123
|
# Returns the current parent based on path parameters
|
@@ -12,7 +12,7 @@ module Plutonium
|
|
12
12
|
# include Plutonium::Resource::Controllers::Authorizable
|
13
13
|
# end
|
14
14
|
#
|
15
|
-
# @note This module assumes the existence of methods like `resource_record
|
15
|
+
# @note This module assumes the existence of methods like `resource_record!`,
|
16
16
|
# `resource_class`, `current_parent`, and `entity_scope_for_authorize`.
|
17
17
|
#
|
18
18
|
# @see ActionPolicy
|
@@ -21,8 +21,8 @@ module Plutonium
|
|
21
21
|
|
22
22
|
# GET /resources/1(.{format})
|
23
23
|
def show
|
24
|
-
authorize_current! resource_record
|
25
|
-
set_page_title resource_record
|
24
|
+
authorize_current! resource_record!
|
25
|
+
set_page_title resource_record!.to_label.titleize
|
26
26
|
|
27
27
|
render :show
|
28
28
|
end
|
@@ -46,7 +46,7 @@ module Plutonium
|
|
46
46
|
@resource_record = resource_class.new resource_params
|
47
47
|
|
48
48
|
respond_to do |format|
|
49
|
-
if resource_record
|
49
|
+
if resource_record!.save
|
50
50
|
format.html do
|
51
51
|
redirect_to redirect_url_after_submit,
|
52
52
|
notice: "#{resource_class.model_name.human} was successfully created."
|
@@ -57,7 +57,7 @@ module Plutonium
|
|
57
57
|
render :new, status: :unprocessable_entity
|
58
58
|
end
|
59
59
|
format.any do
|
60
|
-
@errors = resource_record
|
60
|
+
@errors = resource_record!.errors
|
61
61
|
render "errors", status: :unprocessable_entity
|
62
62
|
end
|
63
63
|
end
|
@@ -66,8 +66,8 @@ module Plutonium
|
|
66
66
|
|
67
67
|
# GET /resources/1/edit
|
68
68
|
def edit
|
69
|
-
authorize_current! resource_record
|
70
|
-
set_page_title "Update #{resource_record
|
69
|
+
authorize_current! resource_record!
|
70
|
+
set_page_title "Update #{resource_record!.to_label.titleize}"
|
71
71
|
|
72
72
|
maybe_apply_submitted_resource_params!
|
73
73
|
|
@@ -76,11 +76,11 @@ module Plutonium
|
|
76
76
|
|
77
77
|
# PATCH/PUT /resources/1(.{format})
|
78
78
|
def update
|
79
|
-
authorize_current! resource_record
|
80
|
-
set_page_title "Update #{resource_record
|
79
|
+
authorize_current! resource_record!
|
80
|
+
set_page_title "Update #{resource_record!.to_label.titleize}"
|
81
81
|
|
82
82
|
respond_to do |format|
|
83
|
-
if resource_record
|
83
|
+
if resource_record!.update(resource_params)
|
84
84
|
format.html do
|
85
85
|
redirect_to redirect_url_after_submit, notice: "#{resource_class.model_name.human} was successfully updated.",
|
86
86
|
status: :see_other
|
@@ -91,7 +91,7 @@ module Plutonium
|
|
91
91
|
render :edit, status: :unprocessable_entity
|
92
92
|
end
|
93
93
|
format.any do
|
94
|
-
@errors = resource_record
|
94
|
+
@errors = resource_record!.errors
|
95
95
|
render "errors", status: :unprocessable_entity
|
96
96
|
end
|
97
97
|
end
|
@@ -100,10 +100,10 @@ module Plutonium
|
|
100
100
|
|
101
101
|
# DELETE /resources/1(.{format})
|
102
102
|
def destroy
|
103
|
-
authorize_current! resource_record
|
103
|
+
authorize_current! resource_record!
|
104
104
|
|
105
105
|
respond_to do |format|
|
106
|
-
resource_record
|
106
|
+
resource_record!.destroy
|
107
107
|
|
108
108
|
format.html do
|
109
109
|
redirect_to redirect_url_after_destroy,
|
@@ -112,11 +112,11 @@ module Plutonium
|
|
112
112
|
format.json { head :no_content }
|
113
113
|
rescue ActiveRecord::InvalidForeignKey
|
114
114
|
format.html do
|
115
|
-
redirect_to resource_url_for(resource_record),
|
115
|
+
redirect_to resource_url_for(resource_record!),
|
116
116
|
alert: "#{resource_class.model_name.human} is referenced by other records."
|
117
117
|
end
|
118
118
|
format.any do
|
119
|
-
@errors = ActiveModel::Errors.new resource_record
|
119
|
+
@errors = ActiveModel::Errors.new resource_record!
|
120
120
|
@errors.add :base, :existing_references, message: "is referenced by other records"
|
121
121
|
|
122
122
|
render "errors", status: :unprocessable_entity
|
@@ -133,9 +133,9 @@ module Plutonium
|
|
133
133
|
|
134
134
|
url = case preferred_action_after_submit
|
135
135
|
when "show"
|
136
|
-
resource_url_for(resource_record) if current_policy.allowed_to? :show?
|
136
|
+
resource_url_for(resource_record!) if current_policy.allowed_to? :show?
|
137
137
|
when "edit"
|
138
|
-
resource_url_for(resource_record
|
138
|
+
resource_url_for(resource_record!, action: :edit) if current_policy.allowed_to? :edit?
|
139
139
|
when "new"
|
140
140
|
resource_url_for(resource_class, action: :new) if current_policy.allowed_to? :new?
|
141
141
|
when "index"
|
@@ -144,7 +144,7 @@ module Plutonium
|
|
144
144
|
# ensure we have a valid value
|
145
145
|
session[:action_after_submit_preference] = "show"
|
146
146
|
end
|
147
|
-
url || resource_url_for(resource_record)
|
147
|
+
url || resource_url_for(resource_record!)
|
148
148
|
end
|
149
149
|
|
150
150
|
def redirect_url_after_destroy
|
@@ -42,7 +42,7 @@ module Plutonium
|
|
42
42
|
if outcome.success?
|
43
43
|
outcome.to_response.process(self) do |value|
|
44
44
|
respond_to do |format|
|
45
|
-
return_url = redirect_url_after_action_on(resource_record)
|
45
|
+
return_url = redirect_url_after_action_on(resource_record!)
|
46
46
|
format.any { redirect_to return_url, status: :see_other }
|
47
47
|
if helpers.current_turbo_frame == "modal"
|
48
48
|
format.turbo_stream do
|
@@ -202,7 +202,7 @@ module Plutonium
|
|
202
202
|
|
203
203
|
def authorize_interactive_record_action!
|
204
204
|
interactive_resource_action = params[:interactive_action]&.to_sym
|
205
|
-
authorize_current! resource_record
|
205
|
+
authorize_current! resource_record!, to: :"#{interactive_resource_action}?"
|
206
206
|
end
|
207
207
|
|
208
208
|
def authorize_interactive_resource_action!
|
@@ -216,7 +216,7 @@ module Plutonium
|
|
216
216
|
|
217
217
|
def build_interactive_record_action_interaction
|
218
218
|
@interaction = current_interactive_action.interaction.new(view_context:)
|
219
|
-
@interaction.attributes = interaction_params.merge(resource: resource_record)
|
219
|
+
@interaction.attributes = interaction_params.merge(resource: resource_record!)
|
220
220
|
@interaction
|
221
221
|
end
|
222
222
|
|
@@ -36,10 +36,10 @@ module Plutonium
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def build_detail
|
39
|
-
current_definition.detail_class.new(resource_record
|
39
|
+
current_definition.detail_class.new(resource_record!, resource_fields: presentable_attributes, resource_associations: permitted_associations, resource_definition: current_definition)
|
40
40
|
end
|
41
41
|
|
42
|
-
def build_form(record = resource_record)
|
42
|
+
def build_form(record = resource_record!)
|
43
43
|
current_definition.form_class.new(record, resource_fields: submittable_attributes, resource_definition: current_definition)
|
44
44
|
end
|
45
45
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/plutonium/resource/associations.rb
|
4
|
+
module Plutonium
|
5
|
+
module Resource
|
6
|
+
module Record
|
7
|
+
module AssociatedWith
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
scope :associated_with, ->(record) do
|
12
|
+
named_scope = :"associated_with_#{record.model_name.singular}"
|
13
|
+
return send(named_scope, record) if respond_to?(named_scope)
|
14
|
+
|
15
|
+
own_association = klass.find_association_from_self_to_record(record)
|
16
|
+
if own_association
|
17
|
+
return klass.query_based_on_association(own_association, record)
|
18
|
+
end
|
19
|
+
|
20
|
+
record_association = klass.find_association_to_self_from_record(record)
|
21
|
+
if record_association
|
22
|
+
Plutonium.logger.warn do
|
23
|
+
[
|
24
|
+
"Using indirect association from #{record.class} to #{klass.name}",
|
25
|
+
"via '#{record_association.name}'.",
|
26
|
+
"This may result in poor query performance for large datasets",
|
27
|
+
"as it requires loading records to perform the association.",
|
28
|
+
"",
|
29
|
+
"Consider defining a direct association or implementing",
|
30
|
+
"a custom scope '#{named_scope}' for better performance."
|
31
|
+
].join("\n")
|
32
|
+
end
|
33
|
+
return where(id: record.public_send(record_association.name))
|
34
|
+
end
|
35
|
+
|
36
|
+
klass.raise_unresolvable_association_error(record, named_scope)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class_methods do
|
41
|
+
def find_association_from_self_to_record(record)
|
42
|
+
reflect_on_all_associations.find do |assoc|
|
43
|
+
assoc.klass.name == record.class.name unless assoc.polymorphic?
|
44
|
+
rescue
|
45
|
+
assoc.check_validity!
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_association_to_self_from_record(record)
|
51
|
+
record.class.reflect_on_all_associations.find do |assoc|
|
52
|
+
assoc.klass.name == name
|
53
|
+
rescue
|
54
|
+
assoc.check_validity!
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def query_based_on_association(assoc, record)
|
60
|
+
case assoc.macro
|
61
|
+
when :has_one
|
62
|
+
joins(assoc.name).where(assoc.name => {record.class.primary_key => record.id})
|
63
|
+
when :belongs_to
|
64
|
+
where(assoc.name => record)
|
65
|
+
when :has_many
|
66
|
+
joins(assoc.name).where(assoc.klass.table_name => record)
|
67
|
+
else
|
68
|
+
raise NotImplementedError, "associated_with->##{assoc.macro}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def raise_unresolvable_association_error(record, named_scope)
|
73
|
+
raise "Could not resolve the association between '#{name}' and '#{record.class.name}'\n\n" \
|
74
|
+
"Define\n" \
|
75
|
+
" 1. the associations between the models\n" \
|
76
|
+
" 2. a named scope on #{name} e.g.\n\n" \
|
77
|
+
"scope :#{named_scope}, ->(#{record.model_name.singular}) { do_something_here }"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|