actionpack 3.2.22.5 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (265) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +641 -418
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -288
  5. data/lib/abstract_controller.rb +1 -8
  6. data/lib/abstract_controller/asset_paths.rb +2 -2
  7. data/lib/abstract_controller/base.rb +39 -37
  8. data/lib/abstract_controller/callbacks.rb +101 -82
  9. data/lib/abstract_controller/collector.rb +7 -3
  10. data/lib/abstract_controller/helpers.rb +23 -11
  11. data/lib/abstract_controller/layouts.rb +68 -73
  12. data/lib/abstract_controller/logger.rb +1 -2
  13. data/lib/abstract_controller/rendering.rb +22 -13
  14. data/lib/abstract_controller/translation.rb +16 -1
  15. data/lib/abstract_controller/url_for.rb +6 -6
  16. data/lib/abstract_controller/view_paths.rb +1 -1
  17. data/lib/action_controller.rb +15 -6
  18. data/lib/action_controller/base.rb +46 -22
  19. data/lib/action_controller/caching.rb +46 -33
  20. data/lib/action_controller/caching/fragments.rb +23 -53
  21. data/lib/action_controller/deprecated.rb +5 -1
  22. data/lib/action_controller/deprecated/integration_test.rb +3 -0
  23. data/lib/action_controller/log_subscriber.rb +11 -8
  24. data/lib/action_controller/metal.rb +16 -30
  25. data/lib/action_controller/metal/conditional_get.rb +76 -32
  26. data/lib/action_controller/metal/data_streaming.rb +20 -26
  27. data/lib/action_controller/metal/exceptions.rb +19 -6
  28. data/lib/action_controller/metal/flash.rb +24 -9
  29. data/lib/action_controller/metal/force_ssl.rb +32 -9
  30. data/lib/action_controller/metal/head.rb +25 -4
  31. data/lib/action_controller/metal/helpers.rb +6 -9
  32. data/lib/action_controller/metal/hide_actions.rb +1 -2
  33. data/lib/action_controller/metal/http_authentication.rb +105 -87
  34. data/lib/action_controller/metal/implicit_render.rb +1 -1
  35. data/lib/action_controller/metal/instrumentation.rb +2 -1
  36. data/lib/action_controller/metal/live.rb +141 -0
  37. data/lib/action_controller/metal/mime_responds.rb +161 -47
  38. data/lib/action_controller/metal/params_wrapper.rb +112 -74
  39. data/lib/action_controller/metal/rack_delegation.rb +9 -3
  40. data/lib/action_controller/metal/redirecting.rb +15 -20
  41. data/lib/action_controller/metal/renderers.rb +11 -9
  42. data/lib/action_controller/metal/rendering.rb +8 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +112 -19
  44. data/lib/action_controller/metal/responder.rb +20 -19
  45. data/lib/action_controller/metal/streaming.rb +12 -18
  46. data/lib/action_controller/metal/strong_parameters.rb +516 -0
  47. data/lib/action_controller/metal/testing.rb +13 -18
  48. data/lib/action_controller/metal/url_for.rb +27 -25
  49. data/lib/action_controller/model_naming.rb +12 -0
  50. data/lib/action_controller/railtie.rb +33 -17
  51. data/lib/action_controller/railties/helpers.rb +22 -0
  52. data/lib/action_controller/record_identifier.rb +18 -72
  53. data/lib/action_controller/test_case.rb +215 -123
  54. data/lib/action_controller/vendor/html-scanner.rb +4 -19
  55. data/lib/action_dispatch.rb +27 -19
  56. data/lib/action_dispatch/http/cache.rb +63 -11
  57. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  58. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  59. data/lib/action_dispatch/http/headers.rb +27 -19
  60. data/lib/action_dispatch/http/mime_negotiation.rb +25 -2
  61. data/lib/action_dispatch/http/mime_type.rb +145 -113
  62. data/lib/action_dispatch/http/mime_types.rb +1 -1
  63. data/lib/action_dispatch/http/parameter_filter.rb +44 -46
  64. data/lib/action_dispatch/http/parameters.rb +12 -5
  65. data/lib/action_dispatch/http/rack_cache.rb +2 -3
  66. data/lib/action_dispatch/http/request.rb +49 -18
  67. data/lib/action_dispatch/http/response.rb +129 -35
  68. data/lib/action_dispatch/http/upload.rb +60 -17
  69. data/lib/action_dispatch/http/url.rb +53 -31
  70. data/lib/action_dispatch/journey.rb +5 -0
  71. data/lib/action_dispatch/journey/backwards.rb +5 -0
  72. data/lib/action_dispatch/journey/formatter.rb +146 -0
  73. data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
  74. data/lib/action_dispatch/journey/gtg/simulator.rb +44 -0
  75. data/lib/action_dispatch/journey/gtg/transition_table.rb +156 -0
  76. data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
  77. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  78. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  79. data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
  80. data/lib/action_dispatch/journey/nodes/node.rb +124 -0
  81. data/lib/action_dispatch/journey/parser.rb +206 -0
  82. data/lib/action_dispatch/journey/parser.y +47 -0
  83. data/lib/action_dispatch/journey/parser_extras.rb +23 -0
  84. data/lib/action_dispatch/journey/path/pattern.rb +196 -0
  85. data/lib/action_dispatch/journey/route.rb +116 -0
  86. data/lib/action_dispatch/journey/router.rb +164 -0
  87. data/lib/action_dispatch/journey/router/strexp.rb +24 -0
  88. data/lib/action_dispatch/journey/router/utils.rb +54 -0
  89. data/lib/action_dispatch/journey/routes.rb +75 -0
  90. data/lib/action_dispatch/journey/scanner.rb +61 -0
  91. data/lib/action_dispatch/journey/visitors.rb +189 -0
  92. data/lib/action_dispatch/journey/visualizer/fsm.css +34 -0
  93. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  94. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  95. data/lib/action_dispatch/middleware/callbacks.rb +9 -4
  96. data/lib/action_dispatch/middleware/cookies.rb +168 -57
  97. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -17
  98. data/lib/action_dispatch/middleware/exception_wrapper.rb +27 -3
  99. data/lib/action_dispatch/middleware/flash.rb +58 -58
  100. data/lib/action_dispatch/middleware/params_parser.rb +14 -29
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +31 -14
  102. data/lib/action_dispatch/middleware/reloader.rb +6 -6
  103. data/lib/action_dispatch/middleware/remote_ip.rb +145 -39
  104. data/lib/action_dispatch/middleware/request_id.rb +2 -6
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +81 -7
  108. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
  109. data/lib/action_dispatch/middleware/show_exceptions.rb +12 -45
  110. data/lib/action_dispatch/middleware/ssl.rb +70 -0
  111. data/lib/action_dispatch/middleware/stack.rb +6 -1
  112. data/lib/action_dispatch/middleware/static.rb +5 -24
  113. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +14 -11
  114. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +25 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +3 -3
  116. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +15 -9
  117. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -5
  118. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +7 -2
  119. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +30 -15
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +39 -13
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +6 -2
  122. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  123. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +144 -0
  124. data/lib/action_dispatch/railtie.rb +16 -6
  125. data/lib/action_dispatch/request/session.rb +181 -0
  126. data/lib/action_dispatch/routing.rb +41 -40
  127. data/lib/action_dispatch/routing/inspector.rb +240 -0
  128. data/lib/action_dispatch/routing/mapper.rb +501 -273
  129. data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -20
  130. data/lib/action_dispatch/routing/redirection.rb +46 -29
  131. data/lib/action_dispatch/routing/route_set.rb +203 -164
  132. data/lib/action_dispatch/routing/routes_proxy.rb +2 -0
  133. data/lib/action_dispatch/routing/url_for.rb +48 -33
  134. data/lib/action_dispatch/testing/assertions/dom.rb +3 -13
  135. data/lib/action_dispatch/testing/assertions/response.rb +32 -40
  136. data/lib/action_dispatch/testing/assertions/routing.rb +40 -39
  137. data/lib/action_dispatch/testing/assertions/selector.rb +15 -20
  138. data/lib/action_dispatch/testing/assertions/tag.rb +20 -23
  139. data/lib/action_dispatch/testing/integration.rb +41 -22
  140. data/lib/action_dispatch/testing/test_process.rb +9 -6
  141. data/lib/action_dispatch/testing/test_request.rb +7 -3
  142. data/lib/action_pack.rb +1 -1
  143. data/lib/action_pack/version.rb +4 -4
  144. data/lib/action_view.rb +17 -8
  145. data/lib/action_view/base.rb +15 -34
  146. data/lib/action_view/buffers.rb +1 -1
  147. data/lib/action_view/context.rb +4 -4
  148. data/lib/action_view/dependency_tracker.rb +91 -0
  149. data/lib/action_view/digestor.rb +85 -0
  150. data/lib/action_view/flows.rb +1 -4
  151. data/lib/action_view/helpers.rb +2 -4
  152. data/lib/action_view/helpers/active_model_helper.rb +3 -4
  153. data/lib/action_view/helpers/asset_tag_helper.rb +211 -353
  154. data/lib/action_view/helpers/asset_url_helper.rb +354 -0
  155. data/lib/action_view/helpers/atom_feed_helper.rb +13 -10
  156. data/lib/action_view/helpers/cache_helper.rb +150 -18
  157. data/lib/action_view/helpers/capture_helper.rb +42 -29
  158. data/lib/action_view/helpers/csrf_helper.rb +0 -2
  159. data/lib/action_view/helpers/date_helper.rb +268 -247
  160. data/lib/action_view/helpers/debug_helper.rb +10 -11
  161. data/lib/action_view/helpers/form_helper.rb +904 -547
  162. data/lib/action_view/helpers/form_options_helper.rb +341 -166
  163. data/lib/action_view/helpers/form_tag_helper.rb +188 -88
  164. data/lib/action_view/helpers/javascript_helper.rb +23 -16
  165. data/lib/action_view/helpers/number_helper.rb +148 -354
  166. data/lib/action_view/helpers/output_safety_helper.rb +3 -3
  167. data/lib/action_view/helpers/record_tag_helper.rb +17 -22
  168. data/lib/action_view/helpers/rendering_helper.rb +2 -4
  169. data/lib/action_view/helpers/sanitize_helper.rb +3 -6
  170. data/lib/action_view/helpers/tag_helper.rb +43 -37
  171. data/lib/action_view/helpers/tags.rb +39 -0
  172. data/lib/action_view/helpers/tags/base.rb +148 -0
  173. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  174. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  175. data/lib/action_view/helpers/tags/collection_check_boxes.rb +43 -0
  176. data/lib/action_view/helpers/tags/collection_helpers.rb +83 -0
  177. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  178. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  179. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  180. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  181. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  182. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  183. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  184. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  185. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  186. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  187. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  188. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  189. data/lib/action_view/helpers/tags/label.rb +65 -0
  190. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  191. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  192. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  193. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  194. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  195. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  196. data/lib/action_view/helpers/tags/select.rb +41 -0
  197. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  198. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  199. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  200. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  201. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  202. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  203. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  204. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  205. data/lib/action_view/helpers/text_helper.rb +126 -113
  206. data/lib/action_view/helpers/translation_helper.rb +32 -16
  207. data/lib/action_view/helpers/url_helper.rb +200 -271
  208. data/lib/action_view/locale/en.yml +1 -105
  209. data/lib/action_view/log_subscriber.rb +6 -4
  210. data/lib/action_view/lookup_context.rb +15 -39
  211. data/lib/action_view/model_naming.rb +12 -0
  212. data/lib/action_view/path_set.rb +9 -39
  213. data/lib/action_view/railtie.rb +6 -22
  214. data/lib/action_view/record_identifier.rb +84 -0
  215. data/lib/action_view/renderer/abstract_renderer.rb +10 -19
  216. data/lib/action_view/renderer/partial_renderer.rb +144 -81
  217. data/lib/action_view/renderer/renderer.rb +2 -19
  218. data/lib/action_view/renderer/streaming_template_renderer.rb +2 -5
  219. data/lib/action_view/renderer/template_renderer.rb +14 -13
  220. data/lib/action_view/routing_url_for.rb +107 -0
  221. data/lib/action_view/template.rb +22 -21
  222. data/lib/action_view/template/error.rb +22 -12
  223. data/lib/action_view/template/handlers.rb +12 -9
  224. data/lib/action_view/template/handlers/builder.rb +1 -1
  225. data/lib/action_view/template/handlers/erb.rb +11 -16
  226. data/lib/action_view/template/handlers/raw.rb +11 -0
  227. data/lib/action_view/template/resolver.rb +111 -83
  228. data/lib/action_view/template/text.rb +12 -8
  229. data/lib/action_view/template/types.rb +57 -0
  230. data/lib/action_view/test_case.rb +66 -43
  231. data/lib/action_view/testing/resolvers.rb +3 -2
  232. data/lib/action_view/vendor/html-scanner.rb +20 -0
  233. data/lib/{action_controller → action_view}/vendor/html-scanner/html/document.rb +0 -0
  234. data/lib/{action_controller → action_view}/vendor/html-scanner/html/node.rb +12 -12
  235. data/lib/{action_controller → action_view}/vendor/html-scanner/html/sanitizer.rb +18 -7
  236. data/lib/{action_controller → action_view}/vendor/html-scanner/html/selector.rb +1 -1
  237. data/lib/{action_controller → action_view}/vendor/html-scanner/html/tokenizer.rb +1 -1
  238. data/lib/{action_controller → action_view}/vendor/html-scanner/html/version.rb +0 -0
  239. metadata +135 -125
  240. data/lib/action_controller/caching/actions.rb +0 -185
  241. data/lib/action_controller/caching/pages.rb +0 -187
  242. data/lib/action_controller/caching/sweeping.rb +0 -97
  243. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  244. data/lib/action_controller/metal/compatibility.rb +0 -65
  245. data/lib/action_controller/metal/session_management.rb +0 -14
  246. data/lib/action_controller/railties/paths.rb +0 -25
  247. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  248. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  249. data/lib/action_dispatch/middleware/head.rb +0 -18
  250. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  251. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  252. data/lib/action_view/asset_paths.rb +0 -142
  253. data/lib/action_view/helpers/asset_paths.rb +0 -7
  254. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  255. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  256. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  257. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  258. data/lib/sprockets/assets.rake +0 -99
  259. data/lib/sprockets/bootstrap.rb +0 -37
  260. data/lib/sprockets/compressors.rb +0 -83
  261. data/lib/sprockets/helpers.rb +0 -6
  262. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  263. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  264. data/lib/sprockets/railtie.rb +0 -62
  265. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,21 +1,20 @@
1
1
  require 'cgi'
2
2
  require 'erb'
3
3
  require 'action_view/helpers/form_helper'
4
- require 'active_support/core_ext/object/blank'
5
4
  require 'active_support/core_ext/string/output_safety'
5
+ require 'active_support/core_ext/array/extract_options'
6
+ require 'active_support/core_ext/array/wrap'
6
7
 
7
8
  module ActionView
8
9
  # = Action View Form Option Helpers
9
10
  module Helpers
10
11
  # Provides a number of methods for turning different kinds of containers into a set of option tags.
11
- # == Options
12
+ #
12
13
  # The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash:
13
14
  #
14
15
  # * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
15
16
  #
16
- # For example,
17
- #
18
- # select("post", "category", Post::CATEGORIES, {:include_blank => true})
17
+ # select("post", "category", Post::CATEGORIES, {include_blank: true})
19
18
  #
20
19
  # could become:
21
20
  #
@@ -25,11 +24,11 @@ module ActionView
25
24
  # <option>poem</option>
26
25
  # </select>
27
26
  #
28
- # Another common case is a select tag for an <tt>belongs_to</tt>-associated object.
27
+ # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
29
28
  #
30
29
  # Example with @post.person_id => 2:
31
30
  #
32
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'})
31
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
33
32
  #
34
33
  # could become:
35
34
  #
@@ -42,9 +41,7 @@ module ActionView
42
41
  #
43
42
  # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
44
43
  #
45
- # Example:
46
- #
47
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'})
44
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
48
45
  #
49
46
  # could become:
50
47
  #
@@ -58,9 +55,7 @@ module ActionView
58
55
  # Like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
59
56
  # option to be in the +html_options+ parameter.
60
57
  #
61
- # Example:
62
- #
63
- # select("album[]", "genre", %w[rap rock country], {}, { :index => nil })
58
+ # select("album[]", "genre", %w[rap rock country], {}, { index: nil })
64
59
  #
65
60
  # becomes:
66
61
  #
@@ -72,9 +67,7 @@ module ActionView
72
67
  #
73
68
  # * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
74
69
  #
75
- # Example:
76
- #
77
- # select("post", "category", Post::CATEGORIES, {:disabled => 'restricted'})
70
+ # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
78
71
  #
79
72
  # could become:
80
73
  #
@@ -87,9 +80,7 @@ module ActionView
87
80
  #
88
81
  # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
89
82
  #
90
- # Example:
91
- #
92
- # collection_select(:post, :category_id, Category.all, :id, :name, {:disabled => lambda{|category| category.archived? }})
83
+ # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
93
84
  #
94
85
  # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
95
86
  # <select name="post[category_id]">
@@ -111,7 +102,7 @@ module ActionView
111
102
  # * A nested collection: see grouped_options_for_select
112
103
  #
113
104
  # Example with @post.person_id => 1:
114
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
105
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true })
115
106
  #
116
107
  # could become:
117
108
  #
@@ -128,8 +119,8 @@ module ActionView
128
119
  # This allows the user to submit a form page more than once with the expected results of creating multiple records.
129
120
  # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
130
121
  #
131
- # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
132
- # or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
122
+ # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>selected: value</tt> to use a different selection
123
+ # or <tt>selected: nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
133
124
  # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
134
125
  #
135
126
  # ==== Gotcha
@@ -140,7 +131,7 @@ module ActionView
140
131
  # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
141
132
  # any mass-assignment idiom like
142
133
  #
143
- # @user.update_attributes(params[:user])
134
+ # @user.update(params[:user])
144
135
  #
145
136
  # wouldn't update roles.
146
137
  #
@@ -153,8 +144,11 @@ module ActionView
153
144
  # form, and parameters extraction gets the last occurrence of any repeated
154
145
  # key in the query string, that works for ordinary forms.
155
146
  #
147
+ # In case if you don't want the helper to generate this hidden field you can specify
148
+ # <tt>include_hidden: false</tt> option.
149
+ #
156
150
  def select(object, method, choices, options = {}, html_options = {})
157
- InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
151
+ Tags::Select.new(object, method, self, choices, options, html_options).render
158
152
  end
159
153
 
160
154
  # Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -164,12 +158,16 @@ module ActionView
164
158
  #
165
159
  # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
166
160
  # of +collection+. The return values are used as the +value+ attribute and contents of each
167
- # <tt><option></tt> tag, respectively.
161
+ # <tt><option></tt> tag, respectively. They can also be any object that responds to +call+, such
162
+ # as a +proc+, that will be called for each member of the +collection+ to
163
+ # retrieve the value/text.
168
164
  #
169
165
  # Example object structure for use with this method:
166
+ #
170
167
  # class Post < ActiveRecord::Base
171
168
  # belongs_to :author
172
169
  # end
170
+ #
173
171
  # class Author < ActiveRecord::Base
174
172
  # has_many :posts
175
173
  # def name_with_initial
@@ -178,7 +176,8 @@ module ActionView
178
176
  # end
179
177
  #
180
178
  # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
181
- # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true)
179
+ #
180
+ # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true)
182
181
  #
183
182
  # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
184
183
  # <select name="post[author_id]">
@@ -188,10 +187,9 @@ module ActionView
188
187
  # <option value="3">M. Clark</option>
189
188
  # </select>
190
189
  def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
191
- InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
190
+ Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render
192
191
  end
193
192
 
194
-
195
193
  # Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
196
194
  # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
197
195
  # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
@@ -211,23 +209,28 @@ module ActionView
211
209
  # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
212
210
  #
213
211
  # Example object structure for use with this method:
212
+ #
214
213
  # class Continent < ActiveRecord::Base
215
214
  # has_many :countries
216
215
  # # attribs: id, name
217
216
  # end
217
+ #
218
218
  # class Country < ActiveRecord::Base
219
219
  # belongs_to :continent
220
220
  # # attribs: id, name, continent_id
221
221
  # end
222
+ #
222
223
  # class City < ActiveRecord::Base
223
224
  # belongs_to :country
224
225
  # # attribs: id, name, country_id
225
226
  # end
226
227
  #
227
228
  # Sample usage:
229
+ #
228
230
  # grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
229
231
  #
230
232
  # Possible output:
233
+ #
231
234
  # <select name="city[country_id]">
232
235
  # <optgroup label="Africa">
233
236
  # <option value="1">South Africa</option>
@@ -240,7 +243,7 @@ module ActionView
240
243
  # </select>
241
244
  #
242
245
  def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
243
- InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
246
+ Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
244
247
  end
245
248
 
246
249
  # Return select and option tags for the given object and method, using
@@ -261,20 +264,19 @@ module ActionView
261
264
  # Finally, this method supports a <tt>:default</tt> option, which selects
262
265
  # a default ActiveSupport::TimeZone if the object's time zone is +nil+.
263
266
  #
264
- # Examples:
265
- # time_zone_select( "user", "time_zone", nil, :include_blank => true)
267
+ # time_zone_select( "user", "time_zone", nil, include_blank: true)
266
268
  #
267
- # time_zone_select( "user", "time_zone", nil, :default => "Pacific Time (US & Canada)" )
269
+ # time_zone_select( "user", "time_zone", nil, default: "Pacific Time (US & Canada)" )
268
270
  #
269
- # time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)")
271
+ # time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, default: "Pacific Time (US & Canada)")
270
272
  #
271
273
  # time_zone_select( "user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
272
274
  #
273
275
  # time_zone_select( "user", 'time_zone', /Australia/)
274
276
  #
275
- # time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, :model => ActiveSupport::TimeZone)
277
+ # time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, model: ActiveSupport::TimeZone)
276
278
  def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
277
- InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
279
+ Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
278
280
  end
279
281
 
280
282
  # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
@@ -283,67 +285,83 @@ module ActionView
283
285
  # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
284
286
  # may also be an array of values to be selected when using a multiple select.
285
287
  #
286
- # Examples (call, result):
287
288
  # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
288
- # <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
289
+ # # => <option value="$">Dollar</option>
290
+ # # => <option value="DKK">Kroner</option>
289
291
  #
290
292
  # options_for_select([ "VISA", "MasterCard" ], "MasterCard")
291
- # <option>VISA</option>\n<option selected="selected">MasterCard</option>
293
+ # # => <option>VISA</option>
294
+ # # => <option selected="selected">MasterCard</option>
292
295
  #
293
296
  # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
294
- # <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
297
+ # # => <option value="$20">Basic</option>
298
+ # # => <option value="$40" selected="selected">Plus</option>
295
299
  #
296
300
  # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
297
- # <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
301
+ # # => <option selected="selected">VISA</option>
302
+ # # => <option>MasterCard</option>
303
+ # # => <option selected="selected">Discover</option>
298
304
  #
299
305
  # You can optionally provide html attributes as the last element of the array.
300
306
  #
301
- # Examples:
302
- # options_for_select([ "Denmark", ["USA", {:class => 'bold'}], "Sweden" ], ["USA", "Sweden"])
303
- # <option value="Denmark">Denmark</option>\n<option value="USA" class="bold" selected="selected">USA</option>\n<option value="Sweden" selected="selected">Sweden</option>
307
+ # options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"])
308
+ # # => <option value="Denmark">Denmark</option>
309
+ # # => <option value="USA" class="bold" selected="selected">USA</option>
310
+ # # => <option value="Sweden" selected="selected">Sweden</option>
304
311
  #
305
- # options_for_select([["Dollar", "$", {:class => "bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]])
306
- # <option value="$" class="bold">Dollar</option>\n<option value="DKK" onclick="alert('HI');">Kroner</option>
312
+ # options_for_select([["Dollar", "$", {class: "bold"}], ["Kroner", "DKK", {onclick: "alert('HI');"}]])
313
+ # # => <option value="$" class="bold">Dollar</option>
314
+ # # => <option value="DKK" onclick="alert('HI');">Kroner</option>
307
315
  #
308
316
  # If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
309
317
  # or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
310
318
  #
311
- # Examples:
312
- # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => "Super Platinum")
313
- # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
319
+ # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: "Super Platinum")
320
+ # # => <option value="Free">Free</option>
321
+ # # => <option value="Basic">Basic</option>
322
+ # # => <option value="Advanced">Advanced</option>
323
+ # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
314
324
  #
315
- # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => ["Advanced", "Super Platinum"])
316
- # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced" disabled="disabled">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
325
+ # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: ["Advanced", "Super Platinum"])
326
+ # # => <option value="Free">Free</option>
327
+ # # => <option value="Basic">Basic</option>
328
+ # # => <option value="Advanced" disabled="disabled">Advanced</option>
329
+ # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
317
330
  #
318
- # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :selected => "Free", :disabled => "Super Platinum")
319
- # <option value="Free" selected="selected">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
331
+ # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], selected: "Free", disabled: "Super Platinum")
332
+ # # => <option value="Free" selected="selected">Free</option>
333
+ # # => <option value="Basic">Basic</option>
334
+ # # => <option value="Advanced">Advanced</option>
335
+ # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
320
336
  #
321
337
  # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
322
338
  def options_for_select(container, selected = nil)
323
339
  return container if String === container
324
340
 
325
- selected, disabled = extract_selected_and_disabled(selected).map do | r |
326
- Array.wrap(r).map { |item| item.to_s }
341
+ selected, disabled = extract_selected_and_disabled(selected).map do |r|
342
+ Array(r).map { |item| item.to_s }
327
343
  end
328
344
 
329
345
  container.map do |element|
330
346
  html_attributes = option_html_attributes(element)
331
347
  text, value = option_text_and_value(element).map { |item| item.to_s }
332
- selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
333
- disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
334
- %(<option value="#{ERB::Util.html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{ERB::Util.html_escape(text)}</option>)
335
- end.join("\n").html_safe
336
348
 
349
+ html_attributes[:selected] = 'selected' if option_value_selected?(value, selected)
350
+ html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled)
351
+ html_attributes[:value] = value
352
+
353
+ content_tag_string(:option, text, html_attributes)
354
+ end.join("\n").html_safe
337
355
  end
338
356
 
339
357
  # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
340
358
  # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
341
- # Example:
359
+ #
342
360
  # options_from_collection_for_select(@people, 'id', 'name')
343
- # This will output the same HTML as if you did this:
344
- # <option value="#{person.id}">#{person.name}</option>
361
+ # # => <option value="#{person.id}">#{person.name}</option>
345
362
  #
346
363
  # This is more often than not used inside a #select_tag like this example:
364
+ #
347
365
  # select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
348
366
  #
349
367
  # If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
@@ -362,12 +380,13 @@ module ActionView
362
380
  # should produce the desired results.
363
381
  def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
364
382
  options = collection.map do |element|
365
- [element.send(text_method), element.send(value_method)]
383
+ [value_for_collection(element, text_method), value_for_collection(element, value_method)]
366
384
  end
367
385
  selected, disabled = extract_selected_and_disabled(selected)
368
- select_deselect = {}
369
- select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected)
370
- select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled)
386
+ select_deselect = {
387
+ :selected => extract_values_from_collection(collection, value_method, selected),
388
+ :disabled => extract_values_from_collection(collection, value_method, disabled)
389
+ }
371
390
 
372
391
  options_for_select(options, select_deselect)
373
392
  end
@@ -391,10 +410,12 @@ module ActionView
391
410
  # to be specified.
392
411
  #
393
412
  # Example object structure for use with this method:
413
+ #
394
414
  # class Continent < ActiveRecord::Base
395
415
  # has_many :countries
396
416
  # # attribs: id, name
397
417
  # end
418
+ #
398
419
  # class Country < ActiveRecord::Base
399
420
  # belongs_to :continent
400
421
  # # attribs: id, name, continent_id
@@ -420,10 +441,10 @@ module ActionView
420
441
  # wrap the output in an appropriate <tt><select></tt> tag.
421
442
  def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
422
443
  collection.map do |group|
423
- group_label_string = eval("group.#{group_label_method}")
424
- "<optgroup label=\"#{ERB::Util.html_escape(group_label_string)}\">" +
425
- options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key) +
426
- '</optgroup>'
444
+ option_tags = options_from_collection_for_select(
445
+ group.send(group_method), option_key_method, option_value_method, selected_key)
446
+
447
+ content_tag(:optgroup, option_tags, :label => group.send(group_label_method))
427
448
  end.join.html_safe
428
449
  end
429
450
 
@@ -438,10 +459,12 @@ module ActionView
438
459
  # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
439
460
  # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
440
461
  # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
441
- # * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this
462
+ #
463
+ # Options:
464
+ # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
442
465
  # prepends an option with a generic prompt - "Please select" - or the given prompt string.
466
+ # * <tt>:divider</tt> - the divider for the options groups.
443
467
  #
444
- # Sample usage (Array):
445
468
  # grouped_options = [
446
469
  # ['North America',
447
470
  # [['United States','US'],'Canada']],
@@ -450,37 +473,70 @@ module ActionView
450
473
  # ]
451
474
  # grouped_options_for_select(grouped_options)
452
475
  #
453
- # Sample usage (Hash):
454
476
  # grouped_options = {
455
- # 'North America' => [['United States','US'], 'Canada'],
456
- # 'Europe' => ['Denmark','Germany','France']
477
+ # 'North America' => [['United States','US'], 'Canada'],
478
+ # 'Europe' => ['Denmark','Germany','France']
457
479
  # }
458
480
  # grouped_options_for_select(grouped_options)
459
481
  #
460
482
  # Possible output:
483
+ # <optgroup label="North America">
484
+ # <option value="US">United States</option>
485
+ # <option value="Canada">Canada</option>
486
+ # </optgroup>
461
487
  # <optgroup label="Europe">
462
488
  # <option value="Denmark">Denmark</option>
463
489
  # <option value="Germany">Germany</option>
464
490
  # <option value="France">France</option>
465
491
  # </optgroup>
466
- # <optgroup label="North America">
492
+ #
493
+ # grouped_options = [
494
+ # [['United States','US'], 'Canada'],
495
+ # ['Denmark','Germany','France']
496
+ # ]
497
+ # grouped_options_for_select(grouped_options, nil, divider: '---------')
498
+ #
499
+ # Possible output:
500
+ # <optgroup label="---------">
467
501
  # <option value="US">United States</option>
468
502
  # <option value="Canada">Canada</option>
469
503
  # </optgroup>
504
+ # <optgroup label="---------">
505
+ # <option value="Denmark">Denmark</option>
506
+ # <option value="Germany">Germany</option>
507
+ # <option value="France">France</option>
508
+ # </optgroup>
470
509
  #
471
510
  # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
472
511
  # wrap the output in an appropriate <tt><select></tt> tag.
473
- def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
474
- body = ''
475
- body << content_tag(:option, prompt, { :value => "" }, true) if prompt
512
+ def grouped_options_for_select(grouped_options, selected_key = nil, options = {})
513
+ if options.is_a?(Hash)
514
+ prompt = options[:prompt]
515
+ divider = options[:divider]
516
+ else
517
+ prompt = options
518
+ options = {}
519
+ message = "Passing the prompt to grouped_options_for_select as an argument is deprecated. " \
520
+ "Please use an options hash like `{ prompt: #{prompt.inspect} }`."
521
+ ActiveSupport::Deprecation.warn message
522
+ end
476
523
 
477
- grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
524
+ body = "".html_safe
478
525
 
479
- grouped_options.each do |group|
480
- body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0])
526
+ if prompt
527
+ body.safe_concat content_tag(:option, prompt_text(prompt), :value => "")
481
528
  end
482
529
 
483
- body.html_safe
530
+ grouped_options.each do |container|
531
+ if divider
532
+ label = divider
533
+ else
534
+ label, container = container
535
+ end
536
+ body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)
537
+ end
538
+
539
+ body
484
540
  end
485
541
 
486
542
  # Returns a string of option tags for pretty much any time zone in the
@@ -502,42 +558,164 @@ module ActionView
502
558
  # NOTE: Only the option tags are returned, you have to wrap this call in
503
559
  # a regular HTML select tag.
504
560
  def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
505
- zone_options = ""
561
+ zone_options = "".html_safe
506
562
 
507
563
  zones = model.all
508
564
  convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
509
565
 
510
566
  if priority_zones
511
567
  if priority_zones.is_a?(Regexp)
512
- priority_zones = model.all.find_all {|z| z =~ priority_zones}
568
+ priority_zones = zones.grep(priority_zones)
513
569
  end
514
- zone_options += options_for_select(convert_zones[priority_zones], selected)
515
- zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
516
570
 
517
- zones = zones.reject { |z| priority_zones.include?( z ) }
571
+ zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
572
+ zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled')
573
+ zone_options.safe_concat "\n"
574
+
575
+ zones = zones - priority_zones
518
576
  end
519
577
 
520
- zone_options += options_for_select(convert_zones[zones], selected)
521
- zone_options.html_safe
578
+ zone_options.safe_concat options_for_select(convert_zones[zones], selected)
579
+ end
580
+
581
+ # Returns radio button tags for the collection of existing return values
582
+ # of +method+ for +object+'s class. The value returned from calling
583
+ # +method+ on the instance +object+ will be selected. If calling +method+
584
+ # returns +nil+, no selection is made.
585
+ #
586
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
587
+ # methods to be called on each member of +collection+. The return values
588
+ # are used as the +value+ attribute and contents of each radio button tag,
589
+ # respectively. They can also be any object that responds to +call+, such
590
+ # as a +proc+, that will be called for each member of the +collection+ to
591
+ # retrieve the value/text.
592
+ #
593
+ # Example object structure for use with this method:
594
+ # class Post < ActiveRecord::Base
595
+ # belongs_to :author
596
+ # end
597
+ # class Author < ActiveRecord::Base
598
+ # has_many :posts
599
+ # def name_with_initial
600
+ # "#{first_name.first}. #{last_name}"
601
+ # end
602
+ # end
603
+ #
604
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
605
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
606
+ #
607
+ # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
608
+ # <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
609
+ # <label for="post_author_id_1">D. Heinemeier Hansson</label>
610
+ # <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
611
+ # <label for="post_author_id_2">D. Thomas</label>
612
+ # <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
613
+ # <label for="post_author_id_3">M. Clark</label>
614
+ #
615
+ # It is also possible to customize the way the elements will be shown by
616
+ # giving a block to the method:
617
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
618
+ # b.label { b.radio_button }
619
+ # end
620
+ #
621
+ # The argument passed to the block is a special kind of builder for this
622
+ # collection, which has the ability to generate the label and radio button
623
+ # for the current item in the collection, with proper text and value.
624
+ # Using it, you can change the label and radio button display order or
625
+ # even use the label as wrapper, as in the example above.
626
+ #
627
+ # The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept
628
+ # extra html options:
629
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
630
+ # b.label(class: "radio_button") { b.radio_button(class: "radio_button") }
631
+ # end
632
+ #
633
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
634
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
635
+ # respectively. You can use them like this:
636
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
637
+ # b.label(:"data-value" => b.value) { b.radio_button + b.text }
638
+ # end
639
+ def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
640
+ Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
641
+ end
642
+
643
+ # Returns check box tags for the collection of existing return values of
644
+ # +method+ for +object+'s class. The value returned from calling +method+
645
+ # on the instance +object+ will be selected. If calling +method+ returns
646
+ # +nil+, no selection is made.
647
+ #
648
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
649
+ # methods to be called on each member of +collection+. The return values
650
+ # are used as the +value+ attribute and contents of each check box tag,
651
+ # respectively. They can also be any object that responds to +call+, such
652
+ # as a +proc+, that will be called for each member of the +collection+ to
653
+ # retrieve the value/text.
654
+ #
655
+ # Example object structure for use with this method:
656
+ # class Post < ActiveRecord::Base
657
+ # has_and_belongs_to_many :author
658
+ # end
659
+ # class Author < ActiveRecord::Base
660
+ # has_and_belongs_to_many :posts
661
+ # def name_with_initial
662
+ # "#{first_name.first}. #{last_name}"
663
+ # end
664
+ # end
665
+ #
666
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
667
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
668
+ #
669
+ # If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return:
670
+ # <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
671
+ # <label for="post_author_ids_1">D. Heinemeier Hansson</label>
672
+ # <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
673
+ # <label for="post_author_ids_2">D. Thomas</label>
674
+ # <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
675
+ # <label for="post_author_ids_3">M. Clark</label>
676
+ # <input name="post[author_ids][]" type="hidden" value="" />
677
+ #
678
+ # It is also possible to customize the way the elements will be shown by
679
+ # giving a block to the method:
680
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
681
+ # b.label { b.check_box }
682
+ # end
683
+ #
684
+ # The argument passed to the block is a special kind of builder for this
685
+ # collection, which has the ability to generate the label and check box
686
+ # for the current item in the collection, with proper text and value.
687
+ # Using it, you can change the label and check box display order or even
688
+ # use the label as wrapper, as in the example above.
689
+ #
690
+ # The builder methods <tt>label</tt> and <tt>check_box</tt> also accept
691
+ # extra html options:
692
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
693
+ # b.label(class: "check_box") { b.check_box(class: "check_box") }
694
+ # end
695
+ #
696
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
697
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
698
+ # respectively. You can use them like this:
699
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
700
+ # b.label(:"data-value" => b.value) { b.check_box + b.text }
701
+ # end
702
+ def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
703
+ Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
522
704
  end
523
705
 
524
706
  private
525
707
  def option_html_attributes(element)
526
- return "" unless Array === element
527
- html_attributes = []
528
- element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
529
- html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
708
+ if Array === element
709
+ element.select { |e| Hash === e }.reduce({}, :merge!)
710
+ else
711
+ {}
530
712
  end
531
- html_attributes.join
532
713
  end
533
714
 
534
715
  def option_text_and_value(option)
535
716
  # Options are [text, value] pairs or strings used for both.
536
- case
537
- when Array === option
538
- option = option.reject { |e| Hash === e }
539
- [option.first, option.last]
540
- when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
717
+ if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
718
+ option = option.reject { |e| Hash === e } if Array === option
541
719
  [option.first, option.last]
542
720
  else
543
721
  [option, option]
@@ -545,20 +723,17 @@ module ActionView
545
723
  end
546
724
 
547
725
  def option_value_selected?(value, selected)
548
- if selected.respond_to?(:include?) && !selected.is_a?(String)
549
- selected.include? value
550
- else
551
- value == selected
552
- end
726
+ Array(selected).include? value
553
727
  end
554
728
 
555
729
  def extract_selected_and_disabled(selected)
556
730
  if selected.is_a?(Proc)
557
- [ selected, nil ]
731
+ [selected, nil]
558
732
  else
559
733
  selected = Array.wrap(selected)
560
734
  options = selected.extract_options!.symbolize_keys
561
- [ options.include?(:selected) ? options[:selected] : selected, options[:disabled] ]
735
+ selected_items = options.fetch(:selected, selected)
736
+ [selected_items, options[:disabled]]
562
737
  end
563
738
  end
564
739
 
@@ -571,88 +746,88 @@ module ActionView
571
746
  selected
572
747
  end
573
748
  end
574
- end
575
-
576
- class InstanceTag #:nodoc:
577
- include FormOptionsHelper
578
-
579
- def to_select_tag(choices, options, html_options)
580
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
581
- choices = choices.to_a if choices.is_a?(Range)
582
-
583
- # Grouped choices look like this:
584
- #
585
- # [nil, []]
586
- # { nil => [] }
587
- #
588
- if !choices.empty? && choices.first.respond_to?(:last) && Array === choices.first.last
589
- option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
590
- else
591
- option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
592
- end
593
-
594
- select_content_tag(option_tags, options, html_options)
595
- end
596
-
597
- def to_collection_select_tag(collection, value_method, text_method, options, html_options)
598
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
599
- select_content_tag(
600
- options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
601
- )
602
- end
603
-
604
- def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
605
- select_content_tag(
606
- option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
607
- )
608
- end
609
-
610
- def to_time_zone_select_tag(priority_zones, options, html_options)
611
- select_content_tag(
612
- time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
613
- )
614
- end
615
749
 
616
- private
617
- def add_options(option_tags, options, value = nil)
618
- if options[:include_blank]
619
- option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
620
- end
621
- if value.blank? && options[:prompt]
622
- prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
623
- option_tags = content_tag_string('option', prompt, :value => '') + "\n" + option_tags
624
- end
625
- option_tags
750
+ def value_for_collection(item, value)
751
+ value.respond_to?(:call) ? value.call(item) : item.send(value)
626
752
  end
627
753
 
628
- def select_content_tag(option_tags, options, html_options)
629
- html_options = html_options.stringify_keys
630
- add_default_name_and_id(html_options)
631
- select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
632
- if html_options["multiple"]
633
- tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
634
- else
635
- select
636
- end
754
+ def prompt_text(prompt)
755
+ prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
637
756
  end
638
757
  end
639
758
 
640
759
  class FormBuilder
760
+ # Wraps ActionView::Helpers::FormOptionsHelper#select for form builders:
761
+ #
762
+ # <%= form_for @post do |f| %>
763
+ # <%= f.select :person_id, Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true }) %>
764
+ # <%= f.submit %>
765
+ # <% end %>
766
+ #
767
+ # Please refer to the documentation of the base helper for details.
641
768
  def select(method, choices, options = {}, html_options = {})
642
769
  @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
643
770
  end
644
771
 
772
+ # Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders:
773
+ #
774
+ # <%= form_for @post do |f| %>
775
+ # <%= f.collection_select :person_id, Author.all, :id, :name_with_initial, prompt: true %>
776
+ # <%= f.submit %>
777
+ # <% end %>
778
+ #
779
+ # Please refer to the documentation of the base helper for details.
645
780
  def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
646
781
  @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
647
782
  end
648
783
 
784
+ # Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders:
785
+ #
786
+ # <%= form_for @city do |f| %>
787
+ # <%= f.grouped_collection_select :country_id, :country_id, @continents, :countries, :name, :id, :name %>
788
+ # <%= f.submit %>
789
+ # <% end %>
790
+ #
791
+ # Please refer to the documentation of the base helper for details.
649
792
  def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
650
793
  @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
651
794
  end
652
795
 
796
+ # Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders:
797
+ #
798
+ # <%= form_for @user do |f| %>
799
+ # <%= f.time_zone_select :time_zone, nil, include_blank: true %>
800
+ # <%= f.submit %>
801
+ # <% end %>
802
+ #
803
+ # Please refer to the documentation of the base helper for details.
653
804
  def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
654
805
  @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
655
806
  end
807
+
808
+ # Wraps ActionView::Helpers::FormOptionsHelper#collection_check_boxes for form builders:
809
+ #
810
+ # <%= form_for @post do |f| %>
811
+ # <%= f.collection_check_boxes :author_ids, Author.all, :id, :name_with_initial %>
812
+ # <%= f.submit %>
813
+ # <% end %>
814
+ #
815
+ # Please refer to the documentation of the base helper for details.
816
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
817
+ @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block)
818
+ end
819
+
820
+ # Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders:
821
+ #
822
+ # <%= form_for @post do |f| %>
823
+ # <%= f.collection_radio_buttons :author_id, Author.all, :id, :name_with_initial %>
824
+ # <%= f.submit %>
825
+ # <% end %>
826
+ #
827
+ # Please refer to the documentation of the base helper for details.
828
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
829
+ @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block)
830
+ end
656
831
  end
657
832
  end
658
833
  end