active_scaffold 3.3.3 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +39 -0
  3. data/README.md +5 -3
  4. data/app/assets/images/active_scaffold/refresh.png +0 -0
  5. data/app/assets/javascripts/jquery/active_scaffold.js +182 -91
  6. data/app/assets/javascripts/jquery/date_picker_bridge.js.erb +14 -16
  7. data/app/assets/javascripts/jquery/draggable_lists.js +33 -26
  8. data/app/assets/javascripts/jquery/jquery.editinplace.js +3 -3
  9. data/app/assets/javascripts/prototype/active_scaffold.js +61 -19
  10. data/app/assets/stylesheets/active_scaffold_colors.css.scss +4 -0
  11. data/app/assets/stylesheets/active_scaffold_images.css.scss +3 -0
  12. data/app/assets/stylesheets/active_scaffold_layout.css +23 -2
  13. data/app/views/active_scaffold_overrides/_add_existing_form.html.erb +1 -3
  14. data/app/views/active_scaffold_overrides/_base_form.html.erb +7 -5
  15. data/app/views/active_scaffold_overrides/_field_search.html.erb +1 -2
  16. data/app/views/active_scaffold_overrides/_form.html.erb +6 -4
  17. data/app/views/active_scaffold_overrides/_form_association.html.erb +4 -3
  18. data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +5 -5
  19. data/app/views/active_scaffold_overrides/_form_association_record.html.erb +8 -6
  20. data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +3 -2
  21. data/app/views/active_scaffold_overrides/_list.html.erb +8 -6
  22. data/app/views/active_scaffold_overrides/_list_column_headings.html.erb +1 -4
  23. data/app/views/active_scaffold_overrides/_list_pagination.html.erb +4 -4
  24. data/app/views/active_scaffold_overrides/_list_pagination_links.html.erb +1 -1
  25. data/app/views/active_scaffold_overrides/_list_record.html.erb +3 -3
  26. data/app/views/active_scaffold_overrides/_refresh_list.js.erb +8 -1
  27. data/app/views/active_scaffold_overrides/_search.html.erb +7 -13
  28. data/app/views/active_scaffold_overrides/_show_columns.html.erb +1 -1
  29. data/app/views/active_scaffold_overrides/on_create.js.erb +4 -4
  30. data/app/views/active_scaffold_overrides/render_field_inplace.html.erb +1 -1
  31. data/app/views/active_scaffold_overrides/row.js.erb +1 -1
  32. data/config/locales/de.yml +106 -95
  33. data/config/locales/en.yml +108 -97
  34. data/config/locales/es.yml +109 -98
  35. data/config/locales/fr.yml +108 -97
  36. data/config/locales/hu.yml +109 -98
  37. data/config/locales/ja.yml +100 -89
  38. data/config/locales/ru.yml +115 -104
  39. data/lib/active_scaffold.rb +18 -294
  40. data/lib/active_scaffold/actions/common_search.rb +50 -17
  41. data/lib/active_scaffold/actions/core.rb +93 -22
  42. data/lib/active_scaffold/actions/create.rb +15 -6
  43. data/lib/active_scaffold/actions/field_search.rb +68 -60
  44. data/lib/active_scaffold/actions/list.rb +49 -28
  45. data/lib/active_scaffold/actions/nested.rb +14 -6
  46. data/lib/active_scaffold/actions/search.rb +36 -35
  47. data/lib/active_scaffold/actions/show.rb +9 -4
  48. data/lib/active_scaffold/actions/subform.rb +1 -1
  49. data/lib/active_scaffold/actions/update.rb +22 -7
  50. data/lib/active_scaffold/active_record_permissions.rb +125 -118
  51. data/lib/active_scaffold/attribute_params.rb +84 -66
  52. data/lib/active_scaffold/bridges.rb +3 -3
  53. data/lib/active_scaffold/bridges/ancestry/ancestry_bridge.rb +10 -5
  54. data/lib/active_scaffold/bridges/cancan.rb +2 -1
  55. data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +13 -2
  56. data/lib/active_scaffold/bridges/carrierwave/form_ui.rb +11 -6
  57. data/lib/active_scaffold/bridges/chosen/helpers.rb +2 -2
  58. data/lib/active_scaffold/bridges/country_helper/country_helper_bridge.rb +45 -29
  59. data/lib/active_scaffold/bridges/date_picker/ext.rb +11 -6
  60. data/lib/active_scaffold/bridges/date_picker/helper.rb +5 -1
  61. data/lib/active_scaffold/bridges/dragonfly/form_ui.rb +10 -5
  62. data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +6 -1
  63. data/lib/active_scaffold/bridges/file_column/form_ui.rb +12 -11
  64. data/lib/active_scaffold/bridges/paperclip/form_ui.rb +14 -6
  65. data/lib/active_scaffold/bridges/paperclip/list_ui.rb +1 -1
  66. data/lib/active_scaffold/bridges/record_select/helpers.rb +15 -12
  67. data/lib/active_scaffold/bridges/shared/date_bridge.rb +7 -8
  68. data/lib/active_scaffold/bridges/tiny_mce.rb +5 -3
  69. data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +4 -5
  70. data/lib/active_scaffold/config/base.rb +4 -0
  71. data/lib/active_scaffold/config/core.rb +12 -5
  72. data/lib/active_scaffold/config/delete.rb +0 -2
  73. data/lib/active_scaffold/config/field_search.rb +1 -4
  74. data/lib/active_scaffold/config/form.rb +0 -2
  75. data/lib/active_scaffold/config/list.rb +31 -1
  76. data/lib/active_scaffold/config/search.rb +0 -3
  77. data/lib/active_scaffold/config/show.rb +0 -6
  78. data/lib/active_scaffold/config/subform.rb +1 -0
  79. data/lib/active_scaffold/configurable.rb +2 -2
  80. data/lib/active_scaffold/constraints.rb +11 -14
  81. data/lib/active_scaffold/core.rb +277 -0
  82. data/lib/active_scaffold/data_structures/action_columns.rb +18 -2
  83. data/lib/active_scaffold/data_structures/action_link.rb +25 -6
  84. data/lib/active_scaffold/data_structures/action_links.rb +9 -4
  85. data/lib/active_scaffold/data_structures/actions.rb +1 -1
  86. data/lib/active_scaffold/data_structures/column.rb +6 -6
  87. data/lib/active_scaffold/data_structures/columns.rb +2 -2
  88. data/lib/active_scaffold/data_structures/nested_info.rb +5 -1
  89. data/lib/active_scaffold/data_structures/sorting.rb +15 -5
  90. data/lib/active_scaffold/delayed_setup.rb +30 -0
  91. data/lib/active_scaffold/engine.rb +25 -0
  92. data/lib/active_scaffold/extensions/action_view_rendering.rb +1 -1
  93. data/lib/active_scaffold/extensions/left_outer_joins.rb +61 -21
  94. data/lib/active_scaffold/extensions/localize.rb +1 -1
  95. data/lib/active_scaffold/extensions/name_option_for_datetime.rb +13 -8
  96. data/lib/active_scaffold/extensions/paginator_extensions.rb +5 -1
  97. data/lib/active_scaffold/extensions/reverse_associations.rb +1 -0
  98. data/lib/active_scaffold/extensions/routing_mapper.rb +1 -1
  99. data/lib/active_scaffold/extensions/unsaved_record.rb +4 -6
  100. data/lib/active_scaffold/finder.rb +79 -27
  101. data/lib/active_scaffold/helpers/association_helpers.rb +48 -18
  102. data/lib/active_scaffold/helpers/controller_helpers.rb +19 -10
  103. data/lib/active_scaffold/helpers/form_column_helpers.rb +185 -87
  104. data/lib/active_scaffold/helpers/human_condition_helpers.rb +2 -1
  105. data/lib/active_scaffold/helpers/id_helpers.rb +14 -8
  106. data/lib/active_scaffold/helpers/list_column_helpers.rb +65 -56
  107. data/lib/active_scaffold/helpers/pagination_helpers.rb +5 -1
  108. data/lib/active_scaffold/helpers/search_column_helpers.rb +21 -18
  109. data/lib/active_scaffold/helpers/view_helpers.rb +102 -64
  110. data/lib/active_scaffold/responds_to_parent.rb +39 -64
  111. data/lib/active_scaffold/tableless.rb +129 -10
  112. data/lib/active_scaffold/version.rb +2 -2
  113. data/test/bridges/bridge_test.rb +1 -1
  114. data/test/bridges/date_picker_test.rb +2 -2
  115. data/test/bridges/paperclip_test.rb +10 -8
  116. data/test/bridges/tiny_mce_test.rb +2 -2
  117. data/test/company.rb +22 -10
  118. data/test/config/base_test.rb +1 -1
  119. data/test/config/core_test.rb +8 -6
  120. data/test/config/create_test.rb +6 -6
  121. data/test/config/delete_test.rb +4 -4
  122. data/test/config/field_search_test.rb +6 -6
  123. data/test/config/list_test.rb +7 -7
  124. data/test/config/nested_test.rb +8 -7
  125. data/test/config/search_test.rb +7 -7
  126. data/test/config/show_test.rb +5 -5
  127. data/test/config/subform_test.rb +1 -1
  128. data/test/config/update_test.rb +5 -4
  129. data/test/data_structures/action_columns_test.rb +15 -16
  130. data/test/data_structures/action_link_test.rb +10 -10
  131. data/test/data_structures/action_links_test.rb +6 -6
  132. data/test/data_structures/actions_test.rb +4 -4
  133. data/test/data_structures/association_column_test.rb +4 -4
  134. data/test/data_structures/column_test.rb +9 -9
  135. data/test/data_structures/columns_test.rb +7 -7
  136. data/test/data_structures/error_message_test.rb +2 -4
  137. data/test/data_structures/set_test.rb +13 -13
  138. data/test/data_structures/sorting_test.rb +8 -8
  139. data/test/data_structures/standard_column_test.rb +2 -2
  140. data/test/data_structures/validation_reflection_test.rb +8 -8
  141. data/test/data_structures/virtual_column_test.rb +5 -5
  142. data/test/extensions/active_record_test.rb +1 -1
  143. data/test/helpers/form_column_helpers_test.rb +5 -5
  144. data/test/helpers/list_column_helpers_test.rb +2 -1
  145. data/test/helpers/pagination_helpers_test.rb +1 -1
  146. data/test/misc/active_record_permissions_test.rb +23 -4
  147. data/test/misc/attribute_params_test.rb +304 -136
  148. data/test/misc/calculation_test.rb +55 -0
  149. data/test/misc/configurable_test.rb +22 -21
  150. data/test/misc/constraints_test.rb +10 -7
  151. data/test/misc/convert_numbers_format_test.rb +149 -0
  152. data/test/misc/finder_test.rb +17 -13
  153. data/test/misc/lang_test.rb +1 -1
  154. data/test/misc/tableless_test.rb +18 -0
  155. data/test/mock_app/app/controllers/addresses_controller.rb +4 -0
  156. data/test/mock_app/app/controllers/buildings_controller.rb +4 -0
  157. data/test/mock_app/app/controllers/cars_controller.rb +4 -0
  158. data/test/mock_app/app/controllers/contacts_controller.rb +4 -0
  159. data/test/mock_app/app/controllers/floors_controller.rb +6 -0
  160. data/test/mock_app/app/controllers/people_controller.rb +4 -0
  161. data/test/mock_app/app/models/address.rb +3 -0
  162. data/test/mock_app/app/models/building.rb +8 -0
  163. data/test/mock_app/app/models/car.rb +3 -0
  164. data/test/mock_app/app/models/contact.rb +3 -0
  165. data/test/mock_app/app/models/file_model.rb +19 -0
  166. data/test/mock_app/app/models/floor.rb +8 -0
  167. data/test/mock_app/app/models/person.rb +11 -0
  168. data/test/mock_app/config/application.rb +2 -0
  169. data/test/mock_app/config/environments/test.rb +1 -1
  170. data/test/mock_app/config/initializers/secret_token.rb +5 -1
  171. data/test/mock_app/config/routes.rb +1 -1
  172. data/test/mock_app/db/schema.rb +51 -0
  173. data/test/model_stub.rb +3 -3
  174. data/test/test_helper.rb +15 -12
  175. metadata +51 -50
  176. data/lib/active_scaffold/extensions/array.rb +0 -7
  177. data/lib/active_scaffold/extensions/cache_association.rb +0 -16
  178. data/lib/active_scaffold/extensions/usa_state.rb +0 -46
  179. data/lib/active_scaffold_env.rb +0 -13
  180. data/test/extensions/array_test.rb +0 -12
  181. data/test/mock_app/public/blank.html +0 -33
  182. data/test/mock_app/public/images/active_scaffold/DO_NOT_EDIT +0 -2
  183. data/test/mock_app/public/images/active_scaffold/default/add.gif +0 -0
  184. data/test/mock_app/public/images/active_scaffold/default/arrow_down.gif +0 -0
  185. data/test/mock_app/public/images/active_scaffold/default/arrow_up.gif +0 -0
  186. data/test/mock_app/public/images/active_scaffold/default/close.gif +0 -0
  187. data/test/mock_app/public/images/active_scaffold/default/cross.png +0 -0
  188. data/test/mock_app/public/images/active_scaffold/default/indicator-small.gif +0 -0
  189. data/test/mock_app/public/images/active_scaffold/default/indicator.gif +0 -0
  190. data/test/mock_app/public/images/active_scaffold/default/magnifier.png +0 -0
  191. data/test/mock_app/public/javascripts/active_scaffold/DO_NOT_EDIT +0 -2
  192. data/test/mock_app/public/javascripts/active_scaffold/default/active_scaffold.js +0 -532
  193. data/test/mock_app/public/javascripts/active_scaffold/default/dhtml_history.js +0 -867
  194. data/test/mock_app/public/javascripts/active_scaffold/default/form_enhancements.js +0 -117
  195. data/test/mock_app/public/javascripts/active_scaffold/default/rico_corner.js +0 -370
  196. data/test/mock_app/public/stylesheets/active_scaffold/DO_NOT_EDIT +0 -2
  197. data/test/mock_app/public/stylesheets/active_scaffold/default/stylesheet-ie.css +0 -35
  198. data/test/mock_app/public/stylesheets/active_scaffold/default/stylesheet.css +0 -848
@@ -1,7 +1,7 @@
1
1
  class Object
2
2
  def as_(key, options = {})
3
3
  unless key.blank?
4
- text = I18n.translate "#{key}", {:scope => [:active_scaffold, *options.delete(:scope)], :default => key.is_a?(String) ? key : key.to_s.titleize}.merge(options)
4
+ text = I18n.translate("#{key}", {:scope => [:active_scaffold, *options.delete(:scope)], :default => key.is_a?(String) ? key : key.to_s.titleize}.merge(options)).html_safe
5
5
  # text = nil if text.include?('translation missing:')
6
6
  end
7
7
  text ||= key
@@ -1,12 +1,17 @@
1
- module ActionView
2
- module Helpers
3
- class InstanceTag
4
- private
5
- def datetime_selector_with_name(options, html_options)
6
- options.merge!(:prefix => options[:name].gsub(/\[[^\[]*\]$/,'')) if options[:name]
7
- datetime_selector_without_name(options, html_options)
1
+ module ActiveScaffold
2
+ module DateSelectExtension
3
+ def datetime_selector_with_name(options, html_options)
4
+ options.merge!(:prefix => options[:name].gsub(/\[[^\[]*\]$/,'')) if options[:name]
5
+ datetime_selector_without_name(options, html_options)
6
+ end
7
+ def self.included(base)
8
+ base.class_eval do
9
+ alias_method_chain :datetime_selector, :name
10
+ private :datetime_selector_without_name, :datetime_selector_with_name, :datetime_selector
8
11
  end
9
- alias_method_chain :datetime_selector, :name
10
12
  end
11
13
  end
12
14
  end
15
+ (defined?(ActionView::Helpers::Tags::DateSelect) ? ActionView::Helpers::Tags::DateSelect : ActionView::Helpers::InstanceTag).class_eval do
16
+ include ActiveScaffold::DateSelectExtension
17
+ end
@@ -4,7 +4,7 @@ class Paginator
4
4
 
5
5
  # Total number of pages
6
6
  def number_of_pages_with_infinite
7
- number_of_pages_without_infinite unless infinite?
7
+ number_of_pages_without_infinite if @count
8
8
  end
9
9
  alias_method_chain :number_of_pages, :infinite
10
10
 
@@ -12,6 +12,10 @@ class Paginator
12
12
  def infinite?
13
13
  @count.nil?
14
14
  end
15
+
16
+ def count
17
+ @count || first.items.size
18
+ end
15
19
 
16
20
  class Page
17
21
  # Checks to see if there's a page after this one
@@ -28,6 +28,7 @@ module ActiveRecord
28
28
 
29
29
  # stage 1 filter: collect associations that point back to this model and use the same foreign_key
30
30
  klass.reflect_on_all_associations.each do |assoc|
31
+ next if assoc == self
31
32
  if self.options[:through]
32
33
  # only iterate has_many :through associations
33
34
  next unless assoc.options[:through]
@@ -2,7 +2,7 @@ module ActionDispatch
2
2
  module Routing
3
3
  ACTIVE_SCAFFOLD_CORE_ROUTING = {
4
4
  :collection => {:show_search => :get, :render_field => :post, :mark => :post},
5
- :member => {:row => :get, :update_column => :post, :render_field => [:get, :post], :mark => :post}
5
+ :member => {:update_column => :post, :render_field => [:get, :post], :mark => :post}
6
6
  }
7
7
  ACTIVE_SCAFFOLD_ASSOCIATION_ROUTING = {
8
8
  :collection => {:edit_associated => :get, :new_existing => :get, :add_existing => :post},
@@ -1,5 +1,5 @@
1
1
  # a simple (manual) unsaved? flag and method. at least it automatically reverts after a save!
2
- class ActiveRecord::Base
2
+ module ActiveScaffold::UnsavedRecord
3
3
  # acts like a dirty? flag, manually thrown during update_record_from_params.
4
4
  def unsaved=(val)
5
5
  @unsaved = (val) ? true : false
@@ -11,10 +11,8 @@ class ActiveRecord::Base
11
11
  end
12
12
 
13
13
  # automatically unsets the unsaved flag
14
- def save_with_unsaved_flag(*args)
15
- result = save_without_unsaved_flag(*args)
16
- self.unsaved = false
17
- return result
14
+ def save(*)
15
+ super.tap { self.unsaved = false }
18
16
  end
19
- alias_method_chain :save, :unsaved_flag
20
17
  end
18
+ ActiveRecord::Base.class_eval { include ActiveScaffold::UnsavedRecord }
@@ -76,7 +76,7 @@ module ActiveScaffold
76
76
  conditions += values*column.search_sql.size if values.present?
77
77
  conditions
78
78
  rescue Exception => e
79
- logger.error Time.now.to_s + "#{e.inspect} -- on the ActiveScaffold column :#{column.name}, search_ui = #{search_ui} in #{self.name}"
79
+ logger.error "#{e.class.name}: #{e.message} -- on the ActiveScaffold column :#{column.name}, search_ui = #{search_ui} in #{self.name}"
80
80
  raise e
81
81
  end
82
82
  end
@@ -270,9 +270,19 @@ module ActiveScaffold
270
270
  @active_scaffold_conditions ||= []
271
271
  end
272
272
 
273
- attr_writer :active_scaffold_includes
273
+ attr_writer :active_scaffold_preload
274
+ def active_scaffold_preload
275
+ @active_scaffold_preload ||= []
276
+ end
277
+
278
+ def active_scaffold_includes=(value)
279
+ ActiveSupport::Deprecation.warn "active_scaffold_includes doesn't exist anymore, use active_scaffold_preload, active_scaffold_outer_joins or active_scaffold_references"
280
+ self.active_scaffold_preload = value
281
+ end
282
+
274
283
  def active_scaffold_includes
275
- @active_scaffold_includes ||= []
284
+ ActiveSupport::Deprecation.warn "active_scaffold_includes doesn't exist anymore, use active_scaffold_preload, active_scaffold_outer_joins or active_scaffold_references"
285
+ self.active_scaffold_preload
276
286
  end
277
287
 
278
288
  attr_writer :active_scaffold_habtm_joins
@@ -284,9 +294,28 @@ module ActiveScaffold
284
294
  def active_scaffold_outer_joins
285
295
  @active_scaffold_outer_joins ||= []
286
296
  end
297
+
298
+ attr_writer :active_scaffold_references
299
+ def active_scaffold_references
300
+ @active_scaffold_references ||= []
301
+ end
302
+
303
+ # Override this method on your controller to define conditions to be used when querying a recordset (e.g. for List). The return of this method should be any format compatible with the :conditions clause of ActiveRecord::Base's find.
304
+ def conditions_for_collection
305
+ end
306
+
307
+ # Override this method on your controller to define joins to be used when querying a recordset (e.g. for List). The return of this method should be any format compatible with the :joins clause of ActiveRecord::Base's find.
308
+ def joins_for_collection
309
+ end
310
+
311
+ # Override this method on your controller to provide custom finder options to the find() call. The return of this method should be a hash.
312
+ def custom_finder_options
313
+ {}
314
+ end
287
315
 
288
316
  def all_conditions
289
317
  [
318
+ id_condition, # for list with id (e.g. /users/:id/index)
290
319
  active_scaffold_conditions, # from the search modules
291
320
  conditions_for_collection, # from the dev
292
321
  conditions_from_params, # from the parameters (e.g. /users/list?first_name=Fred)
@@ -294,11 +323,14 @@ module ActiveScaffold
294
323
  active_scaffold_session_storage[:conditions] # embedding conditions (weaker constraints)
295
324
  ].reject(&:blank?)
296
325
  end
326
+
327
+ def id_condition
328
+ {:id => params[:id]} if params[:id]
329
+ end
297
330
 
298
331
  # returns a single record (the given id) but only if it's allowed for the specified security options.
299
332
  # security options can be a hash for authorized_for? method or a value to check as a :crud_type
300
333
  # accomplishes this by checking model.#{action}_authorized?
301
- # TODO: this should reside on the model, not the controller
302
334
  def find_if_allowed(id, security_options, klass = beginning_of_chain)
303
335
  record = klass.find(id)
304
336
  security_options = {:crud_type => security_options.to_sym} unless security_options.is_a? Hash
@@ -311,32 +343,40 @@ module ActiveScaffold
311
343
  # * :page
312
344
  def finder_options(options = {})
313
345
  search_conditions = all_conditions
314
- full_includes = (active_scaffold_includes.blank? ? nil : active_scaffold_includes)
346
+ full_includes = (active_scaffold_references.blank? ? nil : active_scaffold_references)
315
347
 
316
348
  # create a general-use options array that's compatible with Rails finders
317
349
  finder_options = { :reorder => options[:sorting].try(:clause),
318
350
  :conditions => search_conditions,
319
351
  :joins => joins_for_finder,
320
352
  :outer_joins => active_scaffold_outer_joins,
353
+ :preload => active_scaffold_preload,
321
354
  :includes => full_includes,
322
355
  :select => options[:select]}
356
+ if Rails::VERSION::MAJOR >= 4
357
+ if options[:sorting].try(:sorts_by_sql?)
358
+ options[:sorting].each do |col, _|
359
+ finder_options[:outer_joins] << col.includes if col.includes.present?
360
+ end
361
+ end
362
+ finder_options.merge!(:references => active_scaffold_references)
363
+ end
323
364
 
324
365
  finder_options.merge! custom_finder_options
325
366
  finder_options
326
367
  end
327
368
 
328
- def count_items(find_options = {}, count_includes = nil)
369
+ def count_items(query, find_options = {}, count_includes = nil)
329
370
  count_includes ||= find_options[:includes] unless find_options[:conditions].blank?
330
371
  options = find_options.reject{|k,v| [:select, :reorder].include? k}
372
+ # NOTE: we must use includes in the count query, because some conditions may reference other tables
331
373
  options[:includes] = count_includes
332
374
 
333
- # NOTE: we must use :include in the count query, because some conditions may reference other tables
334
- count_query = append_to_query(beginning_of_chain, options)
335
- count = count_query.count(:distinct => true)
375
+ count = append_to_query(query, options).count
336
376
 
337
377
  # Converts count to an integer if ActiveRecord returned an OrderedHash
338
378
  # that happens when find_options contains a :group key
339
- count = count.length if count.is_a? ActiveSupport::OrderedHash
379
+ count = count.length if count.is_a?(Hash) || count.is_a?(ActiveSupport::OrderedHash) # TODO remove OrderedHash check when ruby 1.8 or rails3 support is removed
340
380
  count
341
381
  end
342
382
 
@@ -348,50 +388,62 @@ module ActiveScaffold
348
388
  options[:page] ||= 1
349
389
 
350
390
  find_options = finder_options(options)
391
+ query = beginning_of_chain.where(nil) # where(nil) is needed because we need a relation
351
392
 
352
393
  # NOTE: we must use :include in the count query, because some conditions may reference other tables
353
394
  if options[:pagination] && options[:pagination] != :infinite
354
- count = count_items(find_options, options[:count_includes])
395
+ count = count_items(query, find_options, options[:count_includes])
355
396
  end
356
397
 
357
- klass = beginning_of_chain
358
- klass = klass.where(nil).uniq if find_options[:outer_joins].present? # HACK: call where(nil) because calling uniq on associations (nested scaffolds) send SQL to DB
398
+ query = append_to_query(query, find_options)
359
399
  # we build the paginator differently for method- and sql-based sorting
360
400
  if options[:sorting] and options[:sorting].sorts_by_method?
361
401
  pager = ::Paginator.new(count, options[:per_page]) do |offset, per_page|
362
- sorted_collection = sort_collection_by_column(append_to_query(klass, find_options).all, *options[:sorting].first)
402
+ calculate_last_modified(query)
403
+ sorted_collection = sort_collection_by_column(query.to_a, *options[:sorting].first)
363
404
  sorted_collection = sorted_collection.slice(offset, per_page) if options[:pagination]
364
405
  sorted_collection
365
406
  end
366
407
  else
367
408
  pager = ::Paginator.new(count, options[:per_page]) do |offset, per_page|
368
- find_options.merge!(:offset => offset, :limit => per_page) if options[:pagination]
369
- append_to_query(klass, find_options)
409
+ query = append_to_query(query, :offset => offset, :limit => per_page) if options[:pagination]
410
+ calculate_last_modified(query)
411
+ query
370
412
  end
371
413
  end
372
414
  pager.page(options[:page])
373
415
  end
374
416
 
417
+ def calculate_last_modified(query)
418
+ if conditional_get_support? && query.klass.columns_hash['updated_at']
419
+ @last_modified = query.maximum(:updated_at)
420
+ end
421
+ end
422
+
375
423
  def calculate_query
376
424
  conditions = all_conditions
377
425
  includes = active_scaffold_config.list.count_includes
378
- includes ||= active_scaffold_includes unless conditions.blank?
426
+ includes ||= active_scaffold_references unless conditions.blank?
427
+ outer_joins = active_scaffold_outer_joins
428
+ outer_joins += includes if includes
379
429
  primary_key = active_scaffold_config.model.primary_key
380
- subquery = append_to_query(beginning_of_chain, :conditions => conditions, :joins => joins_for_finder, :outer_joins => active_scaffold_outer_joins)
381
- subquery = subquery.select(active_scaffold_config.columns[primary_key].field)
382
- if includes
383
- includes_relation = beginning_of_chain.includes(includes)
384
- subquery = subquery.send(:apply_join_dependency, subquery, includes_relation.send(:construct_join_dependency_for_association_find))
385
- end
430
+ subquery = append_to_query(beginning_of_chain, :conditions => conditions, :joins => joins_for_finder, :outer_joins => outer_joins, :select => active_scaffold_config.columns[primary_key].field)
386
431
  active_scaffold_config.model.where(primary_key => subquery)
387
432
  end
388
433
 
389
434
  def append_to_query(query, options)
390
- options.assert_valid_keys :where, :select, :group, :reorder, :limit, :offset, :joins, :outer_joins, :includes, :lock, :readonly, :from, :conditions
391
- query = apply_conditions(query, *options[:conditions]) if options[:conditions]
392
- options.reject{|k, v| k == :conditions || v.blank?}.inject(query) do |query, (k, v)|
393
- query.send((k.to_sym), v)
435
+ options.assert_valid_keys :where, :select, :group, :reorder, :limit, :offset, :joins, :outer_joins, :includes, :lock, :readonly, :from, :conditions, :preload, (:references if Rails::VERSION::MAJOR >= 4)
436
+ query = options.reject{|k,v| v.blank?}.inject(query) do |query, (k, v)|
437
+ k == :conditions ? apply_conditions(query, *v) : query.send(k, v)
438
+ end
439
+ if options[:outer_joins].present?
440
+ if Rails::VERSION::MAJOR >= 4
441
+ query.distinct_value = true
442
+ else
443
+ query = query.uniq
444
+ end
394
445
  end
446
+ query
395
447
  end
396
448
 
397
449
  def joins_for_finder
@@ -1,7 +1,7 @@
1
1
  module ActiveScaffold
2
2
  module Helpers
3
3
  module AssociationHelpers
4
- # Cache the optins for select
4
+ # Cache the options for select
5
5
  def cache_association_options(association, conditions, klass, cache = true)
6
6
  if active_scaffold_config.cache_association_options && cache
7
7
  @_associations_cache ||= Hash.new { |h,k| h[k] = {} }
@@ -13,9 +13,11 @@ module ActiveScaffold
13
13
  end
14
14
 
15
15
  # Provides a way to honor the :conditions on an association while searching the association's klass
16
- def association_options_find(association, conditions = nil, klass = nil)
16
+ def association_options_find(association, conditions = nil, klass = nil, record = nil)
17
+ ActiveSupport::Deprecation.warn "Relying on @record is deprecated, call with record.", caller if record.nil? # TODO Remove when relying on @record is removed
18
+ record ||= @record # TODO Remove when relying on @record is removed
17
19
  if klass.nil? && association.options[:polymorphic]
18
- class_name = @record.send(association.foreign_type)
20
+ class_name = record.send(association.foreign_type)
19
21
  if class_name.present?
20
22
  klass = class_name.constantize
21
23
  else
@@ -27,45 +29,73 @@ module ActiveScaffold
27
29
  klass ||= association.klass
28
30
  end
29
31
 
30
- conditions = options_for_association_conditions(association) if conditions.nil?
32
+ if conditions.nil?
33
+ if method(:options_for_association_conditions).arity.abs == 2
34
+ conditions = options_for_association_conditions(association, record)
35
+ else
36
+ ActiveSupport::Deprecation.warn "Relying on @record is deprecated, include record in your options_for_association_conditions overrided method.", caller if record.nil? # TODO Remove when relying on @record is removed
37
+ conditions = options_for_association_conditions(association)
38
+ end
39
+ end
31
40
  cache_association_options(association, conditions, klass, cache) do
41
+ klass = association_klass_scoped(association, klass, record)
32
42
  relation = klass.where(conditions).where(association.options[:conditions])
33
43
  relation = relation.includes(association.options[:include]) if association.options[:include]
44
+ column = column_for_association(association, record)
45
+ if column && column.try(:sort) && column.sort[:sql]
46
+ if column.includes
47
+ include_assoc = column.includes.find { |assoc| assoc.is_a?(Hash) && assoc.include?(association.name) }
48
+ relation = relation.includes(include_assoc[association.name]) if include_assoc
49
+ end
50
+ relation = relation.order(column.sort[:sql])
51
+ end
34
52
  relation = yield(relation) if block_given?
35
53
  relation.to_a
36
54
  end
37
55
  end
38
56
 
57
+ def column_for_association(association, record)
58
+ active_scaffold_config_for(record.class).columns[association.name] rescue nil
59
+ end
60
+
61
+ def association_klass_scoped(association, klass, record)
62
+ klass
63
+ end
64
+
39
65
  # Sorts the options for select
40
- def sorted_association_options_find(association, conditions = nil)
41
- association_options_find(association, conditions).sort_by(&:to_label)
66
+ def sorted_association_options_find(association, conditions = nil, record = nil)
67
+ options = association_options_find(association, conditions, nil, record)
68
+ column = column_for_association(association, record)
69
+ unless column && column.try(:sort) && column.sort[:sql]
70
+ method = column.options[:label_method] if column
71
+ options = options.sort_by(&(method || :to_label).to_sym)
72
+ end
73
+ options
42
74
  end
43
75
 
44
76
  def association_options_count(association, conditions = nil)
45
77
  association.klass.where(conditions).where(association.options[:conditions]).count
46
78
  end
47
79
 
48
- # returns options for the given association as a collection of [id, label] pairs intended for the +options_for_select+ helper.
49
- def options_for_association(association, include_all = false)
50
- ActiveSupport::Deprecation.warn "options_for_association should not be used, use association_options_find directly"
51
- available_records = association_options_find(association, include_all ? nil : options_for_association_conditions(association))
52
- available_records ||= []
53
- available_records.sort{|a,b| a.to_label <=> b.to_label}.collect { |model| [ model.to_label, model.id ] }
54
- end
55
-
56
- def options_for_association_count(association)
57
- association_options_count(association, options_for_association_conditions(association))
80
+ def options_for_association_count(association, record)
81
+ if method(:options_for_association_conditions).arity.abs == 2
82
+ conditions = options_for_association_conditions(association, record)
83
+ else
84
+ ActiveSupport::Deprecation.warn "Relying on @record is deprecated, include record in your options_for_association_conditions overrided method.", caller if record.nil? # TODO Remove when relying on @record is removed
85
+ conditions = options_for_association_conditions(association)
86
+ end
87
+ association_options_count(association, conditions)
58
88
  end
59
89
 
60
90
  # A useful override for customizing the records present in an association dropdown.
61
91
  # Should work in both the subform and form_ui=>:select modes.
62
92
  # Check association.name to specialize the conditions per-column.
63
- def options_for_association_conditions(association)
93
+ def options_for_association_conditions(association, record = nil)
64
94
  return nil if association.options[:through]
65
95
  case association.macro
66
96
  when :has_one, :has_many
67
97
  # Find only orphaned objects
68
- "#{association.foreign_key} IS NULL"
98
+ {association.foreign_key => nil}
69
99
  when :belongs_to, :has_and_belongs_to_many
70
100
  # Find all
71
101
  nil
@@ -22,7 +22,7 @@ module ActiveScaffold
22
22
  # :sort, :sort_direction, and :page are arguments that stored in the session. they need not propagate.
23
23
  # and wow. no we don't want to propagate :record.
24
24
  # :commit is a special rails variable for form buttons
25
- blacklist = [:adapter, :position, :sort, :sort_direction, :page, :record, :commit, :_method, :authenticity_token, :iframe, :associated_id, :dont_close]
25
+ blacklist = [:adapter, :position, :sort, :sort_direction, :page, :auto_pagination, :record, :commit, :_method, :authenticity_token, :iframe, :associated_id, :dont_close]
26
26
  unless @params_for
27
27
  @params_for = {}
28
28
  params.except(*blacklist).each {|key, value| @params_for[key.to_sym] = value.duplicable? ? value.clone : value}
@@ -65,10 +65,10 @@ module ActiveScaffold
65
65
 
66
66
  def render_parent_options
67
67
  if nested_singular_association?
68
- {:controller => nested.parent_scaffold.controller_path, :action => :row, :id => nested.parent_id}
68
+ {:controller => nested.parent_scaffold.controller_path, :action => :index, :id => nested.parent_id}
69
69
  elsif params[:parent_sti]
70
70
  options = params_for(:controller => params[:parent_sti], :action => render_parent_action, :parent_sti => nil)
71
- options.merge(:id => @record.id) if render_parent_action == :row
71
+ options.merge(:action => :index, :id => @record.to_param) if render_parent_action == :row
72
72
  end
73
73
  end
74
74
 
@@ -86,15 +86,24 @@ module ActiveScaffold
86
86
  @parent_action
87
87
  end
88
88
 
89
- def build_associated(column, record)
90
- if column.singular_association?
91
- if column.association.options[:through]
92
- record.send(:"build_#{column.association.through_reflection.name}").send(:"build_#{column.name}")
93
- else
94
- record.send(:"build_#{column.name}")
89
+ # build an associated record for association
90
+ def build_associated(association, parent_record)
91
+ if association.options[:through]
92
+ # build full chain, only check create_associated on initial parent_record
93
+ parent_record = build_associated(association.through_reflection, parent_record)
94
+ build_associated(association.source_reflection, parent_record).tap do |record|
95
+ save_record_to_association(record, association.source_reflection.reverse, parent_record) # set inverse
95
96
  end
97
+ elsif association.collection?
98
+ parent_record.send(association.name).build
99
+ elsif association.belongs_to? || parent_record.new_record? || parent_record.send(association.name).nil?
100
+ # avoid use build_association in has_one when record is saved and had associated record
101
+ # because associated record would be changed in DB
102
+ parent_record.send("build_#{association.name}")
96
103
  else
97
- record.send(column.name).build
104
+ association.klass.new.tap do |record|
105
+ save_record_to_association(record, association.reverse, parent_record) # set inverse
106
+ end
98
107
  end
99
108
  end
100
109
  end