active_scaffold 3.5.2 → 3.6.0.rc1

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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGELOG → CHANGELOG.rdoc} +66 -0
  3. data/README.md +17 -7
  4. data/app/assets/javascripts/active_scaffold.js.erb +0 -1
  5. data/app/assets/javascripts/jquery/active_scaffold.js +63 -6
  6. data/app/assets/stylesheets/active_scaffold_colors.scss +1 -1
  7. data/app/assets/stylesheets/active_scaffold_layout.css +52 -29
  8. data/app/views/active_scaffold_overrides/_base_form.html.erb +2 -2
  9. data/app/views/active_scaffold_overrides/_form.html.erb +1 -1
  10. data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +3 -2
  11. data/app/views/active_scaffold_overrides/_form_association_record.html.erb +6 -6
  12. data/app/views/active_scaffold_overrides/_horizontal_subform.html.erb +1 -1
  13. data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +1 -1
  14. data/app/views/active_scaffold_overrides/_list.html.erb +2 -1
  15. data/app/views/active_scaffold_overrides/_list_header.html.erb +5 -7
  16. data/app/views/active_scaffold_overrides/_list_messages.html.erb +1 -0
  17. data/app/views/active_scaffold_overrides/_list_record.html.erb +4 -5
  18. data/app/views/active_scaffold_overrides/_list_with_header.html.erb +1 -1
  19. data/app/views/active_scaffold_overrides/_messages.html.erb +1 -0
  20. data/app/views/active_scaffold_overrides/_refresh_list.js.erb +4 -0
  21. data/app/views/active_scaffold_overrides/_render_field.js.erb +2 -1
  22. data/app/views/active_scaffold_overrides/_show_association_horizontal.html.erb +2 -1
  23. data/app/views/active_scaffold_overrides/_show_columns.html.erb +2 -2
  24. data/app/views/active_scaffold_overrides/_show_horizontal_record.html.erb +4 -4
  25. data/app/views/active_scaffold_overrides/_update_calculations.js.erb +1 -1
  26. data/app/views/active_scaffold_overrides/_update_column.js.erb +2 -2
  27. data/app/views/active_scaffold_overrides/action_confirmation.html.erb +2 -2
  28. data/app/views/active_scaffold_overrides/delete.html.erb +2 -2
  29. data/app/views/active_scaffold_overrides/on_action_update.js.erb +16 -6
  30. data/app/views/active_scaffold_overrides/on_update.js.erb +1 -1
  31. data/app/views/active_scaffold_overrides/row.js.erb +1 -1
  32. data/app/views/active_scaffold_overrides/update_column.js.erb +1 -1
  33. data/config/locales/de.yml +2 -1
  34. data/config/locales/en.yml +1 -0
  35. data/config/locales/es.yml +1 -0
  36. data/config/locales/fr.yml +2 -1
  37. data/config/locales/hu.yml +1 -0
  38. data/config/locales/ja.yml +1 -0
  39. data/config/locales/ru.yml +1 -0
  40. data/lib/active_scaffold.rb +19 -16
  41. data/lib/active_scaffold/actions/common_search.rb +11 -8
  42. data/lib/active_scaffold/actions/core.rb +89 -71
  43. data/lib/active_scaffold/actions/create.rb +28 -28
  44. data/lib/active_scaffold/actions/delete.rb +3 -3
  45. data/lib/active_scaffold/actions/field_search.rb +53 -43
  46. data/lib/active_scaffold/actions/list.rb +111 -27
  47. data/lib/active_scaffold/actions/nested.rb +65 -48
  48. data/lib/active_scaffold/actions/search.rb +1 -1
  49. data/lib/active_scaffold/actions/show.rb +4 -4
  50. data/lib/active_scaffold/actions/subform.rb +12 -17
  51. data/lib/active_scaffold/actions/update.rb +96 -77
  52. data/lib/active_scaffold/active_record_permissions.rb +2 -11
  53. data/lib/active_scaffold/attribute_params.rb +104 -86
  54. data/lib/active_scaffold/bridges.rb +8 -8
  55. data/lib/active_scaffold/bridges/active_storage.rb +6 -0
  56. data/lib/active_scaffold/bridges/active_storage/active_storage_bridge.rb +33 -0
  57. data/lib/active_scaffold/bridges/active_storage/active_storage_helpers.rb +54 -0
  58. data/lib/active_scaffold/bridges/active_storage/form_ui.rb +22 -0
  59. data/lib/active_scaffold/bridges/active_storage/list_ui.rb +36 -0
  60. data/lib/active_scaffold/bridges/bitfields.rb +1 -0
  61. data/lib/active_scaffold/bridges/bitfields/bitfields_bridge.rb +12 -15
  62. data/lib/active_scaffold/bridges/calendar_date_select/as_cds_bridge.rb +1 -1
  63. data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +9 -12
  64. data/lib/active_scaffold/bridges/carrierwave/carrierwave_bridge.rb +1 -1
  65. data/lib/active_scaffold/bridges/carrierwave/list_ui.rb +2 -2
  66. data/lib/active_scaffold/bridges/chosen/helpers.rb +11 -9
  67. data/lib/active_scaffold/bridges/date_picker/ext.rb +0 -13
  68. data/lib/active_scaffold/bridges/date_picker/helper.rb +49 -44
  69. data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +1 -1
  70. data/lib/active_scaffold/bridges/file_column/as_file_column_bridge.rb +1 -1
  71. data/lib/active_scaffold/bridges/file_column/file_column_helpers.rb +3 -3
  72. data/lib/active_scaffold/bridges/file_column/form_ui.rb +3 -3
  73. data/lib/active_scaffold/bridges/file_column/test/functional/file_column_keep_test.rb +10 -7
  74. data/lib/active_scaffold/bridges/paper_trail.rb +1 -1
  75. data/lib/active_scaffold/bridges/paperclip/list_ui.rb +1 -1
  76. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge.rb +1 -1
  77. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge_helpers.rb +2 -2
  78. data/lib/active_scaffold/bridges/record_select/helpers.rb +12 -16
  79. data/lib/active_scaffold/bridges/shared/date_bridge.rb +20 -19
  80. data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +3 -1
  81. data/lib/active_scaffold/bridges/usa_state_select/usa_state_select_helper.rb +21 -4
  82. data/lib/active_scaffold/config/base.rb +133 -41
  83. data/lib/active_scaffold/config/core.rb +146 -18
  84. data/lib/active_scaffold/config/delete.rb +14 -1
  85. data/lib/active_scaffold/config/field_search.rb +7 -1
  86. data/lib/active_scaffold/config/form.rb +10 -1
  87. data/lib/active_scaffold/config/list.rb +39 -13
  88. data/lib/active_scaffold/config/mark.rb +4 -2
  89. data/lib/active_scaffold/config/nested.rb +16 -17
  90. data/lib/active_scaffold/config/search.rb +9 -0
  91. data/lib/active_scaffold/config/show.rb +4 -0
  92. data/lib/active_scaffold/config/update.rb +4 -0
  93. data/lib/active_scaffold/configurable.rb +14 -7
  94. data/lib/active_scaffold/constraints.rb +22 -20
  95. data/lib/active_scaffold/core.rb +68 -29
  96. data/lib/active_scaffold/data_structures/action_columns.rb +50 -59
  97. data/lib/active_scaffold/data_structures/action_link.rb +50 -20
  98. data/lib/active_scaffold/data_structures/action_links.rb +15 -13
  99. data/lib/active_scaffold/data_structures/association/abstract.rb +41 -15
  100. data/lib/active_scaffold/data_structures/association/active_mongoid.rb +2 -6
  101. data/lib/active_scaffold/data_structures/association/active_record.rb +6 -2
  102. data/lib/active_scaffold/data_structures/association/mongoid.rb +0 -3
  103. data/lib/active_scaffold/data_structures/column.rb +75 -66
  104. data/lib/active_scaffold/data_structures/columns.rb +3 -2
  105. data/lib/active_scaffold/data_structures/nested_info.rb +21 -19
  106. data/lib/active_scaffold/data_structures/set.rb +8 -0
  107. data/lib/active_scaffold/data_structures/sorting.rb +10 -2
  108. data/lib/active_scaffold/delayed_setup.rb +16 -5
  109. data/lib/active_scaffold/extensions/action_controller_rendering.rb +3 -2
  110. data/lib/active_scaffold/extensions/action_view_rendering.rb +34 -14
  111. data/lib/active_scaffold/extensions/cow_proxy.rb +91 -0
  112. data/lib/active_scaffold/extensions/ice_nine.rb +36 -0
  113. data/lib/active_scaffold/extensions/left_outer_joins.rb +8 -33
  114. data/lib/active_scaffold/extensions/localize.rb +3 -1
  115. data/lib/active_scaffold/extensions/routing_mapper.rb +6 -45
  116. data/lib/active_scaffold/extensions/to_label.rb +3 -2
  117. data/lib/active_scaffold/extensions/unsaved_record.rb +2 -4
  118. data/lib/active_scaffold/finder.rb +104 -73
  119. data/lib/active_scaffold/helpers/action_link_helpers.rb +62 -36
  120. data/lib/active_scaffold/helpers/association_helpers.rb +21 -19
  121. data/lib/active_scaffold/helpers/controller_helpers.rb +23 -10
  122. data/lib/active_scaffold/helpers/form_column_helpers.rb +157 -121
  123. data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -1
  124. data/lib/active_scaffold/helpers/id_helpers.rb +6 -2
  125. data/lib/active_scaffold/helpers/list_column_helpers.rb +82 -53
  126. data/lib/active_scaffold/helpers/pagination_helpers.rb +2 -2
  127. data/lib/active_scaffold/helpers/search_column_helpers.rb +29 -34
  128. data/lib/active_scaffold/helpers/show_column_helpers.rb +3 -5
  129. data/lib/active_scaffold/helpers/view_helpers.rb +38 -35
  130. data/lib/active_scaffold/marked_model.rb +2 -2
  131. data/lib/active_scaffold/orm_checks.rb +3 -7
  132. data/lib/active_scaffold/paginator.rb +7 -7
  133. data/lib/active_scaffold/registry.rb +33 -0
  134. data/lib/active_scaffold/responds_to_parent.rb +8 -11
  135. data/lib/active_scaffold/tableless.rb +67 -65
  136. data/lib/active_scaffold/version.rb +2 -2
  137. data/lib/generators/active_scaffold/controller_generator.rb +2 -2
  138. data/lib/generators/active_scaffold/install_generator.rb +1 -1
  139. data/lib/generators/active_scaffold/resource_generator.rb +2 -2
  140. data/shoulda_macros/macros.rb +3 -1
  141. data/test/bridges/date_picker_test.rb +1 -2
  142. data/test/bridges/paperclip_test.rb +6 -6
  143. data/test/class_with_finder.rb +2 -2
  144. data/test/company.rb +4 -4
  145. data/test/config/create_test.rb +4 -2
  146. data/test/config/nested_test.rb +1 -1
  147. data/test/config/show_test.rb +1 -1
  148. data/test/config/update_test.rb +7 -6
  149. data/test/data_structures/action_columns_test.rb +2 -2
  150. data/test/data_structures/action_links_test.rb +1 -1
  151. data/test/data_structures/column_test.rb +3 -6
  152. data/test/data_structures/columns_test.rb +2 -2
  153. data/test/data_structures/sorting_test.rb +7 -0
  154. data/test/extensions/active_record_test.rb +4 -4
  155. data/test/extensions/routing_mapper_test.rb +2 -2
  156. data/test/helpers/list_column_helpers_test.rb +3 -1
  157. data/test/misc/active_record_permissions_test.rb +3 -11
  158. data/test/misc/attribute_params_test.rb +12 -8
  159. data/test/misc/calculation_test.rb +1 -1
  160. data/test/misc/configurable_test.rb +10 -10
  161. data/test/misc/constraints_test.rb +2 -2
  162. data/test/misc/convert_numbers_format_test.rb +7 -3
  163. data/test/misc/lang_test.rb +1 -1
  164. data/test/misc/parse_datetime_test.rb +3 -4
  165. data/test/misc/tableless_test.rb +6 -0
  166. data/test/mock_app/Rakefile +1 -1
  167. data/test/mock_app/app/assets/config/manifest.js +0 -0
  168. data/test/mock_app/app/controllers/cars_controller.rb +1 -0
  169. data/test/mock_app/app/controllers/people_controller.rb +3 -1
  170. data/test/mock_app/config/application.rb +2 -1
  171. data/test/mock_app/config/boot.rb +1 -1
  172. data/test/mock_app/config/environment.rb +2 -2
  173. data/test/mock_app/config/routes.rb +4 -1
  174. data/test/mock_app/db/schema.rb +2 -0
  175. data/test/performance/list_cars_performance_test.rb +34 -0
  176. data/test/performance/list_people_performance_test.rb +31 -0
  177. data/test/performance_test_help.rb +3 -0
  178. data/test/test_helper.rb +10 -2
  179. metadata +56 -15
  180. data/app/assets/javascripts/prototype/rico_corner.js +0 -370
  181. data/lib/active_scaffold/bridges/file_column/test/test_helper.rb +0 -7
@@ -24,9 +24,9 @@ module ActiveScaffold
24
24
  ul_options = record_or_ul_options if ul_options.nil? && record_or_ul_options.is_a?(Hash)
25
25
  record = record_or_ul_options unless record_or_ul_options.is_a?(Hash)
26
26
  html = content_tag :ul, ul_options do
27
- safe_join links.map { |link| content_tag :li, link }
27
+ safe_join(links.map { |link| content_tag :li, link })
28
28
  end
29
- raw "ActiveScaffold.display_dynamic_action_group('#{get_action_link_id action_link, record}', '#{escape_javascript html}');"
29
+ raw "ActiveScaffold.display_dynamic_action_group('#{get_action_link_id action_link, record}', '#{escape_javascript html}');" # rubocop:disable Rails/OutputSafety
30
30
  end
31
31
 
32
32
  def display_action_links(action_links, record, options, &block)
@@ -67,7 +67,7 @@ module ActiveScaffold
67
67
  html_classes << 'top' if options[:first_action]
68
68
  group_tag = :li
69
69
  end
70
- content = content_tag(group_tag, :class => (html_classes if html_classes.present?), :onclick => ('' if hover_via_click?)) do
70
+ content = content_tag(group_tag, :class => html_classes.presence, :onclick => ('' if hover_via_click?)) do
71
71
  content_tag(:div, as_(link.label), :class => link.name.to_s.downcase) << content_tag(:ul, content)
72
72
  end
73
73
  else
@@ -79,13 +79,15 @@ module ActiveScaffold
79
79
  end
80
80
 
81
81
  def render_action_link(link, record = nil, options = {})
82
- if link.action.nil? || link.column.try(:association).try(:polymorphic?)
83
- link = action_link_to_inline_form(link, record) if link.column.try(:association)
82
+ if link.action.nil? || link.column&.association&.polymorphic?
83
+ link = action_link_to_inline_form(link, record) if link.column&.association
84
84
  options[:authorized] = false if link.action.nil? || link.controller.nil?
85
85
  options.delete :link if link.crud_type == :create
86
86
  end
87
87
  if link.action.nil? || (link.type == :member && options.key?(:authorized) && !options[:authorized])
88
- action_link_html(link, nil, {:link => action_link_text(link, options), :class => "disabled #{link.action}#{" #{link.html_options[:class]}" if link.html_options[:class].present?}", :title => options[:not_authorized_reason]}, record)
88
+ html_class = "disabled #{link.action}#{" #{link.html_options[:class]}" if link.html_options[:class].present?}"
89
+ html_options = {:link => action_link_text(link, options), :class => html_class, :title => options[:not_authorized_reason]}
90
+ action_link_html(link, nil, html_options, record)
89
91
  else
90
92
  url = action_link_url(link, record)
91
93
  html_options = action_link_html_options(link, record, options)
@@ -95,9 +97,9 @@ module ActiveScaffold
95
97
 
96
98
  # setup the action link to inline form
97
99
  def action_link_to_inline_form(link, record)
98
- link = link.clone
100
+ link = link.dup
99
101
  associated = record.send(link.column.association.name)
100
- if link.column.association.try(:polymorphic?)
102
+ if link.column.association&.polymorphic?
101
103
  link.controller = controller_path_for_activerecord(associated.class)
102
104
  return link if link.controller.nil?
103
105
  end
@@ -106,12 +108,12 @@ module ActiveScaffold
106
108
  end
107
109
 
108
110
  def configure_column_link(link, record, associated, actions = nil)
109
- actions ||= link.column.actions_for_association_links
111
+ actions ||= link.controller_actions || []
110
112
  if column_empty?(associated) # if association is empty, we only can link to create form
111
113
  if actions.include?(:new)
112
114
  link.action = 'new'
113
115
  link.crud_type = :create
114
- link.label ||= as_(:create_new)
116
+ link.label ||= :create_new
115
117
  end
116
118
  elsif actions.include?(:edit)
117
119
  link.action = 'edit'
@@ -137,7 +139,7 @@ module ActiveScaffold
137
139
  def column_link_authorized?(link, column, record, associated)
138
140
  if column.association
139
141
  associated_for_authorized =
140
- if column.association.collection? || (associated.respond_to?(:blank?) && associated.blank?)
142
+ if column.association.collection? || associated.nil?
141
143
  column.association.klass
142
144
  else
143
145
  associated
@@ -160,7 +162,7 @@ module ActiveScaffold
160
162
  end
161
163
 
162
164
  def cache_action_link_url?(link, record)
163
- active_scaffold_config.cache_action_link_urls && link.type == :member && !link.dynamic_parameters.is_a?(Proc) && !sti_record?(record)
165
+ active_scaffold_config.user.cache_action_link_urls && link.type == :member && !link.dynamic_parameters.is_a?(Proc) && !sti_record?(record)
164
166
  end
165
167
 
166
168
  def cached_action_link_url(link, record)
@@ -178,8 +180,8 @@ module ActiveScaffold
178
180
 
179
181
  def replace_id_params_in_action_link_url(link, record, url)
180
182
  url = record ? url.sub('--ID--', record.to_param.to_s) : url.clone
181
- if link.column.try(:association).try(:singular?)
182
- child_id = record.send(link.column.association.name).try(:to_param)
183
+ if link.column&.association&.singular?
184
+ child_id = record.send(link.column.association.name)&.to_param
183
185
  if child_id.present?
184
186
  url.sub!('--CHILD_ID--', child_id)
185
187
  else
@@ -209,6 +211,18 @@ module ActiveScaffold
209
211
  url
210
212
  end
211
213
 
214
+ def column_in_params_conditions?(key)
215
+ if key =~ /!$/
216
+ conditions_from_params[1..-1].any? { |node| node.left.name.to_s == key[0..-2] }
217
+ else
218
+ conditions_from_params[0].include?(key)
219
+ end
220
+ end
221
+
222
+ def ignore_param_for_nested?(key)
223
+ NESTED_PARAMS.include?(key) || column_in_params_conditions?(key) || (nested? && nested.param_name == key)
224
+ end
225
+
212
226
  def query_string_for_action_links(link)
213
227
  if defined?(@query_string) && link.parameters.none? { |k, _| @query_string_params.include? k }
214
228
  return [@query_string, @non_nested_query_string]
@@ -224,12 +238,17 @@ module ActiveScaffold
224
238
  keep = false
225
239
  next
226
240
  end
227
- if NESTED_PARAMS.include?(key) || conditions_from_params.include?(key) || (nested? && nested.param_name == key)
241
+ if ignore_param_for_nested?(key)
228
242
  non_nested_query_string_options[key] = value
229
243
  else
230
244
  query_string_options[key] = value
231
245
  end
232
246
  end
247
+ if nested_singular_association? && action_name == 'index'
248
+ # pass current path as return_to, for nested listing on singular association, so forms doesn't return to parent listing
249
+ @query_string_params << :return_to
250
+ non_nested_query_string_options[:return_to] = request.fullpath
251
+ end
233
252
 
234
253
  query_string = query_string_options.to_query if query_string_options.present?
235
254
  if non_nested_query_string_options.present?
@@ -243,7 +262,7 @@ module ActiveScaffold
243
262
  end
244
263
 
245
264
  def cache_action_link_url_options?(link, record)
246
- active_scaffold_config.cache_action_link_urls && (link.type == :collection || !link.dynamic_parameters.is_a?(Proc)) && !sti_record?(record)
265
+ active_scaffold_config.user.cache_action_link_urls && (link.type == :collection || !link.dynamic_parameters.is_a?(Proc)) && !sti_record?(record)
247
266
  end
248
267
 
249
268
  def cached_action_link_url_options(link, record)
@@ -290,8 +309,8 @@ module ActiveScaffold
290
309
  missing_options, url_options = url.partition { |_, v| v.nil? }
291
310
  replacements = {}
292
311
  replacements['--ID--'] = record.id.to_s if record
293
- if link.column.try(:association).try(:singular?)
294
- replacements['--CHILD_ID--'] = record.send(link.column.association.name).try(:id).to_s
312
+ if link.column&.association&.singular?
313
+ replacements['--CHILD_ID--'] = record.send(link.column.association.name)&.id.to_s
295
314
  elsif nested?
296
315
  replacements['--CHILD_ID--'] = params[nested.param_name].to_s
297
316
  end
@@ -303,7 +322,7 @@ module ActiveScaffold
303
322
 
304
323
  def action_link_selected?(link, record)
305
324
  missing_options, url_options = replaced_action_link_url_options(link, record)
306
- safe_params = (Rails.version < '4.2' ? params.to_h : params.to_unsafe_h)
325
+ safe_params = params.to_unsafe_h
307
326
  (url_options - safe_params.to_a).blank? && missing_options.all? { |k, _| params[k].nil? }
308
327
  end
309
328
 
@@ -316,7 +335,7 @@ module ActiveScaffold
316
335
  html_options[:method] = link.method if link.method != :get
317
336
 
318
337
  html_options[:data] ||= {}
319
- html_options[:data][:confirm] = link.confirm(h(record.try(:to_label))) if link.confirm?
338
+ html_options[:data][:confirm] = link.confirm(h(record&.to_label)) if link.confirm?
320
339
  if !options[:page] && !options[:popup] && (options[:inline] || link.inline?)
321
340
  html_options[:class] << ' as_action'
322
341
  html_options[:data][:position] = link.position if link.position
@@ -331,7 +350,10 @@ module ActiveScaffold
331
350
  html_options[:class] << ' active' if action_link_selected?(link, record)
332
351
  end
333
352
 
334
- html_options[:target] = '_blank' if !options[:page] && !options[:inline] && (options[:popup] || link.popup?)
353
+ if !options[:page] && !options[:inline] && (options[:popup] || link.popup?)
354
+ html_options[:target] = '_blank'
355
+ html_options[:rel] = [html_options[:rel], 'noopener noreferrer'].compact.join(' ')
356
+ end
335
357
  html_options[:id] = link_id
336
358
  if link.dhtml_confirm?
337
359
  unless link.inline?
@@ -344,20 +366,23 @@ module ActiveScaffold
344
366
  html_options
345
367
  end
346
368
 
347
- def get_action_link_id(link, record = nil, column = nil)
348
- column ||= link.column
349
- if column.try(:association) && record
350
- id = if column.association.collection?
351
- "#{column.association.name}-#{record.id}"
352
- elsif record.send(column.association.name).present?
353
- "#{column.association.name}-#{record.send(column.association.name).id}-#{record.id}"
354
- else
355
- "#{column.association.name}-#{record.id}"
356
- end
369
+ def get_action_link_id(link, record = nil)
370
+ column = link.column
371
+ if column&.association && record
372
+ associated = record.send(column.association.name) unless column.association.collection?
373
+ id =
374
+ if associated
375
+ "#{column.association.name}-#{associated.id}-#{record.id}"
376
+ else
377
+ "#{column.association.name}-#{record.id}"
378
+ end
379
+ end
380
+ id ||= record&.id&.to_s || (nested? ? nested_parent_id.to_s : '')
381
+ action_link_id = ActiveScaffold::Registry.cache :action_link_id, link.name_to_cache do
382
+ action_id = "#{id_from_controller("#{link.controller}-") if params[:parent_controller] || (link.controller && link.controller != controller.controller_path)}#{link.action}"
383
+ action_link_id(action_id, '--ID--')
357
384
  end
358
- id ||= record.try(:id) || (nested? ? nested_parent_id : '')
359
- action_id = "#{id_from_controller("#{link.controller}-") if params[:parent_controller] || (link.controller && link.controller != controller.controller_path)}#{link.action}"
360
- action_link_id(action_id, id)
385
+ action_link_id.sub('--ID--', id)
361
386
  end
362
387
 
363
388
  def action_link_html(link, url, html_options, record)
@@ -371,15 +396,16 @@ module ActiveScaffold
371
396
  end
372
397
 
373
398
  def url_options_for_nested_link(column, record, link, url_options)
374
- if column.try(:association)
399
+ if column&.association
375
400
  url_options[:parent_scaffold] = controller_path
376
401
  url_options[column.model.name.foreign_key.to_sym] = url_options.delete(:id)
377
402
  url_options[:id] = if column.association.singular? && url_options[:action].to_sym != :index
378
403
  '--CHILD_ID--'
379
404
  end
380
- elsif link.parameters && link.parameters[:named_scope]
405
+ elsif link.parameters&.dig(:named_scope)
381
406
  url_options[:parent_scaffold] = controller_path
382
407
  url_options[active_scaffold_config.model.name.foreign_key.to_sym] = url_options.delete(:id)
408
+ url_options[:id] = nil
383
409
  end
384
410
  end
385
411
 
@@ -16,11 +16,8 @@ module ActiveScaffold
16
16
  def association_options_find(association, conditions = nil, klass = nil, record = nil)
17
17
  if klass.nil? && association.polymorphic?
18
18
  class_name = record.send(association.foreign_type) if association.belongs_to?
19
- if class_name.present?
20
- klass = class_name.constantize
21
- else
22
- return []
23
- end
19
+ return [] if class_name.blank?
20
+ klass = class_name.constantize
24
21
  cache = !block_given?
25
22
  else
26
23
  cache = !block_given? && klass.nil?
@@ -32,12 +29,14 @@ module ActiveScaffold
32
29
  klass = association_klass_scoped(association, klass, record)
33
30
  relation = klass.where(conditions)
34
31
  column = column_for_association(association, record)
35
- if column && column.try(:sort) && column.sort[:sql]
36
- if column.includes
37
- include_assoc = column.includes.find { |assoc| assoc.is_a?(Hash) && assoc.include?(association.name) }
38
- relation = relation.includes(include_assoc[association.name]) if include_assoc
39
- end
40
- relation = relation.order(column.sort[:sql])
32
+ if column&.includes
33
+ include_assoc = column.includes.find { |assoc| assoc.is_a?(Hash) && assoc.include?(association.name) }
34
+ relation = relation.includes(include_assoc[association.name]) if include_assoc
35
+ end
36
+ if column&.sort && column.sort&.dig(:sql)
37
+ # with threasafe enabled, column.sort[:sql] returns proxied strings and
38
+ # regexp capture won't work, which rails uses internally, so to_s is needed
39
+ relation = relation.order(Array(column.sort[:sql]).map(&:to_s))
41
40
  end
42
41
  relation = yield(relation) if block_given?
43
42
  relation.to_a
@@ -45,16 +44,20 @@ module ActiveScaffold
45
44
  end
46
45
 
47
46
  def column_for_association(association, record)
48
- active_scaffold_config_for(record.class).columns[association.name] rescue nil
47
+ active_scaffold_config_for(record.class).columns[association.name]
48
+ rescue StandardError => e
49
+ message = "Error on config for #{record.class.name}:"
50
+ Rails.logger.warn "#{message}\n#{e.message}\n#{e.backtrace.join("\n")}"
51
+ nil
49
52
  end
50
53
 
51
54
  def association_klass_scoped(association, klass, record)
52
- if nested? && nested.through_association? && nested.child_association.try(:through_reflection) == association
55
+ if nested? && nested.through_association? && nested.child_association&.through_reflection == association
53
56
  # only ActiveRecord associations
54
57
  if nested.association.through_reflection.collection?
55
58
  nested_parent_record.send(nested.association.through_reflection.name)
56
59
  else
57
- klass.where(association.association_primary_key => nested_parent_record.send(nested.association.through_reflection.name).try(:id))
60
+ klass.where(association.association_primary_key => nested_parent_record.send(nested.association.through_reflection.name)&.id)
58
61
  end
59
62
  else
60
63
  klass
@@ -65,7 +68,7 @@ module ActiveScaffold
65
68
  def sorted_association_options_find(association, conditions = nil, record = nil)
66
69
  options = association_options_find(association, conditions, nil, record)
67
70
  column = column_for_association(association, record)
68
- unless column && column.try(:sort) && column.sort[:sql]
71
+ unless column&.sort && column.sort&.dig(:sql)
69
72
  method = column.options[:label_method] if column
70
73
  options = options.sort_by(&(method || :to_label).to_sym)
71
74
  end
@@ -86,10 +89,9 @@ module ActiveScaffold
86
89
  # Check association.name to specialize the conditions per-column.
87
90
  def options_for_association_conditions(association, record = nil)
88
91
  return nil if association.through?
89
- if association.has_one? || association.has_many?
90
- # Find only orphaned objects
91
- {association.foreign_key => nil}
92
- end
92
+ return nil unless association.has_one? || association.has_many?
93
+ # Find only orphaned objects
94
+ {association.foreign_key => nil}
93
95
  end
94
96
 
95
97
  def record_select_params_for_add_existing(association, edit_associated_url_options, record)
@@ -2,25 +2,38 @@ module ActiveScaffold
2
2
  module Helpers
3
3
  module ControllerHelpers
4
4
  def self.included(controller)
5
- if controller.respond_to? :helper_method
6
- controller.class_eval do
7
- helper_method :params_for, :conditions_from_params, :render_parent?,
8
- :main_path_to_return, :render_parent_options,
9
- :render_parent_action, :nested_singular_association?,
10
- :main_form_controller, :build_associated,
11
- :generate_temporary_id, :generated_id
12
- end
5
+ return unless controller.respond_to? :helper_method
6
+ controller.class_eval do
7
+ helper_method :params_for, :conditions_from_params, :render_parent?,
8
+ :main_path_to_return, :render_parent_options,
9
+ :render_parent_action, :nested_singular_association?,
10
+ :main_form_controller, :build_associated,
11
+ :generate_temporary_id, :generated_id,
12
+ :active_scaffold_config_for
13
13
  end
14
14
  end
15
15
 
16
16
  include ActiveScaffold::Helpers::IdHelpers
17
17
 
18
+ def active_scaffold_config_for(klass)
19
+ config = self.class.active_scaffold_config_for(klass)
20
+ if ActiveScaffold.threadsafe
21
+ config.user || config.new_user_settings({}, {})
22
+ else
23
+ config
24
+ end
25
+ end
26
+
18
27
  def generate_temporary_id(record = nil, generated_id = nil)
19
28
  (generated_id || (Time.now.to_f * 1000).to_i.to_s).tap do |id|
20
- (@temporary_ids ||= {})[record.class.name] = id if record
29
+ cache_generated_id record, id
21
30
  end
22
31
  end
23
32
 
33
+ def cache_generated_id(record, generated_id)
34
+ (@temporary_ids ||= {})[record.class.name] = generated_id if record && generated_id
35
+ end
36
+
24
37
  def generated_id(record)
25
38
  @temporary_ids[record.class.name] if record && @temporary_ids
26
39
  end
@@ -91,7 +104,7 @@ module ActiveScaffold
91
104
  else
92
105
  exclude_parameters = %i[utf8 associated_id]
93
106
  parameters = {}
94
- if params[:parent_scaffold] && nested? && nested.singular_association?
107
+ if params[:parent_scaffold] && nested_singular_association?
95
108
  parameters[:controller] = params[:parent_scaffold]
96
109
  exclude_parameters.concat [nested.param_name, :association, :parent_scaffold]
97
110
  # parameters[:eid] = params[:parent_scaffold] # not neeeded anymore?
@@ -16,47 +16,43 @@ module ActiveScaffold
16
16
  # first, check if the dev has created an override for this specific field
17
17
  if (method = override_form_field(column))
18
18
  send(method, record, options)
19
+
19
20
  # second, check if the dev has specified a valid form_ui for this column
20
21
  elsif column.form_ui && (method = override_input(column.form_ui))
21
22
  send(method, column, options)
22
- # fallback: we get to make the decision
23
- else
24
- if column.association
25
- if column.form_ui.nil?
26
- # its an association and nothing is specified, we will assume form_ui :select
27
- active_scaffold_input_select(column, options)
28
- else
29
- # if we get here, it's because the column has a form_ui but not one ActiveScaffold knows about.
30
- raise "Unknown form_ui `#{column.form_ui}' for column `#{column.name}'"
31
- end
32
- elsif column.virtual?
33
- options[:value] = format_number_value(record.send(column.name), column.options) if column.number?
34
- active_scaffold_input_virtual(column, options)
35
-
36
- else # regular model attribute column
37
- # if we (or someone else) have created a custom render option for the column type, use that
38
- if (method = override_input(column.column.type))
39
- send(method, column, options)
40
- # final ultimate fallback: use rails' generic input method
41
- else
42
- # for textual fields we pass different options
43
- text_types = %i[text string integer float decimal]
44
- options = active_scaffold_input_text_options(options) if text_types.include?(column.column.type)
45
- if column.column.type == :string && options[:maxlength].blank?
46
- options[:maxlength] = column.column.limit
47
- options[:size] ||= options[:maxlength].to_i > 30 ? 30 : options[:maxlength]
48
- end
49
- options[:value] = format_number_value(record.send(column.name), column.options) if column.number?
50
- text_field(:record, column.name, options.merge(column.options).except(:format))
51
- end
23
+
24
+ elsif column.association
25
+ # if we get here, it's because the column has a form_ui but not one ActiveScaffold knows about.
26
+ raise "Unknown form_ui `#{column.form_ui}' for column `#{column.name}'" if column.form_ui
27
+
28
+ # its an association and nothing is specified, we will assume form_ui :select
29
+ active_scaffold_input_select(column, options)
30
+
31
+ elsif column.virtual?
32
+ options[:value] = format_number_value(record.send(column.name), column.options) if column.number?
33
+ active_scaffold_input_virtual(column, options)
34
+
35
+ elsif (method = override_input(column.column.type)) # regular model attribute column
36
+ # if we (or someone else) have created a custom render option for the column type, use that
37
+ send(method, column, options)
38
+
39
+ else # final ultimate fallback: use rails' generic input method
40
+ # for textual fields we pass different options
41
+ text_types = %i[text string integer float decimal]
42
+ options = active_scaffold_input_text_options(options) if text_types.include?(column.column.type)
43
+ if column.column.type == :string && options[:maxlength].blank?
44
+ options[:maxlength] = column.column.limit
45
+ options[:size] ||= options[:maxlength].to_i > 30 ? 30 : options[:maxlength]
52
46
  end
47
+ options[:value] = format_number_value(record.send(column.name), column.options) if column.number?
48
+ text_field(:record, column.name, options.merge(column.options).except(:format))
53
49
  end
54
50
  rescue StandardError => e
55
51
  logger.error "#{e.class.name}: #{e.message} -- on the ActiveScaffold column = :#{column.name} in #{controller.class}"
56
52
  raise e
57
53
  end
58
54
 
59
- def active_scaffold_render_subform_column(column, scope, crud_type, readonly, add_class = false, record = nil)
55
+ def active_scaffold_render_subform_column(column, scope, crud_type, readonly, add_class = false, record = nil) # rubocop:disable Metrics/ParameterLists
60
56
  if add_class
61
57
  col_class = []
62
58
  col_class << 'required' if column.required?
@@ -66,9 +62,7 @@ module ActiveScaffold
66
62
  col_class = col_class.join(' ')
67
63
  end
68
64
  if readonly && !record.new_record? || !record.authorized_for?(:crud_type => crud_type, :column => column.name)
69
- options = active_scaffold_input_options(column, scope).except(:name)
70
- options[:class] = "#{options[:class]} #{col_class}" if col_class
71
- content_tag :span, get_column_value(record, column), options
65
+ form_attribute(column, record, scope, true, col_class)
72
66
  else
73
67
  renders_as = column_renders_as(column)
74
68
  html = render_column(column, record, renders_as, scope, false, col_class)
@@ -86,7 +80,7 @@ module ActiveScaffold
86
80
 
87
81
  # the standard active scaffold options used for textual inputs
88
82
  def active_scaffold_input_text_options(options = {})
89
- options[:autocomplete] = 'off'
83
+ options[:autocomplete] ||= 'off'
90
84
  options[:class] = "#{options[:class]} text-input".strip
91
85
  options
92
86
  end
@@ -97,7 +91,7 @@ module ActiveScaffold
97
91
  record = options[:object]
98
92
 
99
93
  # Add some HTML5 attributes for in-browser validation and better user experience
100
- if column.required? && (!@disable_required_for_new || scope.nil? || record.try(:persisted?))
94
+ if column.required? && (!@disable_required_for_new || scope.nil? || record&.persisted?)
101
95
  options[:required] = true
102
96
  end
103
97
  options[:placeholder] = column.placeholder if column.placeholder.present?
@@ -114,18 +108,20 @@ module ActiveScaffold
114
108
 
115
109
  def current_form_columns(record, scope, subform_controller = nil)
116
110
  if scope
117
- subform_controller.active_scaffold_config.subform.columns.names
111
+ subform_controller.active_scaffold_config.subform.columns.visible_columns_names
118
112
  elsif %i[new create edit update render_field].include? action_name.to_sym
119
113
  # disable update_columns for inplace_edit (GET render_field)
120
114
  return if action_name == 'render_field' && request.get?
121
- active_scaffold_config.send(record.new_record? ? :create : :update).columns.names
115
+ active_scaffold_config.send(record.new_record? ? :create : :update).columns.visible_columns_names
122
116
  end
123
117
  end
124
118
 
125
119
  def update_columns_options(column, scope, options, force = false)
126
120
  record = options[:object]
127
121
  subform_controller = controller.class.active_scaffold_controller_for(record.class) if scope
128
- form_columns = @main_columns.try(:names) if scope.nil? || subform_controller == controller.class
122
+ if @main_columns && (scope.nil? || subform_controller == controller.class)
123
+ form_columns = @main_columns.visible_columns_names
124
+ end
129
125
  form_columns ||= current_form_columns(record, scope, subform_controller)
130
126
  if force || (form_columns && column.update_columns && (column.update_columns & form_columns).present?)
131
127
  url_params = params_for(:action => 'render_field', :column => column.name, :id => record.to_param)
@@ -134,7 +130,7 @@ module ActiveScaffold
134
130
  url_params = url_params.except(:parent_scaffold, :association, nested.param_name)
135
131
  end
136
132
  if scope
137
- url_params[:parent_controller] ||= url_params[:controller].gsub(/^\//, '')
133
+ url_params[:parent_controller] ||= url_params[:controller].gsub(%r{^/}, '')
138
134
  url_params[:controller] = subform_controller.controller_path
139
135
  url_params[:scope] = scope
140
136
  url_params[:parent_id] = params[:parent_id] || params[:id]
@@ -152,8 +148,8 @@ module ActiveScaffold
152
148
  {}
153
149
  end
154
150
 
155
- def render_column(column, record, renders_as, scope = nil, only_value = false, col_class = nil)
156
- if partial = override_form_field_partial(column)
151
+ def render_column(column, record, renders_as, scope = nil, only_value = false, col_class = nil) # rubocop:disable Metrics/ParameterLists
152
+ if (partial = override_form_field_partial(column))
157
153
  render :partial => partial, :locals => {:column => column, :only_value => only_value, :scope => scope, :col_class => col_class, :record => record}
158
154
  elsif renders_as == :field || override_form_field?(column)
159
155
  form_attribute(column, record, scope, only_value, col_class)
@@ -179,17 +175,23 @@ module ActiveScaffold
179
175
  else
180
176
  field = active_scaffold_input_for column, scope, column_options
181
177
  end
178
+ if field
179
+ field << loading_indicator_tag(:action => :render_field, :id => params[:id]) if column.update_columns
180
+ field << content_tag(:span, column.description, :class => 'description') if column.description.present?
181
+ end
182
182
 
183
183
  content_tag :dl, attributes do
184
- %(<dt>#{label_tag label_for(column, column_options), column.label}</dt><dd>#{field}
185
- #{loading_indicator_tag(:action => :render_field, :id => params[:id]) if column.update_columns}
186
- #{content_tag :span, column.description, :class => 'description' if column.description.present?}
187
- </dd>).html_safe
184
+ content_tag(:dt, label_tag(label_for(column, column_options), form_column_label(column))) <<
185
+ content_tag(:dd, field)
188
186
  end
189
187
  end
190
188
 
191
189
  def label_for(column, options)
192
- options[:id] unless column.form_ui == :select && column.association.try(:collection?)
190
+ options[:id] unless column.form_ui == :select && column.association&.collection?
191
+ end
192
+
193
+ def form_column_label(column)
194
+ column.label
193
195
  end
194
196
 
195
197
  def subform_label(column, hidden)
@@ -205,7 +207,7 @@ module ActiveScaffold
205
207
 
206
208
  def form_hidden_field(column, record, scope)
207
209
  options = active_scaffold_input_options(column, scope)
208
- if column.association.try(:collection?)
210
+ if column.association&.collection?
209
211
  associated = record.send(column.name)
210
212
  if associated.blank?
211
213
  hidden_field_tag options[:name], '', options
@@ -217,7 +219,7 @@ module ActiveScaffold
217
219
  safe_join fields, ''
218
220
  end
219
221
  elsif column.association
220
- hidden_field_tag options[:name], record.send(column.name).try(:id), options
222
+ hidden_field_tag options[:name], record.send(column.name)&.id, options
221
223
  else
222
224
  hidden_field :record, column.name, options.merge(object: record)
223
225
  end
@@ -245,7 +247,7 @@ module ActiveScaffold
245
247
  end
246
248
 
247
249
  def column_show_add_existing(column, record = nil)
248
- column.allow_add_existing && options_for_association_count(column.association, record) > 0
250
+ column.allow_add_existing && options_for_association_count(column.association, record).positive?
249
251
  end
250
252
 
251
253
  def column_show_add_new(column, associated, record)
@@ -263,7 +265,7 @@ module ActiveScaffold
263
265
  def active_scaffold_grouped_options(column, select_options, optgroup)
264
266
  group_column = active_scaffold_config_for(column.association.klass).columns[optgroup]
265
267
  group_label = group_column.options[:label_method] if group_column
266
- group_label ||= group_column.try(:association) ? :to_label : :to_s
268
+ group_label ||= group_column&.association ? :to_label : :to_s
267
269
  select_options.group_by(&optgroup.to_sym).collect do |group, options|
268
270
  [group.send(group_label), options.collect { |r| [r.send(column.options[:label_method] || :to_label), r.id] }]
269
271
  end
@@ -276,9 +278,8 @@ module ActiveScaffold
276
278
  end
277
279
 
278
280
  def active_scaffold_select_name_with_multiple(options)
279
- if options[:multiple] && !options[:name].to_s.ends_with?('[]')
280
- options[:name] = "#{options[:name]}[]"
281
- end
281
+ return if !options[:multiple] || options[:name].to_s.ends_with?('[]')
282
+ options[:name] = "#{options[:name]}[]"
282
283
  end
283
284
 
284
285
  def active_scaffold_input_singular_association(column, html_options, options = {})
@@ -289,7 +290,7 @@ module ActiveScaffold
289
290
  select_options.unshift(associated) unless associated.nil? || select_options.include?(associated)
290
291
 
291
292
  method = column.name
292
- options.merge! :selected => associated.try(:id), :include_blank => as_(:_select_), :object => record
293
+ options.merge! :selected => associated&.id, :include_blank => as_(:_select_), :object => record
293
294
 
294
295
  html_options.merge!(column.options[:html_options] || {})
295
296
  options.merge!(column.options)
@@ -303,10 +304,24 @@ module ActiveScaffold
303
304
  collection_select(:record, method, select_options, :id, column.options[:label_method] || :to_label, options, html_options)
304
305
  end
305
306
  html << active_scaffold_refresh_link(column, html_options, record) if column.options[:refresh_link]
307
+ html << active_scaffold_new_record_subform(column, record, html_options) if column.options[:add_new]
306
308
  html
307
309
  end
308
310
 
309
- def active_scaffold_file_with_remove_link(column, options, content, remove_file_prefix, controls_class, &block)
311
+ def active_scaffold_new_record_subform(column, record, html_options)
312
+ subform_attrs = active_scaffold_subform_attributes(column).merge(style: 'display: none')
313
+ scope = html_options[:name].scan(/record(.*)\[#{column.name}\]/).dig(0, 0)
314
+ new_record = build_associated(column.association, record)
315
+ subform = render(partial: subform_partial_for_column(column), locals: {column: column, parent_record: record, associated: [], show_blank_record: new_record, scope: scope})
316
+ html = content_tag(:div, subform, subform_attrs)
317
+ html << active_scaffold_show_new_subform_link(column, record, html_options[:id], subform_attrs[:id])
318
+ end
319
+
320
+ def active_scaffold_show_new_subform_link(column, record, select_id, subform_id)
321
+ link_to(as_(:create_new), '#', data: {select_id: select_id, subform_id: subform_id, subform_text: as_(:add_existing)}, class: 'show-new-subform')
322
+ end
323
+
324
+ def active_scaffold_file_with_remove_link(column, options, content, remove_file_prefix, controls_class, &block) # rubocop:disable Metrics/ParameterLists
310
325
  options = active_scaffold_input_text_options(options.merge(column.options))
311
326
  if content
312
327
  active_scaffold_file_with_content(column, content, options, remove_file_prefix, controls_class, &block)
@@ -329,12 +344,13 @@ module ActiveScaffold
329
344
  object_name, method = options[:name].split(/\[(#{column.name})\]/)
330
345
  method.sub!(/#{column.name}/, "#{remove_file_prefix}\\0")
331
346
  fields = block_given? ? yield : ''
347
+ link_key = options[:multiple] ? :remove_files : :remove_file
332
348
  input = file_field(:record, column.name, options.merge(:onchange => js_dont_remove_file_code))
333
349
  content_tag(:div, class: controls_class) do
334
350
  content_tag(:div) do
335
351
  safe_join [content, ' | ', fields,
336
352
  hidden_field(object_name, method, :value => 'false', class: 'remove_file'),
337
- content_tag(:a, as_(:remove_file), :href => '#', :onclick => js_remove_file_code)]
353
+ content_tag(:a, as_(link_key), :href => '#', :onclick => js_remove_file_code)]
338
354
  end << content_tag(:div, input, :style => 'display: none')
339
355
  end
340
356
  end
@@ -345,7 +361,7 @@ module ActiveScaffold
345
361
  link_options['data-update_send_form'] = html_options['data-update_send_form']
346
362
  link_options['data-update_send_form_selector'] = html_options['data-update_send_form_selector']
347
363
  else
348
- scope = html_options[:name].scan(/^record((\[[^\]]*\])*)\[#{column.name}\]/)[0].try(:first) if html_options[:name]
364
+ scope = html_options[:name].scan(/^record((\[[^\]]*\])*)\[#{column.name}\]/).dig(0, 0) if html_options[:name]
349
365
  link_options = update_columns_options(column, scope.presence, link_options, true)
350
366
  end
351
367
  link_options[:class] = 'refresh-link'
@@ -363,7 +379,8 @@ module ActiveScaffold
363
379
 
364
380
  html =
365
381
  if select_options.empty?
366
- content_tag(:span, as_(:no_options), :class => "#{options[:class]} no-options", :id => options[:id])
382
+ content_tag(:span, as_(:no_options), :class => "#{options[:class]} no-options", :id => options[:id]) <<
383
+ hidden_field_tag("#{options[:name]}[]", '', :id => nil)
367
384
  else
368
385
  active_scaffold_checkbox_list(column, select_options, associated_options.collect(&:id), options)
369
386
  end
@@ -384,11 +401,11 @@ module ActiveScaffold
384
401
  label_method = column.options[:label_method] || :to_label
385
402
  html = hidden_field_tag("#{options[:name]}[]", '', :id => nil)
386
403
  html << content_tag(:ul, options.merge(:class => "#{options[:class]} checkbox-list#{' draggable-lists' if column.options[:draggable_lists]}")) do
387
- content = ''.html_safe
404
+ content = []
388
405
  select_options.each_with_index do |option, i|
389
406
  content << active_scaffold_checkbox_option(option, label_method, associated_ids, :name => "#{options[:name]}[]", :id => "#{options[:id]}_#{i}_id")
390
407
  end
391
- content
408
+ safe_join content
392
409
  end
393
410
  html
394
411
  end
@@ -417,9 +434,9 @@ module ActiveScaffold
417
434
  end
418
435
 
419
436
  def active_scaffold_input_select(column, html_options)
420
- if column.association.try :singular?
437
+ if column.association&.singular?
421
438
  active_scaffold_input_singular_association(column, html_options)
422
- elsif column.association.try :collection?
439
+ elsif column.association&.collection?
423
440
  active_scaffold_input_plural_association(column, html_options)
424
441
  else
425
442
  active_scaffold_input_enum(column, html_options)
@@ -429,7 +446,8 @@ module ActiveScaffold
429
446
  def active_scaffold_radio_option(option, selected, column, radio_options)
430
447
  if column.association
431
448
  label_method = column.options[:label_method] || :to_label
432
- text, value = [option.send(label_method), option.id]
449
+ text = option.send(label_method)
450
+ value = option.id
433
451
  checked = {:checked => selected == value}
434
452
  else
435
453
  text, value = active_scaffold_translated_option(column, *option)
@@ -451,11 +469,21 @@ module ActiveScaffold
451
469
  active_scaffold_enum_options(column, record)
452
470
  end
453
471
 
454
- selected = record.send(column.association.name).try(:id) if column.association
455
- radios = options.map do |option|
456
- active_scaffold_radio_option(option, selected, column, html_options)
472
+ selected = record.send(column.association.name)&.id if column.association
473
+ if options.present?
474
+ radios = options.map do |option|
475
+ active_scaffold_radio_option(option, selected, column, html_options)
476
+ end
477
+ if column.options[:include_blank]
478
+ label = column.options[:include_blank]
479
+ label = as_(column.options[:include_blank]) if column.options[:include_blank].is_a?(Symbol)
480
+ radios.prepend content_tag(:label, radio_button(:record, column.name, '', html_options.merge(id: nil)) + label)
481
+ end
482
+ safe_join radios
483
+ else
484
+ content_tag(:span, as_(:no_options), :class => "#{html_options[:class]} no-options", :id => html_options[:id]) <<
485
+ hidden_field_tag(html_options[:name], '', :id => nil)
457
486
  end
458
- safe_join radios
459
487
  end
460
488
 
461
489
  def active_scaffold_input_checkbox(column, options)
@@ -463,7 +491,7 @@ module ActiveScaffold
463
491
  end
464
492
 
465
493
  def active_scaffold_input_password(column, options)
466
- active_scaffold_text_input :password_field, column, options
494
+ active_scaffold_text_input :password_field, column, options.reverse_merge(autocomplete: 'new-password')
467
495
  end
468
496
 
469
497
  def active_scaffold_input_textarea(column, options)
@@ -517,16 +545,17 @@ module ActiveScaffold
517
545
 
518
546
  # A color picker
519
547
  def active_scaffold_input_color(column, options)
548
+ html = []
520
549
  options = active_scaffold_input_text_options(options)
521
- if column.column.try(:null)
550
+ if column.column&.null
522
551
  no_color = options[:object].send(column.name).nil?
523
552
  method = no_color ? :hidden_field : :color_field
524
- html = content_tag(:label, check_box_tag('disable', '1', no_color, id: nil, name: nil, class: 'no-color') << " #{as_ column.options[:no_color] || :no_color}")
553
+ html << content_tag(:label, check_box_tag('disable', '1', no_color, id: nil, name: nil, class: 'no-color') << " #{as_ column.options[:no_color] || :no_color}")
525
554
  else
526
555
  method = :color_field
527
- html = ''.html_safe
528
556
  end
529
557
  html << send(method, :record, column.name, options.merge(column.options).except(:format, :no_color))
558
+ safe_join html
530
559
  end
531
560
 
532
561
  #
@@ -537,8 +566,8 @@ module ActiveScaffold
537
566
  record = options.delete(:object)
538
567
  select_options = []
539
568
  select_options << [as_(:_select_), nil] if !column.virtual? && column.column.null
540
- select_options << [as_(:true), true]
541
- select_options << [as_(:false), false]
569
+ select_options << [as_(:true), true] # rubocop:disable Lint/BooleanSymbol
570
+ select_options << [as_(:false), false] # rubocop:disable Lint/BooleanSymbol
542
571
 
543
572
  select_tag(options[:name], options_for_select(select_options, record.send(column.name)), options)
544
573
  end
@@ -621,7 +650,7 @@ module ActiveScaffold
621
650
  end
622
651
 
623
652
  def column_scope(column, scope = nil, record = nil)
624
- if column.association.try(:collection?)
653
+ if column.association&.collection?
625
654
  "#{scope}[#{column.name}][#{record.id || generate_temporary_id(record)}]"
626
655
  else
627
656
  "#{scope}[#{column.name}]"
@@ -634,12 +663,12 @@ module ActiveScaffold
634
663
  remote_controller = active_scaffold_controller_for(record_select_config.model).controller_path
635
664
  options[:controller] = remote_controller
636
665
  options.merge!(active_scaffold_input_text_options)
637
- record_select_field(options[:name], record, options)
666
+ record_select_field(options[:name], nil, options)
638
667
  else
639
668
  select_options = sorted_association_options_find(nested.association, nil, record)
640
669
  select_options ||= active_scaffold_config.model.all
641
670
  select_options = options_from_collection_for_select(select_options, :id, :to_label)
642
- select_tag 'associated_id', ('<option value="">' + as_(:_select_) + '</option>' + select_options).html_safe unless select_options.empty?
671
+ select_tag 'associated_id', (content_tag(:option, as_(:_select_), value: '') + select_options) unless select_options.empty?
643
672
  end
644
673
  end
645
674
 
@@ -652,55 +681,62 @@ module ActiveScaffold
652
681
  end
653
682
 
654
683
  # Try to get numerical constraints from model's validators
655
- def numerical_constraints_for_column(column, options)
656
- if column.numerical_constraints.nil?
657
- numerical_constraints = {}
658
- validators = column.active_record_class.validators.select do |v|
659
- v.is_a?(ActiveModel::Validations::NumericalityValidator) && v.attributes.include?(column.name)
660
- end
661
- equal_to = (val = validators.find { |v| v.options[:equal_to] }) ? val.options[:equal_to] : nil
662
-
663
- # If there is equal_to constraint - use it (unless otherwise specified by user)
664
- if equal_to && !(options[:min] || options[:max])
665
- numerical_constraints[:min] = numerical_constraints[:max] = equal_to
666
- else # find minimum and maximum from validators
667
- # we can safely modify :min and :max by 1 for :greater_tnan or :less_than value only for integer values
668
- only_integer = column.column.type == :integer if column.column
669
- only_integer ||= validators.find { |v| v.options[:only_integer] }.present?
670
- margin = only_integer ? 1 : 0
671
-
672
- # Minimum
673
- unless options[:min]
674
- min = validators.map { |v| v.options[:greater_than_or_equal] }.compact.max
675
- greater_than = validators.map { |v| v.options[:greater_than] }.compact.max
676
- numerical_constraints[:min] = [min, (greater_than + margin if greater_than)].compact.max
677
- end
684
+ def column_numerical_constraints(column, options)
685
+ validators = column.active_record_class.validators.select do |v|
686
+ v.is_a?(ActiveModel::Validations::NumericalityValidator) && v.attributes.include?(column.name)
687
+ end
678
688
 
679
- # Maximum
680
- unless options[:max]
681
- max = validators.map { |v| v.options[:less_than_or_equal] }.compact.min
682
- less_than = validators.map { |v| v.options[:less_than] }.compact.min
683
- numerical_constraints[:max] = [max, (less_than - margin if less_than)].compact.min
684
- end
689
+ equal_validator = validators.find { |v| v.options[:equal_to] }
690
+ # If there is equal_to constraint - use it (unless otherwise specified by user)
691
+ if equal_validator && !(options[:min] || options[:max])
692
+ equal_to = equal_validator.options[:equal_to]
693
+ return {min: equal_to, max: equal_to}
694
+ end
685
695
 
686
- # Set step = 2 for column values restricted to be odd or even (but only if minimum is set)
687
- unless options[:step]
688
- only_odd_valid = validators.any? { |v| v.options[:odd] }
689
- only_even_valid = validators.any? { |v| v.options[:even] } unless only_odd_valid
690
- if !only_integer
691
- numerical_constraints[:step] ||= "0.#{'0' * (column.column.scale - 1)}1" if column.column && column.column.scale.to_i > 0
692
- elsif options[:min] && options[:min].respond_to?(:even?) && (only_odd_valid || only_even_valid)
693
- numerical_constraints[:step] = 2
694
- numerical_constraints[:min] += 1 if only_odd_valid && options[:min].even?
695
- numerical_constraints[:min] += 1 if only_even_valid && options[:min].odd?
696
- end
697
- numerical_constraints[:step] ||= 'any' unless only_integer
698
- end
696
+ numerical_constraints = {}
697
+
698
+ # find minimum and maximum from validators
699
+ # we can safely modify :min and :max by 1 for :greater_tnan or :less_than value only for integer values
700
+ only_integer = column.column.type == :integer if column.column
701
+ only_integer ||= validators.find { |v| v.options[:only_integer] }.present?
702
+ margin = only_integer ? 1 : 0
703
+
704
+ # Minimum
705
+ unless options[:min]
706
+ min = validators.map { |v| v.options[:greater_than_or_equal] }.compact.max
707
+ greater_than = validators.map { |v| v.options[:greater_than] }.compact.max
708
+ numerical_constraints[:min] = [min, (greater_than + margin if greater_than)].compact.max
709
+ end
710
+
711
+ # Maximum
712
+ unless options[:max]
713
+ max = validators.map { |v| v.options[:less_than_or_equal] }.compact.min
714
+ less_than = validators.map { |v| v.options[:less_than] }.compact.min
715
+ numerical_constraints[:max] = [max, (less_than - margin if less_than)].compact.min
716
+ end
717
+
718
+ # Set step = 2 for column values restricted to be odd or even (but only if minimum is set)
719
+ unless options[:step]
720
+ only_odd_valid = validators.any? { |v| v.options[:odd] }
721
+ only_even_valid = validators.any? { |v| v.options[:even] } unless only_odd_valid
722
+ if !only_integer
723
+ numerical_constraints[:step] ||= "0.#{'0' * (column.column.scale - 1)}1" if column.column&.scale.to_i.positive?
724
+ elsif options[:min] && options[:min].respond_to?(:even?) && (only_odd_valid || only_even_valid)
725
+ numerical_constraints[:step] = 2
726
+ numerical_constraints[:min] += 1 if only_odd_valid && options[:min].even?
727
+ numerical_constraints[:min] += 1 if only_even_valid && options[:min].odd?
699
728
  end
729
+ numerical_constraints[:step] ||= 'any' unless only_integer
730
+ end
700
731
 
701
- column.numerical_constraints = numerical_constraints
732
+ numerical_constraints
733
+ end
734
+
735
+ def numerical_constraints_for_column(column, options)
736
+ constraints = Rails.cache.fetch("#{column.cache_key}#numerical_constarints") do
737
+ column_numerical_constraints(column, options)
702
738
  end
703
- column.numerical_constraints.merge(options)
739
+ constraints.merge(options)
704
740
  end
705
741
  end
706
742
  end