actionpack 2.0.5 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. data/CHANGELOG +149 -7
  2. data/MIT-LICENSE +1 -1
  3. data/README +1 -1
  4. data/Rakefile +5 -6
  5. data/lib/action_controller.rb +2 -2
  6. data/lib/action_controller/assertions/model_assertions.rb +2 -1
  7. data/lib/action_controller/assertions/response_assertions.rb +4 -2
  8. data/lib/action_controller/assertions/routing_assertions.rb +3 -3
  9. data/lib/action_controller/assertions/selector_assertions.rb +30 -27
  10. data/lib/action_controller/assertions/tag_assertions.rb +3 -3
  11. data/lib/action_controller/base.rb +103 -129
  12. data/lib/action_controller/benchmarking.rb +3 -3
  13. data/lib/action_controller/caching.rb +41 -652
  14. data/lib/action_controller/caching/actions.rb +144 -0
  15. data/lib/action_controller/caching/fragments.rb +138 -0
  16. data/lib/action_controller/caching/pages.rb +154 -0
  17. data/lib/action_controller/caching/sql_cache.rb +18 -0
  18. data/lib/action_controller/caching/sweeping.rb +97 -0
  19. data/lib/action_controller/cgi_ext/cookie.rb +27 -23
  20. data/lib/action_controller/cgi_ext/stdinput.rb +1 -0
  21. data/lib/action_controller/cgi_process.rb +6 -4
  22. data/lib/action_controller/components.rb +7 -6
  23. data/lib/action_controller/cookies.rb +31 -19
  24. data/lib/action_controller/dispatcher.rb +51 -84
  25. data/lib/action_controller/filters.rb +295 -421
  26. data/lib/action_controller/flash.rb +1 -6
  27. data/lib/action_controller/headers.rb +31 -0
  28. data/lib/action_controller/helpers.rb +26 -9
  29. data/lib/action_controller/http_authentication.rb +1 -1
  30. data/lib/action_controller/integration.rb +65 -13
  31. data/lib/action_controller/layout.rb +24 -39
  32. data/lib/action_controller/mime_responds.rb +7 -3
  33. data/lib/action_controller/mime_type.rb +25 -9
  34. data/lib/action_controller/mime_types.rb +1 -1
  35. data/lib/action_controller/polymorphic_routes.rb +32 -17
  36. data/lib/action_controller/record_identifier.rb +10 -4
  37. data/lib/action_controller/request.rb +46 -30
  38. data/lib/action_controller/request_forgery_protection.rb +10 -9
  39. data/lib/action_controller/request_profiler.rb +29 -8
  40. data/lib/action_controller/rescue.rb +24 -24
  41. data/lib/action_controller/resources.rb +66 -23
  42. data/lib/action_controller/response.rb +2 -2
  43. data/lib/action_controller/routing.rb +113 -1229
  44. data/lib/action_controller/routing/builder.rb +204 -0
  45. data/lib/action_controller/{routing_optimisation.rb → routing/optimisations.rb} +13 -12
  46. data/lib/action_controller/routing/recognition_optimisation.rb +158 -0
  47. data/lib/action_controller/routing/route.rb +240 -0
  48. data/lib/action_controller/routing/route_set.rb +435 -0
  49. data/lib/action_controller/routing/routing_ext.rb +46 -0
  50. data/lib/action_controller/routing/segments.rb +283 -0
  51. data/lib/action_controller/session/active_record_store.rb +13 -8
  52. data/lib/action_controller/session/cookie_store.rb +20 -17
  53. data/lib/action_controller/session_management.rb +10 -3
  54. data/lib/action_controller/streaming.rb +45 -31
  55. data/lib/action_controller/test_case.rb +33 -23
  56. data/lib/action_controller/test_process.rb +39 -35
  57. data/lib/action_controller/url_rewriter.rb +18 -12
  58. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -1
  59. data/lib/action_pack.rb +1 -1
  60. data/lib/action_pack/version.rb +2 -2
  61. data/lib/action_view.rb +11 -3
  62. data/lib/action_view/base.rb +73 -390
  63. data/lib/action_view/helpers/active_record_helper.rb +83 -62
  64. data/lib/action_view/helpers/asset_tag_helper.rb +101 -44
  65. data/lib/action_view/helpers/atom_feed_helper.rb +35 -7
  66. data/lib/action_view/helpers/benchmark_helper.rb +5 -3
  67. data/lib/action_view/helpers/cache_helper.rb +3 -2
  68. data/lib/action_view/helpers/capture_helper.rb +1 -2
  69. data/lib/action_view/helpers/date_helper.rb +104 -82
  70. data/lib/action_view/helpers/form_helper.rb +148 -75
  71. data/lib/action_view/helpers/form_options_helper.rb +44 -23
  72. data/lib/action_view/helpers/form_tag_helper.rb +22 -13
  73. data/lib/action_view/helpers/javascripts/controls.js +1 -1
  74. data/lib/action_view/helpers/javascripts/dragdrop.js +1 -1
  75. data/lib/action_view/helpers/javascripts/effects.js +1 -1
  76. data/lib/action_view/helpers/number_helper.rb +10 -3
  77. data/lib/action_view/helpers/prototype_helper.rb +61 -29
  78. data/lib/action_view/helpers/record_tag_helper.rb +3 -3
  79. data/lib/action_view/helpers/sanitize_helper.rb +23 -17
  80. data/lib/action_view/helpers/scriptaculous_helper.rb +86 -60
  81. data/lib/action_view/helpers/text_helper.rb +153 -125
  82. data/lib/action_view/helpers/url_helper.rb +83 -28
  83. data/lib/action_view/inline_template.rb +20 -0
  84. data/lib/action_view/partial_template.rb +70 -0
  85. data/lib/action_view/partials.rb +31 -73
  86. data/lib/action_view/template.rb +127 -0
  87. data/lib/action_view/template_error.rb +8 -7
  88. data/lib/action_view/template_finder.rb +177 -0
  89. data/lib/action_view/template_handler.rb +18 -1
  90. data/lib/action_view/template_handlers/builder.rb +10 -2
  91. data/lib/action_view/template_handlers/compilable.rb +128 -0
  92. data/lib/action_view/template_handlers/erb.rb +37 -2
  93. data/lib/action_view/template_handlers/rjs.rb +14 -1
  94. data/lib/action_view/test_case.rb +58 -0
  95. data/test/abstract_unit.rb +1 -1
  96. data/test/active_record_unit.rb +3 -6
  97. data/test/activerecord/active_record_store_test.rb +1 -2
  98. data/test/activerecord/render_partial_with_record_identification_test.rb +158 -41
  99. data/test/adv_attr_test.rb +20 -0
  100. data/test/controller/action_pack_assertions_test.rb +16 -19
  101. data/test/controller/addresses_render_test.rb +1 -1
  102. data/test/controller/assert_select_test.rb +13 -6
  103. data/test/controller/base_test.rb +48 -2
  104. data/test/controller/benchmark_test.rb +1 -2
  105. data/test/controller/caching_test.rb +282 -21
  106. data/test/controller/capture_test.rb +1 -1
  107. data/test/controller/cgi_test.rb +1 -1
  108. data/test/controller/components_test.rb +1 -1
  109. data/test/controller/content_type_test.rb +2 -2
  110. data/test/controller/cookie_test.rb +13 -2
  111. data/test/controller/custom_handler_test.rb +14 -10
  112. data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -1
  113. data/test/controller/dispatcher_test.rb +31 -49
  114. data/test/controller/fake_controllers.rb +17 -0
  115. data/test/controller/fake_models.rb +6 -0
  116. data/test/controller/filter_params_test.rb +14 -8
  117. data/test/controller/filters_test.rb +44 -16
  118. data/test/controller/flash_test.rb +2 -2
  119. data/test/controller/header_test.rb +14 -0
  120. data/test/controller/helper_test.rb +19 -15
  121. data/test/controller/html-scanner/document_test.rb +1 -2
  122. data/test/controller/html-scanner/node_test.rb +1 -2
  123. data/test/controller/html-scanner/sanitizer_test.rb +8 -5
  124. data/test/controller/html-scanner/tag_node_test.rb +1 -2
  125. data/test/controller/html-scanner/text_node_test.rb +2 -3
  126. data/test/controller/html-scanner/tokenizer_test.rb +8 -2
  127. data/test/controller/http_authentication_test.rb +1 -1
  128. data/test/controller/integration_test.rb +14 -16
  129. data/test/controller/integration_upload_test.rb +43 -0
  130. data/test/controller/layout_test.rb +26 -6
  131. data/test/controller/mime_responds_test.rb +39 -7
  132. data/test/controller/mime_type_test.rb +29 -5
  133. data/test/controller/new_render_test.rb +105 -34
  134. data/test/controller/polymorphic_routes_test.rb +32 -20
  135. data/test/controller/record_identifier_test.rb +38 -2
  136. data/test/controller/redirect_test.rb +21 -1
  137. data/test/controller/render_test.rb +59 -15
  138. data/test/controller/request_forgery_protection_test.rb +92 -5
  139. data/test/controller/request_test.rb +64 -6
  140. data/test/controller/rescue_test.rb +22 -6
  141. data/test/controller/resources_test.rb +102 -14
  142. data/test/controller/routing_test.rb +231 -19
  143. data/test/controller/selector_test.rb +2 -2
  144. data/test/controller/send_file_test.rb +14 -3
  145. data/test/controller/session/cookie_store_test.rb +16 -4
  146. data/test/controller/session/mem_cache_store_test.rb +3 -4
  147. data/test/controller/session_fixation_test.rb +1 -1
  148. data/test/controller/session_management_test.rb +23 -1
  149. data/test/controller/test_test.rb +39 -18
  150. data/test/controller/url_rewriter_test.rb +35 -1
  151. data/test/controller/verification_test.rb +1 -1
  152. data/test/controller/view_paths_test.rb +15 -12
  153. data/test/controller/webservice_test.rb +48 -3
  154. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  155. data/test/fixtures/company.rb +1 -0
  156. data/test/fixtures/customers/_customer.html.erb +1 -0
  157. data/test/fixtures/db_definitions/sqlite.sql +6 -0
  158. data/test/fixtures/functional_caching/_partial.erb +3 -0
  159. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  160. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  161. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  162. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  163. data/test/fixtures/mascot.rb +3 -0
  164. data/test/fixtures/mascots.yml +4 -0
  165. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  166. data/test/fixtures/multipart/boundary_problem_file +10 -0
  167. data/test/fixtures/public/javascripts/application.js +1 -0
  168. data/test/fixtures/public/javascripts/controls.js +1 -0
  169. data/test/fixtures/public/javascripts/dragdrop.js +1 -0
  170. data/test/fixtures/public/javascripts/effects.js +1 -0
  171. data/test/fixtures/public/javascripts/prototype.js +1 -0
  172. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  173. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  174. data/test/fixtures/reply.rb +1 -0
  175. data/test/fixtures/shared.html.erb +1 -0
  176. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  177. data/test/fixtures/test/_customer_counter.erb +1 -0
  178. data/test/fixtures/test/_form.erb +1 -0
  179. data/test/fixtures/test/_labelling_form.erb +1 -0
  180. data/test/fixtures/test/_raise.html.erb +1 -0
  181. data/test/fixtures/test/greeting.js.rjs +1 -0
  182. data/test/fixtures/topics/_topic.html.erb +1 -0
  183. data/test/template/active_record_helper_test.rb +25 -8
  184. data/test/template/asset_tag_helper_test.rb +100 -17
  185. data/test/template/atom_feed_helper_test.rb +29 -1
  186. data/test/template/benchmark_helper_test.rb +10 -22
  187. data/test/template/date_helper_test.rb +455 -153
  188. data/test/template/erb_util_test.rb +10 -42
  189. data/test/template/form_helper_test.rb +192 -66
  190. data/test/template/form_options_helper_test.rb +19 -8
  191. data/test/template/form_tag_helper_test.rb +11 -8
  192. data/test/template/javascript_helper_test.rb +3 -9
  193. data/test/template/number_helper_test.rb +6 -3
  194. data/test/template/prototype_helper_test.rb +27 -40
  195. data/test/template/record_tag_helper_test.rb +54 -0
  196. data/test/template/sanitize_helper_test.rb +5 -6
  197. data/test/template/scriptaculous_helper_test.rb +7 -13
  198. data/test/template/tag_helper_test.rb +3 -6
  199. data/test/template/template_finder_test.rb +73 -0
  200. data/test/template/template_object_test.rb +95 -0
  201. data/test/template/test_test.rb +56 -0
  202. data/test/template/text_helper_test.rb +46 -33
  203. data/test/template/url_helper_test.rb +8 -10
  204. metadata +65 -12
  205. data/lib/action_view/compiled_templates.rb +0 -69
  206. data/test/action_view_test.rb +0 -44
  207. data/test/activerecord/fixtures_test.rb +0 -24
  208. data/test/controller/fragment_store_setting_test.rb +0 -47
  209. data/test/template/compiled_templates_test.rb +0 -197
  210. data/test/template/deprecate_ivars_test.rb +0 -51
@@ -1,6 +1,7 @@
1
1
  require 'cgi'
2
2
  require 'action_view/helpers/date_helper'
3
3
  require 'action_view/helpers/tag_helper'
4
+ require 'action_view/helpers/form_tag_helper'
4
5
 
5
6
  module ActionView
6
7
  module Helpers
@@ -32,6 +33,15 @@ module ActionView
32
33
  # <input name="commit" type="submit" value="Create" />
33
34
  # </form>
34
35
  #
36
+ # If you are using a partial for your form fields, you can use this shortcut:
37
+ #
38
+ # <% form_for :person, @person, :url => { :action => "create" } do |f| %>
39
+ # <%= render :partial => f %>
40
+ # <%= submit_tag 'Create' %>
41
+ # <% end %>
42
+ #
43
+ # This example will render the <tt>people/_form</tt> partial, setting a local variable called <tt>form</tt> which references the yielded FormBuilder.
44
+ #
35
45
  # The <tt>params</tt> object created when this form is submitted would look like:
36
46
  #
37
47
  # {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
@@ -42,7 +52,7 @@ module ActionView
42
52
  #
43
53
  # If the object name contains square brackets the id for the object will be inserted. For example:
44
54
  #
45
- # <%= text_field "person[]", "name" %>
55
+ # <%= text_field "person[]", "name" %>
46
56
  #
47
57
  # ...will generate the following ERb.
48
58
  #
@@ -57,31 +67,87 @@ module ActionView
57
67
  #
58
68
  # <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
59
69
  #
70
+ # An <tt>index</tt> option may also be passed to <tt>form_for</tt> and <tt>fields_for</tt>. This automatically applies
71
+ # the <tt>index</tt> to all the nested fields.
72
+ #
60
73
  # There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
61
74
  # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
62
75
  module FormHelper
63
- # Creates a form and a scope around a specific model object that is used as a base for questioning about
64
- # values for the fields.
76
+ # Creates a form and a scope around a specific model object that is used as
77
+ # a base for questioning about values for the fields.
65
78
  #
66
- # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
67
- # First name: <%= f.text_field :first_name %>
68
- # Last name : <%= f.text_field :last_name %>
69
- # Biography : <%= f.text_area :biography %>
70
- # Admin? : <%= f.check_box :admin %>
79
+ # Rails provides succint resource-oriented form generation with +form_for+
80
+ # like this:
81
+ #
82
+ # <% form_for @offer do |f| %>
83
+ # <%= f.label :version, 'Version' %>:
84
+ # <%= f.text_field :version %><br />
85
+ # <%= f.label :author, 'Author' %>:
86
+ # <%= f.text_field :author %><br />
87
+ # <% end %>
88
+ #
89
+ # There, +form_for+ is able to generate the rest of RESTful form parameters
90
+ # based on introspection on the record, but to understand what it does we
91
+ # need to dig first into the alternative generic usage it is based upon.
92
+ #
93
+ # === Generic form_for
94
+ #
95
+ # The generic way to call +form_for+ yields a form builder around a model:
96
+ #
97
+ # <% form_for :person, :url => { :action => "update" } do |f| %>
98
+ # <%= f.error_messages %>
99
+ # First name: <%= f.text_field :first_name %><br />
100
+ # Last name : <%= f.text_field :last_name %><br />
101
+ # Biography : <%= f.text_area :biography %><br />
102
+ # Admin? : <%= f.check_box :admin %><br />
71
103
  # <% end %>
72
104
  #
73
- # Worth noting is that the form_for tag is called in a ERb evaluation block, not an ERb output block. So that's <tt><% %></tt>,
74
- # not <tt><%= %></tt>. Also worth noting is that form_for yields a <tt>form_builder</tt> object, in this example as <tt>f</tt>, which emulates
75
- # the API for the stand-alone FormHelper methods, but without the object name. So instead of <tt>text_field :person, :name</tt>,
76
- # you get away with <tt>f.text_field :name</tt>.
105
+ # There, the first argument is a symbol or string with the name of the
106
+ # object the form is about, and also the name of the instance variable the
107
+ # object is stored in.
108
+ #
109
+ # The form builder acts as a regular form helper that somehow carries the
110
+ # model. Thus, the idea is that
111
+ #
112
+ # <%= f.text_field :first_name %>
113
+ #
114
+ # gets expanded to
115
+ #
116
+ # <%= text_field :person, :first_name %>
117
+ #
118
+ # If the instance variable is not <tt>@person</tt> you can pass the actual
119
+ # record as the second argument:
120
+ #
121
+ # <% form_for :person, person, :url => { :action => "update" } do |f| %>
122
+ # ...
123
+ # <% end %>
124
+ #
125
+ # In that case you can think
126
+ #
127
+ # <%= f.text_field :first_name %>
77
128
  #
78
- # Even further, the form_for method allows you to more easily escape the instance variable convention. So while the stand-alone
79
- # approach would require <tt>text_field :person, :name, :object => person</tt>
80
- # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with
81
- # <tt>:person, person</tt> and all subsequent field calls save <tt>:person</tt> and <tt>:object => person</tt>.
129
+ # gets expanded to
82
130
  #
83
- # Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods
84
- # and methods from FormTagHelper. For example:
131
+ # <%= text_field :person, :first_name, :object => person %>
132
+ #
133
+ # You can even display error messages of the wrapped model this way:
134
+ #
135
+ # <%= f.error_messages %>
136
+ #
137
+ # In any of its variants, the rightmost argument to +form_for+ is an
138
+ # optional hash of options:
139
+ #
140
+ # * <tt>:url</tt> - The URL the form is submitted to. It takes the same fields
141
+ # you pass to +url_for+ or +link_to+. In particular you may pass here a
142
+ # named route directly as well. Defaults to the current action.
143
+ # * <tt>:html</tt> - Optional HTML attributes for the form tag.
144
+ #
145
+ # Worth noting is that the +form_for+ tag is called in a ERb evaluation block,
146
+ # not an ERb output block. So that's <tt><% %></tt>, not <tt><%= %></tt>.
147
+ #
148
+ # Also note that +form_for+ doesn't create an exclusive scope. It's still
149
+ # possible to use both the stand-alone FormHelper methods and methods from
150
+ # FormTagHelper. For example:
85
151
  #
86
152
  # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
87
153
  # First name: <%= f.text_field :first_name %>
@@ -90,42 +156,38 @@ module ActionView
90
156
  # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
91
157
  # <% end %>
92
158
  #
93
- # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base,
94
- # like FormOptionHelper#collection_select and DateHelper#datetime_select.
95
- #
96
- # HTML attributes for the form tag can be given as :html => {...}. For example:
97
- #
98
- # <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %>
99
- # ...
100
- # <% end %>
159
+ # This also works for the methods in FormOptionHelper and DateHelper that are
160
+ # designed to work with an object as base, like FormOptionHelper#collection_select
161
+ # and DateHelper#datetime_select.
101
162
  #
102
- # The above form will then have the <tt>id</tt> attribute with the value </tt>person_form</tt>, which you can then
103
- # style with CSS or manipulate with JavaScript.
163
+ # === Resource-oriented style
104
164
  #
105
- # === Relying on record identification
165
+ # As we said above, in addition to manually configuring the +form_for+ call,
166
+ # you can rely on automated resource identification, which will use the conventions
167
+ # and named routes of that approach. This is the preferred way to use +form_for+
168
+ # nowadays.
106
169
  #
107
- # In addition to manually configuring the form_for call, you can also rely on record identification, which will use
108
- # the conventions and named routes of that approach. Examples:
170
+ # For example, if <tt>@post</tt> is an existing record you want to edit
109
171
  #
110
- # <% form_for(@post) do |f| %>
172
+ # <% form_for @post do |f| %>
111
173
  # ...
112
174
  # <% end %>
113
175
  #
114
- # This will expand to be the same as:
176
+ # is equivalent to something like:
115
177
  #
116
178
  # <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
117
179
  # ...
118
180
  # <% end %>
119
181
  #
120
- # And for new records:
182
+ # And for new records
121
183
  #
122
184
  # <% form_for(Post.new) do |f| %>
123
185
  # ...
124
186
  # <% end %>
125
187
  #
126
- # This will expand to be the same as:
188
+ # expands to
127
189
  #
128
- # <% form_for :post, @post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
190
+ # <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
129
191
  # ...
130
192
  # <% end %>
131
193
  #
@@ -135,7 +197,7 @@ module ActionView
135
197
  # ...
136
198
  # <% end %>
137
199
  #
138
- # And for namespaced routes, like admin_post_url:
200
+ # And for namespaced routes, like +admin_post_url+:
139
201
  #
140
202
  # <% form_for([:admin, @post]) do |f| %>
141
203
  # ...
@@ -153,6 +215,12 @@ module ActionView
153
215
  # <%= check_box_tag "person[admin]", @person.company.admin? %>
154
216
  # <% end %>
155
217
  #
218
+ # In this case, if you use this:
219
+ #
220
+ # <%= render :partial => f %>
221
+ #
222
+ # The rendered template is <tt>people/_labelling_form</tt> and the local variable referencing the form builder is called <tt>labelling_form</tt>.
223
+ #
156
224
  # In many cases you will want to wrap the above in another helper, so you could do something like the following:
157
225
  #
158
226
  # def labelled_form_for(record_or_name_or_array, *args, &proc)
@@ -256,13 +324,13 @@ module ActionView
256
324
  #
257
325
  # ==== Examples
258
326
  # label(:post, :title)
259
- # #=> <label for="post_title">Title</label>
327
+ # # => <label for="post_title">Title</label>
260
328
  #
261
329
  # label(:post, :title, "A short title")
262
- # #=> <label for="post_title">A short title</label>
330
+ # # => <label for="post_title">A short title</label>
263
331
  #
264
332
  # label(:post, :title, "A short title", :class => "title_label")
265
- # #=> <label for="post_title" class="title_label">A short title</label>
333
+ # # => <label for="post_title" class="title_label">A short title</label>
266
334
  #
267
335
  def label(object_name, method, text = nil, options = {})
268
336
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
@@ -317,7 +385,7 @@ module ActionView
317
385
  # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
318
386
  # shown.
319
387
  #
320
- # ==== Examples
388
+ # ==== Examples
321
389
  # hidden_field(:signup, :pass_confirm)
322
390
  # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
323
391
  #
@@ -384,10 +452,10 @@ module ActionView
384
452
  # is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything,
385
453
  # we add a hidden value with the same name as the checkbox as a work around.
386
454
  #
387
- # ==== Examples
455
+ # ==== Examples
388
456
  # # Let's say that @post.validated? is 1:
389
457
  # check_box("post", "validated")
390
- # # => <input type="checkbox" id="post_validate" name="post[validated]" value="1" checked="checked" />
458
+ # # => <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
391
459
  # # <input name="post[validated]" type="hidden" value="0" />
392
460
  #
393
461
  # # Let's say that @puppy.gooddog is "no":
@@ -395,8 +463,8 @@ module ActionView
395
463
  # # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
396
464
  # # <input name="puppy[gooddog]" type="hidden" value="no" />
397
465
  #
398
- # check_box("eula", "accepted", {}, "yes", "no", :class => 'eula_check')
399
- # # => <input type="checkbox" id="eula_accepted" name="eula[accepted]" value="no" />
466
+ # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
467
+ # # => <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
400
468
  # # <input name="eula[accepted]" type="hidden" value="no" />
401
469
  #
402
470
  def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
@@ -412,27 +480,26 @@ module ActionView
412
480
  # # Let's say that @post.category returns "rails":
413
481
  # radio_button("post", "category", "rails")
414
482
  # radio_button("post", "category", "java")
415
- # # => <input type="radio" id="post_category" name="post[category]" value="rails" checked="checked" />
416
- # # <input type="radio" id="post_category" name="post[category]" value="java" />
483
+ # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
484
+ # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
417
485
  #
418
486
  # radio_button("user", "receive_newsletter", "yes")
419
487
  # radio_button("user", "receive_newsletter", "no")
420
- # # => <input type="radio" id="user_receive_newsletter" name="user[receive_newsletter]" value="yes" />
421
- # # <input type="radio" id="user_receive_newsletter" name="user[receive_newsletter]" value="no" checked="checked" />
488
+ # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
489
+ # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
422
490
  def radio_button(object_name, method, tag_value, options = {})
423
491
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
424
492
  end
425
493
  end
426
494
 
427
495
  class InstanceTag #:nodoc:
428
- include Helpers::TagHelper
496
+ include Helpers::TagHelper, Helpers::FormTagHelper
429
497
 
430
498
  attr_reader :method_name, :object_name
431
499
 
432
500
  DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
433
501
  DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
434
502
  DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
435
- DEFAULT_DATE_OPTIONS = { :discard_type => true }.freeze unless const_defined?(:DEFAULT_DATE_OPTIONS)
436
503
 
437
504
  def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
438
505
  @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
@@ -448,11 +515,13 @@ module ActionView
448
515
  end
449
516
 
450
517
  def to_label_tag(text = nil, options = {})
518
+ options = options.stringify_keys
451
519
  name_and_id = options.dup
452
520
  add_default_name_and_id(name_and_id)
453
- options["for"] = name_and_id["id"]
521
+ options.delete("index")
522
+ options["for"] ||= name_and_id["id"]
454
523
  content = (text.blank? ? nil : text.to_s) || method_name.humanize
455
- content_tag("label", content, options)
524
+ label_tag(name_and_id["id"], content, options)
456
525
  end
457
526
 
458
527
  def to_input_field_tag(field_type, options = {})
@@ -464,6 +533,7 @@ module ActionView
464
533
  end
465
534
  options["type"] = field_type
466
535
  options["value"] ||= value_before_type_cast(object) unless field_type == "file"
536
+ options["value"] &&= html_escape(options["value"])
467
537
  add_default_name_and_id(options)
468
538
  tag("input", options)
469
539
  end
@@ -481,8 +551,8 @@ module ActionView
481
551
  options["checked"] = "checked" if checked
482
552
  pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
483
553
  options["id"] ||= defined?(@auto_index) ?
484
- "#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" :
485
- "#{@object_name}_#{@method_name}_#{pretty_tag_value}"
554
+ "#{tag_id_with_index(@auto_index)}_#{pretty_tag_value}" :
555
+ "#{tag_id}_#{pretty_tag_value}"
486
556
  add_default_name_and_id(options)
487
557
  tag("input", options)
488
558
  end
@@ -513,15 +583,6 @@ module ActionView
513
583
  tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
514
584
  end
515
585
 
516
- def to_date_tag()
517
- defaults = DEFAULT_DATE_OPTIONS.dup
518
- date = value(object) || Date.today
519
- options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
520
- html_day_select(date, options.call(3)) +
521
- html_month_select(date, options.call(2)) +
522
- html_year_select(date, options.call(1))
523
- end
524
-
525
586
  def to_boolean_select_tag(options = {})
526
587
  options = options.stringify_keys
527
588
  add_default_name_and_id(options)
@@ -574,6 +635,8 @@ module ActionView
574
635
  value != 0
575
636
  when String
576
637
  value == checked_value
638
+ when Array
639
+ value.include?(checked_value)
577
640
  else
578
641
  value.to_i != 0
579
642
  end
@@ -600,23 +663,27 @@ module ActionView
600
663
  end
601
664
 
602
665
  def tag_name
603
- "#{@object_name}[#{@method_name}]"
666
+ "#{@object_name}[#{sanitized_method_name}]"
604
667
  end
605
668
 
606
669
  def tag_name_with_index(index)
607
- "#{@object_name}[#{index}][#{@method_name}]"
670
+ "#{@object_name}[#{index}][#{sanitized_method_name}]"
608
671
  end
609
672
 
610
673
  def tag_id
611
- "#{sanitized_object_name}_#{@method_name}"
674
+ "#{sanitized_object_name}_#{sanitized_method_name}"
612
675
  end
613
676
 
614
677
  def tag_id_with_index(index)
615
- "#{sanitized_object_name}_#{index}_#{@method_name}"
678
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
616
679
  end
617
680
 
618
681
  def sanitized_object_name
619
- @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
682
+ @sanitized_object_name ||= @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
683
+ end
684
+
685
+ def sanitized_method_name
686
+ @sanitized_method_name ||= @method_name.sub(/\?$/,"")
620
687
  end
621
688
  end
622
689
 
@@ -629,12 +696,13 @@ module ActionView
629
696
 
630
697
  def initialize(object_name, object, template, options, proc)
631
698
  @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
699
+ @default_options = @options ? @options.slice(:index) : {}
632
700
  end
633
701
 
634
702
  (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
635
703
  src = <<-end_src
636
704
  def #{selector}(method, options = {})
637
- @template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object))
705
+ @template.send(#{selector.inspect}, @object_name, method, objectify_options(options))
638
706
  end
639
707
  end_src
640
708
  class_eval src, __FILE__, __LINE__
@@ -653,20 +721,20 @@ module ActionView
653
721
  name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
654
722
  args.unshift(object)
655
723
  end
656
-
724
+
657
725
  @template.fields_for(name, *args, &block)
658
726
  end
659
727
 
660
728
  def label(method, text = nil, options = {})
661
- @template.label(@object_name, method, text, options.merge(:object => @object))
729
+ @template.label(@object_name, method, text, objectify_options(options))
662
730
  end
663
731
 
664
732
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
665
- @template.check_box(@object_name, method, options.merge(:object => @object), checked_value, unchecked_value)
733
+ @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
666
734
  end
667
735
 
668
736
  def radio_button(method, tag_value, options = {})
669
- @template.radio_button(@object_name, method, tag_value, options.merge(:object => @object))
737
+ @template.radio_button(@object_name, method, tag_value, objectify_options(options))
670
738
  end
671
739
 
672
740
  def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
@@ -674,12 +742,17 @@ module ActionView
674
742
  end
675
743
 
676
744
  def error_messages(options = {})
677
- @template.error_messages_for(@object_name, options.merge(:object => @object))
745
+ @template.error_messages_for(@object_name, objectify_options(options))
678
746
  end
679
747
 
680
748
  def submit(value = "Save changes", options = {})
681
749
  @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
682
750
  end
751
+
752
+ private
753
+ def objectify_options(options)
754
+ @default_options.merge(options.merge(:object => @object))
755
+ end
683
756
  end
684
757
  end
685
758
 
@@ -53,6 +53,21 @@ module ActionView
53
53
  # <option value="2">Sam</option>
54
54
  # <option value="3">Tobias</option>
55
55
  # </select>
56
+ #
57
+ # 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
58
+ # option to be in the +html_options+ parameter.
59
+ #
60
+ # Example:
61
+ #
62
+ # select("album[]", "genre", %w[rap rock country], {}, { :index => nil })
63
+ #
64
+ # becomes:
65
+ #
66
+ # <select name="album[][genre]" id="album__genre">
67
+ # <option value="rap">rap</option>
68
+ # <option value="rock">rock</option>
69
+ # <option value="country">country</option>
70
+ # </select>
56
71
  module FormOptionsHelper
57
72
  include ERB::Util
58
73
 
@@ -78,8 +93,8 @@ module ActionView
78
93
  # This allows the user to submit a form page more than once with the expected results of creating multiple records.
79
94
  # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
80
95
  #
81
- # By default, post.person_id is the selected option. Specify :selected => value to use a different selection
82
- # or :selected => nil to leave all options unselected.
96
+ # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
97
+ # or <tt>:selected => nil</tt> to leave all options unselected.
83
98
  def select(object, method, choices, options = {}, html_options = {})
84
99
  InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options)
85
100
  end
@@ -104,7 +119,7 @@ module ActionView
104
119
  # end
105
120
  # end
106
121
  #
107
- # Sample usage (selecting the associated +Author+ for an instance of +Post+, <tt>@post</tt>):
122
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
108
123
  # collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true})
109
124
  #
110
125
  # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
@@ -129,10 +144,16 @@ module ActionView
129
144
  # In addition to the <tt>:include_blank</tt> option documented above,
130
145
  # this method also supports a <tt>:model</tt> option, which defaults
131
146
  # to TimeZone. This may be used by users to specify a different time
132
- # zone model object. (See #time_zone_options_for_select for more
147
+ # zone model object. (See +time_zone_options_for_select+ for more
133
148
  # information.)
149
+ #
150
+ # You can also supply an array of TimeZone objects
151
+ # as +priority_zones+, so that they will be listed above the rest of the
152
+ # (long) list. (You can use TimeZone.us_zones as a convenience for
153
+ # obtaining a list of the US time zones.)
154
+ #
134
155
  # Finally, this method supports a <tt>:default</tt> option, which selects
135
- # a default TimeZone if the object's time zone is nil.
156
+ # a default TimeZone if the object's time zone is +nil+.
136
157
  #
137
158
  # Examples:
138
159
  # time_zone_select( "user", "time_zone", nil, :include_blank => true)
@@ -141,6 +162,8 @@ module ActionView
141
162
  #
142
163
  # time_zone_select( "user", 'time_zone', TimeZone.us_zones, :default => "Pacific Time (US & Canada)")
143
164
  #
165
+ # time_zone_select( "user", 'time_zone', [ TimeZone['Alaska'], TimeZone['Hawaii'] ])
166
+ #
144
167
  # time_zone_select( "user", "time_zone", TZInfo::Timezone.all.sort, :model => TZInfo::Timezone)
145
168
  def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
146
169
  InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
@@ -149,7 +172,7 @@ module ActionView
149
172
  # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
150
173
  # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
151
174
  # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
152
- # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+
175
+ # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
153
176
  # may also be an array of values to be selected when using a multiple select.
154
177
  #
155
178
  # Examples (call, result):
@@ -194,24 +217,22 @@ module ActionView
194
217
  options_for_select(options, selected)
195
218
  end
196
219
 
197
- # Returns a string of <tt><option></tt> tags, like <tt>#options_from_collection_for_select</tt>, but
220
+ # Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but
198
221
  # groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
199
222
  #
200
223
  # Parameters:
201
- # +collection+:: An array of objects representing the <tt><optgroup></tt> tags
202
- # +group_method+:: The name of a method which, when called on a member of +collection+, returns an
203
- # array of child objects representing the <tt><option></tt> tags
204
- # +group_label_method+:: The name of a method which, when called on a member of +collection+, returns a
205
- # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag
206
- # +option_key_method+:: The name of a method which, when called on a child object of a member of
207
- # +collection+, returns a value to be used as the +value+ attribute for its
208
- # <tt><option></tt> tag
209
- # +option_value_method+:: The name of a method which, when called on a child object of a member of
210
- # +collection+, returns a value to be used as the contents of its
211
- # <tt><option></tt> tag
212
- # +selected_key+:: A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
213
- # which will have the +selected+ attribute set. Corresponds to the return value
214
- # of one of the calls to +option_key_method+. If +nil+, no selection is made.
224
+ # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
225
+ # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
226
+ # array of child objects representing the <tt><option></tt> tags.
227
+ # * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
228
+ # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
229
+ # * +option_key_method+ - The name of a method which, when called on a child object of a member of
230
+ # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
231
+ # * +option_value_method+ - The name of a method which, when called on a child object of a member of
232
+ # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
233
+ # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
234
+ # which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
235
+ # to +option_key_method+. If +nil+, no selection is made.
215
236
  #
216
237
  # Example object structure for use with this method:
217
238
  # class Continent < ActiveRecord::Base
@@ -277,8 +298,8 @@ module ActionView
277
298
  # a TimeZone.
278
299
  #
279
300
  # By default, +model+ is the TimeZone constant (which can be obtained
280
- # in ActiveRecord as a value object). The only requirement is that the
281
- # +model+ parameter be an object that responds to #all, and returns
301
+ # in Active Record as a value object). The only requirement is that the
302
+ # +model+ parameter be an object that responds to +all+, and returns
282
303
  # an array of objects that represent time zones.
283
304
  #
284
305
  # NOTE: Only the option tags are returned, you have to wrap this call in