active_scaffold 3.5.5 → 3.6.1

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} +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