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,184 @@
1
+ module ActiveScaffold
2
+ module Constraints
3
+
4
+ protected
5
+
6
+ # Returns the current constraints
7
+ def active_scaffold_constraints
8
+ @active_scaffold_constraints ||= active_scaffold_session_storage[:constraints] || {}
9
+ end
10
+
11
+ def set_active_scaffold_constraints
12
+ associations_by_params = {}
13
+ active_scaffold_config.model.reflect_on_all_associations.each do |association|
14
+ associations_by_params[association.klass.name.foreign_key] = association.name unless association.options[:polymorphic]
15
+ end
16
+ params.each do |key, value|
17
+ active_scaffold_constraints[associations_by_params[key]] = value if associations_by_params.include? key
18
+ end
19
+ end
20
+
21
+ # For each enabled action, adds the constrained columns to the ActionColumns object (if it exists).
22
+ # This lets the ActionColumns object skip constrained columns.
23
+ #
24
+ # If the constraint value is a Hash, then we assume the constraint is a multi-level association constraint (the reverse of a has_many :through) and we do NOT register the constraint column.
25
+ def register_constraints_with_action_columns(association_constrained_fields = [], exclude_actions = [])
26
+ constrained_fields = active_scaffold_constraints.reject{|k, v| v.is_a? Hash}.keys.collect{|k| k.to_sym}
27
+ constrained_fields = constrained_fields | association_constrained_fields
28
+ if self.class.uses_active_scaffold?
29
+ # we actually want to do this whether constrained_fields exist or not, so that we can reset the array when they don't
30
+ active_scaffold_config.actions.each do |action_name|
31
+ next if exclude_actions.include?(action_name)
32
+ action = active_scaffold_config.send(action_name)
33
+ next unless action.respond_to? :columns
34
+ action.columns.constraint_columns = constrained_fields
35
+ end
36
+ end
37
+ end
38
+
39
+ # Returns search conditions based on the current scaffold constraints.
40
+ #
41
+ # Supports constraints based on either a column name (in which case it checks for an association
42
+ # or just uses the search_sql) or a database field name.
43
+ #
44
+ # All of this work is primarily to support nested scaffolds in a manner generally useful for other
45
+ # embedded scaffolds.
46
+ def conditions_from_constraints
47
+ conditions = nil
48
+ active_scaffold_constraints.each do |k, v|
49
+ column = active_scaffold_config.columns[k]
50
+ constraint_condition = if column
51
+ # Assume this is a multi-level association constraint.
52
+ # example:
53
+ # data model: Park -> Den -> Bear
54
+ # constraint: :den => {:park => 5}
55
+ if v.is_a? Hash
56
+ far_association = column.association.klass.reflect_on_association(v.keys.first)
57
+ field = far_association.klass.primary_key
58
+ table = far_association.table_name
59
+
60
+ active_scaffold_includes.concat([{k => v.keys.first}]) # e.g. {:den => :park}
61
+ constraint_condition_for("#{table}.#{field}", v.values.first)
62
+
63
+ # association column constraint
64
+ elsif column.association
65
+ if column.association.macro == :has_and_belongs_to_many
66
+ active_scaffold_habtm_joins.concat column.includes
67
+ else
68
+ active_scaffold_includes.concat column.includes
69
+ end
70
+ condition_from_association_constraint(column.association, v)
71
+
72
+ # regular column constraints
73
+ elsif column.searchable?
74
+ active_scaffold_includes.concat column.includes
75
+ constraint_condition_for(column.search_sql, v)
76
+ end
77
+ # unknown-to-activescaffold-but-real-database-column constraint
78
+ elsif active_scaffold_config.model.column_names.include? k.to_s
79
+ constraint_condition_for(k.to_s, v)
80
+ else
81
+ raise ActiveScaffold::MalformedConstraint, constraint_error(active_scaffold_config.model, k), caller
82
+ end
83
+
84
+ conditions = merge_conditions(conditions, constraint_condition)
85
+ end
86
+
87
+ conditions
88
+ end
89
+
90
+ # We do NOT want to use .search_sql. If anything, search_sql will refer
91
+ # to a human-searchable value on the associated record.
92
+ def condition_from_association_constraint(association, value)
93
+ # when the reverse association is a :belongs_to, the id for the associated object only exists as
94
+ # the primary_key on the other table. so for :has_one and :has_many (when the reverse is :belongs_to),
95
+ # we have to use the other model's primary_key.
96
+ #
97
+ # please see the relevant tests for concrete examples.
98
+ field = if [:has_one, :has_many].include?(association.macro)
99
+ association.klass.primary_key
100
+ elsif [:has_and_belongs_to_many].include?(association.macro)
101
+ association.association_foreign_key
102
+ else
103
+ association.options[:foreign_key] || association.name.to_s.foreign_key
104
+ end
105
+
106
+ table = case association.macro
107
+ when :has_and_belongs_to_many
108
+ association.options[:join_table]
109
+
110
+ when :belongs_to
111
+ active_scaffold_config.model.table_name
112
+
113
+ else
114
+ association.table_name
115
+ end
116
+
117
+ if association.options[:primary_key]
118
+ value = association.klass.find(value).send(association.options[:primary_key])
119
+ end
120
+
121
+ condition = constraint_condition_for("#{table}.#{field}", value)
122
+ if association.options[:polymorphic]
123
+ begin
124
+ parent_scaffold = "#{session_info[:parent_scaffold].to_s.camelize}Controller".constantize
125
+ condition = merge_conditions(
126
+ condition,
127
+ constraint_condition_for("#{table}.#{association.name}_type", parent_scaffold.active_scaffold_config.model_id.to_s)
128
+ )
129
+ rescue ActiveScaffold::ControllerNotFound
130
+ nil
131
+ end
132
+ end
133
+
134
+ condition
135
+ end
136
+
137
+ def constraint_error(klass, column_name)
138
+ "Malformed constraint `#{klass}##{column_name}'. If it's a legitimate column, and you are using a nested scaffold, please specify or double-check the reverse association name."
139
+ end
140
+
141
+ # Applies constraints to the given record.
142
+ #
143
+ # Searches through the known columns for association columns. If the given constraint is an association,
144
+ # it assumes that the constraint value is an id. It then does a association.klass.find with the value
145
+ # and adds the associated object to the record.
146
+ #
147
+ # For some operations ActiveRecord will automatically update the database. That's not always ok.
148
+ # If it *is* ok (e.g. you're in a transaction), then set :allow_autosave to true.
149
+ def apply_constraints_to_record(record, options = {})
150
+ options[:allow_autosave] = false if options[:allow_autosave].nil?
151
+
152
+ active_scaffold_constraints.each do |k, v|
153
+ column = active_scaffold_config.columns[k]
154
+ if column and column.association
155
+ if column.plural_association?
156
+ record.send("#{k}").send(:<<, column.association.klass.find(v))
157
+ elsif column.association.options[:polymorphic]
158
+ record.send("#{k}=", params[:parent_model].constantize.find(v))
159
+ else # regular singular association
160
+ record.send("#{k}=", column.association.klass.find(v))
161
+
162
+ # setting the belongs_to side of a has_one isn't safe. if the has_one was already
163
+ # specified, rails won't automatically clear out the previous associated record.
164
+ #
165
+ # note that we can't take the extra step to correct this unless we're permitted to
166
+ # run operations where activerecord auto-saves the object.
167
+ reverse = column.association.klass.reflect_on_association(column.association.reverse)
168
+ if reverse.macro == :has_one and options[:allow_autosave]
169
+ record.send(k).send("#{column.association.reverse}=", record)
170
+ end
171
+ end
172
+ else
173
+ record.send("#{k}=", v)
174
+ end
175
+ end
176
+ end
177
+
178
+ private
179
+
180
+ def constraint_condition_for(sql, value)
181
+ value.nil? ? "#{sql} IS NULL" : ["#{sql} = ?", value]
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,133 @@
1
+ module ActiveScaffold::DataStructures
2
+ # A set of columns. These structures can be nested for organization.
3
+ class ActionColumns < ActiveScaffold::DataStructures::Set
4
+ include ActiveScaffold::Configurable
5
+
6
+ # this lets us refer back to the action responsible for this link, if it exists.
7
+ # the immediate need here is to get the crud_type so we can dynamically filter columns from the set.
8
+ attr_accessor :action
9
+
10
+ # labels are useful for the Create/Update forms, when we display columns in a grouped fashion and want to name them separately
11
+ attr_writer :label
12
+ def label
13
+ as_(@label) if @label
14
+ end
15
+
16
+ # Whether this column set is collapsed by default in contexts where collapsing is supported
17
+ attr_accessor :collapsed
18
+
19
+ # nests a subgroup in the column set
20
+ def add_subgroup(label, &proc)
21
+ columns = ActiveScaffold::DataStructures::ActionColumns.new
22
+ columns.label = label
23
+ columns.action = self.action
24
+ columns.configure &proc
25
+ self.exclude columns.collect_columns
26
+ self.add columns
27
+ end
28
+
29
+ def include?(item)
30
+ @set.each do |c|
31
+ return true if !c.is_a? Symbol and c.include? item
32
+ return true if c == item.to_sym
33
+ end
34
+ return false
35
+ end
36
+
37
+ def names
38
+ self.collect(&:name)
39
+ end
40
+
41
+ protected
42
+
43
+ def collect_columns
44
+ @set.collect {|col| col.is_a?(ActiveScaffold::DataStructures::ActionColumns) ? col.collect_columns : col}
45
+ end
46
+
47
+ # called during clone or dup. makes the clone/dup deeper.
48
+ def initialize_copy(from)
49
+ @set = from.instance_variable_get('@set').clone
50
+ end
51
+
52
+ # A package of stuff to add after the configuration block. This is an attempt at making a certain level of functionality inaccessible during configuration, to reduce possible breakage from misuse.
53
+ # The bulk of the package is a means of connecting the referential column set (ActionColumns) with the actual column objects (Columns). This lets us iterate over the set and yield real column objects.
54
+ module AfterConfiguration
55
+ # Redefine the each method to yield actual Column objects.
56
+ # It will skip constrained and unauthorized columns.
57
+ #
58
+ # Options:
59
+ # * :flatten - whether to recursively iterate on nested sets. default is false.
60
+ # * :for - the record (or class) being iterated over. used for column-level security. default is the class.
61
+ def each(options = {}, &proc)
62
+ options[:for] ||= @columns.active_record_class
63
+ self.unauthorized_columns = []
64
+ @set.each do |item|
65
+ unless item.is_a? ActiveScaffold::DataStructures::ActionColumns
66
+ item = (@columns[item] || ActiveScaffold::DataStructures::Column.new(item.to_sym, @columns.active_record_class))
67
+ next if self.skip_column?(item, options)
68
+ end
69
+ if item.is_a? ActiveScaffold::DataStructures::ActionColumns and options.has_key?(:flatten) and options[:flatten]
70
+ item.each(options, &proc)
71
+ else
72
+ yield item
73
+ end
74
+ end
75
+ end
76
+
77
+ def collect_visible(options = {}, &proc)
78
+ columns = []
79
+ options[:for] ||= @columns.active_record_class
80
+ self.unauthorized_columns = []
81
+ @set.each do |item|
82
+ unless item.is_a? ActiveScaffold::DataStructures::ActionColumns
83
+ item = (@columns[item] || ActiveScaffold::DataStructures::Column.new(item.to_sym, @columns.active_record_class))
84
+ next if self.skip_column?(item, options)
85
+ end
86
+ if item.is_a? ActiveScaffold::DataStructures::ActionColumns and options.has_key?(:flatten) and options[:flatten]
87
+ columns = columns + item.collect(options, &proc)
88
+ else
89
+ columns << item
90
+ end
91
+ end
92
+ columns
93
+ end
94
+
95
+ def skip_column?(column, options)
96
+ result = false
97
+ # skip if this matches a constrained column
98
+ result = true if constraint_columns.include?(column.name.to_sym)
99
+ # skip if this matches the field_name of a constrained column
100
+ result = true if column.field_name and constraint_columns.include?(column.field_name.to_sym)
101
+ # skip this field if it's not authorized
102
+ unless options[:for].authorized_for?(:action => options[:action], :crud_type => options[:crud_type] || self.action.crud_type, :column => column.name)
103
+ self.unauthorized_columns << column.name.to_sym
104
+ result = true
105
+ end
106
+ return result
107
+ end
108
+
109
+ # registers a set of column objects (recursively, for all nested ActionColumns)
110
+ def set_columns(columns)
111
+ @columns = columns
112
+ # iterate over @set instead of self to avoid dealing with security queries
113
+ @set.each do |item|
114
+ item.set_columns(columns) if item.respond_to? :set_columns
115
+ end
116
+ end
117
+
118
+ attr_writer :constraint_columns
119
+ def constraint_columns
120
+ @constraint_columns ||= []
121
+ end
122
+
123
+ attr_writer :unauthorized_columns
124
+ def unauthorized_columns
125
+ @unauthorized_columns ||= []
126
+ end
127
+
128
+ def length
129
+ ((@set - self.constraint_columns) - self.unauthorized_columns).length
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,171 @@
1
+ module ActiveScaffold::DataStructures
2
+ class ActionLink
3
+ # provides a quick way to set any property of the object from a hash
4
+ def initialize(action, options = {})
5
+ # set defaults
6
+ self.action = action.to_s
7
+ self.label = action
8
+ self.confirm = false
9
+ self.type = :collection
10
+ self.inline = true
11
+ self.method = :get
12
+ self.crud_type = :delete if [:destroy].include?(action.to_sym)
13
+ self.crud_type = :create if [:create, :new].include?(action.to_sym)
14
+ self.crud_type = :update if [:edit, :update].include?(action.to_sym)
15
+ self.crud_type ||= :read
16
+ self.parameters = {}
17
+ self.html_options = {}
18
+ self.column = nil
19
+ self.image = nil
20
+
21
+ # apply quick properties
22
+ options.each_pair do |k, v|
23
+ setter = "#{k}="
24
+ self.send(setter, v) if self.respond_to? setter
25
+ end
26
+ end
27
+
28
+ # the action-path for this link. what page to request? this is required!
29
+ attr_accessor :action
30
+
31
+ # the controller for this action link. if nil, the current controller should be assumed.
32
+ attr_writer :controller
33
+
34
+ def controller
35
+ @controller = @controller.call if @controller.is_a?(Proc)
36
+ @controller
37
+ end
38
+
39
+ def static_controller?
40
+ !(@controller.is_a?(Proc) || (@controller == :polymorph))
41
+ end
42
+
43
+ # a hash of request parameters
44
+ attr_accessor :parameters
45
+
46
+ # the RESTful method
47
+ attr_accessor :method
48
+
49
+ # what string to use to represent this action
50
+ attr_writer :label
51
+ def label
52
+ @label.is_a?(Symbol) ? as_(@label) : @label
53
+ end
54
+
55
+ # image to use {:name => 'arrow.png', :size => '16x16'}
56
+ attr_accessor :image
57
+
58
+ # if the action requires confirmation
59
+ attr_writer :confirm
60
+ def confirm(label = '')
61
+ @confirm.is_a?(String) ? @confirm : as_(@confirm, :label => label)
62
+ end
63
+ def confirm?
64
+ @confirm ? true : false
65
+ end
66
+
67
+ # if the action uses a DHTML based (i.e. 2-phase) confirmation
68
+ attr_writer :dhtml_confirm
69
+ def dhtml_confirm
70
+ @dhtml_confirm
71
+ end
72
+ def dhtml_confirm?
73
+ @dhtml_confirm
74
+ end
75
+
76
+ # what method to call on the controller to see if this action_link should be visible
77
+ # note that this is only the UI part of the security. to prevent URL hax0rz, you also need security on requests (e.g. don't execute update method unless authorized).
78
+ attr_writer :security_method
79
+ def security_method
80
+ @security_method || "#{self.action}_authorized?"
81
+ end
82
+
83
+ def security_method_set?
84
+ !!@security_method
85
+ end
86
+
87
+ attr_accessor :ignore_method
88
+
89
+ # the crud type of the (eventual?) action. different than :method, because this crud action may not be imminent.
90
+ # this is used to determine record-level authorization (e.g. record.authorized_for?(:crud_type => link.crud_type).
91
+ # options are :create, :read, :update, and :delete
92
+ attr_accessor :crud_type
93
+
94
+ # an "inline" link is inserted into the existing page
95
+ # exclusive with popup? and page?
96
+ def inline=(val)
97
+ @inline = (val == true)
98
+ self.popup = self.page = false if @inline
99
+ end
100
+ def inline?; @inline end
101
+
102
+ # a "popup" link displays in a separate (browser?) window. this will eventually take arguments.
103
+ # exclusive with inline? and page?
104
+ def popup=(val)
105
+ @popup = (val == true)
106
+ if @popup
107
+ self.inline = self.page = false
108
+
109
+ # the :method parameter doesn't mix with the :popup parameter
110
+ # when/if we start using DHTML popups, we can bring :method back
111
+ self.method = nil
112
+ end
113
+ end
114
+ def popup?; @popup end
115
+
116
+ # a "page" link displays by reloading the current page
117
+ # exclusive with inline? and popup?
118
+ def page=(val)
119
+ @page = (val == true)
120
+ if @page
121
+ self.inline = self.popup = false
122
+
123
+ # when :method is defined, ActionView adds an onclick to use a form ...
124
+ # so it's best to just empty out :method whenever possible.
125
+ # we only ever need to know @method = :get for things that default to POST.
126
+ # the only things that default to POST are forms and ajax calls.
127
+ # when @page = true, we don't use ajax.
128
+ self.method = nil if method == :get
129
+ end
130
+ end
131
+ def page?; @page end
132
+
133
+ # where the result of this action should insert in the display.
134
+ # for :type => :collection, supported values are:
135
+ # :top
136
+ # :bottom
137
+ # :replace (for updating the entire table)
138
+ # false (no attempt at positioning)
139
+ # for :type => :member, supported values are:
140
+ # :before
141
+ # :replace
142
+ # :after
143
+ # false (no attempt at positioning)
144
+ attr_writer :position
145
+ def position
146
+ return @position unless @position.nil? or @position == true
147
+ return :replace if self.type == :member
148
+ return :top if self.type == :collection
149
+ raise "what should the default position be for #{self.type}?"
150
+ end
151
+
152
+ # what type of link this is. currently supported values are :collection and :member.
153
+ attr_accessor :type
154
+
155
+ # html options for the link
156
+ attr_accessor :html_options
157
+
158
+ # nested action_links are referencing a column
159
+ attr_accessor :column
160
+
161
+ # indicates that this a nested_link
162
+ def nested_link?
163
+ @column || (parameters && parameters[:named_scope])
164
+ end
165
+
166
+ # Internal use: generated eid for this action_link
167
+ attr_accessor :eid
168
+
169
+
170
+ end
171
+ end
@@ -0,0 +1,175 @@
1
+ module ActiveScaffold::DataStructures
2
+ class ActionLinks
3
+ include Enumerable
4
+
5
+ def initialize
6
+ @set = []
7
+ @name = :root
8
+ end
9
+
10
+ # adds an ActionLink, creating one from the arguments if need be
11
+ def add(action, options = {})
12
+ link = if action.is_a?(ActiveScaffold::DataStructures::ActionLink) || action.is_a?(ActiveScaffold::DataStructures::ActionLinks)
13
+ action
14
+ else
15
+ ActiveScaffold::DataStructures::ActionLink.new(action, options)
16
+ end
17
+ # NOTE: this duplicate check should be done by defining the comparison operator for an Action data structure
18
+ existing = find_duplicate(link)
19
+ unless existing
20
+ # That s for backwards compatibility if we are in root of action_links
21
+ # we have to move actionlink into members or collection subgroup
22
+ group = (name == :root ? subgroup(link.type, link.type) : self)
23
+ group.add_to_set(link)
24
+ link
25
+ else
26
+ existing
27
+ end
28
+ end
29
+ alias_method :<<, :add
30
+
31
+ def add_to_set(link)
32
+ @set << link
33
+ end
34
+
35
+ # adds a link to a specific group
36
+ # groups are represented as a string separated by a dot
37
+ # eg member.crud
38
+ def add_to_group(link, group = nil)
39
+ add_to = root
40
+ add_to = group.split('.').inject(root){|group, group_name| group.send(group_name)} if group
41
+ add_to << link unless link.nil?
42
+ end
43
+
44
+ # finds an ActionLink by matching the action
45
+ def [](val)
46
+ links = []
47
+ @set.each do |item|
48
+ if item.is_a?(ActiveScaffold::DataStructures::ActionLinks)
49
+ collected = item[val]
50
+ links << collected unless collected.nil?
51
+ else
52
+ links << item if item.action == val.to_s
53
+ end
54
+ end
55
+ links.first
56
+ end
57
+
58
+ def find_duplicate(link)
59
+ links = []
60
+ @set.each do |item|
61
+ if item.is_a?(ActiveScaffold::DataStructures::ActionLinks)
62
+ collected = item.find_duplicate(link)
63
+ links << collected unless collected.nil?
64
+ else
65
+ links << item if item.action == link.action and item.static_controller? && item.controller == link.controller and item.parameters == link.parameters
66
+ end
67
+ end
68
+ links.first
69
+ end
70
+
71
+ def delete(val)
72
+ self.each({:include_set => true}) do |link, set|
73
+ if link.action == val.to_s
74
+ set.delete_if {|item|item.action == val.to_s}
75
+ end
76
+ end
77
+ end
78
+
79
+ # iterates over the links, possibly by type
80
+ def each(options = {}, &block)
81
+ @set.each {|item|
82
+ if item.is_a?(ActiveScaffold::DataStructures::ActionLinks)
83
+ item.each(options, &block)
84
+ else
85
+ if options[:include_set]
86
+ yield item, @set
87
+ else
88
+ yield item
89
+ end
90
+ end
91
+ }
92
+ end
93
+
94
+ def collect_by_type(type = nil)
95
+ links = []
96
+ subgroup(type).each(type) {|link| links << link}
97
+ links
98
+ end
99
+
100
+ def traverse(controller, options = {}, &block)
101
+ traverse_method = options.delete(:reverse).nil? ? :each : :reverse_each
102
+ options[:level] ||= -1
103
+ options[:level] += 1
104
+ first_action = true
105
+ @set.send(traverse_method) do |link|
106
+ if link.is_a?(ActiveScaffold::DataStructures::ActionLinks)
107
+ unless link.empty?
108
+ yield(link, nil, {:node => :start_traversing, :first_action => first_action, :level => options[:level]})
109
+ link.traverse(controller,options, &block)
110
+ yield(link, nil, {:node => :finished_traversing, :first_action => first_action, :level => options[:level]})
111
+ first_action = false
112
+ end
113
+ elsif controller.nil? || !skip_action_link(controller, link, *(Array(options[:for])))
114
+ authorized = options[:for].nil? ? true : options[:for].authorized_for?(:crud_type => link.crud_type, :action => link.action)
115
+ yield(self, link, {:authorized => authorized, :first_action => first_action, :level => options[:level]})
116
+ first_action = false
117
+ end
118
+ end
119
+ options[:level] -= 1
120
+ end
121
+
122
+ def collect
123
+ @set
124
+ end
125
+
126
+ def empty?
127
+ @set.size == 0
128
+ end
129
+
130
+ def subgroup(name, label = nil)
131
+ group = self if name == self.name
132
+ group ||= @set.find do |item|
133
+ name == item.name if item.is_a?(ActiveScaffold::DataStructures::ActionLinks)
134
+ end
135
+
136
+ if group.nil?
137
+ group = ActiveScaffold::DataStructures::ActionLinks.new
138
+ group.label = label || name
139
+ group.name = name
140
+ add_to_set group
141
+ end
142
+ group
143
+ end
144
+
145
+ attr_writer :label
146
+ def label
147
+ as_(@label) if @label
148
+ end
149
+
150
+ def method_missing(name, *args, &block)
151
+ class_eval %{
152
+ def #{name}
153
+ @#{name} ||= subgroup('#{name}'.to_sym)
154
+ yield @#{name} if block_given?
155
+ @#{name}
156
+ end
157
+ }
158
+ send(name, &block)
159
+ end
160
+
161
+ attr_accessor :name
162
+
163
+ protected
164
+
165
+ def skip_action_link(controller, link, *args)
166
+ (!link.ignore_method.nil? and controller.try(link.ignore_method, *args)) || ((link.security_method_set? or controller.respond_to? link.security_method) and !controller.send(link.security_method, *args))
167
+ end
168
+
169
+ # called during clone or dup. makes the clone/dup deeper.
170
+ def initialize_copy(from)
171
+ @set = []
172
+ from.instance_variable_get('@set').each { |link| @set << link.clone }
173
+ end
174
+ end
175
+ end