active_scaffold_vho 3.0.6

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 (279) hide show
  1. data/.autotest +27 -0
  2. data/.document +5 -0
  3. data/CHANGELOG +179 -0
  4. data/Gemfile +13 -0
  5. data/Gemfile.lock +20 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README +63 -0
  8. data/Rakefile +53 -0
  9. data/frontends/default/images/add.gif +0 -0
  10. data/frontends/default/images/arrow_down.gif +0 -0
  11. data/frontends/default/images/arrow_up.gif +0 -0
  12. data/frontends/default/images/close.gif +0 -0
  13. data/frontends/default/images/config.png +0 -0
  14. data/frontends/default/images/cross.png +0 -0
  15. data/frontends/default/images/gears.png +0 -0
  16. data/frontends/default/images/indicator-small.gif +0 -0
  17. data/frontends/default/images/indicator.gif +0 -0
  18. data/frontends/default/images/magnifier.png +0 -0
  19. data/frontends/default/javascripts/jquery/active_scaffold.js +957 -0
  20. data/frontends/default/javascripts/jquery/jquery.editinplace.js +743 -0
  21. data/frontends/default/javascripts/prototype/active_scaffold.js +957 -0
  22. data/frontends/default/javascripts/prototype/dhtml_history.js +867 -0
  23. data/frontends/default/javascripts/prototype/form_enhancements.js +117 -0
  24. data/frontends/default/javascripts/prototype/rico_corner.js +370 -0
  25. data/frontends/default/stylesheets/stylesheet-ie.css +35 -0
  26. data/frontends/default/stylesheets/stylesheet.css +964 -0
  27. data/frontends/default/views/_action_group.html.erb +20 -0
  28. data/frontends/default/views/_add_existing_form.html.erb +30 -0
  29. data/frontends/default/views/_base_form.html.erb +45 -0
  30. data/frontends/default/views/_create_form.html.erb +8 -0
  31. data/frontends/default/views/_create_form_on_list.html.erb +6 -0
  32. data/frontends/default/views/_field_search.html.erb +32 -0
  33. data/frontends/default/views/_form.html.erb +24 -0
  34. data/frontends/default/views/_form_association.html.erb +14 -0
  35. data/frontends/default/views/_form_association_footer.html.erb +40 -0
  36. data/frontends/default/views/_form_attribute.html.erb +15 -0
  37. data/frontends/default/views/_form_hidden_attribute.html.erb +2 -0
  38. data/frontends/default/views/_form_messages.html.erb +5 -0
  39. data/frontends/default/views/_horizontal_subform.html.erb +19 -0
  40. data/frontends/default/views/_horizontal_subform_header.html.erb +10 -0
  41. data/frontends/default/views/_horizontal_subform_record.html.erb +37 -0
  42. data/frontends/default/views/_human_conditions.html.erb +1 -0
  43. data/frontends/default/views/_list.html.erb +18 -0
  44. data/frontends/default/views/_list_actions.html.erb +15 -0
  45. data/frontends/default/views/_list_calculations.html.erb +16 -0
  46. data/frontends/default/views/_list_column_headings.html.erb +12 -0
  47. data/frontends/default/views/_list_header.html.erb +10 -0
  48. data/frontends/default/views/_list_inline_adapter.html.erb +10 -0
  49. data/frontends/default/views/_list_messages.html.erb +32 -0
  50. data/frontends/default/views/_list_pagination.html.erb +11 -0
  51. data/frontends/default/views/_list_pagination_links.html.erb +9 -0
  52. data/frontends/default/views/_list_record.html.erb +14 -0
  53. data/frontends/default/views/_list_record_columns.html.erb +8 -0
  54. data/frontends/default/views/_list_with_header.html.erb +32 -0
  55. data/frontends/default/views/_messages.html.erb +10 -0
  56. data/frontends/default/views/_render_field.js.rjs +13 -0
  57. data/frontends/default/views/_row.html.erb +12 -0
  58. data/frontends/default/views/_search.html.erb +34 -0
  59. data/frontends/default/views/_search_attribute.html.erb +10 -0
  60. data/frontends/default/views/_show.html.erb +8 -0
  61. data/frontends/default/views/_show_columns.html.erb +15 -0
  62. data/frontends/default/views/_update_actions.html.erb +9 -0
  63. data/frontends/default/views/_update_form.html.erb +6 -0
  64. data/frontends/default/views/_vertical_subform.html.erb +12 -0
  65. data/frontends/default/views/_vertical_subform_record.html.erb +38 -0
  66. data/frontends/default/views/action_confirmation.html.erb +13 -0
  67. data/frontends/default/views/add_existing.js.rjs +17 -0
  68. data/frontends/default/views/add_existing_form.html.erb +5 -0
  69. data/frontends/default/views/create.html.erb +5 -0
  70. data/frontends/default/views/delete.html.erb +13 -0
  71. data/frontends/default/views/destroy.js.rjs +11 -0
  72. data/frontends/default/views/edit_associated.js.rjs +11 -0
  73. data/frontends/default/views/field_search.html.erb +5 -0
  74. data/frontends/default/views/form_messages.js.rjs +1 -0
  75. data/frontends/default/views/list.html.erb +1 -0
  76. data/frontends/default/views/list.js.rjs +1 -0
  77. data/frontends/default/views/on_action_update.js.rjs +8 -0
  78. data/frontends/default/views/on_create.js.rjs +41 -0
  79. data/frontends/default/views/on_update.js.rjs +28 -0
  80. data/frontends/default/views/search.html.erb +5 -0
  81. data/frontends/default/views/show.html.erb +5 -0
  82. data/frontends/default/views/update.html.erb +8 -0
  83. data/frontends/default/views/update_column.js.rjs +13 -0
  84. data/frontends/default/views/update_row.js.rjs +1 -0
  85. data/init.rb +9 -0
  86. data/lib/active_record_permissions.rb +134 -0
  87. data/lib/active_scaffold/actions/common_search.rb +22 -0
  88. data/lib/active_scaffold/actions/core.rb +170 -0
  89. data/lib/active_scaffold/actions/create.rb +145 -0
  90. data/lib/active_scaffold/actions/delete.rb +75 -0
  91. data/lib/active_scaffold/actions/field_search.rb +82 -0
  92. data/lib/active_scaffold/actions/list.rb +184 -0
  93. data/lib/active_scaffold/actions/mark.rb +50 -0
  94. data/lib/active_scaffold/actions/nested.rb +250 -0
  95. data/lib/active_scaffold/actions/search.rb +47 -0
  96. data/lib/active_scaffold/actions/show.rb +61 -0
  97. data/lib/active_scaffold/actions/subform.rb +17 -0
  98. data/lib/active_scaffold/actions/update.rb +141 -0
  99. data/lib/active_scaffold/attribute_params.rb +207 -0
  100. data/lib/active_scaffold/bridges/ancestry/bridge.rb +5 -0
  101. data/lib/active_scaffold/bridges/ancestry/lib/ancestry_bridge.rb +39 -0
  102. data/lib/active_scaffold/bridges/bridge.rb +52 -0
  103. data/lib/active_scaffold/bridges/calendar_date_select/bridge.rb +16 -0
  104. data/lib/active_scaffold/bridges/calendar_date_select/lib/as_cds_bridge.rb +79 -0
  105. data/lib/active_scaffold/bridges/carrierwave/bridge.rb +7 -0
  106. data/lib/active_scaffold/bridges/carrierwave/lib/carrierwave_bridge.rb +38 -0
  107. data/lib/active_scaffold/bridges/carrierwave/lib/carrierwave_bridge_helpers.rb +26 -0
  108. data/lib/active_scaffold/bridges/carrierwave/lib/form_ui.rb +35 -0
  109. data/lib/active_scaffold/bridges/carrierwave/lib/list_ui.rb +17 -0
  110. data/lib/active_scaffold/bridges/date_picker/bridge.rb +22 -0
  111. data/lib/active_scaffold/bridges/date_picker/lib/datepicker_bridge.rb +225 -0
  112. data/lib/active_scaffold/bridges/date_picker/public/javascripts/date_picker_bridge.js +22 -0
  113. data/lib/active_scaffold/bridges/file_column/bridge.rb +11 -0
  114. data/lib/active_scaffold/bridges/file_column/lib/as_file_column_bridge.rb +46 -0
  115. data/lib/active_scaffold/bridges/file_column/lib/file_column_helpers.rb +59 -0
  116. data/lib/active_scaffold/bridges/file_column/lib/form_ui.rb +37 -0
  117. data/lib/active_scaffold/bridges/file_column/lib/list_ui.rb +26 -0
  118. data/lib/active_scaffold/bridges/file_column/test/functional/file_column_keep_test.rb +43 -0
  119. data/lib/active_scaffold/bridges/file_column/test/mock_model.rb +9 -0
  120. data/lib/active_scaffold/bridges/file_column/test/test_helper.rb +15 -0
  121. data/lib/active_scaffold/bridges/paperclip/bridge.rb +10 -0
  122. data/lib/active_scaffold/bridges/paperclip/lib/form_ui.rb +27 -0
  123. data/lib/active_scaffold/bridges/paperclip/lib/list_ui.rb +16 -0
  124. data/lib/active_scaffold/bridges/paperclip/lib/paperclip_bridge.rb +38 -0
  125. data/lib/active_scaffold/bridges/paperclip/lib/paperclip_bridge_helpers.rb +26 -0
  126. data/lib/active_scaffold/bridges/semantic_attributes/bridge.rb +5 -0
  127. data/lib/active_scaffold/bridges/semantic_attributes/lib/semantic_attributes_bridge.rb +20 -0
  128. data/lib/active_scaffold/bridges/shared/date_bridge.rb +187 -0
  129. data/lib/active_scaffold/bridges/tiny_mce/bridge.rb +5 -0
  130. data/lib/active_scaffold/bridges/tiny_mce/lib/tiny_mce_bridge.rb +45 -0
  131. data/lib/active_scaffold/bridges/validation_reflection/bridge.rb +8 -0
  132. data/lib/active_scaffold/bridges/validation_reflection/lib/validation_reflection_bridge.rb +21 -0
  133. data/lib/active_scaffold/config/base.rb +62 -0
  134. data/lib/active_scaffold/config/core.rb +220 -0
  135. data/lib/active_scaffold/config/create.rb +51 -0
  136. data/lib/active_scaffold/config/delete.rb +34 -0
  137. data/lib/active_scaffold/config/field_search.rb +75 -0
  138. data/lib/active_scaffold/config/form.rb +47 -0
  139. data/lib/active_scaffold/config/list.rb +174 -0
  140. data/lib/active_scaffold/config/mark.rb +22 -0
  141. data/lib/active_scaffold/config/nested.rb +44 -0
  142. data/lib/active_scaffold/config/search.rb +69 -0
  143. data/lib/active_scaffold/config/show.rb +35 -0
  144. data/lib/active_scaffold/config/subform.rb +35 -0
  145. data/lib/active_scaffold/config/update.rb +46 -0
  146. data/lib/active_scaffold/configurable.rb +29 -0
  147. data/lib/active_scaffold/constraints.rb +184 -0
  148. data/lib/active_scaffold/data_structures/action_columns.rb +133 -0
  149. data/lib/active_scaffold/data_structures/action_link.rb +171 -0
  150. data/lib/active_scaffold/data_structures/action_links.rb +175 -0
  151. data/lib/active_scaffold/data_structures/actions.rb +45 -0
  152. data/lib/active_scaffold/data_structures/column.rb +351 -0
  153. data/lib/active_scaffold/data_structures/columns.rb +75 -0
  154. data/lib/active_scaffold/data_structures/error_message.rb +24 -0
  155. data/lib/active_scaffold/data_structures/nested_info.rb +123 -0
  156. data/lib/active_scaffold/data_structures/set.rb +62 -0
  157. data/lib/active_scaffold/data_structures/sorting.rb +168 -0
  158. data/lib/active_scaffold/finder.rb +333 -0
  159. data/lib/active_scaffold/helpers/association_helpers.rb +40 -0
  160. data/lib/active_scaffold/helpers/controller_helpers.rb +82 -0
  161. data/lib/active_scaffold/helpers/country_helpers.rb +352 -0
  162. data/lib/active_scaffold/helpers/form_column_helpers.rb +347 -0
  163. data/lib/active_scaffold/helpers/human_condition_helpers.rb +59 -0
  164. data/lib/active_scaffold/helpers/id_helpers.rb +127 -0
  165. data/lib/active_scaffold/helpers/list_column_helpers.rb +361 -0
  166. data/lib/active_scaffold/helpers/pagination_helpers.rb +55 -0
  167. data/lib/active_scaffold/helpers/search_column_helpers.rb +243 -0
  168. data/lib/active_scaffold/helpers/show_column_helpers.rb +46 -0
  169. data/lib/active_scaffold/helpers/view_helpers.rb +356 -0
  170. data/lib/active_scaffold/locale/de.rb +120 -0
  171. data/lib/active_scaffold/locale/en.rb +119 -0
  172. data/lib/active_scaffold/locale/es.yml +115 -0
  173. data/lib/active_scaffold/locale/fr.rb +116 -0
  174. data/lib/active_scaffold/locale/hu.yml +63 -0
  175. data/lib/active_scaffold/locale/ja.yml +64 -0
  176. data/lib/active_scaffold/locale/ru.yml +119 -0
  177. data/lib/active_scaffold/marked_model.rb +38 -0
  178. data/lib/active_scaffold/version.rb +9 -0
  179. data/lib/active_scaffold.rb +345 -0
  180. data/lib/active_scaffold_assets.rb +45 -0
  181. data/lib/dhtml_confirm.rb +54 -0
  182. data/lib/environment.rb +14 -0
  183. data/lib/extensions/action_controller_rendering.rb +20 -0
  184. data/lib/extensions/action_view_rendering.rb +113 -0
  185. data/lib/extensions/action_view_resolver.rb +7 -0
  186. data/lib/extensions/active_association_reflection.rb +13 -0
  187. data/lib/extensions/active_record_offset.rb +12 -0
  188. data/lib/extensions/array.rb +7 -0
  189. data/lib/extensions/localize.rb +10 -0
  190. data/lib/extensions/name_option_for_datetime.rb +12 -0
  191. data/lib/extensions/nil_id_in_url_params.rb +7 -0
  192. data/lib/extensions/paginator_extensions.rb +26 -0
  193. data/lib/extensions/reverse_associations.rb +62 -0
  194. data/lib/extensions/routing_mapper.rb +34 -0
  195. data/lib/extensions/to_label.rb +8 -0
  196. data/lib/extensions/unsaved_associated.rb +61 -0
  197. data/lib/extensions/unsaved_record.rb +20 -0
  198. data/lib/extensions/usa_state.rb +46 -0
  199. data/lib/generators/active_scaffold/USAGE +29 -0
  200. data/lib/generators/active_scaffold/active_scaffold_generator.rb +20 -0
  201. data/lib/generators/active_scaffold_controller/USAGE +19 -0
  202. data/lib/generators/active_scaffold_controller/active_scaffold_controller_generator.rb +28 -0
  203. data/lib/generators/active_scaffold_controller/templates/controller.rb +4 -0
  204. data/lib/generators/active_scaffold_setup/USAGE +10 -0
  205. data/lib/generators/active_scaffold_setup/active_scaffold_setup_generator.rb +53 -0
  206. data/lib/paginator.rb +136 -0
  207. data/lib/responds_to_parent.rb +70 -0
  208. data/public/blank.html +33 -0
  209. data/shoulda_macros/macros.rb +136 -0
  210. data/test/bridges/bridge_test.rb +47 -0
  211. data/test/config/base_test.rb +15 -0
  212. data/test/config/create_test.rb +55 -0
  213. data/test/config/list_test.rb +74 -0
  214. data/test/config/show_test.rb +43 -0
  215. data/test/config/update_test.rb +17 -0
  216. data/test/const_mocker.rb +36 -0
  217. data/test/data_structures/action_columns_test.rb +113 -0
  218. data/test/data_structures/action_link_test.rb +78 -0
  219. data/test/data_structures/action_links_test.rb +78 -0
  220. data/test/data_structures/actions_test.rb +25 -0
  221. data/test/data_structures/association_column_test.rb +42 -0
  222. data/test/data_structures/column_test.rb +185 -0
  223. data/test/data_structures/columns_test.rb +69 -0
  224. data/test/data_structures/error_message_test.rb +28 -0
  225. data/test/data_structures/set_test.rb +86 -0
  226. data/test/data_structures/sorting_test.rb +126 -0
  227. data/test/data_structures/standard_column_test.rb +24 -0
  228. data/test/data_structures/virtual_column_test.rb +23 -0
  229. data/test/extensions/active_record_test.rb +45 -0
  230. data/test/extensions/array_test.rb +12 -0
  231. data/test/helpers/form_column_helpers_test.rb +31 -0
  232. data/test/helpers/list_column_helpers_test.rb +31 -0
  233. data/test/helpers/pagination_helpers_test.rb +55 -0
  234. data/test/misc/active_record_permissions_test.rb +154 -0
  235. data/test/misc/attribute_params_test.rb +110 -0
  236. data/test/misc/configurable_test.rb +96 -0
  237. data/test/misc/constraints_test.rb +193 -0
  238. data/test/misc/finder_test.rb +93 -0
  239. data/test/misc/lang_test.rb +12 -0
  240. data/test/mock_app/.gitignore +2 -0
  241. data/test/mock_app/app/controllers/application_controller.rb +10 -0
  242. data/test/mock_app/app/helpers/application_helper.rb +3 -0
  243. data/test/mock_app/config/boot.rb +110 -0
  244. data/test/mock_app/config/database.yml +16 -0
  245. data/test/mock_app/config/environment.rb +43 -0
  246. data/test/mock_app/config/environments/development.rb +17 -0
  247. data/test/mock_app/config/environments/production.rb +28 -0
  248. data/test/mock_app/config/environments/test.rb +28 -0
  249. data/test/mock_app/config/initializers/backtrace_silencers.rb +7 -0
  250. data/test/mock_app/config/initializers/inflections.rb +10 -0
  251. data/test/mock_app/config/initializers/mime_types.rb +5 -0
  252. data/test/mock_app/config/initializers/new_rails_defaults.rb +19 -0
  253. data/test/mock_app/config/initializers/session_store.rb +15 -0
  254. data/test/mock_app/config/locales/en.yml +5 -0
  255. data/test/mock_app/config/routes.rb +43 -0
  256. data/test/mock_app/db/test.sqlite3 +1 -0
  257. data/test/mock_app/public/blank.html +33 -0
  258. data/test/mock_app/public/images/active_scaffold/DO_NOT_EDIT +2 -0
  259. data/test/mock_app/public/images/active_scaffold/default/add.gif +0 -0
  260. data/test/mock_app/public/images/active_scaffold/default/arrow_down.gif +0 -0
  261. data/test/mock_app/public/images/active_scaffold/default/arrow_up.gif +0 -0
  262. data/test/mock_app/public/images/active_scaffold/default/close.gif +0 -0
  263. data/test/mock_app/public/images/active_scaffold/default/cross.png +0 -0
  264. data/test/mock_app/public/images/active_scaffold/default/indicator-small.gif +0 -0
  265. data/test/mock_app/public/images/active_scaffold/default/indicator.gif +0 -0
  266. data/test/mock_app/public/images/active_scaffold/default/magnifier.png +0 -0
  267. data/test/mock_app/public/javascripts/active_scaffold/DO_NOT_EDIT +2 -0
  268. data/test/mock_app/public/javascripts/active_scaffold/default/active_scaffold.js +532 -0
  269. data/test/mock_app/public/javascripts/active_scaffold/default/dhtml_history.js +867 -0
  270. data/test/mock_app/public/javascripts/active_scaffold/default/form_enhancements.js +117 -0
  271. data/test/mock_app/public/javascripts/active_scaffold/default/rico_corner.js +370 -0
  272. data/test/mock_app/public/stylesheets/active_scaffold/DO_NOT_EDIT +2 -0
  273. data/test/mock_app/public/stylesheets/active_scaffold/default/stylesheet-ie.css +35 -0
  274. data/test/mock_app/public/stylesheets/active_scaffold/default/stylesheet.css +839 -0
  275. data/test/model_stub.rb +55 -0
  276. data/test/run_all.rb +8 -0
  277. data/test/test_helper.rb +39 -0
  278. data/uninstall.rb +13 -0
  279. metadata +492 -0
@@ -0,0 +1,168 @@
1
+ module ActiveScaffold::DataStructures
2
+ # encapsulates the column sorting configuration for the List view
3
+ class Sorting
4
+ include Enumerable
5
+
6
+ def initialize(columns)
7
+ @columns = columns
8
+ @clauses = []
9
+ end
10
+
11
+ def set_default_sorting(model)
12
+ model_scope = model.send(:current_scoped_methods)
13
+ order_clause = model_scope.arel.order_clauses.join(",") if model_scope
14
+
15
+ # If an ORDER BY clause is found set default sorting according to it, else
16
+ # fallback to setting primary key ordering
17
+ if order_clause
18
+ set_sorting_from_order_clause(order_clause, model.table_name)
19
+ @default_sorting = true
20
+ else
21
+ set(model.primary_key, 'ASC') if model.column_names.include?(model.primary_key)
22
+ end
23
+ end
24
+
25
+ def set_nested_sorting(table_name, order_clause)
26
+ clear
27
+ set_sorting_from_order_clause(order_clause, table_name)
28
+ end
29
+
30
+ # add a clause to the sorting, assuming the column is sortable
31
+ def add(column_name, direction = nil)
32
+ direction ||= 'ASC'
33
+ direction = direction.to_s.upcase
34
+ column = get_column(column_name)
35
+ raise ArgumentError, "Could not find column #{column_name}" if column.nil?
36
+ raise ArgumentError, "Sorting direction unknown" unless [:ASC, :DESC].include? direction.to_sym
37
+ @clauses << [column, direction] if column.sortable?
38
+ raise ArgumentError, "Can't mix :method- and :sql-based sorting" if mixed_sorting?
39
+ end
40
+
41
+ # an alias for +add+. must accept its arguments in a slightly different form, though.
42
+ def <<(arg)
43
+ add(*arg)
44
+ end
45
+
46
+ # clears the sorting before setting to the given column/direction
47
+ def set(*args)
48
+ clear
49
+ add(*args)
50
+ end
51
+
52
+ # clears the sorting
53
+ def clear
54
+ @default_sorting = false
55
+ @clauses = []
56
+ end
57
+
58
+ # checks whether the given column (a Column object or a column name) is in the sorting
59
+ def sorts_on?(column)
60
+ !get_clause(column).nil?
61
+ end
62
+
63
+ def direction_of(column)
64
+ clause = get_clause(column)
65
+ return if clause.nil?
66
+ clause[1]
67
+ end
68
+
69
+ # checks whether any column is configured to sort by method (using a proc)
70
+ def sorts_by_method?
71
+ @clauses.any? { |sorting| sorting[0].sort.is_a? Hash and sorting[0].sort.has_key? :method }
72
+ end
73
+
74
+ def sorts_by_sql?
75
+ @clauses.any? { |sorting| sorting[0].sort.is_a? Hash and sorting[0].sort.has_key? :sql }
76
+ end
77
+
78
+ # iterate over the clauses
79
+ def each
80
+ @clauses.each { |clause| yield clause }
81
+ end
82
+
83
+ # provides quick access to the first (and sometimes only) clause
84
+ def first
85
+ @clauses.first
86
+ end
87
+
88
+ # builds an order-by clause
89
+ def clause
90
+ return nil if sorts_by_method? || default_sorting?
91
+
92
+ # unless the sorting is by method, create the sql string
93
+ order = []
94
+ each do |sort_column, sort_direction|
95
+ sql = sort_column.sort[:sql]
96
+ next if sql.nil? or sql.empty?
97
+
98
+ order << "#{sql} #{sort_direction}"
99
+ end
100
+
101
+ order.join(', ') unless order.empty?
102
+ end
103
+
104
+ protected
105
+
106
+ # retrieves the sorting clause for the given column
107
+ def get_clause(column)
108
+ column = get_column(column)
109
+ @clauses.find{ |clause| clause[0] == column}
110
+ end
111
+
112
+ # possibly converts the given argument into a column object from @columns (if it's not already)
113
+ def get_column(name_or_column)
114
+ # it's a column
115
+ return name_or_column if name_or_column.is_a? ActiveScaffold::DataStructures::Column
116
+ # it's a name
117
+ name_or_column = name_or_column.to_s.split('.').last if name_or_column.to_s.include? '.'
118
+ return @columns[name_or_column]
119
+ end
120
+
121
+ def mixed_sorting?
122
+ sorts_by_method? and sorts_by_sql?
123
+ end
124
+
125
+ def default_sorting?
126
+ @default_sorting
127
+ end
128
+
129
+ def set_sorting_from_order_clause(order_clause, model_table_name = nil)
130
+ clear
131
+ order_clause.split(',').each do |criterion|
132
+ unless criterion.blank?
133
+ order_parts = extract_order_parts(criterion)
134
+ add(order_parts[:column_name], order_parts[:direction]) unless different_table?(model_table_name, order_parts[:table_name])
135
+ end
136
+ end
137
+ end
138
+
139
+ def extract_order_parts(criterion_parts)
140
+ column_name_part, direction_part = criterion_parts.strip.split(' ')
141
+ column_name_parts = column_name_part.split('.')
142
+ order = {:direction => extract_direction(direction_part),
143
+ :column_name => remove_quotes(column_name_parts.last)}
144
+ order[:table_name] = remove_quotes(column_name_parts[-2]) if column_name_parts.length >= 2
145
+ order
146
+ end
147
+
148
+ def different_table?(model_table_name, order_table_name)
149
+ !order_table_name.nil? && model_table_name != order_table_name
150
+ end
151
+
152
+ def remove_quotes(sql_name)
153
+ if sql_name.starts_with?('"') || sql_name.starts_with?('`')
154
+ sql_name[1, (sql_name.length - 2)]
155
+ else
156
+ sql_name
157
+ end
158
+ end
159
+
160
+ def extract_direction(direction_part)
161
+ if direction_part.to_s.upcase == 'DESC'
162
+ 'DESC'
163
+ else
164
+ 'ASC'
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,333 @@
1
+ module ActiveScaffold
2
+ module Finder
3
+ module ClassMethods
4
+ # Takes a collection of search terms (the tokens) and creates SQL that
5
+ # searches all specified ActiveScaffold columns. A row will match if each
6
+ # token is found in at least one of the columns.
7
+ def create_conditions_for_columns(tokens, columns, text_search = :full)
8
+ # if there aren't any columns, then just return a nil condition
9
+ return unless columns.length > 0
10
+ like_pattern = like_pattern(text_search)
11
+
12
+ tokens = [tokens] if tokens.is_a? String
13
+
14
+ where_clauses = []
15
+ columns.each do |column|
16
+ where_clauses << ((column.column.nil? || column.column.text?) ? "LOWER(#{column.search_sql}) LIKE ?" : "#{column.search_sql} = ?")
17
+ end
18
+ phrase = "(#{where_clauses.join(' OR ')})"
19
+
20
+ sql = ([phrase] * tokens.length).join(' AND ')
21
+ tokens = tokens.collect do |value|
22
+ columns.collect {|column| (column.column.nil? || column.column.text?) ? like_pattern.sub('?', value.downcase) : column.column.type_cast(value)}
23
+ end.flatten
24
+
25
+ [sql, *tokens]
26
+ end
27
+
28
+ # Generates an SQL condition for the given ActiveScaffold column based on
29
+ # that column's database type (or form_ui ... for virtual columns?).
30
+ # TODO: this should reside on the column, not the controller
31
+ def condition_for_column(column, value, text_search = :full)
32
+ like_pattern = like_pattern(text_search)
33
+ return unless column and column.search_sql and not value.blank?
34
+ search_ui = column.search_ui || column.column.type
35
+ begin
36
+ if self.respond_to?("condition_for_#{column.name}_column")
37
+ self.send("condition_for_#{column.name}_column", column, value, like_pattern)
38
+ elsif self.respond_to?("condition_for_#{search_ui}_type")
39
+ self.send("condition_for_#{search_ui}_type", column, value, like_pattern)
40
+ else
41
+ unless column.search_sql.instance_of? Proc
42
+ case search_ui
43
+ when :boolean, :checkbox
44
+ ["#{column.search_sql} = ?", column.column.type_cast(value)]
45
+ when :integer, :decimal, :float
46
+ condition_for_numeric(column, value)
47
+ when :string, :range
48
+ condition_for_range(column, value, like_pattern)
49
+ when :date, :time, :datetime, :timestamp
50
+ condition_for_datetime(column, value)
51
+ when :select, :multi_select, :country, :usa_state
52
+ ["#{column.search_sql} in (?)", Array(value)]
53
+ else
54
+ if column.column.nil? || column.column.text?
55
+ ["LOWER(#{column.search_sql}) LIKE ?", like_pattern.sub('?', value.downcase)]
56
+ else
57
+ ["#{column.search_sql} = ?", column.column.type_cast(value)]
58
+ end
59
+ end
60
+ else
61
+ column.search_sql.call(value)
62
+ end
63
+ end
64
+ rescue Exception => e
65
+ logger.error Time.now.to_s + "#{e.inspect} -- on the ActiveScaffold column :#{column.name}, search_ui = #{search_ui} in #{@controller.class}"
66
+ raise e
67
+ end
68
+ end
69
+
70
+ def condition_for_numeric(column, value)
71
+ if !value.is_a?(Hash)
72
+ ["#{column.search_sql} = ?", condition_value_for_numeric(column, value)]
73
+ elsif value[:from].blank? or not ActiveScaffold::Finder::NumericComparators.include?(value[:opt])
74
+ nil
75
+ elsif value[:opt] == 'BETWEEN'
76
+ ["#{column.search_sql} BETWEEN ? AND ?", condition_value_for_numeric(column, value[:from]), condition_value_for_numeric(column, value[:to])]
77
+ else
78
+ ["#{column.search_sql} #{value[:opt]} ?", condition_value_for_numeric(column, value[:from])]
79
+ end
80
+ end
81
+
82
+ def condition_for_range(column, value, like_pattern = nil)
83
+ if !value.is_a?(Hash)
84
+ if column.column.nil? || column.column.text?
85
+ ["LOWER(#{column.search_sql}) LIKE ?", like_pattern.sub('?', value.downcase)]
86
+ else
87
+ ["#{column.search_sql} = ?", column.column.type_cast(value)]
88
+ end
89
+ elsif value[:from].blank?
90
+ nil
91
+ elsif ActiveScaffold::Finder::StringComparators.values.include?(value[:opt])
92
+ ["#{column.search_sql} LIKE ?", value[:opt].sub('?', value[:from])]
93
+ elsif value[:opt] == 'BETWEEN'
94
+ ["#{column.search_sql} BETWEEN ? AND ?", value[:from], value[:to]]
95
+ elsif ActiveScaffold::Finder::NumericComparators.include?(value[:opt])
96
+ ["#{column.search_sql} #{value[:opt]} ?", value[:from]]
97
+ else
98
+ nil
99
+ end
100
+ end
101
+
102
+ def condition_value_for_datetime(value, conversion = :to_time)
103
+ if value.is_a? Hash
104
+ Time.zone.local(*[:year, :month, :day, :hour, :minute, :second].collect {|part| value[field][part].to_i}) rescue nil
105
+ elsif value.respond_to?(:strftime)
106
+ value.send(conversion)
107
+ else
108
+ Time.zone.parse(value).in_time_zone.send(conversion) rescue nil
109
+ end unless value.nil? || value.blank?
110
+ end
111
+
112
+ def condition_value_for_numeric(column, value)
113
+ return value if value.nil?
114
+ value = i18n_number_to_native_format(value) if [:i18n_number, :currency].include?(column.options[:format])
115
+ case (column.search_ui || column.column.type)
116
+ when :integer then value.to_i rescue value ? 1 : 0
117
+ when :float then value.to_f
118
+ when :decimal then ActiveRecord::ConnectionAdapters::Column.value_to_decimal(value)
119
+ else
120
+ value
121
+ end
122
+ end
123
+
124
+ def i18n_number_to_native_format(value)
125
+ native = '.'
126
+ delimiter = I18n.t('number.format.delimiter')
127
+ separator = I18n.t('number.format.separator')
128
+ return value if value.blank? || !value.is_a?(String)
129
+ unless delimiter == native && !value.include?(separator) && value !~ /\.\d{3}$/
130
+ value.gsub(/[^0-9\-#{I18n.t('number.format.separator')}]/, '').gsub(I18n.t('number.format.separator'), native)
131
+ else
132
+ value
133
+ end
134
+ end
135
+
136
+ def condition_for_datetime(column, value, like_pattern = nil)
137
+ conversion = column.column.type == :date ? :to_date : :to_time
138
+ from_value = condition_value_for_datetime(value[:from], conversion)
139
+ to_value = condition_value_for_datetime(value[:to], conversion)
140
+
141
+ if from_value.nil? and to_value.nil?
142
+ nil
143
+ elsif !from_value
144
+ ["#{column.search_sql} <= ?", to_value.to_s(:db)]
145
+ elsif !to_value
146
+ ["#{column.search_sql} >= ?", from_value.to_s(:db)]
147
+ else
148
+ ["#{column.search_sql} BETWEEN ? AND ?", from_value.to_s(:db), to_value.to_s(:db)]
149
+ end
150
+ end
151
+
152
+ def condition_for_record_select_type(column, value, like_pattern = nil)
153
+ if value.is_a?(Array)
154
+ ["#{column.search_sql} IN (?)", value]
155
+ else
156
+ ["#{column.search_sql} = ?", value]
157
+ end
158
+ end
159
+
160
+ def condition_for_null_type(column, value, like_pattern = nil)
161
+ case value.to_sym
162
+ when :null
163
+ ["#{column.search_sql} is null"]
164
+ when :not_null
165
+ ["#{column.search_sql} is not null"]
166
+ else
167
+ nil
168
+ end
169
+ end
170
+
171
+ def like_pattern(text_search)
172
+ case text_search
173
+ when :full then '%?%'
174
+ when :start then '?%'
175
+ when :end then '%?'
176
+ else '?'
177
+ end
178
+ end
179
+ end
180
+
181
+ NumericComparators = [
182
+ '=',
183
+ '>=',
184
+ '<=',
185
+ '>',
186
+ '<',
187
+ '!=',
188
+ 'BETWEEN'
189
+ ]
190
+ StringComparators = {
191
+ :contains => '%?%',
192
+ :begins_with => '?%',
193
+ :ends_with => '%?'
194
+ }
195
+ NullComparators = [
196
+ :null,
197
+ :not_null
198
+ ]
199
+
200
+
201
+
202
+ def self.included(klass)
203
+ klass.extend ClassMethods
204
+ end
205
+
206
+ protected
207
+
208
+ attr_writer :active_scaffold_conditions
209
+ def active_scaffold_conditions
210
+ @active_scaffold_conditions ||= []
211
+ end
212
+
213
+ attr_writer :active_scaffold_includes
214
+ def active_scaffold_includes
215
+ @active_scaffold_includes ||= []
216
+ end
217
+
218
+ attr_writer :active_scaffold_habtm_joins
219
+ def active_scaffold_habtm_joins
220
+ @active_scaffold_habtm_joins ||= []
221
+ end
222
+
223
+ def all_conditions
224
+ merge_conditions(
225
+ active_scaffold_conditions, # from the search modules
226
+ conditions_for_collection, # from the dev
227
+ conditions_from_params, # from the parameters (e.g. /users/list?first_name=Fred)
228
+ conditions_from_constraints, # from any constraints (embedded scaffolds)
229
+ active_scaffold_session_storage[:conditions] # embedding conditions (weaker constraints)
230
+ )
231
+ end
232
+
233
+ # returns a single record (the given id) but only if it's allowed for the specified action.
234
+ # accomplishes this by checking model.#{action}_authorized?
235
+ # TODO: this should reside on the model, not the controller
236
+ def find_if_allowed(id, crud_type, klass = beginning_of_chain)
237
+ record = klass.find(id)
238
+ raise ActiveScaffold::RecordNotAllowed, "#{klass} with id = #{id}" unless record.authorized_for?(:crud_type => crud_type.to_sym)
239
+ return record
240
+ end
241
+
242
+ # returns a Paginator::Page (not from ActiveRecord::Paginator) for the given parameters
243
+ # options may include:
244
+ # * :sorting - a Sorting DataStructure (basically an array of hashes of field => direction, e.g. [{:field1 => 'asc'}, {:field2 => 'desc'}]). please note that multi-column sorting has some limitations: if any column in a multi-field sort uses method-based sorting, it will be ignored. method sorting only works for single-column sorting.
245
+ # * :per_page
246
+ # * :page
247
+ # TODO: this should reside on the model, not the controller
248
+ def find_page(options = {})
249
+ options.assert_valid_keys :sorting, :per_page, :page, :count_includes, :pagination
250
+
251
+ search_conditions = all_conditions
252
+ full_includes = (active_scaffold_includes.blank? ? nil : active_scaffold_includes)
253
+ options[:per_page] ||= 999999999
254
+ options[:page] ||= 1
255
+ options[:count_includes] ||= full_includes unless search_conditions.nil?
256
+
257
+ klass = beginning_of_chain
258
+
259
+ # create a general-use options array that's compatible with Rails finders
260
+ finder_options = { :order => options[:sorting].try(:clause),
261
+ :where => search_conditions,
262
+ :joins => joins_for_finder,
263
+ :includes => options[:count_includes]}
264
+
265
+ finder_options.merge! custom_finder_options
266
+
267
+ # NOTE: we must use :include in the count query, because some conditions may reference other tables
268
+ count_query = append_to_query(klass, finder_options.reject{|k, v| [:select, :order].include?(k)})
269
+ count = count_query.count unless options[:pagination] == :infinite
270
+
271
+ # Converts count to an integer if ActiveRecord returned an OrderedHash
272
+ # that happens when finder_options contains a :group key
273
+ count = count.length if count.is_a? ActiveSupport::OrderedHash
274
+ finder_options.merge! :includes => full_includes
275
+
276
+ # we build the paginator differently for method- and sql-based sorting
277
+ if options[:sorting] and options[:sorting].sorts_by_method?
278
+ pager = ::Paginator.new(count, options[:per_page]) do |offset, per_page|
279
+ sorted_collection = sort_collection_by_column(append_to_query(klass, finder_options).all, *options[:sorting].first)
280
+ sorted_collection = sorted_collection.slice(offset, per_page) if options[:pagination]
281
+ sorted_collection
282
+ end
283
+ else
284
+ pager = ::Paginator.new(count, options[:per_page]) do |offset, per_page|
285
+ finder_options.merge!(:offset => offset, :limit => per_page) if options[:pagination]
286
+ append_to_query(klass, finder_options).all
287
+ end
288
+ end
289
+ pager.page(options[:page])
290
+ end
291
+
292
+ def append_to_query(query, options)
293
+ options.assert_valid_keys :where, :select, :group, :order, :limit, :offset, :joins, :includes, :lock, :readonly, :from
294
+ options.reject{|k, v| v.blank?}.inject(query) do |query, (k, v)|
295
+ query.send((k.to_sym), v)
296
+ end
297
+ end
298
+
299
+ def joins_for_finder
300
+ case joins_for_collection
301
+ when String
302
+ [ joins_for_collection ]
303
+ when Array
304
+ joins_for_collection
305
+ else
306
+ []
307
+ end + active_scaffold_habtm_joins
308
+ end
309
+
310
+ def merge_conditions(*conditions)
311
+ segments = []
312
+ conditions.each do |condition|
313
+ unless condition.blank?
314
+ sql = active_scaffold_config.model.send(:sanitize_sql, condition)
315
+ segments << sql unless sql.blank?
316
+ end
317
+ end
318
+ "(#{segments.join(') AND (')})" unless segments.empty?
319
+ end
320
+
321
+ # TODO: this should reside on the column, not the controller
322
+ def sort_collection_by_column(collection, column, order)
323
+ sorter = column.sort[:method]
324
+ collection = collection.sort_by { |record|
325
+ value = (sorter.is_a? Proc) ? record.instance_eval(&sorter) : record.instance_eval(sorter)
326
+ value = '' if value.nil?
327
+ value
328
+ }
329
+ collection.reverse! if order.downcase == 'desc'
330
+ collection
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,40 @@
1
+ module ActiveScaffold
2
+ module Helpers
3
+ module AssociationHelpers
4
+ # Provides a way to honor the :conditions on an association while searching the association's klass
5
+ def association_options_find(association, conditions = nil)
6
+ association.klass.where(conditions).where(association.options[:conditions]).all
7
+ end
8
+
9
+ def association_options_count(association, conditions = nil)
10
+ association.klass.where(conditions).where(association.options[:conditions]).count
11
+ end
12
+
13
+ # returns options for the given association as a collection of [id, label] pairs intended for the +options_for_select+ helper.
14
+ def options_for_association(association, include_all = false)
15
+ available_records = association_options_find(association, include_all ? nil : options_for_association_conditions(association))
16
+ available_records ||= []
17
+ available_records.sort{|a,b| a.to_label <=> b.to_label}.collect { |model| [ model.to_label, model.id ] }
18
+ end
19
+
20
+ def options_for_association_count(association)
21
+ association_options_count(association, options_for_association_conditions(association))
22
+ end
23
+
24
+ # A useful override for customizing the records present in an association dropdown.
25
+ # Should work in both the subform and form_ui=>:select modes.
26
+ # Check association.name to specialize the conditions per-column.
27
+ def options_for_association_conditions(association)
28
+ return nil if association.options[:through]
29
+ case association.macro
30
+ when :has_one, :has_many
31
+ # Find only orphaned objects
32
+ "#{association.primary_key_name} IS NULL"
33
+ when :belongs_to, :has_and_belongs_to_many
34
+ # Find all
35
+ nil
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,82 @@
1
+ module ActiveScaffold
2
+ module Helpers
3
+ module ControllerHelpers
4
+ def self.included(controller)
5
+ controller.class_eval { helper_method :params_for, :main_path_to_return, :render_parent?, :render_parent_options, :render_parent_action}
6
+ end
7
+
8
+ include ActiveScaffold::Helpers::IdHelpers
9
+
10
+ def params_for(options = {})
11
+ # :adapter and :position are one-use rendering arguments. they should not propagate.
12
+ # :sort, :sort_direction, and :page are arguments that stored in the session. they need not propagate.
13
+ # and wow. no we don't want to propagate :record.
14
+ # :commit is a special rails variable for form buttons
15
+ blacklist = [:adapter, :position, :sort, :sort_direction, :page, :record, :commit, :_method, :authenticity_token, :iframe]
16
+ unless @params_for
17
+ @params_for = {}
18
+ params.select { |key, value| blacklist.exclude? key.to_sym if key }.each {|key, value| @params_for[key.to_sym] = value.duplicable? ? value.clone : value}
19
+ @params_for[:controller] = '/' + @params_for[:controller].to_s unless @params_for[:controller].to_s.first(1) == '/' # for namespaced controllers
20
+ @params_for.delete(:id) if @params_for[:id].nil?
21
+ end
22
+ @params_for.merge(options)
23
+ end
24
+
25
+ # Parameters to generate url to the main page (override if the ActiveScaffold is used as a component on another controllers page)
26
+ def main_path_to_return
27
+ if params[:return_to]
28
+ params[:return_to]
29
+ else
30
+ parameters = {}
31
+ if params[:parent_controller]
32
+ parameters[:controller] = params[:parent_controller]
33
+ parameters[:eid] = params[:parent_controller]
34
+ end
35
+ if nested?
36
+ parameters[:controller] = nested.parent_scaffold.controller_path
37
+ parameters[:eid] = nil
38
+ end
39
+ if params[:parent_sti]
40
+ parameters[:controller] = params[:parent_sti]
41
+ parameters[:eid] = nil
42
+ end
43
+ parameters[:parent_column] = nil
44
+ parameters[:parent_id] = nil
45
+ parameters[:action] = "index"
46
+ parameters[:id] = nil
47
+ parameters[:associated_id] = nil
48
+ parameters[:utf8] = nil
49
+ params_for(parameters)
50
+ end
51
+ end
52
+
53
+ def render_parent?
54
+ (nested? && (nested.belongs_to? || nested.has_one?) || params[:parent_sti])
55
+ end
56
+
57
+ def render_parent_options
58
+ if nested?
59
+ {:controller => nested.parent_scaffold.controller_path, :action => :row, :id => nested.parent_id}
60
+ elsif params[:parent_sti]
61
+ options = {:controller => params[:parent_sti], :action => render_parent_action(params[:parent_sti])}
62
+ if render_parent_action(params[:parent_sti]) == :index
63
+ options
64
+ else
65
+ options.merge({:id => @record.id})
66
+ end
67
+ end
68
+ end
69
+
70
+ def render_parent_action(controller_path = nil)
71
+ begin
72
+ @parent_action = :row
73
+ parent_controller = "#{controller_path.to_s.camelize}Controller".constantize
74
+ @parent_action = :index if action_name == 'create' && parent_controller.active_scaffold_config.actions.include?(:create) && parent_controller.active_scaffold_config.create.refresh_list == true
75
+ @parent_action = :index if action_name == 'update' && parent_controller.active_scaffold_config.actions.include?(:update) && parent_controller.active_scaffold_config.update.refresh_list == true
76
+ rescue ActiveScaffold::ControllerNotFound
77
+ end if @parent_action.nil?
78
+ @parent_action
79
+ end
80
+ end
81
+ end
82
+ end