actionpack 3.0.1 → 3.0.2

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 (42) hide show
  1. data/CHANGELOG +7 -1
  2. data/lib/abstract_controller/base.rb +1 -0
  3. data/lib/abstract_controller/callbacks.rb +2 -0
  4. data/lib/abstract_controller/rendering.rb +4 -4
  5. data/lib/action_controller/base.rb +2 -0
  6. data/lib/action_controller/metal.rb +8 -3
  7. data/lib/action_controller/metal/head.rb +2 -4
  8. data/lib/action_controller/metal/http_authentication.rb +8 -10
  9. data/lib/action_controller/metal/mime_responds.rb +1 -1
  10. data/lib/action_controller/metal/redirecting.rb +4 -2
  11. data/lib/action_controller/metal/renderers.rb +1 -1
  12. data/lib/action_controller/metal/responder.rb +20 -0
  13. data/lib/action_controller/test_case.rb +7 -0
  14. data/lib/action_controller/vendor/html-scanner/html/node.rb +5 -11
  15. data/lib/action_dispatch/http/request.rb +19 -2
  16. data/lib/action_dispatch/http/response.rb +9 -10
  17. data/lib/action_dispatch/http/upload.rb +21 -29
  18. data/lib/action_dispatch/middleware/cookies.rb +11 -3
  19. data/lib/action_dispatch/railtie.rb +0 -5
  20. data/lib/action_dispatch/routing.rb +75 -22
  21. data/lib/action_dispatch/routing/mapper.rb +429 -60
  22. data/lib/action_dispatch/routing/polymorphic_routes.rb +1 -1
  23. data/lib/action_dispatch/routing/route.rb +2 -1
  24. data/lib/action_dispatch/routing/url_for.rb +4 -5
  25. data/lib/action_dispatch/testing/integration.rb +9 -7
  26. data/lib/action_pack/version.rb +2 -2
  27. data/lib/action_view/base.rb +1 -1
  28. data/lib/action_view/helpers/asset_tag_helper.rb +1 -1
  29. data/lib/action_view/helpers/capture_helper.rb +2 -1
  30. data/lib/action_view/helpers/date_helper.rb +2 -0
  31. data/lib/action_view/helpers/form_helper.rb +15 -12
  32. data/lib/action_view/helpers/form_options_helper.rb +5 -5
  33. data/lib/action_view/helpers/javascript_helper.rb +2 -1
  34. data/lib/action_view/helpers/number_helper.rb +23 -12
  35. data/lib/action_view/helpers/prototype_helper.rb +4 -4
  36. data/lib/action_view/helpers/text_helper.rb +18 -0
  37. data/lib/action_view/helpers/url_helper.rb +12 -10
  38. data/lib/action_view/render/partials.rb +52 -8
  39. data/lib/action_view/render/rendering.rb +1 -1
  40. data/lib/action_view/template/handlers/erb.rb +6 -1
  41. data/lib/action_view/test_case.rb +26 -10
  42. metadata +35 -12
@@ -17,7 +17,7 @@ module ActionDispatch
17
17
  #
18
18
  # == Usage within the framework
19
19
  #
20
- # Polymorphic URL helpers are used in a number of places throughout the Rails framework:
20
+ # Polymorphic URL helpers are used in a number of places throughout the \Rails framework:
21
21
  #
22
22
  # * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
23
23
  # <tt>url_for(@article)</tt>;
@@ -34,7 +34,8 @@ module ActionDispatch
34
34
  if method = conditions[:request_method]
35
35
  case method
36
36
  when Regexp
37
- method.source.upcase
37
+ source = method.source.upcase
38
+ source =~ /\A\^[-A-Z|]+\$\Z/ ? source[1..-2] : source
38
39
  else
39
40
  method.to_s.upcase
40
41
  end
@@ -1,6 +1,6 @@
1
1
  module ActionDispatch
2
2
  module Routing
3
- # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
3
+ # In <tt>config/routes.rb</tt> you define URL-to-controller mappings, but the reverse
4
4
  # is also possible: an URL can be generated from one of your routing definitions.
5
5
  # URL generation functionality is centralized in this module.
6
6
  #
@@ -12,15 +12,14 @@ module ActionDispatch
12
12
  #
13
13
  # == URL generation from parameters
14
14
  #
15
- # As you may know, some functions - such as ActionController::Base#url_for
15
+ # As you may know, some functions, such as ActionController::Base#url_for
16
16
  # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
17
17
  # of parameters. For example, you've probably had the chance to write code
18
18
  # like this in one of your views:
19
19
  #
20
20
  # <%= link_to('Click here', :controller => 'users',
21
21
  # :action => 'new', :message => 'Welcome!') %>
22
- #
23
- # # Generates a link to /users/new?message=Welcome%21
22
+ # # => "/users/new?message=Welcome%21"
24
23
  #
25
24
  # link_to, and all other functions that require URL generation functionality,
26
25
  # actually use ActionController::UrlFor under the hood. And in particular,
@@ -61,7 +60,7 @@ module ActionDispatch
61
60
  #
62
61
  # UrlFor also allows one to access methods that have been auto-generated from
63
62
  # named routes. For example, suppose that you have a 'users' resource in your
64
- # <b>routes.rb</b>:
63
+ # <tt>config/routes.rb</tt>:
65
64
  #
66
65
  # resources :users
67
66
  #
@@ -115,8 +115,8 @@ module ActionDispatch
115
115
  end
116
116
  end
117
117
 
118
- # An integration Session instance represents a set of requests and responses
119
- # performed sequentially by some virtual user. Because you can instantiate
118
+ # An instance of this class represents a set of requests and responses
119
+ # performed sequentially by a test process. Because you can instantiate
120
120
  # multiple sessions and run them side-by-side, you can also mimic (to some
121
121
  # limited extent) multiple simultaneous users interacting with your system.
122
122
  #
@@ -257,12 +257,14 @@ module ActionDispatch
257
257
  end
258
258
  end
259
259
 
260
+ hostname, port = host.split(':')
261
+
260
262
  env = {
261
263
  :method => method,
262
264
  :params => parameters,
263
265
 
264
- "SERVER_NAME" => host.split(':')[0],
265
- "SERVER_PORT" => (https? ? "443" : "80"),
266
+ "SERVER_NAME" => hostname,
267
+ "SERVER_PORT" => port || (https? ? "443" : "80"),
266
268
  "HTTPS" => https? ? "on" : "off",
267
269
  "rack.url_scheme" => https? ? "https" : "http",
268
270
 
@@ -373,12 +375,12 @@ module ActionDispatch
373
375
  end
374
376
  end
375
377
 
376
- # An IntegrationTest is one that spans multiple controllers and actions,
378
+ # An test that spans multiple controllers and actions,
377
379
  # tying them all together to ensure they work together as expected. It tests
378
380
  # more completely than either unit or functional tests do, exercising the
379
381
  # entire stack, from the dispatcher to the database.
380
382
  #
381
- # At its simplest, you simply extend IntegrationTest and write your tests
383
+ # At its simplest, you simply extend <tt>IntegrationTest</tt> and write your tests
382
384
  # using the get/post methods:
383
385
  #
384
386
  # require "test_helper"
@@ -403,7 +405,7 @@ module ActionDispatch
403
405
  # However, you can also have multiple session instances open per test, and
404
406
  # even extend those instances with assertions and methods to create a very
405
407
  # powerful testing DSL that is specific for your application. You can even
406
- # reference any named routes you happen to have defined!
408
+ # reference any named routes you happen to have defined.
407
409
  #
408
410
  # require "test_helper"
409
411
  #
@@ -2,8 +2,8 @@ module ActionPack
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 3
4
4
  MINOR = 0
5
- TINY = 1
6
-
5
+ TINY = 2
6
+
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
9
9
  end
@@ -156,7 +156,7 @@ module ActionView #:nodoc:
156
156
  #
157
157
  # This refreshes the sidebar, removes a person element and highlights the user list.
158
158
  #
159
- # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
159
+ # See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods documentation for more details.
160
160
  class Base
161
161
  module Subclasses
162
162
  end
@@ -836,7 +836,7 @@ module ActionView
836
836
 
837
837
  def expand_javascript_sources(sources, recursive = false)
838
838
  if sources.include?(:all)
839
- all_javascript_files = collect_asset_files(config.javascripts_dir, ('**' if recursive), '*.js')
839
+ all_javascript_files = (collect_asset_files(config.javascripts_dir, ('**' if recursive), '*.js') - ['application']) << 'application'
840
840
  ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
841
841
  else
842
842
  expanded_sources = sources.collect do |source|
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/object/blank'
2
+ require 'active_support/core_ext/string/output_safety'
2
3
 
3
4
  module ActionView
4
5
  # = Action View Capture Helper
@@ -38,7 +39,7 @@ module ActionView
38
39
  value = nil
39
40
  buffer = with_output_buffer { value = yield(*args) }
40
41
  if string = buffer.presence || value and string.is_a?(String)
41
- NonConcattingString.new(string)
42
+ NonConcattingString.new(ERB::Util.html_escape(string))
42
43
  end
43
44
  end
44
45
 
@@ -893,6 +893,8 @@ module ActionView
893
893
  # Returns the separator for a given datetime component
894
894
  def separator(type)
895
895
  case type
896
+ when :year
897
+ @options[:discard_year] ? "" : @options[:date_separator]
896
898
  when :month
897
899
  @options[:discard_month] ? "" : @options[:date_separator]
898
900
  when :day
@@ -202,6 +202,12 @@ module ActionView
202
202
  # ...
203
203
  # <% end %>
204
204
  #
205
+ # You can also set the answer format, like this:
206
+ #
207
+ # <%= form_for(@post, :format => :json) do |f| %>
208
+ # ...
209
+ # <% end %>
210
+ #
205
211
  # If you have an object that needs to be represented as a different
206
212
  # parameter, like a Client that acts as a Person:
207
213
  #
@@ -332,7 +338,9 @@ module ActionView
332
338
 
333
339
  options[:html] ||= {}
334
340
  options[:html].reverse_merge!(html_options)
335
- options[:url] ||= polymorphic_path(object_or_array)
341
+ options[:url] ||= options[:format] ? \
342
+ polymorphic_path(object_or_array, :format => options.delete(:format)) : \
343
+ polymorphic_path(object_or_array)
336
344
  end
337
345
 
338
346
  # Creates a scope around a specific model object like form_for, but
@@ -803,7 +811,7 @@ module ActionView
803
811
  options["incremental"] = true unless options.has_key?("incremental")
804
812
  end
805
813
 
806
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("search", options)
814
+ InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
807
815
  end
808
816
 
809
817
  # Returns a text_field of type "tel".
@@ -1006,14 +1014,9 @@ module ActionView
1006
1014
 
1007
1015
  def value_before_type_cast(object, method_name)
1008
1016
  unless object.nil?
1009
- if object.respond_to?(method_name)
1010
- object.send(method_name)
1011
- # FIXME: this is AR dependent
1012
- elsif object.respond_to?(method_name + "_before_type_cast")
1013
- object.send(method_name + "_before_type_cast")
1014
- else
1015
- raise NoMethodError, "Model #{object.class} does not respond to #{method_name}"
1016
- end
1017
+ object.respond_to?(method_name + "_before_type_cast") ?
1018
+ object.send(method_name + "_before_type_cast") :
1019
+ object.send(method_name)
1017
1020
  end
1018
1021
  end
1019
1022
 
@@ -1258,11 +1261,11 @@ module ActionView
1258
1261
 
1259
1262
  if association.respond_to?(:persisted?)
1260
1263
  association = [association] if @object.send(association_name).is_a?(Array)
1261
- elsif !association.is_a?(Array)
1264
+ elsif !association.respond_to?(:to_ary)
1262
1265
  association = @object.send(association_name)
1263
1266
  end
1264
1267
 
1265
- if association.is_a?(Array)
1268
+ if association.respond_to?(:to_ary)
1266
1269
  explicit_child_index = options[:child_index]
1267
1270
  output = ActiveSupport::SafeBuffer.new
1268
1271
  association.each do |child|
@@ -395,12 +395,12 @@ module ActionView
395
395
  # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
396
396
  # wrap the output in an appropriate <tt><select></tt> tag.
397
397
  def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
398
- collection.inject("") do |options_for_select, group|
398
+ collection.map do |group|
399
399
  group_label_string = eval("group.#{group_label_method}")
400
- options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
401
- options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
402
- options_for_select += '</optgroup>'
403
- end.html_safe
400
+ "<optgroup label=\"#{html_escape(group_label_string)}\">" +
401
+ options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key) +
402
+ '</optgroup>'
403
+ end.join.html_safe
404
404
  end
405
405
 
406
406
  # Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
@@ -65,7 +65,8 @@ module ActionView
65
65
  # //]]>
66
66
  # </script>
67
67
  #
68
- # +html_options+ may be a hash of attributes for the <script> tag. Example:
68
+ # +html_options+ may be a hash of attributes for the <tt>\<script></tt> tag.
69
+ # Example:
69
70
  # javascript_tag "alert('All is good')", :defer => 'defer'
70
71
  # # => <script defer="defer" type="text/javascript">alert('All is good')</script>
71
72
  #
@@ -14,7 +14,7 @@ module ActionView
14
14
  # unchanged if can't be converted into a valid number.
15
15
  module NumberHelper
16
16
 
17
- DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :unit => "$", :separator => ".", :delimiter => ",",
17
+ DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
18
18
  :precision => 2, :significant => false, :strip_insignificant_zeros => false }
19
19
 
20
20
  # Raised when argument +number+ param given to the helpers is invalid and
@@ -83,15 +83,18 @@ module ActionView
83
83
  # in the +options+ hash.
84
84
  #
85
85
  # ==== Options
86
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
87
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
88
- # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
89
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
90
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
91
- # * <tt>:format</tt> - Sets the format of the output string (defaults to "%u%n"). The field types are:
92
- #
93
- # %u The currency unit
94
- # %n The number
86
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
87
+ # * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
88
+ # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
89
+ # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
90
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
91
+ # * <tt>:format</tt> - Sets the format for non-negative numbers (defaults to "%u%n").
92
+ # Fields are <tt>%u</tt> for the currency, and <tt>%n</tt>
93
+ # for the number.
94
+ # * <tt>:negative_format</tt> - Sets the format for negative numbers (defaults to prepending
95
+ # an hyphen to the formatted number given by <tt>:format</tt>).
96
+ # Accepts the same fields than <tt>:format</tt>, except
97
+ # <tt>%n</tt> is here the absolute value of the number.
95
98
  #
96
99
  # ==== Examples
97
100
  # number_to_currency(1234567890.50) # => $1,234,567,890.50
@@ -99,6 +102,8 @@ module ActionView
99
102
  # number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
100
103
  # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,506 €
101
104
  #
105
+ # number_to_currency(1234567890.50, :negative_format => "(%u%n)")
106
+ # # => ($1,234,567,890.51)
102
107
  # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
103
108
  # # => &pound;1234567890,50
104
109
  # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
@@ -112,11 +117,17 @@ module ActionView
112
117
  currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {})
113
118
 
114
119
  defaults = DEFAULT_CURRENCY_VALUES.merge(defaults).merge!(currency)
120
+ defaults[:negative_format] = "-" + options[:format] if options[:format]
115
121
  options = defaults.merge!(options)
116
122
 
117
123
  unit = options.delete(:unit)
118
124
  format = options.delete(:format)
119
125
 
126
+ if number.to_f < 0
127
+ format = options.delete(:negative_format)
128
+ number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
129
+ end
130
+
120
131
  begin
121
132
  value = number_with_precision(number, options.merge(:raise => true))
122
133
  format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
@@ -260,7 +271,7 @@ module ActionView
260
271
  if number == 0
261
272
  digits, rounded_number = 1, 0
262
273
  else
263
- digits = (Math.log10(number) + 1).floor
274
+ digits = (Math.log10(number.abs) + 1).floor
264
275
  rounded_number = BigDecimal.new((number / 10 ** (digits - precision)).to_s).round.to_f * 10 ** (digits - precision)
265
276
  end
266
277
  precision = precision - digits
@@ -459,7 +470,7 @@ module ActionView
459
470
  raise ArgumentError, ":units must be a Hash or String translation scope."
460
471
  end.keys.map{|e_name| DECIMAL_UNITS.invert[e_name] }.sort_by{|e| -e}
461
472
 
462
- number_exponent = Math.log10(number).floor
473
+ number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
463
474
  display_exponent = unit_exponents.find{|e| number_exponent >= e }
464
475
  number /= 10 ** display_exponent
465
476
 
@@ -161,7 +161,7 @@ module ActionView
161
161
 
162
162
  # JavaScriptGenerator generates blocks of JavaScript code that allow you
163
163
  # to change the content and presentation of multiple DOM elements. Use
164
- # this in your Ajax response bodies, either in a <script> tag or as plain
164
+ # this in your Ajax response bodies, either in a <tt>\<script></tt> tag or as plain
165
165
  # JavaScript sent with a Content-type of "text/javascript".
166
166
  #
167
167
  # Create new instances with PrototypeHelper#update_page or with
@@ -224,7 +224,7 @@ module ActionView
224
224
  #
225
225
  # You can also use PrototypeHelper#update_page_tag instead of
226
226
  # PrototypeHelper#update_page to wrap the generated JavaScript in a
227
- # <script> tag.
227
+ # <tt>\<script></tt> tag.
228
228
  module GeneratorMethods
229
229
  def to_s #:nodoc:
230
230
  (@lines * $/).tap do |javascript|
@@ -582,11 +582,11 @@ module ActionView
582
582
  JavaScriptGenerator.new(view_context, &block).to_s.html_safe
583
583
  end
584
584
 
585
- # Works like update_page but wraps the generated JavaScript in a <script>
585
+ # Works like update_page but wraps the generated JavaScript in a <tt>\<script></tt>
586
586
  # tag. Use this to include generated JavaScript in an ERb template.
587
587
  # See JavaScriptGenerator for more information.
588
588
  #
589
- # +html_options+ may be a hash of <script> attributes to be passed
589
+ # +html_options+ may be a hash of <tt>\<script></tt> attributes to be passed
590
590
  # to ActionView::Helpers::JavaScriptHelper#javascript_tag.
591
591
  def update_page_tag(html_options = {}, &block)
592
592
  javascript_tag update_page(&block), html_options
@@ -9,6 +9,24 @@ module ActionView
9
9
  # and transforming strings, which can reduce the amount of inline Ruby code in
10
10
  # your views. These helper methods extend Action View making them callable
11
11
  # within your template files.
12
+ #
13
+ # ==== Sanitization
14
+ #
15
+ # Most text helpers by default sanitize the given content, but do not escape it.
16
+ # This means HTML tags will appear in the page but all malicious code will be removed.
17
+ # Let's look at some examples using the +simple_format+ method:
18
+ #
19
+ # simple_format('<a href="http://example.com/">Example</a>')
20
+ # # => "<p><a href=\"http://example.com/\">Example</a></p>"
21
+ #
22
+ # simple_format('<a href="javascript:alert('no!')">Example</a>')
23
+ # # => "<p><a>Example</a></p>"
24
+ #
25
+ # If you want to escape all content, you should invoke the +h+ method before
26
+ # calling the text helper.
27
+ #
28
+ # simple_format h('<a href="http://example.com/">Example</a>')
29
+ # # => "<p>&lt;a href=\"http://example.com/\"&gt;Example&lt;/a&gt;</p>"
12
30
  module TextHelper
13
31
  extend ActiveSupport::Concern
14
32
 
@@ -13,7 +13,7 @@ module ActionView
13
13
  module UrlHelper
14
14
  # This helper may be included in any class that includes the
15
15
  # URL helpers of a routes (routes.url_helpers). Some methods
16
- # provided here will only work in the4 context of a request
16
+ # provided here will only work in the context of a request
17
17
  # (link_to_unless_current, for instance), which must be provided
18
18
  # as a method called #request on the context.
19
19
 
@@ -235,13 +235,8 @@ module ActionView
235
235
  html_options = convert_options_to_data_attributes(options, html_options)
236
236
  url = url_for(options)
237
237
 
238
- if html_options
239
- html_options = html_options.stringify_keys
240
- href = html_options['href']
241
- tag_options = tag_options(html_options)
242
- else
243
- tag_options = nil
244
- end
238
+ href = html_options['href']
239
+ tag_options = tag_options(html_options)
245
240
 
246
241
  href_attr = "href=\"#{html_escape(url)}\"" unless href
247
242
  "<a #{href_attr}#{tag_options}>#{html_escape(name || url)}</a>".html_safe
@@ -269,8 +264,9 @@ module ActionView
269
264
  # The +options+ hash accepts the same options as url_for.
270
265
  #
271
266
  # There are a few special +html_options+:
272
- # * <tt>:method</tt> - Specifies the anchor name to be appended to the path.
273
- # * <tt>:disabled</tt> - Specifies the anchor name to be appended to the path.
267
+ # * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
268
+ # <tt>:delete</tt> and <tt>:put</tt>. By default it will be <tt>:post</tt>.
269
+ # * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
274
270
  # * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
275
271
  # prompt with the question specified. If the user accepts, the link is
276
272
  # processed normally, otherwise no action is taken.
@@ -593,6 +589,7 @@ module ActionView
593
589
  html_options['data-remote'] = 'true'
594
590
  end
595
591
 
592
+ disable_with = html_options.delete("disable_with")
596
593
  confirm = html_options.delete("confirm")
597
594
 
598
595
  if html_options.key?("popup")
@@ -601,6 +598,7 @@ module ActionView
601
598
 
602
599
  method, href = html_options.delete("method"), html_options['href']
603
600
 
601
+ add_disable_with_to_attributes!(html_options, disable_with) if disable_with
604
602
  add_confirm_to_attributes!(html_options, confirm) if confirm
605
603
  add_method_to_attributes!(html_options, method) if method
606
604
 
@@ -611,6 +609,10 @@ module ActionView
611
609
  html_options["data-confirm"] = confirm if confirm
612
610
  end
613
611
 
612
+ def add_disable_with_to_attributes!(html_options, disable_with)
613
+ html_options["data-disable-with"] = disable_with if disable_with
614
+ end
615
+
614
616
  def add_method_to_attributes!(html_options, method)
615
617
  html_options["rel"] = "nofollow" if method && method.to_s.downcase != "get"
616
618
  html_options["data-method"] = method if method