active_scaffold 3.6.0.pre → 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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGELOG → CHANGELOG.rdoc} +39 -0
  3. data/app/assets/javascripts/active_scaffold.js.erb +0 -1
  4. data/app/assets/javascripts/jquery/active_scaffold.js +35 -4
  5. data/app/assets/stylesheets/active_scaffold_colors.scss +1 -1
  6. data/app/assets/stylesheets/active_scaffold_layout.css +52 -29
  7. data/app/views/active_scaffold_overrides/_list_header.html.erb +5 -7
  8. data/app/views/active_scaffold_overrides/_list_record.html.erb +4 -5
  9. data/app/views/active_scaffold_overrides/_list_with_header.html.erb +1 -1
  10. data/app/views/active_scaffold_overrides/_refresh_list.js.erb +4 -0
  11. data/config/locales/de.yml +2 -1
  12. data/config/locales/en.yml +1 -0
  13. data/config/locales/es.yml +1 -0
  14. data/config/locales/fr.yml +2 -1
  15. data/config/locales/hu.yml +1 -0
  16. data/config/locales/ja.yml +1 -0
  17. data/config/locales/ru.yml +1 -0
  18. data/lib/active_scaffold.rb +8 -3
  19. data/lib/active_scaffold/actions/common_search.rb +11 -8
  20. data/lib/active_scaffold/actions/core.rb +79 -51
  21. data/lib/active_scaffold/actions/create.rb +27 -27
  22. data/lib/active_scaffold/actions/delete.rb +1 -1
  23. data/lib/active_scaffold/actions/field_search.rb +52 -42
  24. data/lib/active_scaffold/actions/list.rb +106 -23
  25. data/lib/active_scaffold/actions/nested.rb +59 -42
  26. data/lib/active_scaffold/actions/show.rb +3 -3
  27. data/lib/active_scaffold/actions/subform.rb +9 -16
  28. data/lib/active_scaffold/actions/update.rb +95 -77
  29. data/lib/active_scaffold/attribute_params.rb +93 -68
  30. data/lib/active_scaffold/bridges/active_storage.rb +6 -0
  31. data/lib/active_scaffold/bridges/active_storage/active_storage_bridge.rb +33 -0
  32. data/lib/active_scaffold/bridges/active_storage/active_storage_helpers.rb +54 -0
  33. data/lib/active_scaffold/bridges/active_storage/form_ui.rb +22 -0
  34. data/lib/active_scaffold/bridges/active_storage/list_ui.rb +36 -0
  35. data/lib/active_scaffold/bridges/bitfields.rb +1 -0
  36. data/lib/active_scaffold/bridges/bitfields/bitfields_bridge.rb +12 -15
  37. data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +6 -0
  38. data/lib/active_scaffold/bridges/carrierwave/list_ui.rb +2 -2
  39. data/lib/active_scaffold/bridges/date_picker/helper.rb +46 -41
  40. data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +1 -1
  41. data/lib/active_scaffold/bridges/file_column/file_column_helpers.rb +2 -2
  42. data/lib/active_scaffold/bridges/file_column/form_ui.rb +3 -3
  43. data/lib/active_scaffold/bridges/file_column/test/functional/file_column_keep_test.rb +3 -1
  44. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge_helpers.rb +2 -2
  45. data/lib/active_scaffold/bridges/record_select/helpers.rb +3 -7
  46. data/lib/active_scaffold/bridges/shared/date_bridge.rb +19 -18
  47. data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +3 -1
  48. data/lib/active_scaffold/bridges/usa_state_select/usa_state_select_helper.rb +20 -3
  49. data/lib/active_scaffold/config/base.rb +58 -34
  50. data/lib/active_scaffold/config/core.rb +31 -12
  51. data/lib/active_scaffold/config/delete.rb +12 -1
  52. data/lib/active_scaffold/config/list.rb +17 -7
  53. data/lib/active_scaffold/config/mark.rb +1 -1
  54. data/lib/active_scaffold/configurable.rb +5 -3
  55. data/lib/active_scaffold/constraints.rb +21 -19
  56. data/lib/active_scaffold/core.rb +35 -26
  57. data/lib/active_scaffold/data_structures/action_columns.rb +1 -1
  58. data/lib/active_scaffold/data_structures/action_link.rb +34 -16
  59. data/lib/active_scaffold/data_structures/action_links.rb +9 -11
  60. data/lib/active_scaffold/data_structures/association/abstract.rb +35 -13
  61. data/lib/active_scaffold/data_structures/association/active_mongoid.rb +2 -6
  62. data/lib/active_scaffold/data_structures/association/active_record.rb +5 -1
  63. data/lib/active_scaffold/data_structures/association/mongoid.rb +0 -3
  64. data/lib/active_scaffold/data_structures/column.rb +49 -58
  65. data/lib/active_scaffold/data_structures/columns.rb +3 -2
  66. data/lib/active_scaffold/data_structures/nested_info.rb +20 -18
  67. data/lib/active_scaffold/data_structures/sorting.rb +5 -0
  68. data/lib/active_scaffold/delayed_setup.rb +16 -6
  69. data/lib/active_scaffold/extensions/action_controller_rendering.rb +1 -1
  70. data/lib/active_scaffold/extensions/action_view_rendering.rb +34 -14
  71. data/lib/active_scaffold/extensions/cow_proxy.rb +50 -2
  72. data/lib/active_scaffold/extensions/localize.rb +3 -1
  73. data/lib/active_scaffold/extensions/routing_mapper.rb +2 -2
  74. data/lib/active_scaffold/extensions/to_label.rb +3 -2
  75. data/lib/active_scaffold/finder.rb +81 -46
  76. data/lib/active_scaffold/helpers/action_link_helpers.rb +47 -21
  77. data/lib/active_scaffold/helpers/association_helpers.rb +13 -11
  78. data/lib/active_scaffold/helpers/controller_helpers.rb +14 -11
  79. data/lib/active_scaffold/helpers/form_column_helpers.rb +133 -99
  80. data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -1
  81. data/lib/active_scaffold/helpers/id_helpers.rb +4 -0
  82. data/lib/active_scaffold/helpers/list_column_helpers.rb +76 -49
  83. data/lib/active_scaffold/helpers/pagination_helpers.rb +2 -2
  84. data/lib/active_scaffold/helpers/search_column_helpers.rb +25 -30
  85. data/lib/active_scaffold/helpers/show_column_helpers.rb +3 -5
  86. data/lib/active_scaffold/helpers/view_helpers.rb +31 -22
  87. data/lib/active_scaffold/orm_checks.rb +2 -2
  88. data/lib/active_scaffold/paginator.rb +1 -3
  89. data/lib/active_scaffold/registry.rb +11 -0
  90. data/lib/active_scaffold/responds_to_parent.rb +6 -5
  91. data/lib/active_scaffold/tableless.rb +6 -8
  92. data/lib/active_scaffold/version.rb +1 -1
  93. data/shoulda_macros/macros.rb +3 -1
  94. data/test/bridges/paperclip_test.rb +1 -1
  95. data/test/company.rb +2 -2
  96. data/test/data_structures/action_columns_test.rb +2 -2
  97. data/test/data_structures/column_test.rb +3 -6
  98. data/test/data_structures/columns_test.rb +2 -2
  99. data/test/extensions/active_record_test.rb +4 -4
  100. data/test/extensions/routing_mapper_test.rb +2 -2
  101. data/test/helpers/list_column_helpers_test.rb +3 -1
  102. data/test/misc/active_record_permissions_test.rb +2 -2
  103. data/test/misc/attribute_params_test.rb +4 -0
  104. data/test/misc/configurable_test.rb +10 -10
  105. data/test/misc/convert_numbers_format_test.rb +4 -0
  106. data/test/mock_app/app/assets/config/manifest.js +0 -0
  107. data/test/mock_app/app/controllers/cars_controller.rb +1 -0
  108. data/test/mock_app/app/controllers/people_controller.rb +3 -1
  109. data/test/mock_app/config/application.rb +1 -0
  110. data/test/mock_app/config/routes.rb +4 -1
  111. data/test/mock_app/db/schema.rb +2 -0
  112. data/test/performance/list_cars_performance_test.rb +34 -0
  113. data/test/performance/list_people_performance_test.rb +31 -0
  114. data/test/performance_test_help.rb +3 -0
  115. data/test/test_helper.rb +2 -1
  116. metadata +22 -12
  117. data/app/assets/javascripts/prototype/rico_corner.js +0 -370
  118. data/lib/active_scaffold/bridges/file_column/test/test_helper.rb +0 -5
@@ -4,6 +4,20 @@ module CowProxy
4
4
  module ActiveScaffold
5
5
  module DataStructures
6
6
  class Column < ::CowProxy::WrapClass(::ActiveScaffold::DataStructures::Column)
7
+ # readonly and called many times in list action
8
+ delegate :name, :cache_key, :delegated_association, :association, to: :__getobj__
9
+
10
+ def link
11
+ return @link if defined?(@link)
12
+ if __getobj__.frozen?
13
+ link_var = __getobj__.instance_variable_get(:@link)
14
+ if link_var.is_a?(Proc)
15
+ @link = link_var.call self
16
+ return @link
17
+ end
18
+ end
19
+ super
20
+ end
7
21
  end
8
22
 
9
23
  class Set < ::CowProxy::WrapClass(::ActiveScaffold::DataStructures::Set)
@@ -20,15 +34,34 @@ module CowProxy
20
34
  end
21
35
  end
22
36
 
37
+ class ActionColumns < ::CowProxy::WrapClass(::ActiveScaffold::DataStructures::ActionColumns)
38
+ def each_column(options = {})
39
+ __getobj__.each_column(options.reverse_merge(core_columns: action.core.columns)) do |column|
40
+ if column.is_a?(::ActiveScaffold::DataStructures::ActionColumns)
41
+ yield ::CowProxy.wrap(column).tap { |group| group.action = action }
42
+ else
43
+ yield column
44
+ end
45
+ end
46
+ end
47
+ end
48
+
23
49
  class ActionLinks < ::CowProxy::WrapClass(::ActiveScaffold::DataStructures::ActionLinks)
24
50
  def method_missing(name, *args, &block)
51
+ CowProxy.debug { "method missing #{name} in #{__getobj__.name}" }
25
52
  return super if name =~ /[!?]$/
26
- __copy_on_write__ if frozen?
27
53
  subgroup =
28
54
  if _instance_variable_defined?("@#{name}")
29
55
  _instance_variable_get("@#{name}")
30
56
  else
31
- _instance_variable_set("@#{name}", __wrap__(__getobj__.subgroup(name, args.first)))
57
+ __copy_on_write__ if __getobj__.frozen?
58
+ group = __getobj__.subgroup(name, args.first)
59
+ if group.frozen?
60
+ group = __wrap__(group)
61
+ else
62
+ CowProxy.debug { "created subgroup #{group.name}" }
63
+ end
64
+ _instance_variable_set("@#{name}", group)
32
65
  end
33
66
  yield subgroup if block
34
67
  subgroup
@@ -37,6 +70,21 @@ module CowProxy
37
70
  def respond_to_missing?(name, *)
38
71
  name !~ /[!?]$/
39
72
  end
73
+
74
+ protected
75
+
76
+ # Copy wrapped values to duplicated wrapped object
77
+ # @see CowProxy::Base#__copy_on_write__
78
+ # @return duplicated wrapped object
79
+ def __copy_on_write__(*)
80
+ index = @parent_proxy.instance_variable_get(:@set).index(__getobj__) if @parent_proxy
81
+ super.tap do
82
+ CowProxy.debug { "replace #{index} with proxy obj in parent #{@parent_proxy.name}" } if index
83
+ @parent_proxy.instance_variable_get(:@set)[index] = self if index
84
+ new_set = __getobj__.instance_variable_get(:@set).dup
85
+ __getobj__.instance_variable_set(:@set, new_set)
86
+ end
87
+ end
40
88
  end
41
89
  end
42
90
  end
@@ -1,7 +1,9 @@
1
1
  class Object
2
2
  def as_(key, options = {})
3
3
  if key.present?
4
- text = I18n.translate(key.to_s, {:scope => [:active_scaffold, *options.delete(:scope)], :default => key.is_a?(String) ? key : key.to_s.titleize}.merge(options)).html_safe
4
+ scope = [:active_scaffold, *options.delete(:scope)]
5
+ options = options.reverse_merge(:scope => scope, :default => key.is_a?(String) ? key : key.to_s.titleize)
6
+ text = I18n.translate(key.to_s, options).html_safe # rubocop:disable Rails/OutputSafety
5
7
  # text = nil if text.include?('translation missing:')
6
8
  end
7
9
  text || key
@@ -16,9 +16,9 @@ module ActiveScaffold
16
16
 
17
17
  def get_actions(actions_hash, options)
18
18
  default_actions = default_actions(actions_hash)
19
- if only = options[:only]
19
+ if (only = options[:only])
20
20
  Array(only).map(&:to_sym)
21
- elsif except = options[:except]
21
+ elsif (except = options[:except])
22
22
  default_actions - Array(except).map(&:to_sym)
23
23
  else
24
24
  default_actions
@@ -1,8 +1,9 @@
1
1
  # the ever-useful to_label method
2
2
  class ActiveRecord::Base
3
3
  def to_label
4
- %i[name label title to_s].each do |attribute|
5
- return send(attribute).to_s if respond_to?(attribute)
4
+ to_label_method = ActiveScaffold::Registry.cache :to_label, self.class.name do
5
+ %i[name label title to_s].find { |attribute| respond_to?(attribute) }
6
6
  end
7
+ send(to_label_method).to_s if to_label_method
7
8
  end
8
9
  end
@@ -1,7 +1,7 @@
1
1
  module ActiveScaffold
2
2
  module Finder
3
3
  def self.like_operator
4
- @@like_operator ||= ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' ? 'ILIKE' : 'LIKE'
4
+ @@like_operator ||= ::ActiveRecord::Base.connection.adapter_name.in?(%w[PostgreSQL PostGIS]) ? 'ILIKE' : 'LIKE'
5
5
  end
6
6
 
7
7
  module ClassMethods
@@ -68,7 +68,9 @@ module ActiveScaffold
68
68
  value = columns_token[column.name]
69
69
  value = /#{value}/ if column.text?
70
70
  column.search_sql.map do |search_sql|
71
- {search_sql => value}
71
+ # call .to_s so String is returned from CowProxy::String in threadsafe mode
72
+ # in other case, or method from Mongoid would fail
73
+ {search_sql.to_s => value}
72
74
  end
73
75
  end.flatten
74
76
  active_scaffold_config.model.or(token_conditions).selector
@@ -101,12 +103,10 @@ module ActiveScaffold
101
103
  sql, *values =
102
104
  if search_ui && respond_to?("condition_for_#{search_ui}_type")
103
105
  send("condition_for_#{search_ui}_type", column, value, like_pattern)
106
+ elsif column.search_sql.instance_of? Proc
107
+ column.search_sql.call(value)
104
108
  else
105
- if column.search_sql.instance_of? Proc
106
- column.search_sql.call(value)
107
- else
108
- condition_for_search_ui(column, value, like_pattern, search_ui)
109
- end
109
+ condition_for_search_ui(column, value, like_pattern, search_ui)
110
110
  end
111
111
  return nil unless sql
112
112
 
@@ -176,12 +176,14 @@ module ActiveScaffold
176
176
  end
177
177
 
178
178
  def tables_for_translating_days_and_months(format)
179
+ # rubocop:disable Style/FormatStringToken
179
180
  keys = {
180
181
  '%A' => 'date.day_names',
181
182
  '%a' => 'date.abbr_day_names',
182
183
  '%B' => 'date.month_names',
183
184
  '%b' => 'date.abbr_month_names'
184
185
  }
186
+ # rubocop:enable Style/FormatStringToken
185
187
  key_index = keys.keys.map { |key| [key, format.index(key)] }.to_h
186
188
  keys.select! { |k, _| key_index[k] }
187
189
  keys.sort_by { |k, _| key_index[k] }.map do |_, k|
@@ -226,37 +228,60 @@ module ActiveScaffold
226
228
  [format, parts[:offset]]
227
229
  end
228
230
 
231
+ def local_time_from_hash(value, conversion = :to_time)
232
+ time = Time.zone.local(*%i[year month day hour minute second].collect { |part| value[part].to_i })
233
+ time.send(conversion)
234
+ rescue StandardError => e
235
+ message = "Error creating time from #{value.inspect}:"
236
+ Rails.logger.warn "#{message}\n#{e.message}\n#{e.backtrace.join("\n")}"
237
+ nil
238
+ end
239
+
240
+ def parse_date_with_format(value, format_name)
241
+ format = I18n.t("date.formats.#{format_name || :default}")
242
+ format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m
243
+ en_value = I18n.locale == :en ? value : translate_days_and_months(value, format)
244
+ Date.strptime(en_value, format)
245
+ rescue StandardError => e
246
+ message = "Error parsing date from #{en_value}"
247
+ message << " (#{value})" if en_value != value
248
+ message << ", with format #{format}" if format
249
+ Rails.logger.warn "#{message}:\n#{e.message}\n#{e.backtrace.join("\n")}"
250
+ nil
251
+ end
252
+
253
+ def parse_time_with_format(value, format, offset)
254
+ format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m
255
+ en_value = I18n.locale == :en ? value : translate_days_and_months(value, format)
256
+ time = Time.strptime(en_value, format)
257
+ offset ? time : Time.zone.local_to_utc(time).in_time_zone
258
+ rescue StandardError => e
259
+ message = "Error parsing time from #{en_value}"
260
+ message << " (#{value})" if en_value != value
261
+ message << ", with format #{format}" if format
262
+ Rails.logger.warn "#{message}:\n#{e.message}\n#{e.backtrace.join("\n")}"
263
+ nil
264
+ end
265
+
229
266
  def condition_value_for_datetime(column, value, conversion = :to_time)
230
- unless value.nil? || value.blank?
231
- if value.is_a? Hash
232
- time = Time.zone.local(*%i[year month day hour minute second].collect { |part| value[part].to_i }) rescue nil
233
- time&.send(conversion)
234
- elsif value.respond_to?(:strftime)
235
- if conversion == :to_time
236
- # Explicitly get the current zone, because TimeWithZone#to_time in rails 3.2.3 returns UTC.
237
- # https://github.com/rails/rails/pull/2453
238
- value.to_time.in_time_zone
239
- else
240
- value.send(conversion)
241
- end
242
- elsif conversion == :to_date
243
- format = I18n.t("date.formats.#{column.options[:format] || :default}")
244
- format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m
245
- value = translate_days_and_months(value, format) if I18n.locale != :en
246
- Date.strptime(value, format) rescue nil
247
- elsif value.include?('T')
248
- Time.zone.parse(value)
249
- else # datetime
250
- format, offset = format_for_datetime(column, value)
251
- format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m
252
- value = translate_days_and_months(value, format) if I18n.locale != :en
253
- time = Time.strptime(value, format) rescue nil
254
- if time
255
- time = Time.zone.local_to_utc(time).in_time_zone unless offset
256
- time = time.send(conversion) unless conversion == :to_time
257
- end
258
- time
267
+ return if value.nil? || value.blank?
268
+ if value.is_a? Hash
269
+ local_time_from_hash(value, conversion)
270
+ elsif value.respond_to?(:strftime)
271
+ if conversion == :to_time
272
+ # Explicitly get the current zone, because TimeWithZone#to_time in rails 3.2.3 returns UTC.
273
+ # https://github.com/rails/rails/pull/2453
274
+ value.to_time.in_time_zone
275
+ else
276
+ value.send(conversion)
259
277
  end
278
+ elsif conversion == :to_date
279
+ parse_date_with_format(value, column.options[:format])
280
+ elsif value.include?('T')
281
+ Time.zone.parse(value)
282
+ else # datetime
283
+ time = parse_time_with_format(value, *format_for_datetime(column, value))
284
+ conversion == :to_time ? time : time.send(conversion)
260
285
  end
261
286
  end
262
287
 
@@ -264,7 +289,12 @@ module ActiveScaffold
264
289
  return value if value.nil?
265
290
  value = column.number_to_native(value) if column.options[:format] && column.search_ui != :number
266
291
  case (column.search_ui || column.column.type)
267
- when :integer then value.to_i rescue value ? 1 : 0
292
+ when :integer then
293
+ if value.is_a?(TrueClass) || value.is_a?(FalseClass)
294
+ value ? 1 : 0
295
+ else
296
+ value.to_i
297
+ end
268
298
  when :float then value.to_f
269
299
  when :decimal
270
300
  ::ActiveRecord::Type::Decimal.new.type_cast_from_user(value)
@@ -362,10 +392,12 @@ module ActiveScaffold
362
392
  @active_scaffold_references ||= []
363
393
  end
364
394
 
365
- # Override this method on your controller to define conditions to be used when querying a recordset (e.g. for List). The return of this method should be any format compatible with the :conditions clause of ActiveRecord::Base's find.
395
+ # Override this method on your controller to define conditions to be used when querying a recordset (e.g. for List).
396
+ # The return of this method should be any format compatible with the :conditions clause of ActiveRecord::Base's find.
366
397
  def conditions_for_collection; end
367
398
 
368
- # Override this method on your controller to define joins to be used when querying a recordset (e.g. for List). The return of this method should be any format compatible with the :joins clause of ActiveRecord::Base's find.
399
+ # Override this method on your controller to define joins to be used when querying a recordset (e.g. for List).
400
+ # The return of this method should be any format compatible with the :joins clause of ActiveRecord::Base's find.
369
401
  def joins_for_collection; end
370
402
 
371
403
  # Override this method on your controller to provide custom finder options to the find() call. The return of this method should be a hash.
@@ -403,7 +435,10 @@ module ActiveScaffold
403
435
  end
404
436
 
405
437
  # valid options may include:
406
- # * :sorting - a Sorting DataStructure (basically an array of hashes of field => direction, e.g. [{:field1 => 'asc'}, {:field2 => 'desc'}]). please note that multi-column sorting has some limitations: if any column in a multi-field sort uses method-based sorting, it will be ignored. method sorting only works for single-column sorting.
438
+ # * :sorting - a Sorting DataStructure (basically an array of hashes of field => direction,
439
+ # e.g. [{:field1 => 'asc'}, {:field2 => 'desc'}]).
440
+ # please note that multi-column sorting has some limitations: if any column in a multi-field
441
+ # sort uses method-based sorting, it will be ignored. method sorting only works for single-column sorting.
407
442
  # * :per_page
408
443
  # * :page
409
444
  def finder_options(options = {})
@@ -411,7 +446,7 @@ module ActiveScaffold
411
446
 
412
447
  # create a general-use options array that's compatible with Rails finders
413
448
  finder_options = {
414
- :reorder => options[:sorting]&.clause((grouped_columns_calculations if grouped_search?)),
449
+ :reorder => options[:sorting]&.clause((grouped_columns_calculations if grouped_search?)).map(&Arel.method(:sql)),
415
450
  :conditions => search_conditions
416
451
  }
417
452
  if active_scaffold_config.mongoid?
@@ -480,7 +515,7 @@ module ActiveScaffold
480
515
  end
481
516
 
482
517
  def calculate_last_modified(query)
483
- return unless conditional_get_support? && query.klass.columns_hash['updated_at']
518
+ return unless conditional_get_support? && ActiveScaffold::OrmChecks.columns_hash(query.klass)['updated_at']
484
519
  @last_modified = query.maximum(:updated_at)
485
520
  end
486
521
 
@@ -497,13 +532,13 @@ module ActiveScaffold
497
532
  end
498
533
 
499
534
  def append_to_query(relation, options)
500
- options.assert_valid_keys :where, :select, :having, :group, :reorder, :order, :limit, :offset, :joins, :left_joins, :left_outer_joins, :includes, :lock, :readonly, :from, :conditions, :preload, :references
535
+ options.assert_valid_keys :where, :select, :having, :group, :reorder, :order, :limit, :offset,
536
+ :joins, :left_joins, :left_outer_joins, :includes, :lock, :readonly,
537
+ :from, :conditions, :preload, :references
501
538
  relation = options.reject { |_, v| v.blank? }.inject(relation) do |rel, (k, v)|
502
539
  k == :conditions ? apply_conditions(rel, *v) : rel.send(k, v)
503
540
  end
504
- if options[:left_outer_joins].present? || options[:left_joins].present?
505
- relation.distinct_value = true
506
- end
541
+ relation.distinct_value = true if options[:left_outer_joins].present? || options[:left_joins].present?
507
542
  relation
508
543
  end
509
544
 
@@ -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
@@ -85,7 +85,9 @@ module ActiveScaffold
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)
@@ -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
@@ -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?
@@ -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
369
+ def get_action_link_id(link, record = nil)
370
+ column = link.column
349
371
  if column&.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
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&.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)
@@ -380,6 +405,7 @@ module ActiveScaffold
380
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