plutonium 0.18.5 → 0.18.7
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/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/models/has_cents.rb +4 -2
- 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
|
@@ -143,8 +143,10 @@ module Plutonium
|
|
143
143
|
#
|
144
144
|
# @param value [Numeric, nil] The decimal value to be set.
|
145
145
|
def #{name}=(value)
|
146
|
-
self.#{cents_name} =
|
147
|
-
(BigDecimal(value.to_s) * #{rate}).to_i
|
146
|
+
self.#{cents_name} = begin
|
147
|
+
(BigDecimal(value.to_s) * #{rate}).to_i if value.present?
|
148
|
+
rescue ArgumentError
|
149
|
+
nil
|
148
150
|
end
|
149
151
|
end
|
150
152
|
|
@@ -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
|