actionpack 3.1.12 → 3.2.0.rc1

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 (128) hide show
  1. data/CHANGELOG.md +5503 -108
  2. data/README.rdoc +3 -3
  3. data/lib/abstract_controller/asset_paths.rb +1 -1
  4. data/lib/abstract_controller/base.rb +1 -1
  5. data/lib/abstract_controller/callbacks.rb +102 -18
  6. data/lib/abstract_controller/helpers.rb +1 -1
  7. data/lib/abstract_controller/layouts.rb +116 -50
  8. data/lib/abstract_controller/logger.rb +1 -1
  9. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  10. data/lib/abstract_controller/rendering.rb +1 -6
  11. data/lib/abstract_controller/view_paths.rb +6 -5
  12. data/lib/action_controller.rb +0 -15
  13. data/lib/action_controller/caching.rb +0 -1
  14. data/lib/action_controller/caching/actions.rb +5 -6
  15. data/lib/action_controller/caching/fragments.rb +18 -18
  16. data/lib/action_controller/caching/pages.rb +7 -6
  17. data/lib/action_controller/caching/sweeping.rb +1 -1
  18. data/lib/action_controller/log_subscriber.rb +8 -4
  19. data/lib/action_controller/metal.rb +7 -1
  20. data/lib/action_controller/metal/conditional_get.rb +49 -4
  21. data/lib/action_controller/metal/data_streaming.rb +17 -5
  22. data/lib/action_controller/metal/force_ssl.rb +8 -5
  23. data/lib/action_controller/metal/helpers.rb +7 -4
  24. data/lib/action_controller/metal/http_authentication.rb +9 -12
  25. data/lib/action_controller/metal/instrumentation.rb +9 -4
  26. data/lib/action_controller/metal/mime_responds.rb +4 -4
  27. data/lib/action_controller/metal/params_wrapper.rb +12 -8
  28. data/lib/action_controller/metal/redirecting.rb +7 -6
  29. data/lib/action_controller/metal/renderers.rb +9 -11
  30. data/lib/action_controller/metal/request_forgery_protection.rb +2 -1
  31. data/lib/action_controller/metal/rescue.rb +13 -0
  32. data/lib/action_controller/metal/responder.rb +11 -23
  33. data/lib/action_controller/metal/streaming.rb +0 -25
  34. data/lib/action_controller/railtie.rb +1 -0
  35. data/lib/action_controller/railties/paths.rb +4 -3
  36. data/lib/action_controller/record_identifier.rb +4 -4
  37. data/lib/action_controller/test_case.rb +60 -56
  38. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +6 -6
  39. data/lib/action_dispatch.rb +5 -1
  40. data/lib/action_dispatch/http/cache.rb +27 -15
  41. data/lib/action_dispatch/http/filter_parameters.rb +3 -1
  42. data/lib/action_dispatch/http/headers.rb +3 -5
  43. data/lib/action_dispatch/http/mime_negotiation.rb +2 -1
  44. data/lib/action_dispatch/http/mime_type.rb +7 -3
  45. data/lib/action_dispatch/http/mime_types.rb +12 -0
  46. data/lib/action_dispatch/http/parameter_filter.rb +3 -1
  47. data/lib/action_dispatch/http/parameters.rb +0 -4
  48. data/lib/action_dispatch/http/request.rb +18 -68
  49. data/lib/action_dispatch/http/response.rb +11 -32
  50. data/lib/action_dispatch/http/upload.rb +3 -14
  51. data/lib/action_dispatch/http/url.rb +1 -1
  52. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  53. data/lib/action_dispatch/middleware/cookies.rb +20 -16
  54. data/lib/action_dispatch/middleware/debug_exceptions.rb +82 -0
  55. data/lib/action_dispatch/middleware/exception_wrapper.rb +78 -0
  56. data/lib/action_dispatch/middleware/flash.rb +6 -9
  57. data/lib/action_dispatch/middleware/params_parser.rb +6 -11
  58. data/lib/action_dispatch/middleware/public_exceptions.rb +30 -0
  59. data/lib/action_dispatch/middleware/reloader.rb +38 -14
  60. data/lib/action_dispatch/middleware/remote_ip.rb +66 -36
  61. data/lib/action_dispatch/middleware/request_id.rb +39 -0
  62. data/lib/action_dispatch/middleware/session/abstract_store.rb +4 -16
  63. data/lib/action_dispatch/middleware/session/cache_store.rb +50 -0
  64. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  65. data/lib/action_dispatch/middleware/show_exceptions.rb +58 -142
  66. data/lib/action_dispatch/middleware/static.rb +2 -10
  67. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  68. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +13 -8
  69. data/lib/action_dispatch/railtie.rb +15 -1
  70. data/lib/action_dispatch/routing.rb +1 -2
  71. data/lib/action_dispatch/routing/mapper.rb +108 -107
  72. data/lib/action_dispatch/routing/redirection.rb +63 -69
  73. data/lib/action_dispatch/routing/route_set.rb +75 -43
  74. data/lib/action_dispatch/routing/routes_proxy.rb +0 -4
  75. data/lib/action_dispatch/routing/url_for.rb +3 -3
  76. data/lib/action_dispatch/testing/assertions/response.rb +5 -7
  77. data/lib/action_dispatch/testing/assertions/routing.rb +10 -9
  78. data/lib/action_dispatch/testing/integration.rb +8 -25
  79. data/lib/action_dispatch/testing/test_process.rb +3 -2
  80. data/lib/action_dispatch/testing/test_request.rb +4 -23
  81. data/lib/action_pack/version.rb +3 -3
  82. data/lib/action_view.rb +1 -5
  83. data/lib/action_view/asset_paths.rb +7 -8
  84. data/lib/action_view/base.rb +7 -5
  85. data/lib/action_view/helpers/asset_paths.rb +1 -1
  86. data/lib/action_view/helpers/asset_tag_helper.rb +4 -8
  87. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +3 -0
  88. data/lib/action_view/helpers/atom_feed_helper.rb +2 -2
  89. data/lib/action_view/helpers/capture_helper.rb +3 -3
  90. data/lib/action_view/helpers/controller_helper.rb +1 -1
  91. data/lib/action_view/helpers/date_helper.rb +26 -18
  92. data/lib/action_view/helpers/debug_helper.rb +1 -1
  93. data/lib/action_view/helpers/form_helper.rb +71 -13
  94. data/lib/action_view/helpers/form_options_helper.rb +65 -34
  95. data/lib/action_view/helpers/form_tag_helper.rb +24 -18
  96. data/lib/action_view/helpers/javascript_helper.rb +12 -3
  97. data/lib/action_view/helpers/number_helper.rb +3 -2
  98. data/lib/action_view/helpers/record_tag_helper.rb +51 -5
  99. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  100. data/lib/action_view/helpers/sanitize_helper.rb +6 -7
  101. data/lib/action_view/helpers/tag_helper.rb +1 -1
  102. data/lib/action_view/helpers/text_helper.rb +5 -4
  103. data/lib/action_view/helpers/url_helper.rb +19 -11
  104. data/lib/action_view/locale/en.yml +6 -0
  105. data/lib/action_view/log_subscriber.rb +1 -1
  106. data/lib/action_view/lookup_context.rb +123 -125
  107. data/lib/action_view/path_set.rb +60 -13
  108. data/lib/action_view/renderer/abstract_renderer.rb +16 -11
  109. data/lib/action_view/renderer/partial_renderer.rb +59 -40
  110. data/lib/action_view/renderer/template_renderer.rb +29 -17
  111. data/lib/action_view/template.rb +0 -1
  112. data/lib/action_view/template/error.rb +6 -5
  113. data/lib/action_view/template/handlers.rb +0 -6
  114. data/lib/action_view/template/handlers/builder.rb +10 -1
  115. data/lib/action_view/template/handlers/erb.rb +2 -2
  116. data/lib/action_view/template/resolver.rb +20 -31
  117. data/lib/action_view/test_case.rb +7 -10
  118. data/lib/sprockets/assets.rake +1 -1
  119. data/lib/sprockets/bootstrap.rb +3 -31
  120. data/lib/sprockets/compressors.rb +69 -7
  121. data/lib/sprockets/helpers/rails_helper.rb +6 -11
  122. data/lib/sprockets/railtie.rb +1 -0
  123. data/lib/sprockets/static_compiler.rb +0 -3
  124. metadata +57 -86
  125. checksums.yaml +0 -7
  126. data/lib/action_dispatch/middleware/closed_error.rb +0 -7
  127. data/lib/action_dispatch/routing/route.rb +0 -67
  128. data/lib/action_view/template/handler.rb +0 -49
@@ -30,7 +30,7 @@ module ActionView
30
30
  begin
31
31
  Marshal::dump(object)
32
32
  "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", "&nbsp; ")}</pre>".html_safe
33
- rescue Exception # errors from Marshal or YAML
33
+ rescue Exception # errors from Marshal or YAML
34
34
  # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
35
35
  "<code class='debug_dump'>#{h(object.inspect)}</code>".html_safe
36
36
  end
@@ -158,6 +158,9 @@ module ActionView
158
158
  # * <tt>:url</tt> - The URL the form is submitted to. It takes the same
159
159
  # fields you pass to +url_for+ or +link_to+. In particular you may pass
160
160
  # here a named route directly as well. Defaults to the current action.
161
+ # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
162
+ # id attributes on form elements. The namespace attribute will be prefixed
163
+ # with underscore on the generated HTML id.
161
164
  # * <tt>:html</tt> - Optional HTML attributes for the form tag.
162
165
  #
163
166
  # Also note that +form_for+ doesn't create an exclusive scope. It's still
@@ -384,8 +387,8 @@ module ActionView
384
387
  as = options[:as]
385
388
  action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
386
389
  options[:html].reverse_merge!(
387
- :class => as ? "#{as}_#{action}" : dom_class(object, action),
388
- :id => as ? "#{as}_#{action}" : dom_id(object, action),
390
+ :class => as ? "#{action}_#{as}" : dom_class(object, action),
391
+ :id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
389
392
  :method => method
390
393
  )
391
394
 
@@ -518,6 +521,18 @@ module ActionView
518
521
  # end
519
522
  # end
520
523
  #
524
+ # Note that the <tt>projects_attributes=</tt> writer method is in fact
525
+ # required for fields_for to correctly identify <tt>:projects</tt> as a
526
+ # collection, and the correct indices to be set in the form markup.
527
+ #
528
+ # When projects is already an association on Person you can use
529
+ # +accepts_nested_attributes_for+ to define the writer method for you:
530
+ #
531
+ # class Person < ActiveRecord::Base
532
+ # has_many :projects
533
+ # accepts_nested_attributes_for :projects
534
+ # end
535
+ #
521
536
  # This model can now be used with a nested fields_for. The block given to
522
537
  # the nested fields_for call will be repeated for each instance in the
523
538
  # collection:
@@ -848,8 +863,8 @@ module ActionView
848
863
  end
849
864
 
850
865
  # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
851
- # assigned to the template (identified by +object+). Inputs of type "search" may be styled differently by
852
- # some browsers
866
+ # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
867
+ # some browsers.
853
868
  #
854
869
  # ==== Examples
855
870
  #
@@ -868,7 +883,7 @@ module ActionView
868
883
  # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
869
884
  # search_field(:user, :name, :autosave => true, :onsearch => true)
870
885
  # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" />
871
-
886
+ #
872
887
  def search_field(object_name, method, options = {})
873
888
  options = options.stringify_keys
874
889
 
@@ -890,7 +905,7 @@ module ActionView
890
905
  #
891
906
  # telephone_field("user", "phone")
892
907
  # # => <input id="user_phone" name="user[phone]" size="30" type="tel" />
893
-
908
+ #
894
909
  def telephone_field(object_name, method, options = {})
895
910
  InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
896
911
  end
@@ -900,7 +915,7 @@ module ActionView
900
915
  #
901
916
  # url_field("user", "homepage")
902
917
  # # => <input id="user_homepage" size="30" name="user[homepage]" type="url" />
903
-
918
+ #
904
919
  def url_field(object_name, method, options = {})
905
920
  InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
906
921
  end
@@ -909,7 +924,7 @@ module ActionView
909
924
  #
910
925
  # email_field("user", "address")
911
926
  # # => <input id="user_address" size="30" name="user[address]" type="email" />
912
-
927
+ #
913
928
  def email_field(object_name, method, options = {})
914
929
  InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
915
930
  end
@@ -948,7 +963,7 @@ module ActionView
948
963
  end
949
964
 
950
965
  class InstanceTag
951
- include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
966
+ include Helpers::TagHelper, Helpers::FormTagHelper
952
967
 
953
968
  attr_reader :object, :method_name, :object_name
954
969
 
@@ -959,6 +974,7 @@ module ActionView
959
974
  def initialize(object_name, method_name, template_object, object = nil)
960
975
  @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
961
976
  @template_object = template_object
977
+
962
978
  @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
963
979
  @object = retrieve_object(object)
964
980
  @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
@@ -977,10 +993,11 @@ module ActionView
977
993
 
978
994
  add_default_name_and_id_for_value(tag_value, name_and_id)
979
995
  options.delete("index")
996
+ options.delete("namespace")
980
997
  options["for"] ||= name_and_id["id"]
981
998
 
982
999
  if block_given?
983
- label_tag(name_and_id["id"], options, &block)
1000
+ @template_object.label_tag(name_and_id["id"], options, &block)
984
1001
  else
985
1002
  content = if text.blank?
986
1003
  object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
@@ -1023,6 +1040,8 @@ module ActionView
1023
1040
 
1024
1041
  def to_number_field_tag(field_type, options = {})
1025
1042
  options = options.stringify_keys
1043
+ options['size'] ||= nil
1044
+
1026
1045
  if range = options.delete("in") || options.delete("within")
1027
1046
  options.update("min" => range.min, "max" => range.max)
1028
1047
  end
@@ -1181,6 +1200,7 @@ module ActionView
1181
1200
  options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
1182
1201
  options["id"] = options.fetch("id"){ tag_id }
1183
1202
  end
1203
+ options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
1184
1204
  end
1185
1205
 
1186
1206
  def tag_name
@@ -1223,8 +1243,12 @@ module ActionView
1223
1243
  parent_builder.multipart = multipart if parent_builder
1224
1244
  end
1225
1245
 
1226
- def self.model_name
1227
- @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, ''))
1246
+ def self._to_partial_path
1247
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
1248
+ end
1249
+
1250
+ def to_partial_path
1251
+ self.class._to_partial_path
1228
1252
  end
1229
1253
 
1230
1254
  def to_model
@@ -1235,7 +1259,7 @@ module ActionView
1235
1259
  @nested_child_index = {}
1236
1260
  @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
1237
1261
  @parent_builder = options[:parent_builder]
1238
- @default_options = @options ? @options.slice(:index) : {}
1262
+ @default_options = @options ? @options.slice(:index, :namespace) : {}
1239
1263
  if @object_name.to_s.match(/\[\]$/)
1240
1264
  if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
1241
1265
  @auto_index = object.to_param
@@ -1262,6 +1286,7 @@ module ActionView
1262
1286
  fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
1263
1287
  fields_options[:builder] ||= options[:builder]
1264
1288
  fields_options[:parent_builder] = self
1289
+ fields_options[:namespace] = fields_options[:parent_builder].options[:namespace]
1265
1290
 
1266
1291
  case record_name
1267
1292
  when String, Symbol
@@ -1339,6 +1364,39 @@ module ActionView
1339
1364
  @template.submit_tag(value, options)
1340
1365
  end
1341
1366
 
1367
+ # Add the submit button for the given form. When no value is given, it checks
1368
+ # if the object is a new resource or not to create the proper label:
1369
+ #
1370
+ # <%= form_for @post do |f| %>
1371
+ # <%= f.button %>
1372
+ # <% end %>
1373
+ #
1374
+ # In the example above, if @post is a new record, it will use "Create Post" as
1375
+ # submit button label, otherwise, it uses "Update Post".
1376
+ #
1377
+ # Those labels can be customized using I18n, under the helpers.submit key and accept
1378
+ # the %{model} as translation interpolation:
1379
+ #
1380
+ # en:
1381
+ # helpers:
1382
+ # button:
1383
+ # create: "Create a %{model}"
1384
+ # update: "Confirm changes to %{model}"
1385
+ #
1386
+ # It also searches for a key specific for the given object:
1387
+ #
1388
+ # en:
1389
+ # helpers:
1390
+ # button:
1391
+ # post:
1392
+ # create: "Add %{model}"
1393
+ #
1394
+ def button(value=nil, options={})
1395
+ value, options = nil, value if value.is_a?(Hash)
1396
+ value ||= submit_default_value
1397
+ @template.button_tag(value, options)
1398
+ end
1399
+
1342
1400
  def emitted_hidden_id?
1343
1401
  @emitted_hidden_id ||= nil
1344
1402
  end
@@ -105,7 +105,10 @@ module ActionView
105
105
 
106
106
  # Create a select tag and a series of contained option tags for the provided object and method.
107
107
  # The option currently held by the object will be selected, provided that the object is available.
108
- # See options_for_select for the required format of the choices parameter.
108
+ #
109
+ # There are two possible formats for the choices parameter, corresponding to other helpers' output:
110
+ # * A flat collection: see options_for_select
111
+ # * A nested collection: see grouped_options_for_select
109
112
  #
110
113
  # Example with @post.person_id => 1:
111
114
  # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
@@ -125,9 +128,31 @@ module ActionView
125
128
  # This allows the user to submit a form page more than once with the expected results of creating multiple records.
126
129
  # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
127
130
  #
128
- # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
131
+ # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
129
132
  # or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
130
133
  # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
134
+ #
135
+ # ==== Gotcha
136
+ #
137
+ # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
138
+ # web browsers do not send any value to server. Unfortunately this introduces a gotcha:
139
+ # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
140
+ # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
141
+ # any mass-assignment idiom like
142
+ #
143
+ # @user.update_attributes(params[:user])
144
+ #
145
+ # wouldn't update roles.
146
+ #
147
+ # To prevent this the helper generates an auxiliary hidden field before
148
+ # every multiple select. The hidden field has the same name as multiple select and blank value.
149
+ #
150
+ # This way, the client either sends only the hidden field (representing
151
+ # the deselected multiple select box), or both fields. Since the HTML specification
152
+ # says key/value pairs have to be sent in the same order they appear in the
153
+ # form, and parameters extraction gets the last occurrence of any repeated
154
+ # key in the query string, that works for ordinary forms.
155
+ #
131
156
  def select(object, method, choices, options = {}, html_options = {})
132
157
  InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
133
158
  end
@@ -255,7 +280,7 @@ module ActionView
255
280
  # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
256
281
  # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
257
282
  # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
258
- # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
283
+ # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
259
284
  # may also be an array of values to be selected when using a multiple select.
260
285
  #
261
286
  # Examples (call, result):
@@ -406,13 +431,13 @@ module ActionView
406
431
  # wraps them with <tt><optgroup></tt> tags.
407
432
  #
408
433
  # Parameters:
409
- # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
434
+ # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
410
435
  # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
411
436
  # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
412
437
  # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
413
438
  # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
414
439
  # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
415
- # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
440
+ # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
416
441
  # * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this
417
442
  # prepends an option with a generic prompt - "Please select" - or the given prompt string.
418
443
  #
@@ -552,56 +577,62 @@ module ActionView
552
577
  include FormOptionsHelper
553
578
 
554
579
  def to_select_tag(choices, options, html_options)
555
- html_options = html_options.stringify_keys
556
- add_default_name_and_id(html_options)
557
- value = value(object)
558
- selected_value = options.has_key?(:selected) ? options[:selected] : value
559
- disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
560
- content_tag("select", add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options)
580
+ selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
581
+
582
+ # Grouped choices look like this:
583
+ #
584
+ # [nil, []]
585
+ # { nil => [] }
586
+ #
587
+ if !choices.empty? && Array === choices.first.last
588
+ option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
589
+ else
590
+ option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
591
+ end
592
+
593
+ select_content_tag(option_tags, options, html_options)
561
594
  end
562
595
 
563
596
  def to_collection_select_tag(collection, value_method, text_method, options, html_options)
564
- html_options = html_options.stringify_keys
565
- add_default_name_and_id(html_options)
566
- value = value(object)
567
- disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
568
- selected_value = options.has_key?(:selected) ? options[:selected] : value
569
- content_tag(
570
- "select", add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options
597
+ selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
598
+ select_content_tag(
599
+ options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
571
600
  )
572
601
  end
573
602
 
574
603
  def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
575
- html_options = html_options.stringify_keys
576
- add_default_name_and_id(html_options)
577
- value = value(object)
578
- content_tag(
579
- "select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options
604
+ select_content_tag(
605
+ option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
580
606
  )
581
607
  end
582
608
 
583
609
  def to_time_zone_select_tag(priority_zones, options, html_options)
584
- html_options = html_options.stringify_keys
585
- add_default_name_and_id(html_options)
586
- value = value(object)
587
- content_tag("select",
588
- add_options(
589
- time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone),
590
- options, value
591
- ), html_options
610
+ select_content_tag(
611
+ time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
592
612
  )
593
613
  end
594
614
 
595
615
  private
596
616
  def add_options(option_tags, options, value = nil)
597
617
  if options[:include_blank]
598
- option_tags = content_tag('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
618
+ option_tags = "<option value=\"\">#{ERB::Util.html_escape(options[:include_blank]) if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
599
619
  end
600
620
  if value.blank? && options[:prompt]
601
621
  prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
602
- option_tags = content_tag('option', prompt, :value => '') + "\n" + option_tags
622
+ option_tags = "<option value=\"\">#{ERB::Util.html_escape(prompt)}</option>\n" + option_tags
623
+ end
624
+ option_tags.html_safe
625
+ end
626
+
627
+ def select_content_tag(option_tags, options, html_options)
628
+ html_options = html_options.stringify_keys
629
+ add_default_name_and_id(html_options)
630
+ select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
631
+ if html_options["multiple"]
632
+ tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
633
+ else
634
+ select
603
635
  end
604
- option_tags
605
636
  end
606
637
  end
607
638
 
@@ -114,11 +114,11 @@ module ActionView
114
114
  html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
115
115
 
116
116
  if options.delete(:include_blank)
117
- option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags)
117
+ option_tags = "<option value=\"\"></option>".html_safe + option_tags
118
118
  end
119
119
 
120
120
  if prompt = options.delete(:prompt)
121
- option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags)
121
+ option_tags = "<option value=\"\">#{prompt}</option>".html_safe + option_tags
122
122
  end
123
123
 
124
124
  content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
@@ -177,9 +177,12 @@ module ActionView
177
177
  # label_tag 'name', nil, :class => 'small_label'
178
178
  # # => <label for="name" class="small_label">Name</label>
179
179
  def label_tag(name = nil, content_or_options = nil, options = nil, &block)
180
- options = content_or_options if block_given? && content_or_options.is_a?(Hash)
181
- options ||= {}
182
- options.stringify_keys!
180
+ if block_given? && content_or_options.is_a?(Hash)
181
+ options = content_or_options = content_or_options.stringify_keys
182
+ else
183
+ options ||= {}
184
+ options = options.stringify_keys
185
+ end
183
186
  options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
184
187
  content_tag :label, content_or_options || name.to_s.humanize, options, &block
185
188
  end
@@ -204,7 +207,7 @@ module ActionView
204
207
  text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
205
208
  end
206
209
 
207
- # Creates a file upload field. If you are using file uploads then you will also need
210
+ # Creates a file upload field. If you are using file uploads then you will also need
208
211
  # to set the multipart option for the form tag:
209
212
  #
210
213
  # <%= form_tag '/upload', :multipart => true do %>
@@ -304,7 +307,7 @@ module ActionView
304
307
  # text_area_tag 'comment', nil, :class => 'comment_input'
305
308
  # # => <textarea class="comment_input" id="comment" name="comment"></textarea>
306
309
  def text_area_tag(name, content = nil, options = {})
307
- options.stringify_keys!
310
+ options = options.stringify_keys
308
311
 
309
312
  if size = options.delete("size")
310
313
  options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
@@ -407,7 +410,7 @@ module ActionView
407
410
  # data-confirm="Are you sure?" />
408
411
  #
409
412
  def submit_tag(value = "Save changes", options = {})
410
- options.stringify_keys!
413
+ options = options.stringify_keys
411
414
 
412
415
  if disable_with = options.delete("disable_with")
413
416
  options["data-disable-with"] = disable_with
@@ -417,7 +420,7 @@ module ActionView
417
420
  options["data-confirm"] = confirm
418
421
  end
419
422
 
420
- tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
423
+ tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
421
424
  end
422
425
 
423
426
  # Creates a button element that defines a <tt>submit</tt> button,
@@ -458,7 +461,7 @@ module ActionView
458
461
  def button_tag(content_or_options = nil, options = nil, &block)
459
462
  options = content_or_options if block_given? && content_or_options.is_a?(Hash)
460
463
  options ||= {}
461
- options.stringify_keys!
464
+ options = options.stringify_keys
462
465
 
463
466
  if disable_with = options.delete("disable_with")
464
467
  options["data-disable-with"] = disable_with
@@ -497,13 +500,13 @@ module ActionView
497
500
  # image_submit_tag("agree.png", :disabled => true, :class => "agree_disagree_button")
498
501
  # # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
499
502
  def image_submit_tag(source, options = {})
500
- options.stringify_keys!
503
+ options = options.stringify_keys
501
504
 
502
505
  if confirm = options.delete("confirm")
503
506
  options["data-confirm"] = confirm
504
507
  end
505
508
 
506
- tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys)
509
+ tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options)
507
510
  end
508
511
 
509
512
  # Creates a field set for grouping HTML form elements.
@@ -579,7 +582,7 @@ module ActionView
579
582
  #
580
583
  # ==== Examples
581
584
  # number_field_tag 'quantity', nil, :in => 1...10
582
- # => <input id="quantity" name="quantity" min="1" max="9" />
585
+ # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
583
586
  def number_field_tag(name, value = nil, options = {})
584
587
  options = options.stringify_keys
585
588
  options["type"] ||= "number"
@@ -597,6 +600,12 @@ module ActionView
597
600
  number_field_tag(name, value, options.stringify_keys.update("type" => "range"))
598
601
  end
599
602
 
603
+ # Creates the hidden UTF8 enforcer tag. Override this method in a helper
604
+ # to customize the tag.
605
+ def utf8_enforcer_tag
606
+ tag(:input, :type => "hidden", :name => "utf8", :value => "&#x2713;".html_safe)
607
+ end
608
+
600
609
  private
601
610
  def html_options_for_form(url_for_options, options)
602
611
  options.stringify_keys.tap do |html_options|
@@ -611,9 +620,6 @@ module ActionView
611
620
  end
612
621
 
613
622
  def extra_tags_for_form(html_options)
614
- snowman_tag = tag(:input, :type => "hidden",
615
- :name => "utf8", :value => "&#x2713;".html_safe)
616
-
617
623
  authenticity_token = html_options.delete("authenticity_token")
618
624
  method = html_options.delete("method").to_s
619
625
 
@@ -629,7 +635,7 @@ module ActionView
629
635
  tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
630
636
  end
631
637
 
632
- tags = snowman_tag << method_tag
638
+ tags = utf8_enforcer_tag << method_tag
633
639
  content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
634
640
  end
635
641
 
@@ -650,7 +656,7 @@ module ActionView
650
656
  if token == false || !protect_against_forgery?
651
657
  ''
652
658
  else
653
- token = form_authenticity_token if token.nil?
659
+ token ||= form_authenticity_token
654
660
  tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
655
661
  end
656
662
  end