crudable-rails 1.3 → 1.5.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/README.md +194 -10
- data/Rakefile +4 -2
- data/lib/crudable/rails/base.rb +133 -19
- data/lib/crudable/rails/controller.rb +1 -2
- data/lib/crudable/rails/engine.rb +7 -1
- data/lib/crudable/rails/generators/scaffold_controller_extension.rb +55 -0
- data/lib/crudable/rails/generators/scaffold_extension.rb +29 -0
- data/lib/crudable/rails/generators/turbo_forms_generator_option.rb +16 -0
- data/lib/crudable/rails/generators/turbo_forms_scaffold_extension.rb +44 -0
- data/lib/crudable/rails/installer.rb +35 -0
- data/lib/crudable/rails/nestable.rb +78 -9
- data/lib/crudable/rails/resourceable.rb +29 -3
- data/lib/crudable/rails/version.rb +1 -1
- data/lib/crudable-rails.rb +8 -1
- data/lib/generators/crudable/controller/USAGE +23 -0
- data/lib/generators/crudable/controller/controller_generator.rb +19 -0
- data/lib/generators/crudable/controller_helpers.rb +58 -0
- data/lib/generators/crudable/install/USAGE +8 -0
- data/lib/generators/crudable/install/install_generator.rb +19 -0
- data/lib/generators/crudable/scaffold/USAGE +20 -0
- data/lib/generators/crudable/scaffold/scaffold_generator.rb +16 -0
- data/lib/generators/crudable/templates/crudable_controller.rb.tt +257 -0
- data/lib/generators/crudable/templates/turbo_forms/_form.html.erb.tt +37 -0
- data/lib/generators/crudable/templates/turbo_forms/edit.html.erb.tt +14 -0
- data/lib/generators/crudable/templates/turbo_forms/edit.turbo_stream.erb.tt +3 -0
- data/lib/generators/crudable/templates/turbo_forms/new.html.erb.tt +13 -0
- data/lib/generators/crudable/templates/turbo_forms/new.turbo_stream.erb.tt +3 -0
- data/lib/tasks/crudable_tasks.rake +14 -4
- metadata +35 -16
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'turbo_forms_generator_option'
|
|
4
|
+
require_relative '../../../generators/crudable/controller_helpers'
|
|
5
|
+
|
|
6
|
+
module Crudable
|
|
7
|
+
module Rails
|
|
8
|
+
module Generators
|
|
9
|
+
module TurboFormsScaffoldExtension
|
|
10
|
+
extend ActiveSupport::Concern
|
|
11
|
+
|
|
12
|
+
prepended do
|
|
13
|
+
include TurboFormsGeneratorOption
|
|
14
|
+
include ::Crudable::Generators::ControllerHelpers
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def copy_view_files
|
|
18
|
+
super
|
|
19
|
+
return unless options.turbo_forms?
|
|
20
|
+
|
|
21
|
+
copy_turbo_forms_view_files
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def copy_turbo_forms_view_files
|
|
27
|
+
view_path = File.join('app/views', controller_file_path)
|
|
28
|
+
|
|
29
|
+
template 'turbo_forms/_form.html.erb',
|
|
30
|
+
File.join(view_path, '_form.html.erb'),
|
|
31
|
+
force: true
|
|
32
|
+
|
|
33
|
+
%w[new edit].each do |view|
|
|
34
|
+
template "turbo_forms/#{view}.html.erb",
|
|
35
|
+
File.join(view_path, "#{view}.html.erb"),
|
|
36
|
+
force: true
|
|
37
|
+
template "turbo_forms/#{view}.turbo_stream.erb",
|
|
38
|
+
File.join(view_path, "#{view}.turbo_stream.erb")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crudable
|
|
4
|
+
module Rails
|
|
5
|
+
module Installer
|
|
6
|
+
CALLBACK_ACTIONS_FALSE =
|
|
7
|
+
/config\.action_controller\.raise_on_missing_callback_actions\s*=\s*false/
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def postinstall(root: ::Rails.root)
|
|
12
|
+
environments_path = root.join('config/environments')
|
|
13
|
+
return [] unless environments_path.directory?
|
|
14
|
+
|
|
15
|
+
environments_path.glob('*.rb').filter_map do |file|
|
|
16
|
+
update_raise_on_missing_callback_actions(file)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update_raise_on_missing_callback_actions(file)
|
|
21
|
+
contents = file.read
|
|
22
|
+
return unless contents.match?(CALLBACK_ACTIONS_FALSE)
|
|
23
|
+
|
|
24
|
+
updated_contents = contents.gsub(
|
|
25
|
+
CALLBACK_ACTIONS_FALSE,
|
|
26
|
+
'config.action_controller.raise_on_missing_callback_actions = true'
|
|
27
|
+
)
|
|
28
|
+
return if updated_contents == contents
|
|
29
|
+
|
|
30
|
+
file.write(updated_contents)
|
|
31
|
+
file.basename.to_s
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -7,20 +7,89 @@ module Crudable
|
|
|
7
7
|
extend ActiveSupport::Concern
|
|
8
8
|
|
|
9
9
|
included do
|
|
10
|
-
before_action :find_parent
|
|
10
|
+
before_action :find_parent
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def find_parent
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
return unless parent_present?
|
|
15
|
+
|
|
16
|
+
parent_scope = parent_class
|
|
17
|
+
parent_scope = parent_class.friendly if parent_friendly_finders?
|
|
18
|
+
instance_variable_set("@#{parent_var_name}", parent_scope.find(params[parent_id_param]))
|
|
19
|
+
self.class.send(:decorates_assigned, parent_var_name.to_sym) if defined?(Draper)
|
|
20
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
21
|
+
# Only catch and render 404 if we're in a request context
|
|
22
|
+
# Check if we have a request object (indicates we're in a request context)
|
|
23
|
+
raise e unless respond_to?(:request, true) && !request.nil?
|
|
24
|
+
|
|
25
|
+
render_not_found
|
|
26
|
+
nil # Explicitly stop execution to prevent further action execution
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parent_id_param
|
|
30
|
+
return nil unless use_parent_as_scope?
|
|
31
|
+
|
|
32
|
+
# Prefer route/path params (ordered by the route definition) over merged params
|
|
33
|
+
# (which may include query params).
|
|
34
|
+
# If multiple *_id params exist (multi-level nesting), default to the deepest/last one.
|
|
35
|
+
@parent_id_param ||= begin
|
|
36
|
+
keys = parent_id_param_keys
|
|
37
|
+
keys.map(&:to_s).grep(/(.+)_id$/).last
|
|
22
38
|
end
|
|
23
39
|
end
|
|
40
|
+
|
|
41
|
+
def parent_class
|
|
42
|
+
@parent_class ||= parent_var_name.classify.constantize
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parent_var_name
|
|
46
|
+
@parent_var_name ||= parent_id_param.sub(/_id$/, '').underscore
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def parent_present?
|
|
50
|
+
!parent_id_param.nil?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def parent_resource_association
|
|
54
|
+
return unless parent_present?
|
|
55
|
+
|
|
56
|
+
# Handle case where resource_class is a CollectionProxy (association proxy)
|
|
57
|
+
# Extract the actual class for comparison
|
|
58
|
+
actual_resource_class = if resource_class.is_a?(ActiveRecord::Associations::CollectionProxy)
|
|
59
|
+
resource_class.klass
|
|
60
|
+
else
|
|
61
|
+
resource_class
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
association = parent_class.reflect_on_all_associations.find { |assoc| assoc.klass == actual_resource_class }
|
|
65
|
+
association&.name
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# By default, if friendly finders are enabled for the resource, we will also attempt to use
|
|
69
|
+
# friendly finders for the parent (when available).
|
|
70
|
+
#
|
|
71
|
+
# Override this in your controller if you want to force parent lookup to use (or not use)
|
|
72
|
+
# FriendlyId, independently of the resource.
|
|
73
|
+
def parent_friendly_finders?
|
|
74
|
+
return false unless defined?(FriendlyId)
|
|
75
|
+
return false unless parent_class.respond_to?(:friendly)
|
|
76
|
+
|
|
77
|
+
friendly_finders?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def use_parent_as_scope?
|
|
81
|
+
true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def parent_id_param_keys
|
|
85
|
+
return request.path_parameters.keys if request_path_parameters_present?
|
|
86
|
+
|
|
87
|
+
params&.keys || []
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def request_path_parameters_present?
|
|
91
|
+
request.respond_to?(:path_parameters) && request.path_parameters.present?
|
|
92
|
+
end
|
|
24
93
|
end
|
|
25
94
|
end
|
|
26
95
|
end
|
|
@@ -31,15 +31,41 @@ module Crudable
|
|
|
31
31
|
def resource_namespace
|
|
32
32
|
namespaces = self.class.name.split('::')
|
|
33
33
|
namespaces.pop
|
|
34
|
-
namespaces.map { |n| n.
|
|
34
|
+
namespaces.map { |n| n.underscore.to_sym }
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def authorize_resource(method = action_name)
|
|
38
|
-
|
|
38
|
+
return unless defined?(Pundit)
|
|
39
|
+
# Check if Pundit::Authorization is included (check class, ancestors, and respond_to)
|
|
40
|
+
return unless pundit_authorization_available?
|
|
41
|
+
|
|
42
|
+
authorize resource_namespace + [authorizable_resource], :"#{method}?"
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
def authorize_class_action(method = action_name)
|
|
42
|
-
|
|
46
|
+
return unless defined?(Pundit)
|
|
47
|
+
# Check if Pundit::Authorization is included (check class, ancestors, and respond_to)
|
|
48
|
+
return unless pundit_authorization_available?
|
|
49
|
+
|
|
50
|
+
authorize([*resource_namespace, resource_class], :"#{method}?")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def pundit_authorization_available?
|
|
54
|
+
return false unless defined?(Pundit)
|
|
55
|
+
return false unless defined?(Pundit::Authorization)
|
|
56
|
+
# Check if included in this class
|
|
57
|
+
return true if self.class.included_modules.include?(Pundit::Authorization)
|
|
58
|
+
|
|
59
|
+
# Check if included in any ancestor (Class or Module)
|
|
60
|
+
# This handles cases where Pundit::Authorization is included in ApplicationController
|
|
61
|
+
ancestors_to_check = self.class.ancestors.select { |a| a.is_a?(Class) || a.is_a?(Module) }
|
|
62
|
+
return true if ancestors_to_check.any? { |ancestor| ancestor.included_modules.include?(Pundit::Authorization) }
|
|
63
|
+
# Fallback: check if authorize method is available (works in most cases)
|
|
64
|
+
# Use method_defined? for more reliable check than respond_to?
|
|
65
|
+
return true if self.class.method_defined?(:authorize) || self.class.private_method_defined?(:authorize)
|
|
66
|
+
|
|
67
|
+
# Last resort: respond_to check
|
|
68
|
+
respond_to?(:authorize, true)
|
|
43
69
|
end
|
|
44
70
|
|
|
45
71
|
def authorizable_resource
|
data/lib/crudable-rails.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'turbo-rails'
|
|
2
4
|
|
|
3
5
|
require 'crudable/rails/version'
|
|
6
|
+
require 'crudable/rails/installer'
|
|
4
7
|
require 'crudable/rails/engine'
|
|
5
8
|
require 'crudable/rails/controller'
|
|
6
9
|
require 'crudable/rails/base'
|
|
@@ -10,7 +13,11 @@ require 'crudable/rails/nestable'
|
|
|
10
13
|
module Crudable
|
|
11
14
|
module Rails
|
|
12
15
|
def self.deprecator
|
|
13
|
-
@deprecator ||= ActiveSupport::Deprecation.new(
|
|
16
|
+
@deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Crudable::Rails')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.postinstall(**)
|
|
20
|
+
Installer.postinstall(**)
|
|
14
21
|
end
|
|
15
22
|
end
|
|
16
23
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Generates a Crudable controller with documented override hooks.
|
|
3
|
+
|
|
4
|
+
Pass the model name, either CamelCased or under_scored. The controller name
|
|
5
|
+
is retrieved as a pluralized version of the model name.
|
|
6
|
+
|
|
7
|
+
To create a controller within a module, specify the model name as a path like
|
|
8
|
+
'parent_module/controller_name'.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
bin/rails generate crudable:controller Post title:string body:text
|
|
12
|
+
|
|
13
|
+
This will create:
|
|
14
|
+
Controller: app/controllers/posts_controller.rb
|
|
15
|
+
|
|
16
|
+
Notes:
|
|
17
|
+
The generated controller includes `crudable` and a `permitted_params` method.
|
|
18
|
+
All other customization hooks are included as commented examples with their
|
|
19
|
+
defaults. Uncomment and tailor as needed.
|
|
20
|
+
|
|
21
|
+
Nested routing is automatically supported: if a parent `*_id` param is present
|
|
22
|
+
and an association exists between the parent and resource, collections and
|
|
23
|
+
resources will be scoped through the parent.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../controller_helpers'
|
|
4
|
+
|
|
5
|
+
module Crudable
|
|
6
|
+
class ControllerGenerator < ::Rails::Generators::NamedBase
|
|
7
|
+
include ::Rails::Generators::ResourceHelpers
|
|
8
|
+
include Crudable::Generators::ControllerHelpers
|
|
9
|
+
|
|
10
|
+
argument :attributes, type: :array, default: [], banner: 'field:type field:type'
|
|
11
|
+
|
|
12
|
+
check_class_collision suffix: 'Controller'
|
|
13
|
+
|
|
14
|
+
def create_controller_file
|
|
15
|
+
template 'crudable_controller.rb',
|
|
16
|
+
File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crudable
|
|
4
|
+
module Generators
|
|
5
|
+
module ControllerHelpers
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
def crudable_template_root
|
|
10
|
+
@crudable_template_root ||= File.expand_path('templates', __dir__)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
included do
|
|
15
|
+
source_paths << crudable_template_root
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def permitted_params_list
|
|
21
|
+
attachments, others = attributes_names.partition { |name| attachments?(name) }
|
|
22
|
+
params = others.map { |name| ":#{name}" }
|
|
23
|
+
params += attachments.map { |name| "#{name}: []" }
|
|
24
|
+
params.join(', ')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def attachments?(name)
|
|
28
|
+
attribute = attributes.find { |attr| attr.name == name }
|
|
29
|
+
attribute&.attachments?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reference_attributes
|
|
33
|
+
attributes.select { |attr| attr.respond_to?(:reference?) && attr.reference? }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def form_attributes
|
|
37
|
+
return attributes if reference_attributes.empty?
|
|
38
|
+
|
|
39
|
+
attributes.reject { |attr| attr.respond_to?(:reference?) && attr.reference? }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def form_with_model
|
|
43
|
+
segments = []
|
|
44
|
+
segments.concat(controller_class_path.map { |namespace| ":#{namespace}" }) if controller_class_path.any?
|
|
45
|
+
|
|
46
|
+
reference_attributes.each do |attribute|
|
|
47
|
+
segments << "@#{attribute.name}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
segments << "@#{singular_table_name}"
|
|
51
|
+
|
|
52
|
+
return segments.first if segments.size == 1
|
|
53
|
+
|
|
54
|
+
"[#{segments.join(', ')}]"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Installs crudable-rails configuration in the host application.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
bin/rails generate crudable:install
|
|
6
|
+
|
|
7
|
+
This will set config.action_controller.raise_on_missing_callback_actions
|
|
8
|
+
to true in any environment file that currently sets it to false.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Crudable
|
|
4
|
+
module Generators
|
|
5
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
6
|
+
desc 'Install crudable-rails configuration'
|
|
7
|
+
|
|
8
|
+
def postinstall
|
|
9
|
+
updated = ::Crudable::Rails::Installer.postinstall
|
|
10
|
+
|
|
11
|
+
if updated.any?
|
|
12
|
+
say "Set raise_on_missing_callback_actions to true in: #{updated.join(', ')}", :green
|
|
13
|
+
else
|
|
14
|
+
say 'No environment files with raise_on_missing_callback_actions = false were found.', :yellow
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Generates a full Rails scaffold (model, migration, views, routes, tests) with a
|
|
3
|
+
Crudable controller instead of the default scaffold controller.
|
|
4
|
+
|
|
5
|
+
Pass the model name, either CamelCased or under_scored, and optional attributes.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
bin/rails generate crudable:scaffold Post title:string body:text
|
|
9
|
+
bin/rails generate crudable:scaffold Post title:string body:text --turbo-forms
|
|
10
|
+
|
|
11
|
+
This will create the same files as `rails generate scaffold`, but the controller
|
|
12
|
+
will use `crudable` with documented override hooks.
|
|
13
|
+
|
|
14
|
+
With --turbo-forms, new and edit views wrap the form partial in a dom_id target
|
|
15
|
+
and generate matching turbo_stream templates for validation error responses.
|
|
16
|
+
|
|
17
|
+
Notes:
|
|
18
|
+
Nested routing is automatically supported: if a parent `*_id` param is present
|
|
19
|
+
and an association exists between the parent and resource, collections and
|
|
20
|
+
resources will be scoped through the parent.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/rails/scaffold/scaffold_generator'
|
|
4
|
+
require_relative '../controller_helpers'
|
|
5
|
+
|
|
6
|
+
module Crudable
|
|
7
|
+
class ScaffoldGenerator < ::Rails::Generators::ScaffoldGenerator
|
|
8
|
+
include Crudable::Generators::ControllerHelpers
|
|
9
|
+
|
|
10
|
+
def crudable_controller
|
|
11
|
+
template 'crudable_controller.rb',
|
|
12
|
+
File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb"),
|
|
13
|
+
force: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
<% module_namespacing do -%>
|
|
2
|
+
class <%= controller_class_name %>Controller < ApplicationController
|
|
3
|
+
crudable
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# Optional overrides (self-documenting hooks)
|
|
9
|
+
#
|
|
10
|
+
# `crudable` mixes in `Crudable::Rails::Base` (which includes `Resourceable`
|
|
11
|
+
# and `Nestable`), which provides the REST actions and calls these hooks.
|
|
12
|
+
# Uncomment and tailor any of the following as needed.
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
#
|
|
15
|
+
# # Override the model class when it cannot be inferred from the controller name.
|
|
16
|
+
# def resource_class
|
|
17
|
+
# <%= class_name %>
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # Customize the record passed to Pundit (e.g. namespaced authorization).
|
|
21
|
+
# def authorizable_resource
|
|
22
|
+
# @<%= singular_table_name %>
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# # If this resource is singular (e.g. SettingsController), return true.
|
|
26
|
+
# def singleton?
|
|
27
|
+
# false
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# # Which param key to use when finding a member resource.
|
|
31
|
+
# def finder_param
|
|
32
|
+
# :id
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# # Enable FriendlyId finders/redirects.
|
|
36
|
+
# def friendly_finders?
|
|
37
|
+
# return false unless defined?(FriendlyId) && resource_class.respond_to?(:friendly)
|
|
38
|
+
#
|
|
39
|
+
# true
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# # Nested parent lookup: use FriendlyId for the parent when available.
|
|
43
|
+
# def parent_friendly_finders?
|
|
44
|
+
# return false unless defined?(FriendlyId)
|
|
45
|
+
# return false unless parent_class.respond_to?(:friendly)
|
|
46
|
+
#
|
|
47
|
+
# friendly_finders?
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# # Enable pagination (requires Kaminari).
|
|
51
|
+
# def paginate_resource?
|
|
52
|
+
# return false unless defined?(Kaminari)
|
|
53
|
+
#
|
|
54
|
+
# true
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# # Customize the base scope for index (before HasScope and Pundit).
|
|
58
|
+
# def find_scope
|
|
59
|
+
# if parent_resource_association.present?
|
|
60
|
+
# instance_variable_get("@#{parent_var_name}").send(parent_resource_association)
|
|
61
|
+
# else
|
|
62
|
+
# <%= class_name %>
|
|
63
|
+
# end
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# # Customize the scope used for index. Primary hook for filtering.
|
|
67
|
+
# # (HasScope is applied automatically if present.)
|
|
68
|
+
# def authorizable_scope
|
|
69
|
+
# scope = find_scope
|
|
70
|
+
# return scope.all unless authorize_with_pundit?
|
|
71
|
+
# return scope.all unless pundit_authorization_available?
|
|
72
|
+
#
|
|
73
|
+
# policy_scope(resource_namespace + [scope])
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# # Full scope used for index (after HasScope, Pundit, etc).
|
|
77
|
+
# def resource_scope
|
|
78
|
+
# return apply_scopes(authorizable_scope) if defined?(HasScope)
|
|
79
|
+
#
|
|
80
|
+
# authorizable_scope
|
|
81
|
+
# end
|
|
82
|
+
#
|
|
83
|
+
# # Skip initializing a new instance in create (useful for custom create flows).
|
|
84
|
+
# def skip_initialize_create?
|
|
85
|
+
# false
|
|
86
|
+
# end
|
|
87
|
+
#
|
|
88
|
+
# # Discard/soft-delete support (requires Discard). When true, destroy will
|
|
89
|
+
# # choose discard vs destroy based on record state and params.
|
|
90
|
+
# def discard?
|
|
91
|
+
# false
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
# # Control whether Pundit authorization should be performed.
|
|
95
|
+
# def authorize_with_pundit?
|
|
96
|
+
# defined?(Pundit)
|
|
97
|
+
# end
|
|
98
|
+
#
|
|
99
|
+
# # Authorization lifecycle hooks (called inside create/update).
|
|
100
|
+
# def before_authorize_create; end
|
|
101
|
+
# def after_authorize_create; end
|
|
102
|
+
# def before_authorize_update; end
|
|
103
|
+
# def after_authorize_update; end
|
|
104
|
+
#
|
|
105
|
+
# # Post-success hooks (side-effects, instrumentation, etc).
|
|
106
|
+
# def on_successful_create
|
|
107
|
+
# flash[:success] = after_create_notice
|
|
108
|
+
# end
|
|
109
|
+
#
|
|
110
|
+
# def on_successful_update
|
|
111
|
+
# flash[:success] = after_update_notice
|
|
112
|
+
# end
|
|
113
|
+
#
|
|
114
|
+
# def on_successful_destroy
|
|
115
|
+
# flash[:success] = after_destroy_notice
|
|
116
|
+
# end
|
|
117
|
+
#
|
|
118
|
+
# # Failure setup hooks (e.g., rebuild form collections).
|
|
119
|
+
# def on_failed_create_setup; end
|
|
120
|
+
# def on_failed_update_setup; end
|
|
121
|
+
#
|
|
122
|
+
# def on_failed_destroy
|
|
123
|
+
# flash[:alert] = after_failed_destroy_alert
|
|
124
|
+
# end
|
|
125
|
+
#
|
|
126
|
+
# # Customize success redirects/flash messages.
|
|
127
|
+
# def after_create_redirect_path
|
|
128
|
+
# if respond_to?(:parent_present?) && parent_present? && instance_variable_get("@#{parent_var_name}")
|
|
129
|
+
# parent = instance_variable_get("@#{parent_var_name}")
|
|
130
|
+
# resource_namespace + [parent, :<%= plural_table_name %>]
|
|
131
|
+
# else
|
|
132
|
+
# resource_namespace + [:<%= plural_table_name %>]
|
|
133
|
+
# end
|
|
134
|
+
# rescue NoMethodError
|
|
135
|
+
# resource_namespace + [:<%= plural_table_name %>]
|
|
136
|
+
# end
|
|
137
|
+
#
|
|
138
|
+
# def after_create_notice
|
|
139
|
+
# t('crudable.created', model_name: <%= class_name %>.model_name.human)
|
|
140
|
+
# end
|
|
141
|
+
#
|
|
142
|
+
# def after_update_redirect_path
|
|
143
|
+
# resource_namespace + [@<%= singular_table_name %>]
|
|
144
|
+
# end
|
|
145
|
+
#
|
|
146
|
+
# def after_update_notice
|
|
147
|
+
# t('crudable.updated', model_name: <%= class_name %>.model_name.human)
|
|
148
|
+
# end
|
|
149
|
+
#
|
|
150
|
+
# def after_destroy_redirect_path
|
|
151
|
+
# resource_namespace + [:<%= plural_table_name %>]
|
|
152
|
+
# end
|
|
153
|
+
#
|
|
154
|
+
# def after_failed_destroy_redirect_path
|
|
155
|
+
# after_destroy_redirect_path
|
|
156
|
+
# end
|
|
157
|
+
#
|
|
158
|
+
# def after_destroy_notice
|
|
159
|
+
# t('crudable.destroyed', model_name: <%= class_name %>.model_name.human)
|
|
160
|
+
# end
|
|
161
|
+
#
|
|
162
|
+
# def after_failed_destroy_alert
|
|
163
|
+
# t('crudable.not_destroyed', model_name: <%= class_name %>.model_name.human)
|
|
164
|
+
# end
|
|
165
|
+
#
|
|
166
|
+
# # Customize rendering for success/failure outcomes (Turbo/HTML).
|
|
167
|
+
# def on_successful_create_render
|
|
168
|
+
# redirect_to after_create_redirect_path
|
|
169
|
+
# end
|
|
170
|
+
#
|
|
171
|
+
# def on_failed_create_render
|
|
172
|
+
# resource = @<%= singular_table_name %>
|
|
173
|
+
# @<%= singular_table_name %> = new_instance if resource.nil?
|
|
174
|
+
# respond_to do |format|
|
|
175
|
+
# format.turbo_stream { render_action(:new) }
|
|
176
|
+
# format.html { render :new, status: :unprocessable_content }
|
|
177
|
+
# end
|
|
178
|
+
# end
|
|
179
|
+
#
|
|
180
|
+
# def on_successful_update_render
|
|
181
|
+
# redirect_to after_update_redirect_path
|
|
182
|
+
# end
|
|
183
|
+
#
|
|
184
|
+
# def on_failed_update_render
|
|
185
|
+
# respond_to do |format|
|
|
186
|
+
# format.turbo_stream { render_action(:edit) }
|
|
187
|
+
# format.html { render :edit, status: :unprocessable_content }
|
|
188
|
+
# end
|
|
189
|
+
# end
|
|
190
|
+
#
|
|
191
|
+
# def on_successful_destroy_render
|
|
192
|
+
# redirect_to after_destroy_redirect_path
|
|
193
|
+
# end
|
|
194
|
+
#
|
|
195
|
+
# def on_failed_destroy_render
|
|
196
|
+
# redirect_to after_failed_destroy_redirect_path
|
|
197
|
+
# end
|
|
198
|
+
#
|
|
199
|
+
# # Customize pagination when enabled.
|
|
200
|
+
# def paginate_resource
|
|
201
|
+
# return unless paginate_resource?
|
|
202
|
+
#
|
|
203
|
+
# @<%= plural_table_name %> = @<%= plural_table_name %>.page(params[:page]).per(params[:per])
|
|
204
|
+
# end
|
|
205
|
+
#
|
|
206
|
+
# # Customize 404 responses.
|
|
207
|
+
# def render_not_found
|
|
208
|
+
# head :not_found
|
|
209
|
+
# end
|
|
210
|
+
#
|
|
211
|
+
# # Nested resources: parent scoping is automatic when a `*_id` param is present.
|
|
212
|
+
# def use_parent_as_scope?
|
|
213
|
+
# true
|
|
214
|
+
# end
|
|
215
|
+
#
|
|
216
|
+
# # Nested resources: customize which `*_id` param is treated as the parent id.
|
|
217
|
+
# def parent_id_param
|
|
218
|
+
# return nil unless use_parent_as_scope?
|
|
219
|
+
#
|
|
220
|
+
# @parent_id_param ||= begin
|
|
221
|
+
# keys = parent_id_param_keys
|
|
222
|
+
# keys.map(&:to_s).grep(/(.+)_id$/).last
|
|
223
|
+
# end
|
|
224
|
+
# end
|
|
225
|
+
#
|
|
226
|
+
# # Override when create and update need different permitted attributes.
|
|
227
|
+
# def create_params
|
|
228
|
+
# resource_params
|
|
229
|
+
# end
|
|
230
|
+
#
|
|
231
|
+
# def update_params
|
|
232
|
+
# resource_params
|
|
233
|
+
# end
|
|
234
|
+
#
|
|
235
|
+
# # Strong params API used by `resource_params`.
|
|
236
|
+
# def param_method
|
|
237
|
+
# @param_method ||= ActionController::Parameters.method_defined?(:expect) ? :expect : :require
|
|
238
|
+
# end
|
|
239
|
+
#
|
|
240
|
+
# # def resource_params
|
|
241
|
+
# # if param_method == :expect
|
|
242
|
+
# # params.expect(<%= singular_table_name %>: permitted_params)
|
|
243
|
+
# # else
|
|
244
|
+
# # params.require(:<%= singular_table_name %>).permit(*permitted_params)
|
|
245
|
+
# # end
|
|
246
|
+
# # end
|
|
247
|
+
#
|
|
248
|
+
# Required: return the attributes permitted for create/update.
|
|
249
|
+
def permitted_params
|
|
250
|
+
<%- if attributes_names.empty? -%>
|
|
251
|
+
[]
|
|
252
|
+
<%- else -%>
|
|
253
|
+
[<%= permitted_params_list %>]
|
|
254
|
+
<%- end -%>
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
<% end -%>
|