active_scaffold 3.6.20 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
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