active_scaffold 3.5.5 → 3.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGELOG → CHANGELOG.rdoc} +75 -0
  3. data/README.md +21 -10
  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 +26 -10
  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 +3 -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 +43 -41
  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 +83 -67
  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 +3 -3
  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 +71 -15
  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
@@ -1,29 +1,37 @@
1
1
  module ActiveScaffold::DataStructures
2
2
  class ActionLink
3
+ NO_OPTIONS = {}.freeze
4
+
3
5
  # provides a quick way to set any property of the object from a hash
4
6
  def initialize(action, options = {})
5
7
  # set defaults
6
- self.action = action
7
- self.label = action
8
- self.confirm = false
9
- self.type = :collection
8
+ @action = action
9
+ @label = action
10
+ @confirm = false
11
+ @type = :collection
12
+ @method = :get
13
+ @crud_type =
14
+ case action&.to_sym
15
+ when :destroy then :delete
16
+ when :create, :new then :create
17
+ when :update, :edit then :update
18
+ else :read
19
+ end
20
+ @column = nil
21
+ @image = nil
22
+ @controller = nil
23
+ @parameters = nil
24
+ @dynamic_parameters = nil
25
+ @html_options = nil
26
+ @weight = 0
10
27
  self.inline = true
11
- self.method = :get
12
- self.crud_type = :delete if [:destroy].include?(action.try(:to_sym))
13
- self.crud_type = :create if %i[create new].include?(action.try(:to_sym))
14
- self.crud_type = :update if %i[edit update].include?(action.try(:to_sym))
15
- self.crud_type ||= :read
16
- self.column = nil
17
- self.image = nil
18
- self.dynamic_parameters = nil
19
- self.weight = 0
20
28
 
21
29
  # apply quick properties
22
30
  options.each_pair do |k, v|
23
31
  setter = "#{k}="
24
32
  send(setter, v) if respond_to? setter
25
33
  end
26
- self.toggle = self.action.try(:to_sym) == :index && (parameters.present? || dynamic_parameters) unless options.include? :toggle
34
+ self.toggle = self.action&.to_sym == :index && !position && (parameters.present? || dynamic_parameters) unless options.include? :toggle
27
35
  end
28
36
 
29
37
  def initialize_copy(action_link)
@@ -52,7 +60,8 @@ module ActiveScaffold::DataStructures
52
60
  # a hash of request parameters
53
61
  attr_writer :parameters
54
62
  def parameters
55
- @parameters ||= {}
63
+ return @parameters || NO_OPTIONS if frozen?
64
+ @parameters ||= NO_OPTIONS.dup
56
65
  end
57
66
 
58
67
  # if active class is added to link when current request matches link
@@ -69,7 +78,7 @@ module ActiveScaffold::DataStructures
69
78
  # what string to use to represent this action
70
79
  attr_writer :label
71
80
  def label
72
- @label.is_a?(Symbol) ? as_(@label) : @label
81
+ @label.is_a?(Symbol) ? ActiveScaffold::Registry.cache(:translations, @label) { as_(@label) } : @label
73
82
  end
74
83
 
75
84
  # image to use {:name => 'arrow.png', :size => '16x16'}
@@ -82,7 +91,8 @@ module ActiveScaffold::DataStructures
82
91
  end
83
92
 
84
93
  def confirm(label = '')
85
- @confirm.is_a?(String) ? @confirm : as_(@confirm, :label => label)
94
+ return @confirm if !confirm? || @confirm.is_a?(String)
95
+ ActiveScaffold::Registry.cache(:translations, @confirm) { as_(@confirm) } % {label: label}
86
96
  end
87
97
 
88
98
  def confirm?
@@ -186,7 +196,8 @@ module ActiveScaffold::DataStructures
186
196
  # html options for the link
187
197
  attr_writer :html_options
188
198
  def html_options
189
- @html_options ||= {}
199
+ return @html_options || NO_OPTIONS if frozen?
200
+ @html_options ||= NO_OPTIONS.dup
190
201
  end
191
202
 
192
203
  # nested action_links are referencing a column
@@ -198,13 +209,32 @@ module ActiveScaffold::DataStructures
198
209
  @keep_open
199
210
  end
200
211
 
212
+ # for links in singular associations, copied from
213
+ # column.actions_for_association_links, excluding
214
+ # actions not available in association's controller
215
+ attr_accessor :controller_actions
216
+
201
217
  # indicates that this a nested_link
202
218
  def nested_link?
203
- @column || (parameters && parameters[:named_scope])
219
+ @column || parameters&.dig(:named_scope)
204
220
  end
205
221
 
206
222
  def name_to_cache
207
- @name_to_cache ||= "#{controller || 'self'}_#{type}_#{action}#{'_' if parameters.present?}#{parameters.map { |k, v| "#{k}=#{v.is_a?(Array) ? v.join(',') : v}" }.join('_')}"
223
+ return @name_to_cache if defined? @name_to_cache
224
+ [
225
+ controller || 'self',
226
+ type,
227
+ action,
228
+ *parameters.map { |k, v| "#{k}=#{v.is_a?(Array) ? v.join(',') : v}" }
229
+ ].compact.join('_').tap do |name_to_cache|
230
+ @name_to_cache = name_to_cache unless frozen?
231
+ end
232
+ end
233
+
234
+ def freeze
235
+ # force generating cache_key, except for column's link without action, or polymorphic associations
236
+ name_to_cache if action && !column&.association&.polymorphic?
237
+ super
208
238
  end
209
239
  end
210
240
  end
@@ -53,8 +53,8 @@ module ActiveScaffold::DataStructures
53
53
  if item.is_a?(ActiveScaffold::DataStructures::ActionLinks)
54
54
  collected = item[val]
55
55
  links << collected unless collected.nil?
56
- else
57
- links << item if item.action.to_s == val.to_s
56
+ elsif item.action.to_s == val.to_s
57
+ links << item
58
58
  end
59
59
  end
60
60
  links.first
@@ -66,8 +66,8 @@ module ActiveScaffold::DataStructures
66
66
  if item.is_a?(ActiveScaffold::DataStructures::ActionLinks)
67
67
  collected = item.find_duplicate(link)
68
68
  links << collected unless collected.nil?
69
- else
70
- links << item if item.action == link.action && item.static_controller? && item.controller == link.controller && item.parameters == link.parameters
69
+ elsif item.action == link.action && item.static_controller? && item.controller == link.controller && item.parameters == link.parameters
70
+ links << item
71
71
  end
72
72
  end
73
73
  links.first
@@ -90,7 +90,6 @@ module ActiveScaffold::DataStructures
90
90
  break
91
91
  else
92
92
  group.delete_group(name)
93
- break
94
93
  end
95
94
  end
96
95
  end
@@ -101,12 +100,10 @@ module ActiveScaffold::DataStructures
101
100
  @set.sort_by(&:weight).send(method) do |item|
102
101
  if item.is_a?(ActiveScaffold::DataStructures::ActionLinks) && !options[:groups]
103
102
  item.each(options, &block)
103
+ elsif options[:include_set]
104
+ yield item, @set
104
105
  else
105
- if options[:include_set]
106
- yield item, @set
107
- else
108
- yield item
109
- end
106
+ yield item
110
107
  end
111
108
  end
112
109
  end
@@ -147,16 +144,21 @@ module ActiveScaffold::DataStructures
147
144
  end
148
145
 
149
146
  def method_missing(name, *args, &block)
150
- class_eval %{
151
- def #{name}(label = nil)
147
+ return super if name =~ /[!?]$/
148
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
149
+ def #{name}(label = nil) # rubocop:disable Style/CommentedKeyword
152
150
  @#{name} ||= subgroup('#{name}'.to_sym, label)
153
151
  yield @#{name} if block_given?
154
152
  @#{name}
155
153
  end
156
- }
154
+ METHOD
157
155
  send(name, args.first, &block)
158
156
  end
159
157
 
158
+ def respond_to_missing?(name, *)
159
+ name !~ /[!?]$/
160
+ end
161
+
160
162
  attr_accessor :name
161
163
  attr_accessor :weight
162
164
 
@@ -4,21 +4,29 @@ module ActiveScaffold::DataStructures::Association
4
4
  @association = association
5
5
  end
6
6
  attr_writer :reverse
7
- delegate :name, :klass, :foreign_key, :==, to: :@association
7
+ delegate :name, :foreign_key, :==, to: :@association
8
8
 
9
9
  def allow_join?
10
10
  !polymorphic?
11
11
  end
12
12
 
13
+ def klass(record = nil)
14
+ if polymorphic?
15
+ record&.send(foreign_type)&.safe_constantize
16
+ else
17
+ @association.klass
18
+ end
19
+ end
20
+
13
21
  def belongs_to?
14
22
  @association.macro == :belongs_to
15
23
  end
16
24
 
17
- def has_one?
25
+ def has_one? # rubocop:disable Naming/PredicateName
18
26
  @association.macro == :has_one
19
27
  end
20
28
 
21
- def has_many?
29
+ def has_many? # rubocop:disable Naming/PredicateName
22
30
  @association.macro == :has_many
23
31
  end
24
32
 
@@ -30,10 +38,22 @@ module ActiveScaffold::DataStructures::Association
30
38
  !collection?
31
39
  end
32
40
 
41
+ def collection?
42
+ has_many? || habtm?
43
+ end
44
+
33
45
  def through?
34
46
  false
35
47
  end
36
48
 
49
+ def through_singular?
50
+ through? && !through_reflection.collection?
51
+ end
52
+
53
+ def through_collection?
54
+ through? && through_reflection.collection?
55
+ end
56
+
37
57
  def polymorphic?
38
58
  false
39
59
  end
@@ -48,6 +68,8 @@ module ActiveScaffold::DataStructures::Association
48
68
 
49
69
  def scope; end
50
70
 
71
+ def as; end
72
+
51
73
  def respond_to_target?
52
74
  false
53
75
  end
@@ -66,23 +88,24 @@ module ActiveScaffold::DataStructures::Association
66
88
 
67
89
  def reverse(klass = nil)
68
90
  unless polymorphic? || defined?(@reverse)
69
- @reverse ||= inverse || get_reverse.try(:name)
91
+ @reverse ||= inverse || get_reverse&.name
70
92
  end
71
- @reverse || (get_reverse(klass).try(:name) unless klass.nil?)
93
+ @reverse || (get_reverse(klass)&.name unless klass.nil?)
72
94
  end
73
95
 
74
96
  def inverse_for?(klass)
75
- inverse_class = reverse_association(klass).try(:inverse_klass)
97
+ inverse_class = reverse_association(klass)&.inverse_klass
76
98
  inverse_class.present? && (inverse_class == klass || klass < inverse_class)
77
99
  end
78
100
 
79
101
  def reverse_association(klass = nil)
80
- assoc = if polymorphic?
81
- get_reverse(klass) unless klass.nil?
82
- else
83
- return unless reverse_name = reverse(klass)
84
- reflect_on_association(reverse_name)
85
- end
102
+ assoc =
103
+ if polymorphic?
104
+ get_reverse(klass) unless klass.nil?
105
+ else
106
+ reverse_name = reverse(klass)
107
+ reflect_on_association(reverse_name) if reverse_name
108
+ end
86
109
  self.class.new(assoc) if assoc
87
110
  end
88
111
 
@@ -110,13 +133,13 @@ module ActiveScaffold::DataStructures::Association
110
133
  associations = self.class.reflect_on_all_associations(klass)
111
134
  # collect associations that point back to this model and use the same foreign_key
112
135
  associations.each_with_object([]) do |assoc, reverse_matches|
113
- reverse_matches << assoc if reverse_match? assoc
136
+ reverse_matches << assoc if assoc != @association && reverse_match?(assoc)
114
137
  end
115
138
  end
116
139
 
117
140
  def reverse_match?(assoc)
118
- return false if assoc == @association
119
- return false unless assoc.polymorphic? || assoc.class_name == inverse_klass.try(:name)
141
+ return assoc.name == as if as || assoc.polymorphic?
142
+ return false if assoc.class_name != inverse_klass&.name
120
143
 
121
144
  if through?
122
145
  reverse_through_match?(assoc)
@@ -17,18 +17,14 @@ module ActiveScaffold::DataStructures::Association
17
17
  %i[belongs_to_record belongs_to_document].include?(@association.macro)
18
18
  end
19
19
 
20
- def has_one?
20
+ def has_one? # rubocop:disable Naming/PredicateName
21
21
  %i[has_one_record has_one_document].include?(@association.macro)
22
22
  end
23
23
 
24
- def has_many?
24
+ def has_many? # rubocop:disable Naming/PredicateName
25
25
  %i[has_many_records has_many_documents].include?(@association.macro)
26
26
  end
27
27
 
28
- def collection?
29
- %i[has_many_documents has_many_records].include?(@association.macro)
30
- end
31
-
32
28
  def table_name
33
29
  @association.klass < ActiveRecord::Base ? @association.klass.table_name : super
34
30
  end
@@ -48,7 +48,7 @@ module ActiveScaffold::DataStructures::Association
48
48
 
49
49
  # name of inverse
50
50
  def inverse
51
- @association.inverse_of.try(:name)
51
+ @association.inverse_of&.name
52
52
  end
53
53
 
54
54
  def quoted_table_name
@@ -75,7 +75,11 @@ module ActiveScaffold::DataStructures::Association
75
75
 
76
76
  def scope_values
77
77
  return {} unless @association.scope
78
- @scope_values ||= @association.klass.instance_exec(&@association.scope).values rescue {}
78
+ @scope_values ||= @association.klass.instance_exec(&@association.scope).values
79
+ rescue StandardError => e
80
+ message = "Error evaluating scope for #{@association.name} in #{@association.klass.name}:"
81
+ Rails.logger.warn "#{message}\n#{e.message}\n#{e.backtrace.join("\n")}"
82
+ {}
79
83
  end
80
84
 
81
85
  def reverse_through_match?(assoc)
@@ -1,9 +1,6 @@
1
1
  module ActiveScaffold::DataStructures::Association
2
2
  class Mongoid < Abstract
3
3
  delegate :inverse_klass, :as, :dependent, :inverse, to: :@association
4
- def collection?
5
- %i[has_many has_and_belongs_to_many].include?(@association.macro)
6
- end
7
4
 
8
5
  # polymorphic belongs_to
9
6
  def polymorphic?
@@ -2,12 +2,14 @@ module ActiveScaffold::DataStructures
2
2
  class Column
3
3
  include ActiveScaffold::Configurable
4
4
  include ActiveScaffold::OrmChecks
5
+ NO_PARAMS = Set.new.freeze
6
+ NO_OPTIONS = {}.freeze
5
7
 
6
8
  attr_reader :active_record_class
7
9
  alias model active_record_class
8
10
 
9
11
  # this is the name of the getter on the ActiveRecord model. it is the only absolutely required attribute ... all others will be inferred from this name.
10
- attr_accessor :name
12
+ attr_reader :name
11
13
 
12
14
  # Whether to enable inplace editing for this column. Currently works for text columns, in the List.
13
15
  attr_reader :inplace_edit
@@ -31,8 +33,8 @@ module ActiveScaffold::DataStructures
31
33
 
32
34
  # Any extra parameters this particular column uses. This is for create/update purposes.
33
35
  def params
34
- # lazy initialize
35
- @params ||= Set.new
36
+ return @params || NO_PARAMS if frozen?
37
+ @params ||= NO_PARAMS.dup
36
38
  end
37
39
 
38
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.
@@ -112,16 +114,9 @@ module ActiveScaffold::DataStructures
112
114
  # supported options:
113
115
  # * for association columns
114
116
  # * :select - displays a simple <select> or a collection of checkboxes to (dis)associate records
115
- def form_ui=(value)
116
- self.list_method = nil if @list_ui.nil? && value != @form_ui
117
- @form_ui = value
118
- end
119
- attr_reader :form_ui
117
+ attr_accessor :form_ui
120
118
 
121
- def list_ui=(value)
122
- self.list_method = nil if value != @list_ui
123
- @list_ui = value
124
- end
119
+ attr_writer :list_ui
125
120
 
126
121
  def list_ui
127
122
  @list_ui || form_ui
@@ -140,10 +135,12 @@ module ActiveScaffold::DataStructures
140
135
  # a place to store dev's column specific options
141
136
  attr_writer :options
142
137
  def options
143
- @options ||= {}
138
+ return @options || NO_OPTIONS if frozen?
139
+ @options ||= NO_OPTIONS.dup
144
140
  end
145
141
 
146
142
  def link
143
+ return @link.call(self) if frozen? && @link.is_a?(Proc)
147
144
  @link = @link.call(self) if @link.is_a? Proc
148
145
  @link
149
146
  end
@@ -182,10 +179,11 @@ module ActiveScaffold::DataStructures
182
179
  # a collection of associations to pre-load when finding the records on a page
183
180
  attr_reader :includes
184
181
  def includes=(value)
185
- @includes = case value
186
- when Array then value
187
- else value ? [value] : value # not convert nil to [nil]
188
- end
182
+ @includes =
183
+ case value
184
+ when Array then value
185
+ else value ? [value] : value # not convert nil to [nil]
186
+ end
189
187
  end
190
188
 
191
189
  # a collection of associations to do left join when this column is included on search
@@ -194,10 +192,11 @@ module ActiveScaffold::DataStructures
194
192
  end
195
193
 
196
194
  def search_joins=(value)
197
- @search_joins = case value
198
- when Array then value
199
- else [value] # automatically convert to an array
200
- end
195
+ @search_joins =
196
+ case value
197
+ when Array then value
198
+ else [value] # automatically convert to an array
199
+ end
201
200
  end
202
201
 
203
202
  # a collection of columns to load when eager loading is disabled, if it's nil all columns will be loaded
@@ -210,7 +209,7 @@ module ActiveScaffold::DataStructures
210
209
  def search_sql=(value)
211
210
  @search_sql =
212
211
  if value
213
- (value == true || value.is_a?(Proc)) ? value : Array(value)
212
+ value == true || value.is_a?(Proc) ? value : Array(value)
214
213
  else
215
214
  value
216
215
  end
@@ -241,6 +240,9 @@ module ActiveScaffold::DataStructures
241
240
  @associated_number
242
241
  end
243
242
 
243
+ # what string to use to join records from plural associations
244
+ attr_accessor :association_join_text
245
+
244
246
  # whether a blank row must be shown in the subform
245
247
  cattr_accessor :show_blank_record, instance_accessor: false
246
248
  @@show_blank_record = true
@@ -300,21 +302,26 @@ module ActiveScaffold::DataStructures
300
302
  end
301
303
  end
302
304
 
305
+ # cache key to cache column info
306
+ attr_reader :cache_key
307
+
303
308
  # instantiation is handled internally through the DataStructures::Columns object
304
309
  def initialize(name, active_record_class, delegated_association = nil) #:nodoc:
305
- self.name = name.to_sym
310
+ @name = name.to_sym
306
311
  @active_record_class = active_record_class
307
- @column = _columns_hash[self.name.to_s]
312
+ @column = _columns_hash[name.to_s]
308
313
  @delegated_association = delegated_association
314
+ @cache_key = [@active_record_class.name, name].compact.map(&:to_s).join('#')
309
315
  setup_association_info
310
316
 
311
- @autolink = self.association.present?
317
+ @link = nil
318
+ @autolink = association.present?
312
319
  @table = _table_name
313
320
  @associated_limit = self.class.associated_limit
314
321
  @associated_number = self.class.associated_number
315
322
  @show_blank_record = self.class.show_blank_record
316
323
  @send_form_on_update_column = self.class.send_form_on_update_column
317
- @actions_for_association_links = self.class.actions_for_association_links.clone if self.association
324
+ @actions_for_association_links = self.class.actions_for_association_links.dup if association
318
325
  @select_columns = default_select_columns
319
326
 
320
327
  @text = @column.nil? || [:string, :text, String].include?(column_type)
@@ -328,16 +335,17 @@ module ActiveScaffold::DataStructures
328
335
  @form_ui = :number
329
336
  @options = {:format => :i18n_number}
330
337
  else
331
- @form_ui = case @column.type
332
- when :boolean then :checkbox
333
- when :text then :textarea
334
- end
338
+ @form_ui =
339
+ case @column.type
340
+ when :boolean then :checkbox
341
+ when :text then :textarea
342
+ end
335
343
  end
336
344
  end
337
345
  @allow_add_existing = true
338
346
  @form_ui = self.class.association_form_ui if @association && self.class.association_form_ui
339
347
 
340
- self.includes = [association.name] if association && association.allow_join?
348
+ self.includes = [association.name] if association&.allow_join?
341
349
  if delegated_association
342
350
  self.includes = includes ? [delegated_association.name => includes] : [delegated_association.name]
343
351
  end
@@ -345,7 +353,7 @@ module ActiveScaffold::DataStructures
345
353
 
346
354
  # default all the configurable variables
347
355
  self.css_class = ''
348
- self.required = active_record_class.validators_on(self.name).any? do |val|
356
+ self.required = active_record_class.validators_on(name).any? do |val|
349
357
  validator_force_required?(val)
350
358
  end
351
359
  self.sort = true
@@ -365,18 +373,23 @@ module ActiveScaffold::DataStructures
365
373
  order_weight.nonzero? ? order_weight : name.to_s <=> other.name.to_s
366
374
  end
367
375
 
376
+ def convert_to_native?
377
+ number? && options[:format] && form_ui != :number
378
+ end
379
+
368
380
  def number_to_native(value)
369
381
  return value if value.blank? || !value.is_a?(String)
370
382
  native = '.' # native ruby separator
371
383
  format = {:separator => '', :delimiter => ''}.merge! I18n.t('number.format', :default => {})
372
- specific = case options[:format]
373
- when :currency
374
- I18n.t('number.currency.format', :default => nil)
375
- when :size
376
- I18n.t('number.human.format', :default => nil)
377
- when :percentage
378
- I18n.t('number.percentage.format', :default => nil)
379
- end
384
+ specific =
385
+ case options[:format]
386
+ when :currency
387
+ I18n.t('number.currency.format', :default => nil)
388
+ when :size
389
+ I18n.t('number.human.format', :default => nil)
390
+ when :percentage
391
+ I18n.t('number.percentage.format', :default => nil)
392
+ end
380
393
  format.merge! specific unless specific.nil?
381
394
  if format[:separator].blank? || !value.include?(format[:separator]) && value.include?(native) && (format[:delimiter] != native || value !~ /\.\d{3}$/)
382
395
  value
@@ -385,11 +398,9 @@ module ActiveScaffold::DataStructures
385
398
  end
386
399
  end
387
400
 
388
- # to cache method to get value in list
389
- attr_accessor :list_method
390
-
391
- # cache constraints for numeric columns (get in ActiveScaffold::Helpers::FormColumnHelpers::numerical_constraints_for_column)
392
- attr_accessor :numerical_constraints
401
+ def default_for_empty_value
402
+ (column.null ? nil : column.default) if column
403
+ end
393
404
 
394
405
  # the table.field name for this column, if applicable
395
406
  def field
@@ -407,16 +418,18 @@ module ActiveScaffold::DataStructures
407
418
  protected
408
419
 
409
420
  def setup_association_info
410
- assoc = active_record_class.reflect_on_association(self.name)
411
- @association = if assoc
412
- case
413
- when active_record? then Association::ActiveRecord.new(assoc)
414
- when mongoid? then Association::Mongoid.new(assoc)
415
- end
416
- elsif defined?(ActiveMongoid) && model < ActiveMongoid::Associations
417
- assoc = active_record_class.reflect_on_am_association(name)
418
- Association::ActiveMongoid.new(assoc) if assoc
419
- end
421
+ assoc = active_record_class.reflect_on_association(name)
422
+ @association =
423
+ if assoc
424
+ if active_record?
425
+ Association::ActiveRecord.new(assoc)
426
+ elsif mongoid?
427
+ Association::Mongoid.new(assoc)
428
+ end
429
+ elsif defined?(ActiveMongoid) && model < ActiveMongoid::Associations
430
+ assoc = active_record_class.reflect_on_am_association(name)
431
+ Association::ActiveMongoid.new(assoc) if assoc
432
+ end
420
433
  end
421
434
 
422
435
  def validator_force_required?(val)
@@ -438,7 +451,7 @@ module ActiveScaffold::DataStructures
438
451
  def default_select_columns
439
452
  if association.nil? && column
440
453
  [field]
441
- elsif association.try(:polymorphic?)
454
+ elsif association&.polymorphic?
442
455
  [field, quoted_field(quoted_field_name(association.foreign_type))]
443
456
  elsif association
444
457
  if association.belongs_to?
@@ -448,7 +461,7 @@ module ActiveScaffold::DataStructures
448
461
  if _columns_hash[count_column = "#{association.name}_count"]
449
462
  columns << quoted_field(quoted_field_name(count_column))
450
463
  end
451
- if association.through_reflection.try(:belongs_to?)
464
+ if association.through_reflection&.belongs_to?
452
465
  columns << quoted_field(quoted_field_name(association.through_reflection.foreign_key))
453
466
  end
454
467
  columns
@@ -474,16 +487,12 @@ module ActiveScaffold::DataStructures
474
487
  end
475
488
 
476
489
  def initialize_sort
477
- if virtual?
478
- # we don't automatically enable method sorting for virtual columns because it's slow, and we expect fewer complaints this way.
479
- self.sort = false
480
- else
490
+ self.sort =
481
491
  if column && !tableless?
482
- self.sort = {:sql => field}
492
+ {:sql => field}
483
493
  else
484
- self.sort = false
494
+ false
485
495
  end
486
- end
487
496
  end
488
497
 
489
498
  def initialize_search_sql
@@ -501,9 +510,9 @@ module ActiveScaffold::DataStructures
501
510
  attr_reader :table
502
511
 
503
512
  def estimate_weight
504
- if association.try(:singular?)
513
+ if association&.singular?
505
514
  400
506
- elsif association.try(:collection?)
515
+ elsif association&.collection?
507
516
  500
508
517
  elsif %i[created_at updated_at].include?(name)
509
518
  600