active_scaffold 3.6.20 → 3.7.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rdoc +27 -0
  3. data/README.md +27 -16
  4. data/app/assets/javascripts/jquery/active_scaffold.js +38 -6
  5. data/app/assets/javascripts/jquery/active_scaffold_chosen.js +6 -5
  6. data/app/assets/javascripts/jquery/tiny_mce_bridge.js +18 -4
  7. data/app/assets/stylesheets/active_scaffold_layout.css +12 -1
  8. data/app/views/active_scaffold_overrides/_base_form.html.erb +5 -1
  9. data/app/views/active_scaffold_overrides/_field_search.html.erb +1 -0
  10. data/app/views/active_scaffold_overrides/_render_field.js.erb +19 -11
  11. data/config/locales/ja.yml +59 -59
  12. data/lib/active_scaffold/actions/common_search.rb +2 -2
  13. data/lib/active_scaffold/actions/core.rb +30 -10
  14. data/lib/active_scaffold/actions/field_search.rb +9 -6
  15. data/lib/active_scaffold/actions/nested.rb +7 -7
  16. data/lib/active_scaffold/attribute_params.rb +19 -57
  17. data/lib/active_scaffold/bridges/active_storage/active_storage_helpers.rb +0 -3
  18. data/lib/active_scaffold/bridges/active_storage/list_ui.rb +1 -1
  19. data/lib/active_scaffold/bridges/active_storage.rb +3 -0
  20. data/lib/active_scaffold/bridges/bitfields/bitfields_bridge.rb +2 -2
  21. data/lib/active_scaffold/bridges/date_picker/helper.rb +4 -4
  22. data/lib/active_scaffold/bridges/paper_trail/actions.rb +4 -1
  23. data/lib/active_scaffold/bridges/record_select/helpers.rb +2 -2
  24. data/lib/active_scaffold/bridges/tiny_mce.rb +1 -1
  25. data/lib/active_scaffold/bridges/usa_state_select/usa_state_select_helper.rb +1 -6
  26. data/lib/active_scaffold/config/core.rb +1 -1
  27. data/lib/active_scaffold/config/field_search.rb +9 -1
  28. data/lib/active_scaffold/config/form.rb +9 -1
  29. data/lib/active_scaffold/core.rb +2 -8
  30. data/lib/active_scaffold/data_structures/action_columns.rb +0 -25
  31. data/lib/active_scaffold/data_structures/action_links.rb +1 -1
  32. data/lib/active_scaffold/data_structures/association/abstract.rb +8 -0
  33. data/lib/active_scaffold/data_structures/association/active_mongoid.rb +8 -0
  34. data/lib/active_scaffold/data_structures/association/active_record.rb +1 -13
  35. data/lib/active_scaffold/data_structures/association/mongoid.rb +21 -8
  36. data/lib/active_scaffold/data_structures/column.rb +31 -5
  37. data/lib/active_scaffold/data_structures/columns.rb +12 -12
  38. data/lib/active_scaffold/data_structures/nested_info.rb +12 -0
  39. data/lib/active_scaffold/data_structures/sorting.rb +1 -1
  40. data/lib/active_scaffold/engine.rb +0 -1
  41. data/lib/active_scaffold/extensions/action_view_rendering.rb +13 -5
  42. data/lib/active_scaffold/extensions/cow_proxy.rb +1 -1
  43. data/lib/active_scaffold/extensions/unsaved_record.rb +9 -3
  44. data/lib/active_scaffold/finder.rb +5 -1
  45. data/lib/active_scaffold/helpers/action_link_helpers.rb +1 -1
  46. data/lib/active_scaffold/helpers/form_column_helpers.rb +48 -22
  47. data/lib/active_scaffold/helpers/list_column_helpers.rb +3 -2
  48. data/lib/active_scaffold/helpers/search_column_helpers.rb +8 -2
  49. data/lib/active_scaffold/helpers/view_helpers.rb +1 -1
  50. data/lib/active_scaffold/registry.rb +10 -15
  51. data/lib/active_scaffold/tableless.rb +10 -79
  52. data/lib/active_scaffold/version.rb +2 -2
  53. data/lib/active_scaffold.rb +0 -7
  54. data/lib/generators/active_scaffold/install_generator.rb +2 -2
  55. data/test/bridges/bridge_test.rb +1 -1
  56. data/test/bridges/paperclip_test.rb +16 -13
  57. data/test/bridges/tiny_mce_test.rb +1 -1
  58. data/test/config/base_test.rb +1 -1
  59. data/test/config/core_test.rb +1 -1
  60. data/test/config/create_test.rb +1 -1
  61. data/test/config/delete_test.rb +1 -1
  62. data/test/config/field_search_test.rb +1 -1
  63. data/test/config/list_test.rb +1 -1
  64. data/test/config/nested_test.rb +1 -1
  65. data/test/config/search_test.rb +1 -1
  66. data/test/config/show_test.rb +1 -1
  67. data/test/config/subform_test.rb +1 -1
  68. data/test/config/update_test.rb +1 -1
  69. data/test/data_structures/action_columns_test.rb +1 -1
  70. data/test/data_structures/action_link_test.rb +1 -1
  71. data/test/data_structures/action_links_test.rb +1 -1
  72. data/test/data_structures/actions_test.rb +1 -1
  73. data/test/data_structures/association_column_test.rb +1 -1
  74. data/test/data_structures/column_test.rb +1 -1
  75. data/test/data_structures/columns_test.rb +1 -1
  76. data/test/data_structures/set_test.rb +1 -1
  77. data/test/data_structures/sorting_test.rb +1 -1
  78. data/test/data_structures/standard_column_test.rb +1 -1
  79. data/test/data_structures/validation_reflection_test.rb +1 -1
  80. data/test/data_structures/virtual_column_test.rb +1 -1
  81. data/test/extensions/active_record_test.rb +1 -1
  82. data/test/helpers/pagination_helpers_test.rb +1 -1
  83. data/test/misc/active_record_permissions_test.rb +1 -1
  84. data/test/misc/attribute_params_test.rb +1 -1
  85. data/test/misc/calculation_test.rb +1 -1
  86. data/test/misc/configurable_test.rb +1 -1
  87. data/test/misc/constraints_test.rb +1 -1
  88. data/test/misc/convert_numbers_format_test.rb +1 -1
  89. data/test/misc/finder_test.rb +1 -1
  90. data/test/misc/lang_test.rb +1 -1
  91. data/test/misc/parse_datetime_test.rb +1 -1
  92. data/test/misc/tableless_test.rb +1 -1
  93. data/test/test_helper.rb +4 -4
  94. metadata +5 -13
  95. data/lib/active_scaffold/delayed_setup.rb +0 -41
  96. data/lib/active_scaffold/extensions/left_outer_joins.rb +0 -43
@@ -2,15 +2,28 @@ module ActiveScaffold::DataStructures::Association
2
2
  class Mongoid < Abstract
3
3
  delegate :inverse_klass, :as, :dependent, :inverse, to: :@association
4
4
 
5
+ def belongs_to?
6
+ # once Ruby 2.6 support is dropped, use macro_mapping? always
7
+ defined?(::Mongoid::Association) ? macro_mapping?(:belongs_to) : super
8
+ end
9
+
10
+ def has_one? # rubocop:disable Naming/PredicateName
11
+ defined?(::Mongoid::Association) ? macro_mapping?(:has_one) : super
12
+ end
13
+
14
+ def has_many? # rubocop:disable Naming/PredicateName
15
+ defined?(::Mongoid::Association) ? macro_mapping?(:has_many) : super
16
+ end
17
+
18
+ def habtm?
19
+ defined?(::Mongoid::Association) ? macro_mapping?(:has_and_belongs_to_many) : super
20
+ end
21
+
5
22
  # polymorphic belongs_to
6
23
  def polymorphic?
7
24
  belongs_to? && @association.polymorphic?
8
25
  end
9
26
 
10
- def primary_key
11
- @association[:primary_key]
12
- end
13
-
14
27
  def association_primary_key
15
28
  @association.primary_key
16
29
  end
@@ -19,10 +32,6 @@ module ActiveScaffold::DataStructures::Association
19
32
  @association.type
20
33
  end
21
34
 
22
- def counter_cache
23
- @association[:counter_cache]
24
- end
25
-
26
35
  def table_name
27
36
  @association.klass.collection.name
28
37
  end
@@ -38,5 +47,9 @@ module ActiveScaffold::DataStructures::Association
38
47
  def self.reflect_on_all_associations(klass)
39
48
  klass.relations.values
40
49
  end
50
+
51
+ def macro_mapping?(macro)
52
+ @association.is_a? ::Mongoid::Association::MACRO_MAPPING[macro]
53
+ end
41
54
  end
42
55
  end
@@ -40,14 +40,27 @@ module ActiveScaffold::DataStructures
40
40
  # the display-name of the column. this will be used, for instance, as the column title in the table and as the field name in the form.
41
41
  # if left alone it will utilize human_attribute_name which includes localization
42
42
  attr_writer :label
43
- def label
44
- as_(@label) || active_record_class.human_attribute_name(name.to_s)
43
+ def label(record = nil, scope = nil)
44
+ if @label.respond_to?(:call)
45
+ if record
46
+ @label.call(record, self, scope)
47
+ else
48
+ # sometimes label is called without a record in context (ie, from table
49
+ # headers). In this case fall back to the humanized attribute name
50
+ # instead of the Proc
51
+ active_record_class.human_attribute_name(name.to_s)
52
+ end
53
+ else
54
+ as_(@label) || active_record_class.human_attribute_name(name.to_s)
55
+ end
45
56
  end
46
57
 
47
58
  # a textual description of the column and its contents. this will be displayed with any associated form input widget, so you may want to consider adding a content example.
48
59
  attr_writer :description
49
- def description
50
- if @description
60
+ def description(record = nil, scope = nil)
61
+ if @description&.respond_to?(:call)
62
+ @description.call(record, self, scope)
63
+ elsif @description
51
64
  @description
52
65
  else
53
66
  I18n.t name, :scope => [:activerecord, :description, active_record_class.to_s.underscore.to_sym], :default => ''
@@ -87,6 +100,19 @@ module ActiveScaffold::DataStructures
87
100
  cattr_accessor :send_form_on_update_column, instance_accessor: false
88
101
  attr_accessor :send_form_on_update_column
89
102
 
103
+ # add a custom attr_accessor that can contain a Proc (or boolean or symbol)
104
+ # that will be called when the column renders, such that we can dynamically
105
+ # hide or show the column with an element that can be replaced by
106
+ # update_columns, but won't affect the form submission.
107
+ # The value can be set in the scaffold controller as follows to dynamically
108
+ # hide the column based on a Proc's output:
109
+ # config.columns[:my_column].hide_form_column_if = Proc.new { |record, column, scope| record.vehicle_type == 'tractor' }
110
+ # OR to always hide the column:
111
+ # config.columns[:my_column].hide_form_column_if = true
112
+ # OR to call a method on the record to determine whether to hide the column:
113
+ # config.columns[:my_column].hide_form_column_if = :hide_tractor_fields?
114
+ attr_accessor :hide_form_column_if
115
+
90
116
  # sorting on a column can be configured four ways:
91
117
  # sort = true default, uses intelligent sorting sql default
92
118
  # sort = false sometimes sorting doesn't make sense
@@ -328,7 +354,7 @@ module ActiveScaffold::DataStructures
328
354
  @actions_for_association_links = self.class.actions_for_association_links.dup if association
329
355
  @select_columns = default_select_columns
330
356
 
331
- @text = @column.nil? || [:string, :text, String].include?(column_type)
357
+ @text = @column.nil? || [:string, :text, :citext, String].include?(column_type)
332
358
  @number = false
333
359
  if @column
334
360
  if active_record_class.respond_to?(:defined_enums) && active_record_class.defined_enums[name.to_s]
@@ -13,7 +13,7 @@ module ActiveScaffold::DataStructures
13
13
  # IT IS NOT MEANT FOR PUBLIC USE (but if you know what you're doing, go ahead)
14
14
  def _inheritable=(value)
15
15
  @sorted = true
16
- @_inheritable = value
16
+ @_inheritable = ::Set.new(value)
17
17
  end
18
18
 
19
19
  # This accessor is used by ActionColumns to create new Column objects without adding them to this set
@@ -21,8 +21,8 @@ module ActiveScaffold::DataStructures
21
21
 
22
22
  def initialize(active_record_class, *args)
23
23
  @active_record_class = active_record_class
24
- @_inheritable = []
25
- @set = []
24
+ @_inheritable = ::Set.new
25
+ @set = {}
26
26
  @sorted = nil
27
27
 
28
28
  add(*args)
@@ -35,9 +35,11 @@ module ActiveScaffold::DataStructures
35
35
  args = args.collect(&:to_sym)
36
36
 
37
37
  # make the columns inheritable
38
- @_inheritable.concat(args)
38
+ @_inheritable.merge(args)
39
39
  # then add columns to @set (unless they already exist)
40
- args.each { |a| @set << ActiveScaffold::DataStructures::Column.new(a.to_sym, @active_record_class) unless find_by_name(a) }
40
+ args.each do |a|
41
+ @set[a.to_sym] = ActiveScaffold::DataStructures::Column.new(a, @active_record_class) unless find_by_name(a)
42
+ end
41
43
  end
42
44
  alias << add
43
45
 
@@ -55,7 +57,7 @@ module ActiveScaffold::DataStructures
55
57
  klass = column.association.klass
56
58
  columns.each do |col|
57
59
  next if find_by_name col
58
- @set << ActiveScaffold::DataStructures::Column.new(col, klass, column.association)
60
+ @set[col.to_sym] = ActiveScaffold::DataStructures::Column.new(col, klass, column.association)
59
61
  end
60
62
  end
61
63
 
@@ -66,24 +68,22 @@ module ActiveScaffold::DataStructures
66
68
 
67
69
  # returns an array of columns with the provided names
68
70
  def find_by_names(*names)
69
- @set.find_all { |column| names.include? column.name }
71
+ names.map { |name| find_by_name name }
70
72
  end
71
73
 
72
74
  # returns the column of the given name.
73
75
  def find_by_name(name)
74
- # this works because of `def column.=='
75
- column = @set.find { |c| c == name }
76
- column
76
+ @set[name.to_sym]
77
77
  end
78
78
  alias [] find_by_name
79
79
 
80
80
  def each
81
- @set.each { |i| yield i }
81
+ @set.each_value { |name| yield name }
82
82
  end
83
83
 
84
84
  def _inheritable
85
85
  if @sorted
86
- @_inheritable
86
+ @_inheritable.to_a
87
87
  else
88
88
  @_inheritable.sort do |a, b|
89
89
  self[a] <=> self[b]
@@ -70,6 +70,10 @@ module ActiveScaffold::DataStructures
70
70
  def match_model?(model)
71
71
  false
72
72
  end
73
+
74
+ def create_with_parent?
75
+ false
76
+ end
73
77
  end
74
78
 
75
79
  class NestedInfoAssociation < NestedInfo
@@ -104,6 +108,14 @@ module ActiveScaffold::DataStructures
104
108
  association.through_singular? && source_reflection.reverse
105
109
  end
106
110
 
111
+ def create_with_parent?
112
+ if has_many? && !association.through?
113
+ false
114
+ elsif child_association || create_through_singular?
115
+ true
116
+ end
117
+ end
118
+
107
119
  def source_reflection
108
120
  @source_reflection ||= ActiveScaffold::DataStructures::Association::ActiveRecord.new(association.source_reflection)
109
121
  end
@@ -41,7 +41,7 @@ module ActiveScaffold::DataStructures
41
41
  column = get_column(column_name)
42
42
  raise ArgumentError, "Could not find column #{column_name}" if column.nil?
43
43
  raise ArgumentError, 'Sorting direction unknown' unless %i[ASC DESC].include? direction.to_sym
44
- @clauses << [column, direction.untaint] if column.sortable?
44
+ @clauses << [column, direction] if column.sortable?
45
45
  raise ArgumentError, "Can't mix :method- and :sql-based sorting" if mixed_sorting?
46
46
  end
47
47
 
@@ -3,7 +3,6 @@ module ActiveScaffold
3
3
  initializer 'active_scaffold.action_controller' do
4
4
  ActiveSupport.on_load :action_controller do
5
5
  include ActiveScaffold::Core
6
- include ActiveScaffold::DelayedSetup if ActiveScaffold.delayed_setup
7
6
  include ActiveScaffold::RespondsToParent
8
7
  include ActiveScaffold::Helpers::ControllerHelpers
9
8
  include ActiveScaffold::ActiveRecordPermissions::ModelUserAccess::Controller
@@ -119,6 +119,17 @@ module ActiveScaffold #:nodoc:
119
119
  end
120
120
  end
121
121
 
122
+ def remote_controller_config(controller_path)
123
+ # attempt to retrieve the active_scaffold_config by constantizing the controller path
124
+ "#{controller_path}_controller".camelize.constantize.active_scaffold_config
125
+ rescue NameError
126
+ # if we couldn't determine the controller config by instantiating the
127
+ # controller class, parse the ActiveRecord model name from the
128
+ # controller path, which might be a namespaced controller (e.g., 'admin/admins')
129
+ model = controller_path.to_s.sub(%r{.*/}, '').singularize
130
+ active_scaffold_config_for(model)
131
+ end
132
+
122
133
  def render_embedded(options)
123
134
  require 'digest/md5'
124
135
 
@@ -144,13 +155,10 @@ module ActiveScaffold #:nodoc:
144
155
  else
145
156
  url = url_for(url_options)
146
157
  content_tag(:div, :id => id, :class => 'active-scaffold-component', :data => {:refresh => url}) do
147
- # parse the ActiveRecord model name from the controller path, which
148
- # might be a namespaced controller (e.g., 'admin/admins')
149
- model = remote_controller.to_s.sub(%r{.*/}, '').singularize
150
158
  content_tag(:div, :class => 'active-scaffold-header') do
151
159
  content_tag(:h2) do
152
- link_label = options[:label] || active_scaffold_config_for(model).list.label
153
- link_to(link_label, url, remote: true, class: 'load-embedded', data: {error_msg: as_(:error_500)}) <<
160
+ label = options[:label] || remote_controller_config(remote_controller).list.label
161
+ link_to(label, url, remote: true, class: 'load-embedded', data: {error_msg: as_(:error_500)}) <<
154
162
  loading_indicator_tag(url_options)
155
163
  end
156
164
  end
@@ -49,7 +49,7 @@ module CowProxy
49
49
  class ActionLinks < ::CowProxy::WrapClass(::ActiveScaffold::DataStructures::ActionLinks)
50
50
  def method_missing(name, *args, &block)
51
51
  CowProxy.debug { "method missing #{name} in #{__getobj__.name}" }
52
- return super if name =~ /[!?]$/
52
+ return super if name.match?(/[!?]$/)
53
53
  subgroup =
54
54
  if _instance_variable_defined?("@#{name}")
55
55
  _instance_variable_get("@#{name}")
@@ -11,15 +11,21 @@ module ActiveScaffold::UnsavedRecord
11
11
  end
12
12
 
13
13
  # automatically unsets the unsaved flag
14
- def save(*)
14
+ def save(**)
15
15
  super.tap { self.unsaved = false }
16
16
  end
17
17
 
18
18
  def keeping_errors
19
19
  old_errors = errors.dup if errors.present?
20
20
  result = yield
21
- old_errors&.each do |attr|
22
- old_errors[attr].each { |msg| errors.add(attr, msg) unless errors.added?(attr, msg) }
21
+ old_errors&.each do |e|
22
+ if e.is_a?(String) || e.is_a?(Symbol)
23
+ # Rails <6.1 errors API.
24
+ old_errors[e].each { |msg| errors.add(e, msg) unless errors.added?(e, msg) }
25
+ else
26
+ # Rails >=6.1 errors API (https://code.lulalala.com/2020/0531-1013.html).
27
+ errors.add(e.attribute, e.message) unless errors.added?(e.attribute, e.message)
28
+ end
23
29
  end
24
30
  result && old_errors.blank?
25
31
  end
@@ -122,7 +122,11 @@ module ActiveScaffold
122
122
  def condition_for_search_ui(column, value, like_pattern, search_ui)
123
123
  case search_ui
124
124
  when :boolean, :checkbox
125
- ['%<search_sql>s = ?', column.column ? ActiveScaffold::Core.column_type_cast(value, column.column) : value]
125
+ if value == 'null'
126
+ condition_for_null_type(column, value)
127
+ else
128
+ ['%<search_sql>s = ?', column.column ? ActiveScaffold::Core.column_type_cast(value, column.column) : value]
129
+ end
126
130
  when :integer, :decimal, :float
127
131
  condition_for_numeric(column, value)
128
132
  when :string, :range
@@ -212,7 +212,7 @@ module ActiveScaffold
212
212
  end
213
213
 
214
214
  def column_in_params_conditions?(key)
215
- if key =~ /!$/
215
+ if key.match?(/!$/)
216
216
  conditions_from_params[1..-1].any? { |node| node.left.name.to_s == key[0..-2] }
217
217
  else
218
218
  conditions_from_params[0].include?(key)
@@ -38,8 +38,7 @@ module ActiveScaffold
38
38
 
39
39
  else # final ultimate fallback: use rails' generic input method
40
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)
41
+ options = active_scaffold_input_text_options(options) if column.text? || column.number?
43
42
  if column.column.type == :string && options[:maxlength].blank?
44
43
  options[:maxlength] = column.column.limit
45
44
  options[:size] ||= options[:maxlength].to_i > 30 ? 30 : options[:maxlength]
@@ -107,7 +106,11 @@ module ActiveScaffold
107
106
  classes = "#{column.name}-input"
108
107
  classes += ' numeric-input' if column.number?
109
108
 
110
- {:name => name, :class => classes, :id => id_control}.merge(options)
109
+ if column.options[:collapsible]
110
+ collapsible_id = "container_#{id_control}"
111
+ end
112
+
113
+ {name: name, class: classes, id: id_control, collapsible_id: collapsible_id}.merge(options)
111
114
  end
112
115
 
113
116
  def current_form_columns(record, scope, subform_controller = nil)
@@ -120,15 +123,15 @@ module ActiveScaffold
120
123
  end
121
124
  end
122
125
 
123
- def update_columns_options(column, scope, options, force = false)
126
+ def update_columns_options(column, scope, options, force = false, form_columns: nil, url_params: {})
124
127
  record = options[:object]
125
128
  subform_controller = controller.class.active_scaffold_controller_for(record.class) if scope
126
129
  if @main_columns && (scope.nil? || subform_controller == controller.class)
127
- form_columns = @main_columns.visible_columns_names
130
+ form_columns ||= @main_columns.visible_columns_names
128
131
  end
129
- form_columns ||= current_form_columns(record, scope, subform_controller)
132
+ form_columns ||= options[:form_columns] || current_form_columns(record, scope, subform_controller)
130
133
  if force || (form_columns && column.update_columns && (column.update_columns & form_columns).present?)
131
- url_params = params_for(:action => 'render_field', :column => column.name, :id => record.to_param)
134
+ url_params.reverse_merge! params_for(action: 'render_field', column: column.name, id: record.to_param)
132
135
  if nested? && scope
133
136
  url_params[:nested] = url_params.slice(:parent_scaffold, :association, nested.param_name)
134
137
  url_params = url_params.except(:parent_scaffold, :association, nested.param_name)
@@ -153,7 +156,14 @@ module ActiveScaffold
153
156
  end
154
157
 
155
158
  def render_column(column, record, renders_as, scope = nil, only_value = false, col_class = nil) # rubocop:disable Metrics/ParameterLists
156
- if (partial = override_form_field_partial(column))
159
+ if form_column_is_hidden?(column, record, scope)
160
+ # creates an element that can be replaced by the update_columns routine,
161
+ # but will not affect the value of the submitted form in this state:
162
+ # <dl><input type="hidden" class="<%= column.name %>-input"></dl>
163
+ content_tag :dl, style: 'display: none' do
164
+ hidden_field_tag(nil, nil, :class => "#{column.name}-input")
165
+ end
166
+ elsif (partial = override_form_field_partial(column))
157
167
  render :partial => partial, :locals => {:column => column, :only_value => only_value, :scope => scope, :col_class => col_class, :record => record}
158
168
  elsif renders_as == :field || override_form_field?(column)
159
169
  form_attribute(column, record, scope, only_value, col_class)
@@ -164,6 +174,16 @@ module ActiveScaffold
164
174
  end
165
175
  end
166
176
 
177
+ def form_column_is_hidden?(column, record, scope = nil)
178
+ if column.hide_form_column_if&.respond_to?(:call)
179
+ column.hide_form_column_if.call(record, column, scope)
180
+ elsif column.hide_form_column_if&.is_a?(Symbol)
181
+ record.send(column.hide_form_column_if)
182
+ else
183
+ column.hide_form_column_if
184
+ end
185
+ end
186
+
167
187
  def form_attribute(column, record, scope = nil, only_value = false, col_class = nil)
168
188
  column_options = active_scaffold_input_options(column, scope, :object => record)
169
189
  attributes = field_attributes(column, record)
@@ -181,12 +201,15 @@ module ActiveScaffold
181
201
  end
182
202
  if field
183
203
  field << loading_indicator_tag(:action => :render_field, :id => params[:id]) if column.update_columns
184
- field << content_tag(:span, column.description, :class => 'description') if column.description.present?
204
+ desc = column.description(record, scope)
205
+ field << content_tag(:span, desc, :class => 'description') if desc.present?
185
206
  end
186
207
 
208
+ label = label_tag(label_for(column, column_options), form_column_label(column, record, scope))
209
+ collapsible_id = column_options.delete :collapsible_id
210
+ label << h(' ') << link_to_visibility_toggle(collapsible_id) if collapsible_id
187
211
  content_tag :dl, attributes do
188
- content_tag(:dt, label_tag(label_for(column, column_options), form_column_label(column))) <<
189
- content_tag(:dd, field)
212
+ content_tag(:dt, label) << content_tag(:dd, field, id: collapsible_id)
190
213
  end
191
214
  end
192
215
 
@@ -194,8 +217,8 @@ module ActiveScaffold
194
217
  options[:id] unless column.form_ui == :select && column.association&.collection?
195
218
  end
196
219
 
197
- def form_column_label(column)
198
- column.label
220
+ def form_column_label(column, record = nil, scope = nil)
221
+ column.label(record, scope)
199
222
  end
200
223
 
201
224
  def subform_label(column, hidden)
@@ -298,7 +321,7 @@ module ActiveScaffold
298
321
 
299
322
  html_options.merge!(column.options[:html_options] || {})
300
323
  options.merge!(column.options)
301
- active_scaffold_select_name_with_multiple html_options
324
+ html_options.delete(:multiple) # no point using multiple in a form for singular assoc, but may be set for field search
302
325
  active_scaffold_translate_select_options(options)
303
326
 
304
327
  html =
@@ -312,7 +335,7 @@ module ActiveScaffold
312
335
  html
313
336
  end
314
337
 
315
- def active_scaffold_new_record_subform(column, record, html_options, new_record_attributes: nil, locals: {}, skip_link: false) # rubocop:disable Metrics/ParameterLists
338
+ def active_scaffold_new_record_subform(column, record, html_options, new_record_attributes: nil, locals: {}, skip_link: false)
316
339
  klass =
317
340
  if column.association.polymorphic? && column.association.belongs_to?
318
341
  type = record.send(column.association.foreign_type)
@@ -603,14 +626,17 @@ module ActiveScaffold
603
626
  # Column.type-based inputs
604
627
  #
605
628
 
606
- def active_scaffold_input_boolean(column, options)
607
- record = options.delete(:object)
608
- select_options = []
609
- select_options << [as_(:_select_), nil] if !column.virtual? && column.column.null
610
- select_options << [as_(:true), true] # rubocop:disable Lint/BooleanSymbol
611
- select_options << [as_(:false), false] # rubocop:disable Lint/BooleanSymbol
629
+ def active_scaffold_input_boolean(column, html_options)
630
+ record = html_options.delete(:object)
631
+ html_options.merge!(column.options[:html_options] || {})
612
632
 
613
- select_tag(options[:name], options_for_select(select_options, record.send(column.name)), options)
633
+ options = {selected: record.send(column.name), object: record}
634
+ options[:include_blank] = :_select_ if column.column&.null
635
+ options.merge!(column.options)
636
+ active_scaffold_translate_select_options(options)
637
+
638
+ options_for_select = [[as_(:true), true], [as_(:false), false]] # rubocop:disable Lint/BooleanSymbol
639
+ select(:record, column.name, options_for_select, options, html_options)
614
640
  end
615
641
 
616
642
  def active_scaffold_input_date(column, options)
@@ -113,8 +113,9 @@ module ActiveScaffold
113
113
  end
114
114
 
115
115
  def tel_to(text)
116
- groups = text.to_s.scan(/(?:^\+)?\d+/)
117
- extension = groups.pop if text.to_s =~ /\s*[^\d\s]+\s*\d+$/
116
+ text = text.to_s
117
+ groups = text.scan(/(?:^\+)?\d+/)
118
+ extension = groups.pop if text.match?(/\s*[^\d\s]+\s*\d+$/)
118
119
  link_to text, "tel:#{[groups.join('-'), extension].compact.join(',')}"
119
120
  end
120
121
 
@@ -6,6 +6,8 @@ module ActiveScaffold
6
6
  # It does not do any rendering. It only decides which method is responsible for rendering.
7
7
  def active_scaffold_search_for(column, options = nil)
8
8
  options ||= active_scaffold_search_options(column)
9
+ search_columns = active_scaffold_config.field_search.columns.visible_columns_names
10
+ options = update_columns_options(column, nil, options, form_columns: search_columns, url_params: {form_action: :field_search})
9
11
  record = options[:object]
10
12
  if column.delegated_association
11
13
  record = record.send(column.delegated_association.name) || column.active_record_class.new
@@ -42,8 +44,7 @@ module ActiveScaffold
42
44
 
43
45
  else # final ultimate fallback: use rails' generic input method
44
46
  # for textual fields we pass different options
45
- text_types = %i[text string integer float decimal]
46
- options = active_scaffold_input_text_options(options) if text_types.include?(column.column.type)
47
+ options = active_scaffold_input_text_options(options) if column.text? || column.number?
47
48
  text_field(:record, column.name, options.merge(column.options))
48
49
  end
49
50
  rescue StandardError => e
@@ -134,6 +135,11 @@ module ActiveScaffold
134
135
  def active_scaffold_search_boolean(column, options)
135
136
  select_options = []
136
137
  select_options << [as_(:_select_), nil]
138
+ if column.column&.null
139
+ null_label = column.options[:include_blank] || :null
140
+ null_label = as_(null_label) if null_label.is_a?(Symbol)
141
+ select_options << [null_label, 'null']
142
+ end
137
143
  select_options << [as_(:true), true] # rubocop:disable Lint/BooleanSymbol
138
144
  select_options << [as_(:false), false] # rubocop:disable Lint/BooleanSymbol
139
145
 
@@ -247,7 +247,7 @@ module ActiveScaffold
247
247
 
248
248
  message = options.include?(:message) ? options[:message] : as_('errors.template.body')
249
249
 
250
- error_messages = objects.sum do |object|
250
+ error_messages = objects.sum([]) do |object|
251
251
  object.errors.full_messages.map do |msg|
252
252
  options[:list_type] != :br ? content_tag(:li, msg) : msg
253
253
  end
@@ -1,33 +1,28 @@
1
1
  module ActiveScaffold
2
2
  class Registry
3
- extend ActiveSupport::PerThreadRegistry
4
- attr_accessor :current_user_proc, :current_ability_proc, :marked_records
3
+ thread_mattr_accessor :current_user_proc, :current_ability_proc, :marked_records
5
4
 
6
- def user_settings
7
- @user_settings ||= {}
5
+ def self.user_settings
6
+ RequestStore.store[:attr_Registry_user_settings] ||= {}
8
7
  end
9
8
 
10
- def constraint_columns
11
- @constraint_columns ||= Hash.new { |h, k| h[k] = [] }
9
+ def self.constraint_columns
10
+ RequestStore.store[:attr_Registry_constraint_columns] ||= Hash.new { |h, k| h[k] = [] }
12
11
  end
13
12
 
14
- def unauthorized_columns
15
- @unauthorized_columns ||= Hash.new { |h, k| h[k] = [] }
13
+ def self.unauthorized_columns
14
+ RequestStore.store[:attr_Registry_unauthorized_columns] ||= Hash.new { |h, k| h[k] = [] }
16
15
  end
17
16
 
18
- def cache(kind, key = nil, &block)
17
+ def self.cache(kind, key = nil, &block)
19
18
  unless key
20
19
  key = kind
21
20
  kind = :cache
22
21
  end
23
- @cache ||= {}
24
- cache = @cache[kind] ||= {}
22
+ RequestStore.store[:attr_Registry_cache] ||= {}
23
+ cache = RequestStore.store[:attr_Registry_cache][kind] ||= {}
25
24
  return cache[key] if cache.include? key
26
25
  cache[key] ||= yield
27
26
  end
28
-
29
- def self.instance
30
- RequestStore.store[@per_thread_registry_key] ||= new
31
- end
32
27
  end
33
28
  end