active_scaffold 3.5.4 → 3.6.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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGELOG → CHANGELOG.rdoc} +72 -0
  3. data/README.md +20 -8
  4. data/app/assets/javascripts/active_scaffold.js.erb +0 -1
  5. data/app/assets/javascripts/jquery/active_scaffold.js +98 -7
  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.html.erb +2 -1
  11. data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +3 -2
  12. data/app/views/active_scaffold_overrides/_form_association_record.html.erb +9 -7
  13. data/app/views/active_scaffold_overrides/_horizontal_subform.html.erb +4 -4
  14. data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +2 -1
  15. data/app/views/active_scaffold_overrides/_list.html.erb +2 -1
  16. data/app/views/active_scaffold_overrides/_list_header.html.erb +5 -7
  17. data/app/views/active_scaffold_overrides/_list_messages.html.erb +1 -0
  18. data/app/views/active_scaffold_overrides/_list_record.html.erb +4 -5
  19. data/app/views/active_scaffold_overrides/_list_with_header.html.erb +1 -1
  20. data/app/views/active_scaffold_overrides/_messages.html.erb +1 -0
  21. data/app/views/active_scaffold_overrides/_refresh_list.js.erb +4 -0
  22. data/app/views/active_scaffold_overrides/_render_field.js.erb +2 -1
  23. data/app/views/active_scaffold_overrides/_show_association_horizontal.html.erb +2 -1
  24. data/app/views/active_scaffold_overrides/_show_columns.html.erb +2 -2
  25. data/app/views/active_scaffold_overrides/_show_horizontal_record.html.erb +4 -4
  26. data/app/views/active_scaffold_overrides/_update_calculations.js.erb +1 -1
  27. data/app/views/active_scaffold_overrides/_update_column.js.erb +2 -2
  28. data/app/views/active_scaffold_overrides/_vertical_subform.html.erb +2 -2
  29. data/app/views/active_scaffold_overrides/action_confirmation.html.erb +2 -2
  30. data/app/views/active_scaffold_overrides/delete.html.erb +2 -2
  31. data/app/views/active_scaffold_overrides/on_action_update.js.erb +16 -6
  32. data/app/views/active_scaffold_overrides/on_update.js.erb +1 -1
  33. data/app/views/active_scaffold_overrides/row.js.erb +1 -1
  34. data/app/views/active_scaffold_overrides/update_column.js.erb +2 -2
  35. data/config/locales/de.yml +2 -1
  36. data/config/locales/en.yml +1 -0
  37. data/config/locales/es.yml +1 -0
  38. data/config/locales/fr.yml +2 -1
  39. data/config/locales/hu.yml +1 -0
  40. data/config/locales/ja.yml +1 -0
  41. data/config/locales/ru.yml +1 -0
  42. data/lib/active_scaffold.rb +19 -16
  43. data/lib/active_scaffold/actions/common_search.rb +11 -8
  44. data/lib/active_scaffold/actions/core.rb +91 -70
  45. data/lib/active_scaffold/actions/create.rb +28 -28
  46. data/lib/active_scaffold/actions/delete.rb +3 -3
  47. data/lib/active_scaffold/actions/field_search.rb +53 -43
  48. data/lib/active_scaffold/actions/list.rb +111 -27
  49. data/lib/active_scaffold/actions/nested.rb +65 -48
  50. data/lib/active_scaffold/actions/search.rb +1 -1
  51. data/lib/active_scaffold/actions/show.rb +4 -4
  52. data/lib/active_scaffold/actions/subform.rb +23 -22
  53. data/lib/active_scaffold/actions/update.rb +96 -77
  54. data/lib/active_scaffold/active_record_permissions.rb +2 -11
  55. data/lib/active_scaffold/attribute_params.rb +102 -94
  56. data/lib/active_scaffold/bridges.rb +8 -8
  57. data/lib/active_scaffold/bridges/active_storage.rb +6 -0
  58. data/lib/active_scaffold/bridges/active_storage/active_storage_bridge.rb +34 -0
  59. data/lib/active_scaffold/bridges/active_storage/active_storage_helpers.rb +54 -0
  60. data/lib/active_scaffold/bridges/active_storage/form_ui.rb +22 -0
  61. data/lib/active_scaffold/bridges/active_storage/list_ui.rb +36 -0
  62. data/lib/active_scaffold/bridges/bitfields.rb +2 -1
  63. data/lib/active_scaffold/bridges/bitfields/bitfields_bridge.rb +12 -15
  64. data/lib/active_scaffold/bridges/bitfields/list_ui.rb +19 -0
  65. data/lib/active_scaffold/bridges/calendar_date_select/as_cds_bridge.rb +1 -1
  66. data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +9 -12
  67. data/lib/active_scaffold/bridges/carrierwave/carrierwave_bridge.rb +1 -1
  68. data/lib/active_scaffold/bridges/carrierwave/list_ui.rb +2 -2
  69. data/lib/active_scaffold/bridges/chosen/helpers.rb +7 -6
  70. data/lib/active_scaffold/bridges/date_picker/ext.rb +0 -13
  71. data/lib/active_scaffold/bridges/date_picker/helper.rb +49 -44
  72. data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +1 -1
  73. data/lib/active_scaffold/bridges/file_column/as_file_column_bridge.rb +1 -1
  74. data/lib/active_scaffold/bridges/file_column/file_column_helpers.rb +3 -3
  75. data/lib/active_scaffold/bridges/file_column/form_ui.rb +3 -3
  76. data/lib/active_scaffold/bridges/file_column/test/functional/file_column_keep_test.rb +10 -7
  77. data/lib/active_scaffold/bridges/paper_trail.rb +1 -1
  78. data/lib/active_scaffold/bridges/paper_trail/actions.rb +3 -1
  79. data/lib/active_scaffold/bridges/paperclip/list_ui.rb +1 -1
  80. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge.rb +1 -1
  81. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge_helpers.rb +2 -2
  82. data/lib/active_scaffold/bridges/record_select/helpers.rb +15 -17
  83. data/lib/active_scaffold/bridges/shared/date_bridge.rb +20 -19
  84. data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +3 -1
  85. data/lib/active_scaffold/bridges/usa_state_select/usa_state_select_helper.rb +21 -4
  86. data/lib/active_scaffold/config/base.rb +133 -41
  87. data/lib/active_scaffold/config/core.rb +146 -18
  88. data/lib/active_scaffold/config/delete.rb +14 -1
  89. data/lib/active_scaffold/config/field_search.rb +7 -1
  90. data/lib/active_scaffold/config/form.rb +10 -1
  91. data/lib/active_scaffold/config/list.rb +39 -13
  92. data/lib/active_scaffold/config/mark.rb +4 -2
  93. data/lib/active_scaffold/config/nested.rb +16 -17
  94. data/lib/active_scaffold/config/search.rb +9 -0
  95. data/lib/active_scaffold/config/show.rb +4 -0
  96. data/lib/active_scaffold/config/update.rb +4 -0
  97. data/lib/active_scaffold/configurable.rb +14 -7
  98. data/lib/active_scaffold/constraints.rb +22 -20
  99. data/lib/active_scaffold/core.rb +67 -28
  100. data/lib/active_scaffold/data_structures/action_columns.rb +50 -59
  101. data/lib/active_scaffold/data_structures/action_link.rb +50 -20
  102. data/lib/active_scaffold/data_structures/action_links.rb +15 -13
  103. data/lib/active_scaffold/data_structures/association/abstract.rb +38 -15
  104. data/lib/active_scaffold/data_structures/association/active_mongoid.rb +2 -6
  105. data/lib/active_scaffold/data_structures/association/active_record.rb +6 -2
  106. data/lib/active_scaffold/data_structures/association/mongoid.rb +0 -3
  107. data/lib/active_scaffold/data_structures/column.rb +75 -66
  108. data/lib/active_scaffold/data_structures/columns.rb +3 -2
  109. data/lib/active_scaffold/data_structures/nested_info.rb +33 -19
  110. data/lib/active_scaffold/data_structures/set.rb +8 -0
  111. data/lib/active_scaffold/data_structures/sorting.rb +10 -2
  112. data/lib/active_scaffold/delayed_setup.rb +16 -5
  113. data/lib/active_scaffold/extensions/action_controller_rendering.rb +3 -2
  114. data/lib/active_scaffold/extensions/action_view_rendering.rb +93 -32
  115. data/lib/active_scaffold/extensions/cow_proxy.rb +95 -0
  116. data/lib/active_scaffold/extensions/ice_nine.rb +36 -0
  117. data/lib/active_scaffold/extensions/left_outer_joins.rb +8 -33
  118. data/lib/active_scaffold/extensions/localize.rb +3 -1
  119. data/lib/active_scaffold/extensions/routing_mapper.rb +6 -45
  120. data/lib/active_scaffold/extensions/to_label.rb +3 -2
  121. data/lib/active_scaffold/extensions/unsaved_record.rb +2 -4
  122. data/lib/active_scaffold/finder.rb +110 -77
  123. data/lib/active_scaffold/helpers/action_link_helpers.rb +62 -36
  124. data/lib/active_scaffold/helpers/association_helpers.rb +18 -16
  125. data/lib/active_scaffold/helpers/controller_helpers.rb +34 -10
  126. data/lib/active_scaffold/helpers/form_column_helpers.rb +196 -124
  127. data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -1
  128. data/lib/active_scaffold/helpers/id_helpers.rb +6 -2
  129. data/lib/active_scaffold/helpers/list_column_helpers.rb +90 -57
  130. data/lib/active_scaffold/helpers/pagination_helpers.rb +2 -2
  131. data/lib/active_scaffold/helpers/search_column_helpers.rb +29 -34
  132. data/lib/active_scaffold/helpers/show_column_helpers.rb +3 -5
  133. data/lib/active_scaffold/helpers/view_helpers.rb +39 -36
  134. data/lib/active_scaffold/marked_model.rb +2 -2
  135. data/lib/active_scaffold/orm_checks.rb +3 -7
  136. data/lib/active_scaffold/paginator.rb +7 -7
  137. data/lib/active_scaffold/registry.rb +33 -0
  138. data/lib/active_scaffold/responds_to_parent.rb +8 -11
  139. data/lib/active_scaffold/tableless.rb +82 -66
  140. data/lib/active_scaffold/version.rb +2 -2
  141. data/lib/generators/active_scaffold/controller_generator.rb +2 -2
  142. data/lib/generators/active_scaffold/install_generator.rb +52 -4
  143. data/lib/generators/active_scaffold/resource_generator.rb +2 -2
  144. data/shoulda_macros/macros.rb +3 -1
  145. data/test/bridges/date_picker_test.rb +1 -2
  146. data/test/bridges/paperclip_test.rb +6 -6
  147. data/test/class_with_finder.rb +2 -2
  148. data/test/company.rb +4 -4
  149. data/test/config/create_test.rb +4 -2
  150. data/test/config/nested_test.rb +1 -1
  151. data/test/config/show_test.rb +1 -1
  152. data/test/config/update_test.rb +7 -6
  153. data/test/data_structures/action_columns_test.rb +2 -2
  154. data/test/data_structures/action_links_test.rb +1 -1
  155. data/test/data_structures/column_test.rb +3 -6
  156. data/test/data_structures/columns_test.rb +2 -2
  157. data/test/data_structures/sorting_test.rb +7 -0
  158. data/test/extensions/action_view_rendering_test.rb +20 -0
  159. data/test/extensions/active_record_test.rb +4 -4
  160. data/test/extensions/routing_mapper_test.rb +2 -2
  161. data/test/helpers/list_column_helpers_test.rb +3 -1
  162. data/test/misc/active_record_permissions_test.rb +3 -11
  163. data/test/misc/attribute_params_test.rb +12 -8
  164. data/test/misc/calculation_test.rb +1 -1
  165. data/test/misc/configurable_test.rb +10 -10
  166. data/test/misc/constraints_test.rb +2 -2
  167. data/test/misc/convert_numbers_format_test.rb +7 -3
  168. data/test/misc/lang_test.rb +1 -1
  169. data/test/misc/parse_datetime_test.rb +3 -4
  170. data/test/misc/tableless_test.rb +14 -0
  171. data/test/mock_app/Rakefile +1 -1
  172. data/test/mock_app/app/assets/config/manifest.js +0 -0
  173. data/test/mock_app/app/controllers/cars_controller.rb +1 -0
  174. data/test/mock_app/app/controllers/people_controller.rb +5 -1
  175. data/test/mock_app/app/controllers/roles_controller.rb +4 -0
  176. data/test/mock_app/app/views/active_scaffold_overrides/_form.html.erb +2 -0
  177. data/test/mock_app/app/views/active_scaffold_overrides/list.html.erb +2 -0
  178. data/test/mock_app/app/views/people/_first_name_form_column.html.erb +2 -0
  179. data/test/mock_app/app/views/people/_form.html.erb +2 -0
  180. data/test/mock_app/app/views/people/list.html.erb +2 -0
  181. data/test/mock_app/config/application.rb +2 -1
  182. data/test/mock_app/config/boot.rb +1 -1
  183. data/test/mock_app/config/environment.rb +2 -2
  184. data/test/mock_app/config/routes.rb +4 -1
  185. data/test/mock_app/db/schema.rb +2 -0
  186. data/test/performance/list_cars_performance_test.rb +34 -0
  187. data/test/performance/list_people_performance_test.rb +31 -0
  188. data/test/performance_test_help.rb +3 -0
  189. data/test/test_helper.rb +12 -4
  190. metadata +69 -18
  191. data/app/assets/javascripts/prototype/rico_corner.js +0 -370
  192. data/lib/active_scaffold/bridges/file_column/test/test_helper.rb +0 -7
@@ -37,7 +37,7 @@ module ActiveScaffold
37
37
  # because the object may change (someone may log in or out). So we give ActiveRecord a proc that ties to the
38
38
  # current_user_method on this ApplicationController.
39
39
  def assign_current_user_to_models
40
- ActiveRecord::Base.current_user_proc = proc { send(ActiveRecordPermissions.current_user_method) }
40
+ ActiveScaffold::Registry.current_user_proc = proc { send(ActiveRecordPermissions.current_user_method) }
41
41
  end
42
42
  end
43
43
 
@@ -48,18 +48,9 @@ module ActiveScaffold
48
48
  end
49
49
 
50
50
  module ClassMethods
51
- # The proc to call that retrieves the current_user from the ApplicationController.
52
- def current_user_proc
53
- Thread.current[:current_user_proc]
54
- end
55
-
56
- def current_user_proc=(proc_value)
57
- Thread.current[:current_user_proc] = proc_value
58
- end
59
-
60
51
  # Class-level access to the current user
61
52
  def current_user
62
- ActiveRecord::Base.current_user_proc.call if ActiveRecord::Base.current_user_proc
53
+ ActiveScaffold::Registry.current_user_proc&.call
63
54
  end
64
55
  end
65
56
 
@@ -41,13 +41,8 @@ module ActiveScaffold
41
41
  difference = value.select(&:persisted?).size - parent_record.send(counter_attr)
42
42
 
43
43
  if parent_record.new_record?
44
- if Rails.version >= '4.2.0'
45
- parent_record.send "#{column.name}=", value
46
- parent_record.send "#{counter_attr}_will_change!"
47
- else # < 4.2
48
- parent_record.send "#{counter_attr}=", difference
49
- parent_record.send "#{column.name}=", value
50
- end
44
+ parent_record.send "#{column.name}=", value
45
+ parent_record.send "#{counter_attr}_will_change!"
51
46
  else
52
47
  # don't decrement counter for deleted records, on destroy they will update counter
53
48
  difference += (parent_record.send(column.name) - value).size
@@ -57,7 +52,7 @@ module ActiveScaffold
57
52
  # update counters on old parents if belongs_to is changed
58
53
  value.select(&:persisted?).each do |record|
59
54
  key = record.send(column.association.foreign_key)
60
- parent_record.class.decrement_counter counter_attr, key if key && key != parent_record.id
55
+ parent_record.class.decrement_counter counter_attr, key if key && key != parent_record.id # rubocop:disable Rails/SkipsModelValidations
61
56
  end
62
57
  parent_record.send "#{column.name}=", value if parent_record.persisted?
63
58
  end
@@ -76,6 +71,15 @@ module ActiveScaffold
76
71
  !params_hash?(value) && association.belongs_to? && association.counter_cache_hack?
77
72
  end
78
73
 
74
+ def multi_parameter_attributes(attributes)
75
+ params_hash(attributes).each_with_object({}) do |(k, v), result|
76
+ next unless k.include? '('
77
+ column_name = k.split('(').first
78
+ result[column_name] ||= []
79
+ result[column_name] << [k, v]
80
+ end
81
+ end
82
+
79
83
  # Takes attributes (as from params[:record]) and applies them to the parent_record. Also looks for
80
84
  # association attributes and attempts to instantiate them as associated objects.
81
85
  #
@@ -85,28 +89,22 @@ module ActiveScaffold
85
89
  crud_type = parent_record.new_record? ? :create : :update
86
90
  return parent_record unless parent_record.authorized_for?(:crud_type => crud_type)
87
91
 
88
- multi_parameter_attributes = {}
89
- attributes.each do |k, v|
90
- next unless k.include? '('
91
- column_name = k.split('(').first
92
- multi_parameter_attributes[column_name] ||= []
93
- multi_parameter_attributes[column_name] << [k, v]
94
- end
92
+ multi_parameter_attrs = multi_parameter_attributes(attributes)
95
93
 
96
- columns.each :for => parent_record, :crud_type => crud_type, :flatten => true do |column|
94
+ columns.each_column(for: parent_record, crud_type: crud_type, flatten: true) do |column|
97
95
  begin
98
96
  # Set any passthrough parameters that may be associated with this column (ie, file column "keep" and "temp" attributes)
99
- unless column.params.empty?
100
- column.params.each { |p| parent_record.send("#{p}=", attributes[p]) if attributes.key? p }
101
- end
97
+ column.params.select { |p| attributes.key? p }.each { |p| parent_record.send("#{p}=", attributes[p]) }
102
98
 
103
- if multi_parameter_attributes.key? column.name.to_s
104
- parent_record.send(:assign_multiparameter_attributes, multi_parameter_attributes[column.name.to_s])
99
+ if multi_parameter_attrs.key? column.name.to_s
100
+ parent_record.send(:assign_multiparameter_attributes, multi_parameter_attrs[column.name.to_s])
105
101
  elsif attributes.key? column.name
106
- value = update_column_from_params(parent_record, column, attributes[column.name], avoid_changes)
102
+ update_column_from_params(parent_record, column, attributes[column.name], avoid_changes)
107
103
  end
108
- rescue => e
109
- Rails.logger.error "#{e.class.name}: #{e.message} -- on the ActiveScaffold column = :#{column.name} for #{parent_record.inspect}#{" with value #{value}" if value}"
104
+ rescue StandardError => e
105
+ message = "on the ActiveScaffold column = :#{column.name} for #{parent_record.inspect} "\
106
+ "(value from params #{attributes[column.name].inspect})"
107
+ Rails.logger.error "#{e.class.name}: #{e.message} -- #{message}"
110
108
  raise
111
109
  end
112
110
  end
@@ -116,38 +114,45 @@ module ActiveScaffold
116
114
 
117
115
  def update_column_from_params(parent_record, column, attribute, avoid_changes = false)
118
116
  value = column_value_from_param_value(parent_record, column, attribute, avoid_changes)
119
- if avoid_changes && column.association
120
- parent_record.association(column.name).target = value
121
- parent_record.send("#{column.association.foreign_key}=", value.try(:id)) if column.association.belongs_to?
122
- elsif column.association && counter_cache_hack?(column.association, attribute)
123
- parent_record.send "#{column.association.foreign_key}=", value.try(:id)
117
+ if column.association
118
+ if avoid_changes
119
+ parent_record.association(column.name).target = value
120
+ parent_record.send("#{column.association.foreign_key}=", value&.id) if column.association.belongs_to?
121
+ else
122
+ update_column_association(parent_record, column, attribute, value)
123
+ end
124
+ else
125
+ parent_record.send "#{column.name}=", value
126
+ end
127
+ # needed? probably done on find_or_create_for_params, need more testing
128
+ if column.association&.reverse_association&.belongs_to?
129
+ Array(value).each { |v| v.send("#{column.association.reverse}=", parent_record) if v.new_record? }
130
+ end
131
+ value
132
+ end
133
+
134
+ def update_column_association(parent_record, column, attribute, value)
135
+ if counter_cache_hack?(column.association, attribute)
136
+ parent_record.send "#{column.association.foreign_key}=", value&.id
124
137
  parent_record.association(column.name).target = value
125
- elsif column.association.try(:collection?) && column.association.through? && !column.association.through_reflection.collection?
138
+ elsif column.association.collection? && column.association.through_singular?
126
139
  through = column.association.through_reflection.name
127
140
  through_record = parent_record.send(through)
128
141
  through_record ||= parent_record.send "build_#{through}"
129
142
  through_record.send "#{column.association.source_reflection.name}=", value
143
+ elsif hack_for_has_many_counter_cache?(parent_record, column)
144
+ hack_for_has_many_counter_cache(parent_record, column, value)
130
145
  else
131
- begin
132
- if column.association && hack_for_has_many_counter_cache?(parent_record, column)
133
- hack_for_has_many_counter_cache(parent_record, column, value)
134
- else
135
- parent_record.send "#{column.name}=", value
136
- end
137
- rescue ActiveRecord::RecordNotSaved
138
- parent_record.errors.add column.name, :invalid
139
- parent_record.association(column.name).target = value if column.association
140
- end
146
+ parent_record.send "#{column.name}=", value
141
147
  end
142
- if column.association.try(:reverse_association).try(:belongs_to?)
143
- Array(value).each { |v| v.send("#{column.association.reverse}=", parent_record) if v.new_record? }
144
- end
145
- value
148
+ rescue ActiveRecord::RecordNotSaved
149
+ parent_record.errors.add column.name, :invalid
150
+ parent_record.association(column.name).target = value
146
151
  end
147
152
 
148
153
  def column_value_from_param_value(parent_record, column, value, avoid_changes = false)
149
154
  # convert the value, possibly by instantiating associated objects
150
- form_ui = column.form_ui || column.column.try(:type)
155
+ form_ui = column.form_ui || column.column&.type
151
156
  if form_ui && respond_to?("column_value_for_#{form_ui}_type", true)
152
157
  send("column_value_for_#{form_ui}_type", parent_record, column, value)
153
158
  elsif params_hash? value
@@ -177,27 +182,24 @@ module ActiveScaffold
177
182
  Date.parse("#{value}-01")
178
183
  end
179
184
 
180
- def column_value_from_param_simple_value(parent_record, column, value)
181
- if column.association.try :singular?
182
- if value.present?
183
- if column.association.polymorphic?
184
- class_name = parent_record.send(column.association.foreign_type)
185
- class_name.constantize.find(value) if class_name.present?
186
- else
187
- # it's a single id
188
- column.association.klass.find(value)
189
- end
190
- end
191
- elsif column.association.try :collection?
185
+ def association_value_from_param_simple_value(parent_record, column, value)
186
+ if column.association.singular?
187
+ column.association.klass(parent_record)&.find(value) if value.present?
188
+ else # column.association.collection?
192
189
  column_plural_assocation_value_from_value(column, Array(value))
193
- elsif column.number? && column.options[:format] && column.form_ui != :number
190
+ end
191
+ end
192
+
193
+ def column_value_from_param_simple_value(parent_record, column, value)
194
+ if column.association
195
+ association_value_from_param_simple_value(parent_record, column, value)
196
+ elsif column.convert_to_native?
194
197
  column.number_to_native(value)
195
- else
198
+ elsif value.is_a?(String) && value.empty? && !column.virtual?
196
199
  # convert empty strings into nil. this works better with 'null => true' columns (and validations),
197
200
  # for 'null => false' columns is just converted to default value from column
198
- if value.is_a?(String) && value.empty? && !column.column.nil?
199
- value = column.column.null ? nil : column.column.default
200
- end
201
+ column.default_for_empty_value
202
+ else
201
203
  value
202
204
  end
203
205
  end
@@ -213,9 +215,9 @@ module ActiveScaffold
213
215
  end
214
216
 
215
217
  def column_value_from_param_hash_value(parent_record, column, value, avoid_changes = false)
216
- if column.association.try :singular?
218
+ if column.association&.singular?
217
219
  manage_nested_record_from_params(parent_record, column, value, avoid_changes)
218
- elsif column.association.try :collection?
220
+ elsif column.association&.collection?
219
221
  # HACK: to be able to delete all associated records, hash will include "0" => ""
220
222
  values = value.values.reject(&:blank?)
221
223
  values.collect { |val| manage_nested_record_from_params(parent_record, column, val, avoid_changes) }.compact
@@ -228,7 +230,7 @@ module ActiveScaffold
228
230
  return nil unless build_record_from_params?(attributes, column, parent_record)
229
231
  record = find_or_create_for_params(attributes, column, parent_record)
230
232
  if record
231
- record_columns = active_scaffold_config_for(column.association.klass).subform.columns
233
+ record_columns = active_scaffold_config_for(record.class).subform.columns
232
234
  prev_constraints = record_columns.constraint_columns
233
235
  record_columns.constraint_columns = [column.association.reverse].compact
234
236
  update_record_from_params(record, record_columns, attributes, avoid_changes)
@@ -240,8 +242,9 @@ module ActiveScaffold
240
242
 
241
243
  def build_record_from_params?(params, column, record)
242
244
  current = record.send(column.name)
243
- klass = column.association.klass
244
- (column.association.collection? && !column.show_blank_record?(current)) || !attributes_hash_is_empty?(params, klass)
245
+ return true if column.association.collection? && !column.show_blank_record?(current)
246
+ klass = column.association.klass(record)
247
+ klass && !attributes_hash_is_empty?(params, klass)
245
248
  end
246
249
 
247
250
  # Attempts to create or find an instance of the klass of the association in parent_column from the
@@ -249,21 +252,26 @@ module ActiveScaffold
249
252
  # otherwise it will build a new one.
250
253
  def find_or_create_for_params(params, parent_column, parent_record)
251
254
  current = parent_record.send(parent_column.name)
252
- klass = parent_column.association.klass
255
+ klass = parent_column.association.klass(parent_record)
253
256
  if params.key? klass.primary_key
254
257
  record_from_current_or_find(klass, params[klass.primary_key], current)
255
258
  elsif klass.authorized_for?(:crud_type => :create)
256
- parent_column.association.klass.new
259
+ association = parent_column.association
260
+ record = klass.new
261
+ if association.reverse_association&.belongs_to? && (association.collection? || current.nil?)
262
+ record.send("#{parent_column.association.reverse}=", parent_record)
263
+ end
264
+ record
257
265
  end
258
266
  end
259
267
 
260
268
  # Attempts to find an instance of klass (which must be an ActiveRecord object) with id primary key
261
269
  # Returns record from current if it's included or find from DB
262
270
  def record_from_current_or_find(klass, id, current)
263
- if current && current.is_a?(ActiveRecord::Base) && current.id.to_s == id
271
+ if current.is_a?(ActiveRecord::Base) && current.id.to_s == id
264
272
  # modifying the current object of a singular association
265
273
  current
266
- elsif current && current.respond_to?(:any?) && current.any? { |o| o.id.to_s == id }
274
+ elsif current.respond_to?(:any?) && current.any? { |o| o.id.to_s == id }
267
275
  # modifying one of the current objects in a plural association
268
276
  current.detect { |o| o.id.to_s == id }
269
277
  else # attaching an existing but not-current object
@@ -271,36 +279,20 @@ module ActiveScaffold
271
279
  end
272
280
  end
273
281
 
274
- def save_record_to_association(record, association, value)
275
- if association.try(:collection?)
276
- record.send(association.name) << value
277
- elsif association
278
- record.send("#{association.name}=", value)
279
- end
280
- end
281
-
282
282
  # Determines whether the given attributes hash is "empty".
283
283
  # This isn't a literal emptiness - it's an attempt to discern whether the user intended it to be empty or not.
284
284
  def attributes_hash_is_empty?(hash, klass)
285
- # old style date form management... ignore them too
286
- part_ignore_column_types = [:datetime, :date, :time, Time, Date]
287
-
288
285
  hash.all? do |key, value|
289
286
  # convert any possible multi-parameter attributes like 'created_at(5i)' to simply 'created_at'
290
- parts = key.to_s.split('(')
291
- column_name = parts.first
292
- column = ActiveScaffold::OrmChecks.columns_hash(klass)[column_name]
293
- column_type = ActiveScaffold::OrmChecks.column_type(klass, column_name) if column
287
+ column_name = key.to_s.split('(', 2)[0]
294
288
 
295
289
  # datetimes will always have a value. so we ignore them when checking whether the hash is empty.
296
290
  # this could be a bad idea. but the current situation (excess record entry) seems worse.
297
- next true if column && parts.length > 1 && part_ignore_column_types.include?(column_type)
291
+ next true if mulitpart_ignored?(key, klass)
298
292
 
299
293
  # defaults are pre-filled on the form. we can't use them to determine if the user intends a new row.
300
294
  # booleans always have value, so they are ignored if not changed from default
301
- default_value = column_default_value(column_name, klass, column)
302
- casted_value = column ? ActiveScaffold::Core.column_type_cast(value, column) : value
303
- next true if casted_value == default_value
295
+ next true if default_value?(column_name, klass, value)
304
296
 
305
297
  if params_hash? value
306
298
  attributes_hash_is_empty?(value, klass)
@@ -312,14 +304,30 @@ module ActiveScaffold
312
304
  end
313
305
  end
314
306
 
315
- def column_default_value(column_name, klass, column)
307
+ # old style date form management... ignore them
308
+ MULTIPART_IGNORE_TYPES = [:datetime, :date, :time, Time, Date].freeze
309
+
310
+ def mulitpart_ignored?(param_name, klass)
311
+ column_name, multipart = param_name.to_s.split('(', 2)
312
+ return false unless multipart
313
+ column_type = ActiveScaffold::OrmChecks.column_type(klass, column_name)
314
+ MULTIPART_IGNORE_TYPES.include?(column_type) if column_type
315
+ end
316
+
317
+ def default_value?(column_name, klass, value)
318
+ column = ActiveScaffold::OrmChecks.columns_hash(klass)[column_name]
319
+ default_value = column_default_value(column_name, klass)
320
+ casted_value = ActiveScaffold::Core.column_type_cast(value, column)
321
+ casted_value == default_value
322
+ end
323
+
324
+ def column_default_value(column_name, klass)
325
+ column = ActiveScaffold::OrmChecks.columns_hash(klass)[column_name]
316
326
  return unless column
317
327
  if ActiveScaffold::OrmChecks.mongoid? klass
318
328
  column.default_val
319
329
  elsif ActiveScaffold::OrmChecks.active_record? klass
320
- if Rails.version < '4.2'
321
- column.default
322
- elsif Rails.version < '5.0'
330
+ if Rails.version < '5.0'
323
331
  column.type_cast_from_database(column.default)
324
332
  else
325
333
  column_type = ActiveScaffold::OrmChecks.column_type(klass, column_name)
@@ -11,7 +11,7 @@ module ActiveScaffold
11
11
  self.bridges = {}
12
12
 
13
13
  def self.register(file)
14
- match = file.match(/(active_scaffold\/bridges\/(.*))\.rb\Z/)
14
+ match = file.match(%r{(active_scaffold/bridges/(.*))\.rb\Z})
15
15
  bridges[match[2].to_sym] = match[1] if match
16
16
  end
17
17
 
@@ -34,18 +34,18 @@ module ActiveScaffold
34
34
 
35
35
  def self.run_all
36
36
  return false if bridges_run
37
- bridges.keys.each do |bridge_name|
38
- bridge = self[bridge_name]
39
- bridge.run if bridge
37
+ bridges.each_key do |bridge_name|
38
+ self[bridge_name]&.run
40
39
  end
40
+ ActiveScaffold::Config::Core.freeze if ActiveScaffold.threadsafe
41
41
  self.bridges_run = true
42
42
  end
43
43
 
44
44
  def self.prepare_all
45
45
  return false if bridges_prepared
46
- bridges.keys.each do |bridge_name|
46
+ bridges.each_key do |bridge_name|
47
47
  bridge = self[bridge_name]
48
- bridge.prepare if bridge && bridge.install?
48
+ bridge.prepare if bridge&.install?
49
49
  end
50
50
  self.bridges_prepared = true
51
51
  end
@@ -53,14 +53,14 @@ module ActiveScaffold
53
53
  def self.all_stylesheets
54
54
  bridges.keys.collect do |bridge_name|
55
55
  bridge = self[bridge_name]
56
- bridge.stylesheets if bridge && bridge.install?
56
+ bridge.stylesheets if bridge&.install?
57
57
  end.compact.flatten
58
58
  end
59
59
 
60
60
  def self.all_javascripts
61
61
  bridges.keys.collect do |bridge_name|
62
62
  bridge = self[bridge_name]
63
- bridge.javascripts if bridge && bridge.install?
63
+ bridge.javascripts if bridge&.install?
64
64
  end.compact.flatten
65
65
  end
66
66
  end
@@ -0,0 +1,6 @@
1
+ class ActiveScaffold::Bridges::ActiveStorage < ActiveScaffold::DataStructures::Bridge
2
+ def self.install
3
+ Dir[File.join(__dir__, 'active_storage', '*.rb')].each { |file| require file }
4
+ ActiveScaffold::Config::Core.send :prepend, ActiveScaffold::Bridges::ActiveStorage::ActiveStorageBridge
5
+ end
6
+ end
@@ -0,0 +1,34 @@
1
+ module ActiveScaffold
2
+ module Bridges
3
+ class ActiveStorage
4
+ module ActiveStorageBridge
5
+ def initialize(model_id)
6
+ super
7
+ return unless ActiveScaffold::Bridges::ActiveStorage::ActiveStorageBridgeHelpers.klass_has_active_storage_fields?(model)
8
+
9
+ model.send :extend, ActiveScaffold::Bridges::ActiveStorage::ActiveStorageBridgeHelpers
10
+
11
+ # include the "delete" helpers for use with active scaffold, unless they are already included
12
+ model.generate_delete_helpers
13
+
14
+ update.multipart = true
15
+ create.multipart = true
16
+
17
+ model.active_storage_has_one_fields.each { |field| configure_active_storage_field(field.to_sym, :has_one) }
18
+ model.active_storage_has_many_fields.each { |field| configure_active_storage_field(field.to_sym, :has_many) }
19
+ end
20
+
21
+ private
22
+
23
+ def configure_active_storage_field(field, field_type)
24
+ columns << field
25
+ columns.exclude "#{field}_attachment#{'s' if field_type == :has_many}".to_sym
26
+ columns.exclude "#{field}_blob#{'s' if field_type == :has_many}".to_sym
27
+ columns[field].includes ||= ["#{field}_attachment#{'s' if field_type == :has_many}".to_sym, "#{field}_blob#{'s' if field_type == :has_many}".to_sym]
28
+ columns[field].form_ui ||= "active_storage_#{field_type}".to_sym
29
+ columns[field].params.add "delete_#{field}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ module ActiveScaffold
2
+ module Bridges
3
+ class ActiveStorage
4
+ module ActiveStorageBridgeHelpers
5
+ mattr_accessor :thumbnail_variant
6
+ self.thumbnail_variant = {resize_to_limit: [nil, 30]}
7
+
8
+ class << self
9
+ # has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: false
10
+ def active_storage_has_one_fields(klass)
11
+ klass.reflect_on_all_associations(:has_one)&.select { |reflection| reflection.class_name == 'ActiveStorage::Attachment' } &.collect { |association| association.name[0..-12] } || []
12
+ end
13
+
14
+ # has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: false do
15
+ def active_storage_has_many_fields(klass)
16
+ klass.reflect_on_all_associations(:has_many)&.select { |reflection| reflection.class_name == 'ActiveStorage::Attachment' } &.collect { |association| association.name[0..-13] } || []
17
+ end
18
+
19
+ def klass_has_active_storage_fields?(klass)
20
+ active_storage_has_one_fields(klass).present? || active_storage_has_many_fields(klass).present?
21
+ end
22
+
23
+ def generate_delete_helpers(klass)
24
+ (active_storage_has_one_fields(klass) | active_storage_has_many_fields(klass)).each do |field|
25
+ klass.send :class_eval, <<-CODE, __FILE__, __LINE__ + 1 unless klass.method_defined?(:"#{field}_with_delete=")
26
+ attr_reader :delete_#{field}
27
+
28
+ def delete_#{field}=(value)
29
+ value = (value=="true") if String===value
30
+ return unless value
31
+
32
+ # passing nil to the file column causes the file to be deleted.
33
+ self.#{field}.purge
34
+ end
35
+ CODE
36
+ end
37
+ end
38
+ end
39
+
40
+ def active_storage_has_one_fields
41
+ @active_storage_has_one_fields ||= ActiveStorageBridgeHelpers.active_storage_has_one_fields(self)
42
+ end
43
+
44
+ def active_storage_has_many_fields
45
+ @active_storage_has_many_fields ||= ActiveStorageBridgeHelpers.active_storage_has_many_fields(self)
46
+ end
47
+
48
+ def generate_delete_helpers
49
+ ActiveStorageBridgeHelpers.generate_delete_helpers(self)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end