actionpack 3.0.0.beta → 3.0.0.beta2

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 (118) hide show
  1. data/CHANGELOG +291 -260
  2. data/lib/abstract_controller.rb +5 -2
  3. data/lib/abstract_controller/assigns.rb +21 -0
  4. data/lib/abstract_controller/base.rb +13 -5
  5. data/lib/abstract_controller/collector.rb +2 -0
  6. data/lib/abstract_controller/helpers.rb +4 -14
  7. data/lib/abstract_controller/layouts.rb +50 -99
  8. data/lib/abstract_controller/logger.rb +2 -2
  9. data/lib/abstract_controller/rendering.rb +105 -173
  10. data/lib/abstract_controller/view_paths.rb +69 -0
  11. data/lib/action_controller.rb +1 -2
  12. data/lib/action_controller/base.rb +10 -32
  13. data/lib/action_controller/caching.rb +19 -18
  14. data/lib/action_controller/caching/actions.rb +17 -11
  15. data/lib/action_controller/caching/fragments.rb +5 -17
  16. data/lib/action_controller/caching/pages.rb +24 -24
  17. data/lib/action_controller/caching/sweeping.rb +1 -3
  18. data/lib/action_controller/deprecated.rb +0 -2
  19. data/lib/action_controller/deprecated/base.rb +143 -0
  20. data/lib/action_controller/metal.rb +29 -26
  21. data/lib/action_controller/metal/compatibility.rb +18 -87
  22. data/lib/action_controller/metal/cookies.rb +0 -1
  23. data/lib/action_controller/metal/head.rb +1 -0
  24. data/lib/action_controller/metal/helpers.rb +2 -2
  25. data/lib/action_controller/metal/hide_actions.rb +4 -6
  26. data/lib/action_controller/metal/http_authentication.rb +18 -33
  27. data/lib/action_controller/metal/implicit_render.rb +21 -0
  28. data/lib/action_controller/metal/instrumentation.rb +1 -1
  29. data/lib/action_controller/metal/mime_responds.rb +2 -1
  30. data/lib/action_controller/metal/rack_delegation.rb +3 -8
  31. data/lib/action_controller/metal/redirecting.rb +2 -1
  32. data/lib/action_controller/metal/renderers.rb +4 -2
  33. data/lib/action_controller/metal/rendering.rb +31 -44
  34. data/lib/action_controller/metal/request_forgery_protection.rb +41 -4
  35. data/lib/action_controller/metal/responder.rb +2 -0
  36. data/lib/action_controller/metal/session_management.rb +0 -36
  37. data/lib/action_controller/metal/streaming.rb +20 -47
  38. data/lib/action_controller/metal/testing.rb +0 -1
  39. data/lib/action_controller/metal/url_for.rb +11 -148
  40. data/lib/action_controller/middleware.rb +2 -1
  41. data/lib/action_controller/polymorphic_routes.rb +1 -2
  42. data/lib/action_controller/railtie.rb +63 -10
  43. data/lib/action_controller/railties/{subscriber.rb → log_subscriber.rb} +5 -12
  44. data/lib/action_controller/railties/url_helpers.rb +14 -0
  45. data/lib/action_controller/record_identifier.rb +20 -1
  46. data/lib/action_controller/test_case.rb +123 -12
  47. data/lib/action_dispatch.rb +1 -0
  48. data/lib/action_dispatch/http/cache.rb +20 -3
  49. data/lib/action_dispatch/http/filter_parameters.rb +40 -25
  50. data/lib/action_dispatch/http/mime_negotiation.rb +6 -17
  51. data/lib/action_dispatch/http/mime_type.rb +2 -7
  52. data/lib/action_dispatch/http/request.rb +12 -33
  53. data/lib/action_dispatch/http/response.rb +35 -15
  54. data/lib/action_dispatch/http/upload.rb +2 -0
  55. data/lib/action_dispatch/http/url.rb +5 -32
  56. data/lib/action_dispatch/middleware/callbacks.rb +1 -1
  57. data/lib/action_dispatch/middleware/cookies.rb +4 -3
  58. data/lib/action_dispatch/middleware/params_parser.rb +4 -3
  59. data/lib/action_dispatch/middleware/remote_ip.rb +51 -0
  60. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -0
  61. data/lib/action_dispatch/middleware/session/cookie_store.rb +6 -8
  62. data/lib/action_dispatch/middleware/show_exceptions.rb +0 -14
  63. data/lib/action_dispatch/middleware/stack.rb +6 -2
  64. data/lib/action_dispatch/railtie.rb +3 -1
  65. data/lib/action_dispatch/routing.rb +2 -0
  66. data/lib/action_dispatch/routing/deprecated_mapper.rb +35 -7
  67. data/lib/action_dispatch/routing/mapper.rb +134 -48
  68. data/lib/action_dispatch/routing/route.rb +2 -2
  69. data/lib/action_dispatch/routing/route_set.rb +217 -158
  70. data/lib/action_dispatch/routing/url_for.rb +139 -0
  71. data/lib/action_dispatch/testing/assertions/response.rb +14 -61
  72. data/lib/action_dispatch/testing/assertions/routing.rb +25 -14
  73. data/lib/action_dispatch/testing/integration.rb +32 -50
  74. data/lib/action_dispatch/testing/performance_test.rb +3 -1
  75. data/lib/action_dispatch/testing/test_process.rb +2 -0
  76. data/lib/action_dispatch/testing/test_request.rb +2 -0
  77. data/lib/action_pack/version.rb +4 -3
  78. data/lib/action_view.rb +11 -6
  79. data/lib/action_view/base.rb +33 -121
  80. data/lib/action_view/context.rb +0 -2
  81. data/lib/action_view/helpers.rb +26 -23
  82. data/lib/action_view/helpers/active_model_helper.rb +28 -18
  83. data/lib/action_view/helpers/asset_tag_helper.rb +109 -54
  84. data/lib/action_view/helpers/atom_feed_helper.rb +2 -2
  85. data/lib/action_view/helpers/cache_helper.rb +22 -1
  86. data/lib/action_view/helpers/capture_helper.rb +22 -22
  87. data/lib/action_view/helpers/date_helper.rb +6 -5
  88. data/lib/action_view/helpers/form_helper.rb +78 -63
  89. data/lib/action_view/helpers/form_options_helper.rb +6 -4
  90. data/lib/action_view/helpers/form_tag_helper.rb +26 -15
  91. data/lib/action_view/helpers/javascript_helper.rb +90 -10
  92. data/lib/action_view/helpers/number_helper.rb +315 -118
  93. data/lib/action_view/helpers/prototype_helper.rb +19 -46
  94. data/lib/action_view/helpers/record_tag_helper.rb +4 -4
  95. data/lib/action_view/helpers/tag_helper.rb +7 -24
  96. data/lib/action_view/helpers/text_helper.rb +8 -7
  97. data/lib/action_view/helpers/translation_helper.rb +7 -5
  98. data/lib/action_view/helpers/url_helper.rb +19 -16
  99. data/lib/action_view/locale/en.yml +45 -6
  100. data/lib/action_view/lookup_context.rb +190 -0
  101. data/lib/action_view/paths.rb +22 -63
  102. data/lib/action_view/railtie.rb +14 -4
  103. data/lib/action_view/railties/{subscriber.rb → log_subscriber.rb} +1 -1
  104. data/lib/action_view/render/layouts.rb +73 -0
  105. data/lib/action_view/render/partials.rb +15 -41
  106. data/lib/action_view/render/rendering.rb +27 -78
  107. data/lib/action_view/template.rb +20 -24
  108. data/lib/action_view/template/error.rb +22 -2
  109. data/lib/action_view/template/handlers/erb.rb +33 -9
  110. data/lib/action_view/template/handlers/rjs.rb +1 -2
  111. data/lib/action_view/template/resolver.rb +46 -104
  112. data/lib/action_view/template/text.rb +5 -12
  113. data/lib/action_view/test_case.rb +14 -23
  114. metadata +83 -40
  115. data/lib/abstract_controller/compatibility.rb +0 -18
  116. data/lib/abstract_controller/localized_cache.rb +0 -49
  117. data/lib/action_controller/metal/configuration.rb +0 -28
  118. data/lib/action_controller/url_rewriter.rb +0 -76
@@ -1,6 +1,7 @@
1
1
  require 'cgi'
2
2
  require 'action_view/helpers/tag_helper'
3
3
  require 'active_support/core_ext/object/returning'
4
+ require 'active_support/core_ext/object/blank'
4
5
 
5
6
  module ActionView
6
7
  module Helpers
@@ -10,6 +11,11 @@ module ActionView
10
11
  # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
11
12
  # <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>.
12
13
  module FormTagHelper
14
+ extend ActiveSupport::Concern
15
+
16
+ include UrlHelper
17
+ include TextHelper
18
+
13
19
  # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
14
20
  # ActionController::Base#url_for. The method for the form defaults to POST.
15
21
  #
@@ -32,12 +38,12 @@ module ActionView
32
38
  # form_tag('/upload', :multipart => true)
33
39
  # # => <form action="/upload" method="post" enctype="multipart/form-data">
34
40
  #
35
- # <% form_tag('/posts')do -%>
41
+ # <%= form_tag('/posts')do -%>
36
42
  # <div><%= submit_tag 'Save' %></div>
37
43
  # <% end -%>
38
44
  # # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
39
45
  #
40
- # <% form_tag('/posts', :remote => true) %>
46
+ # <%= form_tag('/posts', :remote => true) %>
41
47
  # # => <form action="/posts" method="post" data-remote="true">
42
48
  #
43
49
  def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
@@ -87,12 +93,16 @@ module ActionView
87
93
  # # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
88
94
  # # <option>Paris</option><option>Rome</option></select>
89
95
  def select_tag(name, option_tags = nil, options = {})
96
+ if Array === option_tags
97
+ ActiveSupport::Deprecation.warn 'Passing an array of option_tags to select_tag implicitly joins them without marking them as HTML-safe. Pass option_tags.join.html_safe instead.', caller
98
+ end
99
+
90
100
  html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
91
101
  if blank = options.delete(:include_blank)
92
102
  if blank.kind_of?(String)
93
- option_tags = "<option value=\"\">#{blank}</option>" + option_tags
103
+ option_tags = "<option value=\"\">#{blank}</option>".html_safe + option_tags
94
104
  else
95
- option_tags = "<option value=\"\"></option>" + option_tags
105
+ option_tags = "<option value=\"\"></option>".html_safe + option_tags
96
106
  end
97
107
  end
98
108
  content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
@@ -279,7 +289,7 @@ module ActionView
279
289
  escape = options.key?("escape") ? options.delete("escape") : true
280
290
  content = html_escape(content) if escape
281
291
 
282
- content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
292
+ content_tag :textarea, content.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
283
293
  end
284
294
 
285
295
  # Creates a check box form input tag.
@@ -425,26 +435,26 @@ module ActionView
425
435
  # <tt>options</tt> accept the same values as tag.
426
436
  #
427
437
  # ==== Examples
428
- # <% field_set_tag do %>
438
+ # <%= field_set_tag do %>
429
439
  # <p><%= text_field_tag 'name' %></p>
430
440
  # <% end %>
431
441
  # # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
432
442
  #
433
- # <% field_set_tag 'Your details' do %>
443
+ # <%= field_set_tag 'Your details' do %>
434
444
  # <p><%= text_field_tag 'name' %></p>
435
445
  # <% end %>
436
446
  # # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
437
447
  #
438
- # <% field_set_tag nil, :class => 'format' do %>
448
+ # <%= field_set_tag nil, :class => 'format' do %>
439
449
  # <p><%= text_field_tag 'name' %></p>
440
450
  # <% end %>
441
451
  # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
442
452
  def field_set_tag(legend = nil, options = nil, &block)
443
453
  content = capture(&block)
444
- concat(tag(:fieldset, options, true))
445
- concat(content_tag(:legend, legend)) unless legend.blank?
446
- concat(content)
447
- safe_concat("</fieldset>")
454
+ output = tag(:fieldset, options, true)
455
+ output.safe_concat(content_tag(:legend, legend)) unless legend.blank?
456
+ output.concat(content)
457
+ output.safe_concat("</fieldset>")
448
458
  end
449
459
 
450
460
  private
@@ -477,9 +487,10 @@ module ActionView
477
487
 
478
488
  def form_tag_in_block(html_options, &block)
479
489
  content = capture(&block)
480
- concat(form_tag_html(html_options))
481
- concat(content)
482
- safe_concat("</form>")
490
+ output = ActiveSupport::SafeBuffer.new
491
+ output.safe_concat(form_tag_html(html_options))
492
+ output << content
493
+ output.safe_concat("</form>")
483
494
  end
484
495
 
485
496
  def token_tag
@@ -1,5 +1,4 @@
1
1
  require 'action_view/helpers/tag_helper'
2
- require 'action_view/helpers/prototype_helper'
3
2
 
4
3
  module ActionView
5
4
  module Helpers
@@ -71,7 +70,7 @@ module ActionView
71
70
  #
72
71
  # Instead of passing the content as an argument, you can also use a block
73
72
  # in which case, you pass your +html_options+ as the first parameter.
74
- # <% javascript_tag :defer => 'defer' do -%>
73
+ # <%= javascript_tag :defer => 'defer' do -%>
75
74
  # alert('All is good')
76
75
  # <% end -%>
77
76
  def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
@@ -83,17 +82,98 @@ module ActionView
83
82
  content_or_options_with_block
84
83
  end
85
84
 
86
- tag = content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
87
-
88
- if block_called_from_erb?(block)
89
- concat(tag)
90
- else
91
- tag
92
- end
85
+ content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
93
86
  end
94
87
 
95
88
  def javascript_cdata_section(content) #:nodoc:
96
- "\n//#{cdata_section("\n#{content}\n//")}\n"
89
+ "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
90
+ end
91
+
92
+ # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
93
+ # onclick handler.
94
+ #
95
+ # The first argument +name+ is used as the button's value or display text.
96
+ #
97
+ # The next arguments are optional and may include the javascript function definition and a hash of html_options.
98
+ #
99
+ # The +function+ argument can be omitted in favor of an +update_page+
100
+ # block, which evaluates to a string when the template is rendered
101
+ # (instead of making an Ajax request first).
102
+ #
103
+ # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
104
+ #
105
+ # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
106
+ #
107
+ # Examples:
108
+ # button_to_function "Greeting", "alert('Hello world!')"
109
+ # button_to_function "Delete", "if (confirm('Really?')) do_delete()"
110
+ # button_to_function "Details" do |page|
111
+ # page[:details].visual_effect :toggle_slide
112
+ # end
113
+ # button_to_function "Details", :class => "details_button" do |page|
114
+ # page[:details].visual_effect :toggle_slide
115
+ # end
116
+ def button_to_function(name, *args, &block)
117
+ html_options = args.extract_options!.symbolize_keys
118
+
119
+ function = block_given? ? update_page(&block) : args[0] || ''
120
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
121
+
122
+ tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
123
+ end
124
+
125
+ # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
126
+ # onclick handler and return false after the fact.
127
+ #
128
+ # The first argument +name+ is used as the link text.
129
+ #
130
+ # The next arguments are optional and may include the javascript function definition and a hash of html_options.
131
+ #
132
+ # The +function+ argument can be omitted in favor of an +update_page+
133
+ # block, which evaluates to a string when the template is rendered
134
+ # (instead of making an Ajax request first).
135
+ #
136
+ # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
137
+ #
138
+ # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
139
+ #
140
+ #
141
+ # Examples:
142
+ # link_to_function "Greeting", "alert('Hello world!')"
143
+ # Produces:
144
+ # <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
145
+ #
146
+ # link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
147
+ # Produces:
148
+ # <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
149
+ # <img src="/images/delete.png?" alt="Delete"/>
150
+ # </a>
151
+ #
152
+ # link_to_function("Show me more", nil, :id => "more_link") do |page|
153
+ # page[:details].visual_effect :toggle_blind
154
+ # page[:more_link].replace_html "Show me less"
155
+ # end
156
+ # Produces:
157
+ # <a href="#" id="more_link" onclick="try {
158
+ # $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
159
+ # $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
160
+ # }
161
+ # catch (e) {
162
+ # alert('RJS error:\n\n' + e.toString());
163
+ # alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
164
+ # \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
165
+ # throw e
166
+ # };
167
+ # return false;">Show me more</a>
168
+ #
169
+ def link_to_function(name, *args, &block)
170
+ html_options = args.extract_options!.symbolize_keys
171
+
172
+ function = block_given? ? update_page(&block) : args[0] || ''
173
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
174
+ href = html_options[:href] || '#'
175
+
176
+ content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
97
177
  end
98
178
  end
99
179
  end
@@ -1,12 +1,27 @@
1
1
  require 'active_support/core_ext/big_decimal/conversions'
2
2
  require 'active_support/core_ext/float/rounding'
3
+ require 'active_support/core_ext/object/blank'
3
4
 
4
5
  module ActionView
5
6
  module Helpers #:nodoc:
7
+
6
8
  # Provides methods for converting numbers into formatted strings.
7
9
  # Methods are provided for phone numbers, currency, percentage,
8
- # precision, positional notation, and file size.
10
+ # precision, positional notation, file size and pretty printing.
11
+ #
12
+ # Most methods expect a +number+ argument, and will return it
13
+ # unchanged if can't be converted into a valid number.
9
14
  module NumberHelper
15
+
16
+ # Raised when argument +number+ param given to the helpers is invalid and
17
+ # the option :raise is set to +true+.
18
+ class InvalidNumberError < StandardError
19
+ attr_accessor :number
20
+ def initialize(number)
21
+ @number = number
22
+ end
23
+ end
24
+
10
25
  # Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format
11
26
  # in the +options+ hash.
12
27
  #
@@ -28,27 +43,36 @@ module ActionView
28
43
  # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
29
44
  # => +1.123.555.1234 x 1343
30
45
  def number_to_phone(number, options = {})
31
- number = number.to_s.strip unless number.nil?
46
+ return nil if number.nil?
47
+
48
+ begin
49
+ Float(number)
50
+ is_number_html_safe = true
51
+ rescue ArgumentError, TypeError
52
+ if options[:raise]
53
+ raise InvalidNumberError, number
54
+ else
55
+ is_number_html_safe = number.to_s.html_safe?
56
+ end
57
+ end
58
+
59
+ number = number.to_s.strip
32
60
  options = options.symbolize_keys
33
61
  area_code = options[:area_code] || nil
34
62
  delimiter = options[:delimiter] || "-"
35
63
  extension = options[:extension].to_s.strip || nil
36
64
  country_code = options[:country_code] || nil
37
65
 
38
- begin
39
- str = ""
40
- str << "+#{country_code}#{delimiter}" unless country_code.blank?
41
- str << if area_code
42
- number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3")
43
- else
44
- number.gsub!(/([0-9]{0,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
45
- number.starts_with?('-') ? number.slice!(1..-1) : number
46
- end
47
- str << " x #{extension}" unless extension.blank?
48
- str
49
- rescue
50
- number
66
+ str = ""
67
+ str << "+#{country_code}#{delimiter}" unless country_code.blank?
68
+ str << if area_code
69
+ number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3")
70
+ else
71
+ number.gsub!(/([0-9]{0,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
72
+ number.starts_with?('-') ? number.slice!(1..-1) : number
51
73
  end
74
+ str << " x #{extension}" unless extension.blank?
75
+ is_number_html_safe ? str.html_safe : str
52
76
  end
53
77
 
54
78
  # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format
@@ -74,37 +98,42 @@ module ActionView
74
98
  # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
75
99
  # # => 1234567890,50 &pound;
76
100
  def number_to_currency(number, options = {})
101
+ return nil if number.nil?
102
+
77
103
  options.symbolize_keys!
78
104
 
79
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
80
- currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
105
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
106
+ currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {})
81
107
  defaults = defaults.merge(currency)
82
108
 
83
- precision = options[:precision] || defaults[:precision]
84
- unit = options[:unit] || defaults[:unit]
85
- separator = options[:separator] || defaults[:separator]
86
- delimiter = options[:delimiter] || defaults[:delimiter]
87
- format = options[:format] || defaults[:format]
88
- separator = '' if precision == 0
109
+ options = options.reverse_merge(defaults)
110
+
111
+ unit = options.delete(:unit)
112
+ format = options.delete(:format)
89
113
 
90
114
  begin
91
- format.gsub(/%n/, number_with_precision(number,
92
- :precision => precision,
93
- :delimiter => delimiter,
94
- :separator => separator)
95
- ).gsub(/%u/, unit).html_safe
96
- rescue
97
- number
115
+ value = number_with_precision(number, options.merge(:raise => true))
116
+ format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
117
+ rescue InvalidNumberError => e
118
+ if options[:raise]
119
+ raise
120
+ else
121
+ formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit)
122
+ e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
123
+ end
98
124
  end
125
+
99
126
  end
100
127
 
101
128
  # Formats a +number+ as a percentage string (e.g., 65%). You can customize the
102
129
  # format in the +options+ hash.
103
130
  #
104
131
  # ==== Options
105
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
106
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
132
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
133
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+)
134
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
107
135
  # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
136
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+)
108
137
  #
109
138
  # ==== Examples
110
139
  # number_to_percentage(100) # => 100.000%
@@ -112,23 +141,24 @@ module ActionView
112
141
  # number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000%
113
142
  # number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
114
143
  def number_to_percentage(number, options = {})
144
+ return nil if number.nil?
145
+
115
146
  options.symbolize_keys!
116
147
 
117
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
118
- percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {}
148
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
149
+ percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :default => {})
119
150
  defaults = defaults.merge(percentage)
120
151
 
121
- precision = options[:precision] || defaults[:precision]
122
- separator = options[:separator] || defaults[:separator]
123
- delimiter = options[:delimiter] || defaults[:delimiter]
152
+ options = options.reverse_merge(defaults)
124
153
 
125
154
  begin
126
- number_with_precision(number,
127
- :precision => precision,
128
- :separator => separator,
129
- :delimiter => delimiter) + "%"
130
- rescue
131
- number
155
+ "#{number_with_precision(number, options.merge(:raise => true))}%".html_safe
156
+ rescue InvalidNumberError => e
157
+ if options[:raise]
158
+ raise
159
+ else
160
+ e.number.to_s.html_safe? ? "#{e.number}%".html_safe : "#{e.number}%"
161
+ end
132
162
  end
133
163
  end
134
164
 
@@ -137,7 +167,7 @@ module ActionView
137
167
  #
138
168
  # ==== Options
139
169
  # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
140
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
170
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
141
171
  #
142
172
  # ==== Examples
143
173
  # number_with_delimiter(12345678) # => 12,345,678
@@ -150,142 +180,190 @@ module ActionView
150
180
  # You can still use <tt>number_with_delimiter</tt> with the old API that accepts the
151
181
  # +delimiter+ as its optional second and the +separator+ as its
152
182
  # optional third parameter:
153
- # number_with_delimiter(12345678, " ") # => 12 345.678
183
+ # number_with_delimiter(12345678, " ") # => 12 345 678
154
184
  # number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05
155
185
  def number_with_delimiter(number, *args)
156
186
  options = args.extract_options!
157
187
  options.symbolize_keys!
158
188
 
159
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
189
+ begin
190
+ Float(number)
191
+ rescue ArgumentError, TypeError
192
+ if options[:raise]
193
+ raise InvalidNumberError, number
194
+ else
195
+ return number
196
+ end
197
+ end
198
+
199
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
160
200
 
161
201
  unless args.empty?
162
202
  ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' +
163
203
  'instead of separate delimiter and precision arguments.', caller)
164
- delimiter = args[0] || defaults[:delimiter]
165
- separator = args[1] || defaults[:separator]
204
+ options[:delimiter] ||= args[0] if args[0]
205
+ options[:separator] ||= args[1] if args[1]
166
206
  end
167
207
 
168
- delimiter ||= (options[:delimiter] || defaults[:delimiter])
169
- separator ||= (options[:separator] || defaults[:separator])
208
+ options = options.reverse_merge(defaults)
209
+
210
+ parts = number.to_s.split('.')
211
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
212
+ parts.join(options[:separator]).html_safe
170
213
 
171
- begin
172
- parts = number.to_s.split('.')
173
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
174
- parts.join(separator)
175
- rescue
176
- number
177
- end
178
214
  end
179
215
 
180
- # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision of 2).
216
+ # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision
217
+ # of 2 if +:significant+ is +false+, and 5 if +:significant+ is +true+).
181
218
  # You can customize the format in the +options+ hash.
182
219
  #
183
220
  # ==== Options
184
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
185
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
221
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
222
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+)
223
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
186
224
  # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
225
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+)
187
226
  #
188
227
  # ==== Examples
189
- # number_with_precision(111.2345) # => 111.235
190
- # number_with_precision(111.2345, :precision => 2) # => 111.23
191
- # number_with_precision(13, :precision => 5) # => 13.00000
192
- # number_with_precision(389.32314, :precision => 0) # => 389
228
+ # number_with_precision(111.2345) # => 111.235
229
+ # number_with_precision(111.2345, :precision => 2) # => 111.23
230
+ # number_with_precision(13, :precision => 5) # => 13.00000
231
+ # number_with_precision(389.32314, :precision => 0) # => 389
232
+ # number_with_precision(111.2345, :significant => true) # => 111
233
+ # number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
234
+ # number_with_precision(13, :precision => 5, :significant => true) # => 13.000
235
+ # number_with_precision(13, :precision => 5, :significant => true, strip_insignificant_zeros => true)
236
+ # # => 13
237
+ # number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3
193
238
  # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
194
239
  # # => 1.111,23
195
240
  #
196
241
  # You can still use <tt>number_with_precision</tt> with the old API that accepts the
197
242
  # +precision+ as its optional second parameter:
198
- # number_with_precision(number_with_precision(111.2345, 2) # => 111.23
243
+ # number_with_precision(111.2345, 2) # => 111.23
199
244
  def number_with_precision(number, *args)
245
+
200
246
  options = args.extract_options!
201
247
  options.symbolize_keys!
202
248
 
203
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
204
- precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale],
205
- :raise => true) rescue {}
249
+ number = begin
250
+ Float(number)
251
+ rescue ArgumentError, TypeError
252
+ if options[:raise]
253
+ raise InvalidNumberError, number
254
+ else
255
+ return number
256
+ end
257
+ end
258
+
259
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
260
+ precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {})
206
261
  defaults = defaults.merge(precision_defaults)
207
262
 
263
+ #Backwards compatibility
208
264
  unless args.empty?
209
265
  ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' +
210
266
  'instead of a separate precision argument.', caller)
211
- precision = args[0] || defaults[:precision]
267
+ options[:precision] ||= args[0] if args[0]
212
268
  end
213
269
 
214
- precision ||= (options[:precision] || defaults[:precision])
215
- separator ||= (options[:separator] || defaults[:separator])
216
- delimiter ||= (options[:delimiter] || defaults[:delimiter])
270
+ options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false
271
+ precision = options.delete :precision
272
+ significant = options.delete :significant
273
+ strip_insignificant_zeros = options.delete :strip_insignificant_zeros
217
274
 
218
- begin
219
- rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision
220
- number_with_delimiter("%01.#{precision}f" % rounded_number,
221
- :separator => separator,
222
- :delimiter => delimiter)
223
- rescue
224
- number
275
+ if significant and precision > 0
276
+ if number == 0
277
+ digits, rounded_number = 1, 0
278
+ else
279
+ digits = (Math.log10(number) + 1).floor
280
+ rounded_number = BigDecimal.new((number / 10 ** (digits - precision)).to_s).round.to_f * 10 ** (digits - precision)
281
+ end
282
+ precision = precision - digits
283
+ precision = precision > 0 ? precision : 0 #don't let it be negative
284
+ else
285
+ rounded_number = BigDecimal.new((number * (10 ** precision)).to_s).round.to_f / 10 ** precision
225
286
  end
287
+ formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
288
+ if strip_insignificant_zeros
289
+ escaped_separator = Regexp.escape(options[:separator])
290
+ formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '').html_safe
291
+ else
292
+ formatted_number
293
+ end
294
+
226
295
  end
227
296
 
228
297
  STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
229
298
 
230
- # Formats the bytes in +size+ into a more understandable representation
299
+ # Formats the bytes in +number+ into a more understandable representation
231
300
  # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
232
- # reporting file sizes to users. This method returns nil if
233
- # +size+ cannot be converted into a number. You can customize the
301
+ # reporting file sizes to users. You can customize the
234
302
  # format in the +options+ hash.
235
303
  #
304
+ # See <tt>number_to_human</tt> if you want to pretty-print a generic number.
305
+ #
236
306
  # ==== Options
237
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 1).
238
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
307
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
308
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
309
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
239
310
  # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
240
- #
311
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
241
312
  # ==== Examples
242
313
  # number_to_human_size(123) # => 123 Bytes
243
- # number_to_human_size(1234) # => 1.2 KB
314
+ # number_to_human_size(1234) # => 1.21 KB
244
315
  # number_to_human_size(12345) # => 12.1 KB
245
- # number_to_human_size(1234567) # => 1.2 MB
246
- # number_to_human_size(1234567890) # => 1.1 GB
247
- # number_to_human_size(1234567890123) # => 1.1 TB
248
- # number_to_human_size(1234567, :precision => 2) # => 1.18 MB
249
- # number_to_human_size(483989, :precision => 0) # => 473 KB
250
- # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,18 MB
316
+ # number_to_human_size(1234567) # => 1.18 MB
317
+ # number_to_human_size(1234567890) # => 1.15 GB
318
+ # number_to_human_size(1234567890123) # => 1.12 TB
319
+ # number_to_human_size(1234567, :precision => 2) # => 1.2 MB
320
+ # number_to_human_size(483989, :precision => 2) # => 470 KB
321
+ # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
251
322
  #
252
- # Zeros after the decimal point are always stripped out, regardless of the
253
- # specified precision:
254
- # helper.number_to_human_size(1234567890123, :precision => 5) # => "1.12283 TB"
255
- # helper.number_to_human_size(524288000, :precision=>5) # => "500 MB"
323
+ # Unsignificant zeros after the fractional separator are stripped out by default (set
324
+ # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
325
+ # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
326
+ # number_to_human_size(524288000, :precision=>5) # => "500 MB"
256
327
  #
257
328
  # You can still use <tt>number_to_human_size</tt> with the old API that accepts the
258
329
  # +precision+ as its optional second parameter:
259
- # number_to_human_size(1234567, 2) # => 1.18 MB
260
- # number_to_human_size(483989, 0) # => 473 KB
330
+ # number_to_human_size(1234567, 1) # => 1 MB
331
+ # number_to_human_size(483989, 2) # => 470 KB
261
332
  def number_to_human_size(number, *args)
262
- return nil if number.nil?
263
-
264
333
  options = args.extract_options!
265
334
  options.symbolize_keys!
266
335
 
267
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
268
- human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {}
336
+ number = begin
337
+ Float(number)
338
+ rescue ArgumentError, TypeError
339
+ if options[:raise]
340
+ raise InvalidNumberError, number
341
+ else
342
+ return number
343
+ end
344
+ end
345
+
346
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
347
+ human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
269
348
  defaults = defaults.merge(human)
270
349
 
271
350
  unless args.empty?
272
351
  ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' +
273
352
  'instead of a separate precision argument.', caller)
274
- precision = args[0] || defaults[:precision]
353
+ options[:precision] ||= args[0] if args[0]
275
354
  end
276
355
 
277
- precision ||= (options[:precision] || defaults[:precision])
278
- separator ||= (options[:separator] || defaults[:separator])
279
- delimiter ||= (options[:delimiter] || defaults[:delimiter])
356
+ options = options.reverse_merge(defaults)
357
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
358
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
280
359
 
281
360
  storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
282
361
 
283
362
  if number.to_i < 1024
284
363
  unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
285
- storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
364
+ storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe
286
365
  else
287
366
  max_exp = STORAGE_UNITS.size - 1
288
- number = Float(number)
289
367
  exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
290
368
  exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
291
369
  number /= 1024 ** exponent
@@ -293,19 +371,138 @@ module ActionView
293
371
  unit_key = STORAGE_UNITS[exponent]
294
372
  unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
295
373
 
296
- begin
297
- escaped_separator = Regexp.escape(separator)
298
- formatted_number = number_with_precision(number,
299
- :precision => precision,
300
- :separator => separator,
301
- :delimiter => delimiter
302
- ).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
303
- storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
304
- rescue
305
- number
374
+ formatted_number = number_with_precision(number, options)
375
+ storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe
376
+ end
377
+ end
378
+
379
+ DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
380
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
381
+
382
+ # Pretty prints (formats and approximates) a number in a way it is more readable by humans
383
+ # (eg.: 1200000000 becomes "1.2 Billion"). This is useful for numbers that
384
+ # can get very large (and too hard to read).
385
+ #
386
+ # See <tt>number_to_human_size</tt> if you want to print a file size.
387
+ #
388
+ # You can also define you own unit-quantifier names if you want to use other decimal units
389
+ # (eg.: 1500 becomes "1.5 kilometers", 0.150 becomes "150 mililiters", etc). You may define
390
+ # a wide range of unit quantifiers, even fractional ones (centi, deci, mili, etc).
391
+ #
392
+ # ==== Options
393
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
394
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
395
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
396
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
397
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
398
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a string containing an i18n scope where to find this hash. It might have the following keys:
399
+ # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt>
400
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt>
401
+ # * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are:
402
+ #
403
+ # %u The quantifier (ex.: 'thousand')
404
+ # %n The number
405
+ #
406
+ # ==== Examples
407
+ # number_to_human(123) # => "123"
408
+ # number_to_human(1234) # => "1.23 Thousand"
409
+ # number_to_human(12345) # => "12.3 Thousand"
410
+ # number_to_human(1234567) # => "1.23 Million"
411
+ # number_to_human(1234567890) # => "1.23 Billion"
412
+ # number_to_human(1234567890123) # => "1.23 Trillion"
413
+ # number_to_human(1234567890123456) # => "1.23 Quadrillion"
414
+ # number_to_human(1234567890123456789) # => "1230 Quadrillion"
415
+ # number_to_human(489939, :precision => 2) # => "490 Thousand"
416
+ # number_to_human(489939, :precision => 4) # => "489.9 Thousand"
417
+ # number_to_human(1234567, :precision => 4,
418
+ # :significant => false) # => "1.2346 Million"
419
+ # number_to_human(1234567, :precision => 1,
420
+ # :separator => ',',
421
+ # :significant => false) # => "1,2 Million"
422
+ #
423
+ # Unsignificant zeros after the decimal separator are stripped out by default (set
424
+ # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
425
+ # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion"
426
+ # number_to_human(500000000, :precision=>5) # => "500 Million"
427
+ #
428
+ # ==== Custom Unit Quantifiers
429
+ #
430
+ # You can also use your own custom unit quantifiers:
431
+ # number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt"
432
+ #
433
+ # If in your I18n locale you have:
434
+ # distance:
435
+ # centi:
436
+ # one: "centimeter"
437
+ # other: "centimeters"
438
+ # unit:
439
+ # one: "meter"
440
+ # other: "meters"
441
+ # thousand:
442
+ # one: "kilometer"
443
+ # other: "kilometers"
444
+ # billion: "gazilion-distance"
445
+ #
446
+ # Then you could do:
447
+ #
448
+ # number_to_human(543934, :units => :distance) # => "544 kilometers"
449
+ # number_to_human(54393498, :units => :distance) # => "54400 kilometers"
450
+ # number_to_human(54393498000, :units => :distance) # => "54.4 gazilion-distance"
451
+ # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters"
452
+ # number_to_human(1, :units => :distance) # => "1 meter"
453
+ # number_to_human(0.34, :units => :distance) # => "34 centimeters"
454
+ #
455
+ def number_to_human(number, options = {})
456
+ options.symbolize_keys!
457
+
458
+ number = begin
459
+ Float(number)
460
+ rescue ArgumentError, TypeError
461
+ if options[:raise]
462
+ raise InvalidNumberError, number
463
+ else
464
+ return number
306
465
  end
307
466
  end
467
+
468
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
469
+ human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
470
+ defaults = defaults.merge(human)
471
+
472
+ options = options.reverse_merge(defaults)
473
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
474
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
475
+
476
+ units = options.delete :units
477
+ unit_exponents = case units
478
+ when Hash
479
+ units
480
+ when String, Symbol
481
+ I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
482
+ when nil
483
+ I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true)
484
+ else
485
+ raise ArgumentError, ":units must be a Hash or String translation scope."
486
+ end.keys.map{|e_name| DECIMAL_UNITS.invert[e_name] }.sort_by{|e| -e}
487
+
488
+ number_exponent = Math.log10(number).floor
489
+ display_exponent = unit_exponents.find{|e| number_exponent >= e }
490
+ number /= 10 ** display_exponent
491
+
492
+ unit = case units
493
+ when Hash
494
+ units[DECIMAL_UNITS[display_exponent]]
495
+ when String, Symbol
496
+ I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
497
+ else
498
+ I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
499
+ end
500
+
501
+ decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
502
+ formatted_number = number_with_precision(number, options)
503
+ decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
308
504
  end
505
+
309
506
  end
310
507
  end
311
508
  end