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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +194 -10
  3. data/Rakefile +4 -2
  4. data/lib/crudable/rails/base.rb +133 -19
  5. data/lib/crudable/rails/controller.rb +1 -2
  6. data/lib/crudable/rails/engine.rb +7 -1
  7. data/lib/crudable/rails/generators/scaffold_controller_extension.rb +55 -0
  8. data/lib/crudable/rails/generators/scaffold_extension.rb +29 -0
  9. data/lib/crudable/rails/generators/turbo_forms_generator_option.rb +16 -0
  10. data/lib/crudable/rails/generators/turbo_forms_scaffold_extension.rb +44 -0
  11. data/lib/crudable/rails/installer.rb +35 -0
  12. data/lib/crudable/rails/nestable.rb +78 -9
  13. data/lib/crudable/rails/resourceable.rb +29 -3
  14. data/lib/crudable/rails/version.rb +1 -1
  15. data/lib/crudable-rails.rb +8 -1
  16. data/lib/generators/crudable/controller/USAGE +23 -0
  17. data/lib/generators/crudable/controller/controller_generator.rb +19 -0
  18. data/lib/generators/crudable/controller_helpers.rb +58 -0
  19. data/lib/generators/crudable/install/USAGE +8 -0
  20. data/lib/generators/crudable/install/install_generator.rb +19 -0
  21. data/lib/generators/crudable/scaffold/USAGE +20 -0
  22. data/lib/generators/crudable/scaffold/scaffold_generator.rb +16 -0
  23. data/lib/generators/crudable/templates/crudable_controller.rb.tt +257 -0
  24. data/lib/generators/crudable/templates/turbo_forms/_form.html.erb.tt +37 -0
  25. data/lib/generators/crudable/templates/turbo_forms/edit.html.erb.tt +14 -0
  26. data/lib/generators/crudable/templates/turbo_forms/edit.turbo_stream.erb.tt +3 -0
  27. data/lib/generators/crudable/templates/turbo_forms/new.html.erb.tt +13 -0
  28. data/lib/generators/crudable/templates/turbo_forms/new.turbo_stream.erb.tt +3 -0
  29. data/lib/tasks/crudable_tasks.rake +14 -4
  30. 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, only: %i[index new create]
10
+ before_action :find_parent
11
11
  end
12
12
 
13
13
  def find_parent
14
- params.each do |name, value|
15
- next unless name =~ /(.+)_id$/
16
-
17
- object_name = Regexp.last_match(1)
18
- object_scope = object_name.classify.constantize
19
- object_scope = object_scope.friendly if friendly_finders?
20
- instance_variable_set("@#{object_name}", object_scope.find(value))
21
- self.class.send(:decorates_assigned, object_name.to_sym) if defined?(Draper)
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.downcase.to_sym }
34
+ namespaces.map { |n| n.underscore.to_sym }
35
35
  end
36
36
 
37
37
  def authorize_resource(method = action_name)
38
- authorize resource_namespace + [authorizable_resource], "#{method}?".to_sym
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
- authorize([*resource_namespace, resource_class], "#{method}?".to_sym)
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
@@ -3,6 +3,6 @@
3
3
  # Crudable Version
4
4
  module Crudable
5
5
  module Rails
6
- VERSION = '1.3'
6
+ VERSION = '1.5.0'
7
7
  end
8
8
  end
@@ -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("2.0", "Crudable::Rails")
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 -%>