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.
- data/CHANGELOG +291 -260
- data/lib/abstract_controller.rb +5 -2
- data/lib/abstract_controller/assigns.rb +21 -0
- data/lib/abstract_controller/base.rb +13 -5
- data/lib/abstract_controller/collector.rb +2 -0
- data/lib/abstract_controller/helpers.rb +4 -14
- data/lib/abstract_controller/layouts.rb +50 -99
- data/lib/abstract_controller/logger.rb +2 -2
- data/lib/abstract_controller/rendering.rb +105 -173
- data/lib/abstract_controller/view_paths.rb +69 -0
- data/lib/action_controller.rb +1 -2
- data/lib/action_controller/base.rb +10 -32
- data/lib/action_controller/caching.rb +19 -18
- data/lib/action_controller/caching/actions.rb +17 -11
- data/lib/action_controller/caching/fragments.rb +5 -17
- data/lib/action_controller/caching/pages.rb +24 -24
- data/lib/action_controller/caching/sweeping.rb +1 -3
- data/lib/action_controller/deprecated.rb +0 -2
- data/lib/action_controller/deprecated/base.rb +143 -0
- data/lib/action_controller/metal.rb +29 -26
- data/lib/action_controller/metal/compatibility.rb +18 -87
- data/lib/action_controller/metal/cookies.rb +0 -1
- data/lib/action_controller/metal/head.rb +1 -0
- data/lib/action_controller/metal/helpers.rb +2 -2
- data/lib/action_controller/metal/hide_actions.rb +4 -6
- data/lib/action_controller/metal/http_authentication.rb +18 -33
- data/lib/action_controller/metal/implicit_render.rb +21 -0
- data/lib/action_controller/metal/instrumentation.rb +1 -1
- data/lib/action_controller/metal/mime_responds.rb +2 -1
- data/lib/action_controller/metal/rack_delegation.rb +3 -8
- data/lib/action_controller/metal/redirecting.rb +2 -1
- data/lib/action_controller/metal/renderers.rb +4 -2
- data/lib/action_controller/metal/rendering.rb +31 -44
- data/lib/action_controller/metal/request_forgery_protection.rb +41 -4
- data/lib/action_controller/metal/responder.rb +2 -0
- data/lib/action_controller/metal/session_management.rb +0 -36
- data/lib/action_controller/metal/streaming.rb +20 -47
- data/lib/action_controller/metal/testing.rb +0 -1
- data/lib/action_controller/metal/url_for.rb +11 -148
- data/lib/action_controller/middleware.rb +2 -1
- data/lib/action_controller/polymorphic_routes.rb +1 -2
- data/lib/action_controller/railtie.rb +63 -10
- data/lib/action_controller/railties/{subscriber.rb → log_subscriber.rb} +5 -12
- data/lib/action_controller/railties/url_helpers.rb +14 -0
- data/lib/action_controller/record_identifier.rb +20 -1
- data/lib/action_controller/test_case.rb +123 -12
- data/lib/action_dispatch.rb +1 -0
- data/lib/action_dispatch/http/cache.rb +20 -3
- data/lib/action_dispatch/http/filter_parameters.rb +40 -25
- data/lib/action_dispatch/http/mime_negotiation.rb +6 -17
- data/lib/action_dispatch/http/mime_type.rb +2 -7
- data/lib/action_dispatch/http/request.rb +12 -33
- data/lib/action_dispatch/http/response.rb +35 -15
- data/lib/action_dispatch/http/upload.rb +2 -0
- data/lib/action_dispatch/http/url.rb +5 -32
- data/lib/action_dispatch/middleware/callbacks.rb +1 -1
- data/lib/action_dispatch/middleware/cookies.rb +4 -3
- data/lib/action_dispatch/middleware/params_parser.rb +4 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +51 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +6 -8
- data/lib/action_dispatch/middleware/show_exceptions.rb +0 -14
- data/lib/action_dispatch/middleware/stack.rb +6 -2
- data/lib/action_dispatch/railtie.rb +3 -1
- data/lib/action_dispatch/routing.rb +2 -0
- data/lib/action_dispatch/routing/deprecated_mapper.rb +35 -7
- data/lib/action_dispatch/routing/mapper.rb +134 -48
- data/lib/action_dispatch/routing/route.rb +2 -2
- data/lib/action_dispatch/routing/route_set.rb +217 -158
- data/lib/action_dispatch/routing/url_for.rb +139 -0
- data/lib/action_dispatch/testing/assertions/response.rb +14 -61
- data/lib/action_dispatch/testing/assertions/routing.rb +25 -14
- data/lib/action_dispatch/testing/integration.rb +32 -50
- data/lib/action_dispatch/testing/performance_test.rb +3 -1
- data/lib/action_dispatch/testing/test_process.rb +2 -0
- data/lib/action_dispatch/testing/test_request.rb +2 -0
- data/lib/action_pack/version.rb +4 -3
- data/lib/action_view.rb +11 -6
- data/lib/action_view/base.rb +33 -121
- data/lib/action_view/context.rb +0 -2
- data/lib/action_view/helpers.rb +26 -23
- data/lib/action_view/helpers/active_model_helper.rb +28 -18
- data/lib/action_view/helpers/asset_tag_helper.rb +109 -54
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -2
- data/lib/action_view/helpers/cache_helper.rb +22 -1
- data/lib/action_view/helpers/capture_helper.rb +22 -22
- data/lib/action_view/helpers/date_helper.rb +6 -5
- data/lib/action_view/helpers/form_helper.rb +78 -63
- data/lib/action_view/helpers/form_options_helper.rb +6 -4
- data/lib/action_view/helpers/form_tag_helper.rb +26 -15
- data/lib/action_view/helpers/javascript_helper.rb +90 -10
- data/lib/action_view/helpers/number_helper.rb +315 -118
- data/lib/action_view/helpers/prototype_helper.rb +19 -46
- data/lib/action_view/helpers/record_tag_helper.rb +4 -4
- data/lib/action_view/helpers/tag_helper.rb +7 -24
- data/lib/action_view/helpers/text_helper.rb +8 -7
- data/lib/action_view/helpers/translation_helper.rb +7 -5
- data/lib/action_view/helpers/url_helper.rb +19 -16
- data/lib/action_view/locale/en.yml +45 -6
- data/lib/action_view/lookup_context.rb +190 -0
- data/lib/action_view/paths.rb +22 -63
- data/lib/action_view/railtie.rb +14 -4
- data/lib/action_view/railties/{subscriber.rb → log_subscriber.rb} +1 -1
- data/lib/action_view/render/layouts.rb +73 -0
- data/lib/action_view/render/partials.rb +15 -41
- data/lib/action_view/render/rendering.rb +27 -78
- data/lib/action_view/template.rb +20 -24
- data/lib/action_view/template/error.rb +22 -2
- data/lib/action_view/template/handlers/erb.rb +33 -9
- data/lib/action_view/template/handlers/rjs.rb +1 -2
- data/lib/action_view/template/resolver.rb +46 -104
- data/lib/action_view/template/text.rb +5 -12
- data/lib/action_view/test_case.rb +14 -23
- metadata +83 -40
- data/lib/abstract_controller/compatibility.rb +0 -18
- data/lib/abstract_controller/localized_cache.rb +0 -49
- data/lib/action_controller/metal/configuration.rb +0 -28
- 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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
445
|
-
|
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
|
-
|
481
|
-
|
482
|
-
|
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
|
-
#
|
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
|
-
|
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
|
+
# $("details").visualEffect("toggle_blind");
|
159
|
+
# $("more_link").update("Show me less");
|
160
|
+
# }
|
161
|
+
# catch (e) {
|
162
|
+
# alert('RJS error:\n\n' + e.toString());
|
163
|
+
# alert('$(\"details\").visualEffect(\"toggle_blind\");
|
164
|
+
# \n$(\"more_link\").update(\"Show me less\");');
|
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
|
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
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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 => "£", :separator => ",", :delimiter => "", :format => "%n %u")
|
75
99
|
# # => 1234567890,50 £
|
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], :
|
80
|
-
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
106
|
-
# * <tt>:
|
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], :
|
118
|
-
percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :
|
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
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
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
|
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
|
-
|
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
|
165
|
-
separator
|
204
|
+
options[:delimiter] ||= args[0] if args[0]
|
205
|
+
options[:separator] ||= args[1] if args[1]
|
166
206
|
end
|
167
207
|
|
168
|
-
|
169
|
-
|
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
|
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
|
185
|
-
# * <tt>:
|
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)
|
190
|
-
# number_with_precision(111.2345, :precision => 2)
|
191
|
-
# number_with_precision(13, :precision => 5)
|
192
|
-
# number_with_precision(389.32314, :precision => 0)
|
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(
|
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
|
-
|
204
|
-
|
205
|
-
|
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
|
267
|
+
options[:precision] ||= args[0] if args[0]
|
212
268
|
end
|
213
269
|
|
214
|
-
|
215
|
-
|
216
|
-
|
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
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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 +
|
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.
|
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
|
238
|
-
# * <tt>:
|
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.
|
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.
|
246
|
-
# number_to_human_size(1234567890) # => 1.
|
247
|
-
# number_to_human_size(1234567890123) # => 1.
|
248
|
-
# number_to_human_size(1234567, :precision => 2) # => 1.
|
249
|
-
# number_to_human_size(483989, :precision =>
|
250
|
-
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,
|
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
|
-
#
|
253
|
-
#
|
254
|
-
#
|
255
|
-
#
|
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,
|
260
|
-
# number_to_human_size(483989,
|
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
|
-
|
268
|
-
|
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
|
353
|
+
options[:precision] ||= args[0] if args[0]
|
275
354
|
end
|
276
355
|
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
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
|