active_scaffold 3.5.3 → 3.6.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGELOG → CHANGELOG.rdoc} +73 -0
  3. data/README.md +17 -7
  4. data/app/assets/javascripts/active_scaffold.js.erb +0 -1
  5. data/app/assets/javascripts/jquery/active_scaffold.js +97 -6
  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 +1 -0
  63. data/lib/active_scaffold/bridges/bitfields/bitfields_bridge.rb +12 -15
  64. data/lib/active_scaffold/bridges/calendar_date_select/as_cds_bridge.rb +1 -1
  65. data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +9 -12
  66. data/lib/active_scaffold/bridges/carrierwave/carrierwave_bridge.rb +1 -1
  67. data/lib/active_scaffold/bridges/carrierwave/list_ui.rb +2 -2
  68. data/lib/active_scaffold/bridges/chosen/helpers.rb +11 -9
  69. data/lib/active_scaffold/bridges/date_picker/ext.rb +0 -13
  70. data/lib/active_scaffold/bridges/date_picker/helper.rb +49 -44
  71. data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +1 -1
  72. data/lib/active_scaffold/bridges/file_column/as_file_column_bridge.rb +1 -1
  73. data/lib/active_scaffold/bridges/file_column/file_column_helpers.rb +3 -3
  74. data/lib/active_scaffold/bridges/file_column/form_ui.rb +3 -3
  75. data/lib/active_scaffold/bridges/file_column/test/functional/file_column_keep_test.rb +10 -7
  76. data/lib/active_scaffold/bridges/paper_trail.rb +1 -1
  77. data/lib/active_scaffold/bridges/paper_trail/actions.rb +3 -1
  78. data/lib/active_scaffold/bridges/paperclip/list_ui.rb +1 -1
  79. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge.rb +1 -1
  80. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge_helpers.rb +2 -2
  81. data/lib/active_scaffold/bridges/record_select/helpers.rb +15 -17
  82. data/lib/active_scaffold/bridges/shared/date_bridge.rb +20 -19
  83. data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +3 -1
  84. data/lib/active_scaffold/bridges/usa_state_select/usa_state_select_helper.rb +21 -4
  85. data/lib/active_scaffold/config/base.rb +133 -41
  86. data/lib/active_scaffold/config/core.rb +146 -18
  87. data/lib/active_scaffold/config/delete.rb +14 -1
  88. data/lib/active_scaffold/config/field_search.rb +7 -1
  89. data/lib/active_scaffold/config/form.rb +10 -1
  90. data/lib/active_scaffold/config/list.rb +39 -13
  91. data/lib/active_scaffold/config/mark.rb +4 -2
  92. data/lib/active_scaffold/config/nested.rb +16 -17
  93. data/lib/active_scaffold/config/search.rb +9 -0
  94. data/lib/active_scaffold/config/show.rb +4 -0
  95. data/lib/active_scaffold/config/update.rb +4 -0
  96. data/lib/active_scaffold/configurable.rb +14 -7
  97. data/lib/active_scaffold/constraints.rb +22 -20
  98. data/lib/active_scaffold/core.rb +67 -28
  99. data/lib/active_scaffold/data_structures/action_columns.rb +50 -59
  100. data/lib/active_scaffold/data_structures/action_link.rb +50 -20
  101. data/lib/active_scaffold/data_structures/action_links.rb +15 -13
  102. data/lib/active_scaffold/data_structures/association/abstract.rb +38 -15
  103. data/lib/active_scaffold/data_structures/association/active_mongoid.rb +2 -6
  104. data/lib/active_scaffold/data_structures/association/active_record.rb +6 -2
  105. data/lib/active_scaffold/data_structures/association/mongoid.rb +0 -3
  106. data/lib/active_scaffold/data_structures/column.rb +75 -66
  107. data/lib/active_scaffold/data_structures/columns.rb +3 -2
  108. data/lib/active_scaffold/data_structures/nested_info.rb +33 -19
  109. data/lib/active_scaffold/data_structures/set.rb +8 -0
  110. data/lib/active_scaffold/data_structures/sorting.rb +10 -2
  111. data/lib/active_scaffold/delayed_setup.rb +16 -5
  112. data/lib/active_scaffold/extensions/action_controller_rendering.rb +3 -2
  113. data/lib/active_scaffold/extensions/action_view_rendering.rb +34 -14
  114. data/lib/active_scaffold/extensions/cow_proxy.rb +95 -0
  115. data/lib/active_scaffold/extensions/ice_nine.rb +36 -0
  116. data/lib/active_scaffold/extensions/left_outer_joins.rb +8 -33
  117. data/lib/active_scaffold/extensions/localize.rb +3 -1
  118. data/lib/active_scaffold/extensions/routing_mapper.rb +6 -45
  119. data/lib/active_scaffold/extensions/to_label.rb +3 -2
  120. data/lib/active_scaffold/extensions/unsaved_record.rb +2 -4
  121. data/lib/active_scaffold/finder.rb +110 -77
  122. data/lib/active_scaffold/helpers/action_link_helpers.rb +62 -36
  123. data/lib/active_scaffold/helpers/association_helpers.rb +21 -19
  124. data/lib/active_scaffold/helpers/controller_helpers.rb +34 -10
  125. data/lib/active_scaffold/helpers/form_column_helpers.rb +196 -124
  126. data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -1
  127. data/lib/active_scaffold/helpers/id_helpers.rb +6 -2
  128. data/lib/active_scaffold/helpers/list_column_helpers.rb +86 -57
  129. data/lib/active_scaffold/helpers/pagination_helpers.rb +2 -2
  130. data/lib/active_scaffold/helpers/search_column_helpers.rb +29 -34
  131. data/lib/active_scaffold/helpers/show_column_helpers.rb +3 -5
  132. data/lib/active_scaffold/helpers/view_helpers.rb +38 -35
  133. data/lib/active_scaffold/marked_model.rb +2 -2
  134. data/lib/active_scaffold/orm_checks.rb +3 -7
  135. data/lib/active_scaffold/paginator.rb +7 -7
  136. data/lib/active_scaffold/registry.rb +33 -0
  137. data/lib/active_scaffold/responds_to_parent.rb +8 -11
  138. data/lib/active_scaffold/tableless.rb +67 -65
  139. data/lib/active_scaffold/version.rb +2 -2
  140. data/lib/generators/active_scaffold/controller_generator.rb +2 -2
  141. data/lib/generators/active_scaffold/install_generator.rb +1 -1
  142. data/lib/generators/active_scaffold/resource_generator.rb +2 -2
  143. data/shoulda_macros/macros.rb +3 -1
  144. data/test/bridges/date_picker_test.rb +1 -2
  145. data/test/bridges/paperclip_test.rb +6 -6
  146. data/test/class_with_finder.rb +2 -2
  147. data/test/company.rb +4 -4
  148. data/test/config/create_test.rb +4 -2
  149. data/test/config/nested_test.rb +1 -1
  150. data/test/config/show_test.rb +1 -1
  151. data/test/config/update_test.rb +7 -6
  152. data/test/data_structures/action_columns_test.rb +2 -2
  153. data/test/data_structures/action_links_test.rb +1 -1
  154. data/test/data_structures/column_test.rb +3 -6
  155. data/test/data_structures/columns_test.rb +2 -2
  156. data/test/data_structures/sorting_test.rb +7 -0
  157. data/test/extensions/active_record_test.rb +4 -4
  158. data/test/extensions/routing_mapper_test.rb +2 -2
  159. data/test/helpers/list_column_helpers_test.rb +3 -1
  160. data/test/misc/active_record_permissions_test.rb +3 -11
  161. data/test/misc/attribute_params_test.rb +12 -8
  162. data/test/misc/calculation_test.rb +1 -1
  163. data/test/misc/configurable_test.rb +10 -10
  164. data/test/misc/constraints_test.rb +2 -2
  165. data/test/misc/convert_numbers_format_test.rb +7 -3
  166. data/test/misc/lang_test.rb +1 -1
  167. data/test/misc/parse_datetime_test.rb +3 -4
  168. data/test/misc/tableless_test.rb +6 -0
  169. data/test/mock_app/Rakefile +1 -1
  170. data/test/mock_app/app/assets/config/manifest.js +0 -0
  171. data/test/mock_app/app/controllers/cars_controller.rb +1 -0
  172. data/test/mock_app/app/controllers/people_controller.rb +3 -1
  173. data/test/mock_app/config/application.rb +2 -1
  174. data/test/mock_app/config/boot.rb +1 -1
  175. data/test/mock_app/config/environment.rb +2 -2
  176. data/test/mock_app/config/routes.rb +4 -1
  177. data/test/mock_app/db/schema.rb +2 -0
  178. data/test/performance/list_cars_performance_test.rb +34 -0
  179. data/test/performance/list_people_performance_test.rb +31 -0
  180. data/test/performance_test_help.rb +3 -0
  181. data/test/test_helper.rb +10 -2
  182. metadata +55 -20
  183. data/app/assets/javascripts/prototype/rico_corner.js +0 -370
  184. data/lib/active_scaffold/bridges/file_column/test/test_helper.rb +0 -7
@@ -24,11 +24,11 @@ module ActiveScaffold::Actions
24
24
  end
25
25
 
26
26
  def destroy_respond_to_xml
27
- render :xml => successful? ? '' : response_object, :only => active_scaffold_config.list.columns.names, :status => response_status
27
+ render :xml => successful? ? '' : response_object, :only => active_scaffold_config.list.columns.visible_columns_names, :status => response_status
28
28
  end
29
29
 
30
30
  def destroy_respond_to_json
31
- render :json => successful? ? '' : response_object, :only => active_scaffold_config.list.columns.names, :status => response_status
31
+ render :json => successful? ? '' : response_object, :only => active_scaffold_config.list.columns.visible_columns_names, :status => response_status
32
32
  end
33
33
 
34
34
  def destroy_find_record
@@ -65,7 +65,7 @@ module ActiveScaffold::Actions
65
65
  private
66
66
 
67
67
  def delete_authorized_filter
68
- link = active_scaffold_config.delete.link || active_scaffold_config.delete.class.link
68
+ link = active_scaffold_config.delete.link || self.class.active_scaffold_config.delete.class.link
69
69
  raise ActiveScaffold::ActionNotAllowed unless Array(send(link.security_method))[0]
70
70
  end
71
71
 
@@ -23,11 +23,11 @@ module ActiveScaffold::Actions
23
23
  end
24
24
 
25
25
  def store_search_params_into_session
26
- set_field_search_default_params(active_scaffold_config.field_search.default_params) unless active_scaffold_config.field_search.default_params.nil?
26
+ init_field_search_params(active_scaffold_config.field_search.default_params) unless active_scaffold_config.field_search.default_params.nil?
27
27
  super
28
28
  end
29
29
 
30
- def set_field_search_default_params(default_params)
30
+ def init_field_search_params(default_params)
31
31
  return unless (params[:search].is_a?(String) || search_params.nil?) && params[:search].blank?
32
32
  params[:search] = default_params.is_a?(Proc) ? instance_eval(&default_params) : default_params
33
33
  end
@@ -56,24 +56,25 @@ module ActiveScaffold::Actions
56
56
 
57
57
  def custom_finder_options
58
58
  if grouped_search?
59
- group_sql = calculation_for_group_by(search_group_column.try(:field) || search_group_name)
60
- group_by = group_sql.respond_to?(:to_sql) ? group_sql.to_sql : group_sql
61
-
62
- select_query = quoted_select_columns(search_group_column.try(:select_columns))
59
+ group_sql = calculation_for_group_by(search_group_column&.field || search_group_name)
60
+ select_query = grouped_search_select
63
61
  select_query << group_sql.as(search_group_column.name.to_s) if search_group_column && group_sql.respond_to?(:to_sql)
64
- if active_scaffold_config.model.columns_hash.include?(active_scaffold_config.model.inheritance_column)
65
- select_query << active_scaffold_config.columns[active_scaffold_config.model.inheritance_column].field
66
- end
67
- grouped_columns_calculations.each do |name, part|
68
- select_query << (part.respond_to?(:as) ? part : Arel::Nodes::SqlLiteral.new(part)).as(name.to_s)
69
- end
70
-
71
- {group: group_by, select: select_query}
62
+ {group: group_sql, select: select_query}
72
63
  else
73
64
  super
74
65
  end
75
66
  end
76
67
 
68
+ def grouped_search_select
69
+ select_query = quoted_select_columns(search_group_column&.select_columns || [search_group_name])
70
+ if active_scaffold_config.model.columns_hash.include?(active_scaffold_config.model.inheritance_column)
71
+ select_query << active_scaffold_config.columns[active_scaffold_config.model.inheritance_column].field
72
+ end
73
+ grouped_columns_calculations.each do |name, part|
74
+ select_query << (part.respond_to?(:as) ? part : Arel::Nodes::SqlLiteral.new(part)).as(name.to_s)
75
+ end
76
+ end
77
+
77
78
  def grouped_columns_calculations
78
79
  @grouped_columns_calculations ||= list_columns[1..-1].each_with_object({}) do |c, h|
79
80
  h[c.name] = calculation_for_group_search(c)
@@ -113,12 +114,13 @@ module ActiveScaffold::Actions
113
114
  end
114
115
 
115
116
  def list_columns
116
- @list_columns ||= if grouped_search?
117
- columns = grouped_columns || super.select(&:calculation?)
118
- [search_group_column || search_group_name].concat columns
119
- else
120
- super
121
- end
117
+ @list_columns ||=
118
+ if grouped_search?
119
+ columns = grouped_columns || super.select(&:calculation?)
120
+ columns.unshift(search_group_column || search_group_name)
121
+ else
122
+ super
123
+ end
122
124
  end
123
125
 
124
126
  def grouped_columns
@@ -142,34 +144,42 @@ module ActiveScaffold::Actions
142
144
 
143
145
  def do_search
144
146
  if field_search_params.present?
145
- filtered_columns = []
146
- text_search = active_scaffold_config.field_search.text_search
147
- columns = active_scaffold_config.field_search.columns
148
- count_includes = active_scaffold_config.list.user.count_includes
149
- search_params.each do |key, value|
150
- next unless columns.include? key
151
- column = active_scaffold_config.columns[key]
152
- search_condition = self.class.condition_for_column(column, value, text_search)
153
- next if search_condition.blank?
154
-
155
- if count_includes.nil? && column.includes.present? && list_columns.include?(column) && !grouped_search?
156
- active_scaffold_references << column.includes
157
- elsif column.search_joins.present?
158
- active_scaffold_outer_joins << column.search_joins
159
- end
160
- active_scaffold_conditions << search_condition
161
- filtered_columns << column
162
- end
163
- if filtered_columns.present? || grouped_search?
164
- @filtered = active_scaffold_config.field_search.human_conditions ? filtered_columns : true
165
- end
166
-
167
- active_scaffold_config.list.user.page = nil
147
+ do_field_search
168
148
  else
169
149
  super
170
150
  end
171
151
  end
172
152
 
153
+ def do_field_search
154
+ filtered_columns = []
155
+ text_search = active_scaffold_config.field_search.text_search
156
+ columns = active_scaffold_config.field_search.columns
157
+ count_includes = active_scaffold_config.list.user.count_includes
158
+ search_params.each do |key, value|
159
+ next unless columns.include? key
160
+ column = active_scaffold_config.columns[key]
161
+ search_condition = self.class.condition_for_column(column, value, text_search)
162
+ next if search_condition.blank?
163
+
164
+ joins_for_search_on_column(column, count_includes)
165
+ active_scaffold_conditions << search_condition
166
+ filtered_columns << column
167
+ end
168
+ if filtered_columns.present? || grouped_search?
169
+ @filtered = active_scaffold_config.field_search.human_conditions ? filtered_columns : true
170
+ end
171
+
172
+ active_scaffold_config.list.user.page = nil
173
+ end
174
+
175
+ def joins_for_search_on_column(column, count_includes)
176
+ if count_includes.nil? && column.includes.present? && list_columns.include?(column) && !grouped_search?
177
+ active_scaffold_references << column.includes
178
+ elsif column.search_joins.present?
179
+ active_scaffold_outer_joins << column.search_joins
180
+ end
181
+ end
182
+
173
183
  def field_search_ignore?
174
184
  active_scaffold_config.list.always_show_search && active_scaffold_config.list.search_partial == 'field_search'
175
185
  end
@@ -2,7 +2,7 @@ module ActiveScaffold::Actions
2
2
  module List
3
3
  def self.included(base)
4
4
  base.before_action :list_authorized_filter, :only => :index
5
- base.helper_method :list_columns
5
+ base.helper_method :list_columns, :count_on_association_class?
6
6
  end
7
7
 
8
8
  def index
@@ -49,11 +49,11 @@ module ActiveScaffold::Actions
49
49
  end
50
50
 
51
51
  def list_respond_to_xml
52
- render :xml => response_object, :only => list_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(list_columns_names), :methods => virtual_columns(list_columns_names), :status => response_status
52
+ response_to_api(:xml, list_columns_names)
53
53
  end
54
54
 
55
55
  def list_respond_to_json
56
- render :json => response_object, :only => list_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(list_columns_names), :methods => virtual_columns(list_columns_names), :status => response_status
56
+ response_to_api(:json, list_columns_names)
57
57
  end
58
58
 
59
59
  def row_respond_to_html
@@ -71,7 +71,7 @@ module ActiveScaffold::Actions
71
71
  if respond_to?(:"#{action}_columns", true)
72
72
  send(:"#{action}_columns")
73
73
  else
74
- active_scaffold_config.send(action).columns.collect_visible(:flatten => true)
74
+ active_scaffold_config.send(action).columns.visible_columns(flatten: true)
75
75
  end
76
76
  joins_cols, preload_cols = columns.select { |c| c.includes.present? }.partition do |col|
77
77
  includes_need_join?(col, sorting) && !grouped_search?
@@ -82,7 +82,7 @@ module ActiveScaffold::Actions
82
82
  end
83
83
 
84
84
  def set_includes_for_sorting(columns, sorting)
85
- sorting.each do |col, _|
85
+ sorting.each_column do |col|
86
86
  next unless col.includes.present? && !columns.include?(col)
87
87
  if active_scaffold_config.model.connection.needs_order_expressions_in_select?
88
88
  active_scaffold_references << col.includes
@@ -93,12 +93,12 @@ module ActiveScaffold::Actions
93
93
  end
94
94
 
95
95
  def includes_need_join?(column, sorting = active_scaffold_config.list.user.sorting)
96
- sorting.sorts_on?(column) || scoped_habtm?(column)
96
+ (sorting.sorts_by_sql? && sorting.sorts_on?(column)) || scoped_habtm?(column)
97
97
  end
98
98
 
99
99
  def scoped_habtm?(column)
100
- assoc = column.association if column.association.try :collection?
101
- assoc && assoc.habtm? && assoc.scope
100
+ assoc = column.association if column.association&.collection?
101
+ assoc&.habtm? && assoc.scope
102
102
  end
103
103
 
104
104
  def get_row(crud_type_or_security_options = :read)
@@ -106,37 +106,115 @@ module ActiveScaffold::Actions
106
106
  super
107
107
  end
108
108
 
109
+ def current_page
110
+ set_includes_for_columns
111
+
112
+ page = find_page(find_page_options)
113
+ total_pages = page.pager.number_of_pages
114
+ if !page.pager.infinite? && !total_pages.zero? && page.number > total_pages
115
+ page = page.pager.last
116
+ active_scaffold_config.list.user.page = page.number
117
+ end
118
+ page
119
+ end
120
+
109
121
  # The actual algorithm to prepare for the list view
110
122
  def do_list
111
123
  # id: nil needed in params_for because rails reuse it even
112
124
  # if it was deleted from params (like do_refresh_list does)
113
125
  @remove_id_from_list_links = params[:id].blank?
114
- set_includes_for_columns
126
+ @page = current_page
127
+ @records = @page.items
128
+ cache_column_counts
129
+ end
130
+
131
+ def columns_to_cache_counts
132
+ list_columns.select do |col|
133
+ col.association&.collection? && col.includes.blank? && col.associated_number? &&
134
+ !ActiveScaffold::OrmChecks.tableless?(col.association.klass) &&
135
+ !col.association.reverse_association&.counter_cache
136
+ end
137
+ end
138
+
139
+ def cache_column_counts
140
+ @counts = columns_to_cache_counts.each_with_object({}) do |column, counts|
141
+ if ActiveScaffold::OrmChecks.active_record?(column.association.klass)
142
+ counts[column.name] = count_query_for_column(column).count
143
+ elsif ActiveScaffold::OrmChecks.mongoid?(column.association.klass)
144
+ counts[column.name] = mongoid_count_for_column(column)
145
+ end
146
+ end
147
+ end
148
+
149
+ def count_on_association_class?(column)
150
+ column.association.has_many? && !column.association.through? &&
151
+ (!column.association.as || column.association.reverse_association)
152
+ end
153
+
154
+ def count_query_for_column(column)
155
+ if count_on_association_class?(column)
156
+ count_query_on_association_class(column)
157
+ else
158
+ count_query_with_join(column)
159
+ end
160
+ end
161
+
162
+ def count_query_on_association_class(column)
163
+ key = column.association.primary_key || :id
164
+ query = column.association.klass.where(column.association.foreign_key => @records.map(&key))
165
+ if column.association.as
166
+ query.where!(column.association.reverse_association.foreign_type => active_scaffold_config.model.name)
167
+ end
168
+ if column.association.scope
169
+ query = query.instance_exec(&column.association.scope)
170
+ end
171
+ query.group(column.association.foreign_key)
172
+ end
173
+
174
+ def count_query_with_join(column)
175
+ klass = column.association.klass
176
+ query = active_scaffold_config.model.where(active_scaffold_config.primary_key => @records.map(&:id))
177
+ .joins(column.name).group(active_scaffold_config.primary_key)
178
+ .select("#{klass.quoted_table_name}.#{klass.quoted_primary_key}")
179
+ query = query.uniq if column.association.scope && klass.instance_exec(&column.association.scope).values[:distinct]
180
+ query
181
+ end
182
+
183
+ def mongoid_count_for_column(column)
184
+ matches = {column.association.foreign_key => {'$in': @records.map(&:id)}}
185
+ if column.association.as
186
+ matches[column.association.reverse_association.foreign_type] = {'$eq': active_scaffold_config.model.name}
187
+ end
188
+ group = {_id: "$#{column.association.foreign_key}", count: {'$sum' => 1}}
189
+ query = column.association.klass.collection.aggregate([{'$match' => matches}, {'$group' => group}])
190
+ query.each_with_object({}) do |row, hash|
191
+ hash[row['_id']] = row['count']
192
+ end
193
+ end
194
+
195
+ def find_page_options
196
+ options = {
197
+ :sorting => active_scaffold_config.list.user.sorting,
198
+ :count_includes => active_scaffold_config.list.user.count_includes
199
+ }
115
200
 
116
- options = {:sorting => active_scaffold_config.list.user.sorting,
117
- :count_includes => active_scaffold_config.list.user.count_includes}
118
- paginate = params[:format].nil? ? (accepts? :html, :js) : %w[html js].include?(params[:format])
201
+ paginate = params[:format].nil? ? accepts?(:html, :js) : %w[html js].include?(params[:format])
119
202
  options[:pagination] = active_scaffold_config.list.pagination if paginate
120
203
  if options[:pagination]
121
204
  options[:per_page] = active_scaffold_config.list.user.per_page
122
205
  options[:page] = active_scaffold_config.list.user.page
123
206
  end
207
+
124
208
  if active_scaffold_config.list.auto_select_columns
125
209
  auto_select_columns = list_columns + [active_scaffold_config.columns[active_scaffold_config.model.primary_key]]
126
210
  options[:select] = auto_select_columns.map { |c| quoted_select_columns(c.select_columns) }.compact.flatten
127
211
  end
128
212
 
129
- page = find_page(options)
130
- total_pages = page.pager.number_of_pages
131
- if !page.pager.infinite? && !total_pages.zero? && page.number > total_pages
132
- page = page.pager.last
133
- active_scaffold_config.list.user.page = page.number
134
- end
135
- @page, @records = page, page.items
213
+ options
136
214
  end
137
215
 
138
216
  def quoted_select_columns(columns)
139
- columns.map { |c| active_scaffold_config.columns[c].try(:field) || c } if columns
217
+ columns&.map { |c| active_scaffold_config.columns[c]&.field || c }
140
218
  end
141
219
 
142
220
  def do_refresh_list
@@ -149,17 +227,23 @@ module ActiveScaffold::Actions
149
227
  end
150
228
 
151
229
  def each_record_in_page
152
- current_page = active_scaffold_config.list.user.page
153
- do_search if respond_to? :do_search, true
154
- active_scaffold_config.list.user.page = current_page
155
- do_list
156
- @page.items.each { |record| yield record }
230
+ page_items.each { |record| yield record }
157
231
  end
158
232
 
159
233
  def each_record_in_scope
160
234
  scoped_query.each { |record| yield record }
161
235
  end
162
236
 
237
+ def page_items
238
+ @page_items ||= begin
239
+ page_number = active_scaffold_config.list.user.page
240
+ do_search if respond_to? :do_search, true
241
+ active_scaffold_config.list.user.page = page_number
242
+ @page = current_page
243
+ @page.items
244
+ end
245
+ end
246
+
163
247
  def scoped_query
164
248
  @scoped_query ||= begin
165
249
  do_search if respond_to? :do_search, true
@@ -189,7 +273,7 @@ module ActiveScaffold::Actions
189
273
  {:etag => active_scaffold_config.list.user.sorting.clause}
190
274
  end
191
275
  end
192
- objects.present? ? objects : super
276
+ objects.presence || super
193
277
  end
194
278
 
195
279
  private
@@ -216,7 +300,7 @@ module ActiveScaffold::Actions
216
300
  end
217
301
 
218
302
  def list_columns
219
- @list_columns ||= active_scaffold_config.list.columns.collect_visible
303
+ @list_columns ||= active_scaffold_config.list.columns.visible_columns
220
304
  end
221
305
 
222
306
  def list_columns_names
@@ -1,5 +1,7 @@
1
1
  module ActiveScaffold::Actions
2
- # The Nested module basically handles automatically linking controllers together. It does this by creating column links with the right parameters, and by providing any supporting systems (like a /:controller/nested action for returning associated scaffolds).
2
+ # The Nested module basically handles automatically linking controllers together.
3
+ # It does this by creating column links with the right parameters, and by providing
4
+ # any supporting systems (like a /:controller/nested action for returning associated scaffolds).
3
5
  module Nested
4
6
  def self.included(base)
5
7
  super
@@ -27,17 +29,17 @@ module ActiveScaffold::Actions
27
29
  def set_nested
28
30
  @nested = nil
29
31
  return unless params[:parent_scaffold] && (params[:association] || params[:named_scope])
30
- @nested = ActiveScaffold::DataStructures::NestedInfo.get(active_scaffold_config.model, params)
31
- register_constraints_with_action_columns(@nested.constrained_fields) unless @nested.nil?
32
+ @nested = ActiveScaffold::DataStructures::NestedInfo.get(self.class.active_scaffold_config.model, params)
32
33
  end
33
34
 
34
35
  def configure_nested
35
36
  return unless nested?
37
+ register_constraints_with_action_columns(nested.constrained_fields)
38
+ return unless active_scaffold_config.actions.include? :list
36
39
  active_scaffold_config.list.user.label = nested_label
37
- unless active_scaffold_config.nested.ignore_order_from_association
38
- chain = beginning_of_chain
39
- active_scaffold_config.list.user.nested_default_sorting = nested_default_sorting(chain) if nested.sorted?(chain)
40
- end
40
+ return if active_scaffold_config.nested.ignore_order_from_association
41
+ chain = beginning_of_chain
42
+ active_scaffold_config.list.user.nested_default_sorting = nested_default_sorting(chain) if nested.sorted?(chain)
41
43
  end
42
44
 
43
45
  def nested_label
@@ -57,41 +59,39 @@ module ActiveScaffold::Actions
57
59
  end
58
60
 
59
61
  def include_habtm_actions
60
- if nested? && nested.habtm?
62
+ if nested&.habtm?
61
63
  # Production mode is ok with adding a link everytime the scaffold is nested - we are not ok with that.
62
- active_scaffold_config.action_links.add('new_existing', :label => :add_existing, :type => :collection, :security_method => :add_existing_authorized?) unless active_scaffold_config.action_links['new_existing']
63
- if active_scaffold_config.nested.shallow_delete
64
- active_scaffold_config.action_links.add('destroy_existing', :label => :remove, :type => :member, :confirm => :are_you_sure_to_delete, :method => :delete, :position => false, :security_method => :delete_existing_authorized?) unless active_scaffold_config.action_links['destroy_existing']
65
- if active_scaffold_config.actions.include?(:delete)
66
- active_scaffold_config.action_links.delete('destroy') if active_scaffold_config.action_links['destroy']
67
- end
68
- end
69
- else
70
- # Production mode is caching this link into a non nested scaffold
71
- active_scaffold_config.action_links.delete('new_existing') if active_scaffold_config.action_links['new_existing']
72
-
73
- if active_scaffold_config.nested.shallow_delete
74
- active_scaffold_config.action_links.delete('destroy_existing') if active_scaffold_config.action_links['destroy_existing']
75
- if active_scaffold_config.actions.include?(:delete) && active_scaffold_config.delete.link
76
- active_scaffold_config.action_links.add(active_scaffold_config.delete.link) unless active_scaffold_config.action_links['destroy']
77
- end
64
+ unless active_scaffold_config.action_links['new_existing']
65
+ active_scaffold_config.action_links.add('new_existing', :label => :add_existing, :type => :collection, :security_method => :add_existing_authorized?)
78
66
  end
67
+ add_shallow_links if active_scaffold_config.nested.shallow_delete
68
+ elsif !ActiveScaffold.threadsafe
69
+ # Production mode is caching this link into a non nested scaffold, when threadsafe is disabled
70
+ active_scaffold_config.action_links.delete('new_existing')
71
+ restore_shallow_links if active_scaffold_config.nested.shallow_delete
79
72
  end
80
73
  end
81
74
 
75
+ def add_shallow_links
76
+ unless active_scaffold_config.action_links['destroy_existing']
77
+ link_options = {:label => :remove, :type => :member, :confirm => :are_you_sure_to_delete, :method => :delete, :position => false, :security_method => :delete_existing_authorized?}
78
+ active_scaffold_config.action_links.add('destroy_existing', link_options)
79
+ end
80
+ active_scaffold_config.action_links.delete('destroy') if active_scaffold_config.actions.include?(:delete)
81
+ end
82
+
83
+ def restore_shallow_links
84
+ if active_scaffold_config.actions.include?(:delete) && active_scaffold_config.delete.link
85
+ link = active_scaffold_config.delete.link
86
+ active_scaffold_config.action_links.add(link) unless active_scaffold_config.action_links[link.action]
87
+ end
88
+ active_scaffold_config.action_links.delete('destroy_existing')
89
+ end
90
+
82
91
  def beginning_of_chain
83
- if nested? && nested.association
84
- if nested.association.collection?
85
- nested_parent_record.send(nested.association.name)
86
- elsif nested.association.through? # has_one :through
87
- active_scaffold_config.model.where(active_scaffold_config.model.primary_key => nested_parent_record.send(nested.association.name).try(:id))
88
- elsif nested.association.has_one?
89
- active_scaffold_config.model.where(nested.child_association.foreign_key => nested_parent_record.send(nested.association.association_primary_key))
90
- elsif nested.association.belongs_to?
91
- nested_belongs_to_chain
92
- else # never should get here
93
- active_scaffold_config.model
94
- end
92
+ # only if nested is related to current controller, e.g. not when adding record in subform inside subform
93
+ if nested? && nested.match_model?(active_scaffold_config.model)
94
+ nested_chain_with_association
95
95
  elsif nested? && nested.scope
96
96
  nested_parent_record.send(nested.scope)
97
97
  else
@@ -99,23 +99,40 @@ module ActiveScaffold::Actions
99
99
  end
100
100
  end
101
101
 
102
- def nested_belongs_to_chain
103
- primary_key = active_scaffold_config.mongoid? ? '_id' : active_scaffold_config.model.primary_key
104
- active_scaffold_config.model.where(primary_key => nested_parent_record.send(nested.association.name))
102
+ def nested_chain_with_association
103
+ if nested.association.collection?
104
+ nested_parent_record.send(nested.association.name)
105
+ elsif nested.association.through? # has_one :through
106
+ active_scaffold_config.model.where(active_scaffold_config.model.primary_key => nested_parent_record.send(nested.association.name)&.id)
107
+ elsif nested.association.has_one?
108
+ active_scaffold_config.model.where(nested.child_association.name => nested_parent_record)
109
+ elsif nested.association.belongs_to?
110
+ primary_key = active_scaffold_config.mongoid? ? '_id' : active_scaffold_config.model.primary_key
111
+ active_scaffold_config.model.where(primary_key => nested_parent_record.send(nested.association.name))
112
+ else # never should get here
113
+ raise 'missing condition for nested beginning_of_chain'
114
+ end
105
115
  end
106
116
 
107
117
  def nested_parent_record(crud = :read)
108
118
  @nested_parent_record ||= find_if_allowed(nested.parent_id, crud, nested.parent_model)
109
119
  end
110
120
 
121
+ def create_association_with_parent?
122
+ # has_many is done by beginning_of_chain and rails if direct association, not in through associations
123
+ return false if nested.has_many? && !nested.association.through?
124
+ nested.child_association && nested_parent_record
125
+ end
126
+
111
127
  def create_association_with_parent(record)
112
- # has_many is done by beginning_of_chain and rails
113
- return unless (nested.belongs_to? || nested.has_one? || nested.habtm?) && nested.child_association
114
- return if (parent = nested_parent_record).nil?
128
+ return unless create_association_with_parent?
115
129
  if nested.child_association.singular?
116
- record.send("#{nested.child_association.name}=", parent)
130
+ record.send("#{nested.child_association.name}=", nested_parent_record)
131
+ elsif nested.association.through_singular? && nested.child_association.through_singular?
132
+ through = nested_parent_record.send(nested.association.through_reflection.name)
133
+ record.send("#{nested.child_association.through_reflection.name}=", through)
117
134
  else
118
- record.send(nested.child_association.name) << parent
135
+ record.send(nested.child_association.name) << nested_parent_record
119
136
  end
120
137
  end
121
138
 
@@ -181,11 +198,11 @@ module ActiveScaffold::Actions::Nested
181
198
  end
182
199
 
183
200
  def add_existing_respond_to_xml
184
- render :xml => response_object, :only => active_scaffold_config.list.columns.names, :status => response_status
201
+ render :xml => response_object, :only => active_scaffold_config.list.columns.visible_columns_names, :status => response_status
185
202
  end
186
203
 
187
204
  def add_existing_respond_to_json
188
- render :json => response_object, :only => active_scaffold_config.list.columns.names, :status => response_status
205
+ render :json => response_object, :only => active_scaffold_config.list.columns.visible_columns_names, :status => response_status
189
206
  end
190
207
 
191
208
  def destroy_existing_respond_to_html
@@ -198,11 +215,11 @@ module ActiveScaffold::Actions::Nested
198
215
  end
199
216
 
200
217
  def destroy_existing_respond_to_xml
201
- render :xml => successful? ? '' : response_object, :only => active_scaffold_config.list.columns.names, :status => response_status
218
+ render :xml => successful? ? '' : response_object, :only => active_scaffold_config.list.columns.visible_columns_names, :status => response_status
202
219
  end
203
220
 
204
221
  def destroy_existing_respond_to_json
205
- render :json => successful? ? '' : response_object, :only => active_scaffold_config.list.columns.names, :status => response_status
222
+ render :json => successful? ? '' : response_object, :only => active_scaffold_config.list.columns.visible_columns_names, :status => response_status
206
223
  end
207
224
 
208
225
  def add_existing_authorized?(record = nil)