aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md11
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb214
-rw-r--r--actionpack/lib/action_view/locale/en.yml98
-rw-r--r--actionpack/test/controller/helper_test.rb30
-rw-r--r--actionpack/test/fixtures/helpers1_pack/pack1_helper.rb5
-rw-r--r--actionpack/test/fixtures/helpers2_pack/pack2_helper.rb5
-rw-r--r--actionpack/test/template/number_helper_test.rb11
-rw-r--r--activemodel/CHANGELOG.md2
-rw-r--r--activemodel/lib/active_model/validations/validates.rb1
-rw-r--r--activemodel/test/cases/validations_test.rb5
-rw-r--r--activerecord/CHANGELOG.md20
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb1
-rw-r--r--activerecord/lib/active_record/core.rb7
-rw-r--r--activerecord/lib/active_record/migration.rb1
-rw-r--r--activerecord/lib/active_record/railtie.rb6
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb17
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb20
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb22
-rw-r--r--activerecord/test/cases/calculations_test.rb16
-rw-r--r--activesupport/CHANGELOG.md4
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb34
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb135
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb2
-rw-r--r--activesupport/lib/active_support/locale/en.yml99
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb2
-rw-r--r--activesupport/lib/active_support/number_helper.rb531
-rw-r--r--activesupport/lib/active_support/testing/performance.rb8
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb12
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb5
-rw-r--r--activesupport/test/core_ext/module_test.rb2
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb261
-rw-r--r--activesupport/test/number_helper_test.rb375
-rw-r--r--activesupport/test/test_case_test.rb58
-rw-r--r--activesupport/test/testing/performance_test.rb40
-rw-r--r--guides/source/active_support_core_extensions.textile70
-rw-r--r--guides/source/initialization.textile96
-rw-r--r--guides/source/security.textile11
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile20
-rw-r--r--railties/lib/rails/application.rb13
-rw-r--r--railties/lib/rails/commands/runner.rb1
-rw-r--r--railties/lib/rails/engine.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/railtie.rb10
-rw-r--r--railties/test/application/rake_test.rb23
-rw-r--r--railties/test/application/runner_test.rb10
-rw-r--r--railties/test/railties/engine_test.rb4
-rw-r--r--railties/test/railties/railtie_test.rb16
55 files changed, 2017 insertions, 401 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index ba66b525bc..2e7b3190fc 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,16 @@
## Rails 4.0.0 (unreleased) ##
+* change a way of ordering helpers from several directories. Previously,
+ when loading helpers from multiple paths, all of the helpers files were
+ gathered into one array an then they were sorted. Helpers from different
+ directories should not be mixed before loading them to make loading more
+ predictable. The most common use case for such behavior is loading helpers
+ from engines. When you load helpers from application and engine Foo, in
+ that order, first rails will load all of the helpers from application,
+ sorted alphabetically and then it will do the same for Foo engine.
+
+ *Piotr Sarnacki*
+
* `truncate` now always returns an escaped HTMl-safe string. The option `:escape` can be used as
false to not escape the result.
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 86d061e3b7..66cdfd40ff 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -95,9 +95,9 @@ module ActionController
helpers = []
Array(path).each do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ helpers += names.sort
end
- helpers.sort!
helpers.uniq!
helpers
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index dfc26acfad..8f97d1f014 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -1,8 +1,8 @@
# encoding: utf-8
-require 'active_support/core_ext/big_decimal/conversions'
-require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/string/output_safety'
+require 'active_support/number_helper'
module ActionView
# = Action View Number Helpers
@@ -16,9 +16,6 @@ module ActionView
# unchanged if can't be converted into a valid number.
module NumberHelper
- DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
- :precision => 2, :significant => false, :strip_insignificant_zeros => false }
-
# Raised when argument +number+ param given to the helpers is invalid and
# the option :raise is set to +true+.
class InvalidNumberError < StandardError
@@ -63,25 +60,7 @@ module ActionView
options = options.symbolize_keys
parse_float(number, true) if options[:raise]
-
- number = number.to_s.strip
- area_code = options[:area_code]
- delimiter = options[:delimiter] || "-"
- extension = options[:extension]
- country_code = options[:country_code]
-
- if area_code
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
- else
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
- end
-
- str = ''
- str << "+#{country_code}#{delimiter}" unless country_code.blank?
- str << number
- str << " x #{extension}" unless extension.blank?
- ERB::Util.html_escape(str)
+ ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
end
# Formats a +number+ into a currency string (e.g., $13.65). You
@@ -128,34 +107,9 @@ module ActionView
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
return unless number
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- currency = translations_for('currency', options[:locale])
- currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
-
- defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
- defaults[:negative_format] = "-" + options[:format] if options[:format]
- options = defaults.merge!(options)
-
- unit = options.delete(:unit)
- format = options.delete(:format)
-
- if number.to_f < 0
- format = options.delete(:negative_format)
- number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
- end
-
- begin
- value = number_with_precision(number, options.merge(:raise => true))
- format.gsub('%n', value).gsub('%u', unit).html_safe
- rescue InvalidNumberError => e
- if options[:raise]
- raise
- else
- formatted_number = format.gsub('%n', e.number).gsub('%u', unit)
- e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
- end
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_currency(number, options) }
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -196,24 +150,9 @@ module ActionView
# number_to_percentage("98a", :raise => true) # => InvalidNumberError
def number_to_percentage(number, options = {})
return unless number
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- defaults = format_translations('percentage', options[:locale])
- options = defaults.merge!(options)
-
- format = options[:format] || "%n%"
-
- begin
- value = number_with_precision(number, options.merge(:raise => true))
- format.gsub(/%n/, value).html_safe
- rescue InvalidNumberError => e
- if options[:raise]
- raise
- else
- formatted_number = format.gsub(/%n/, e.number)
- e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
- end
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_percentage(number, options) }
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -246,15 +185,9 @@ module ActionView
#
# number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- parse_float(number, options[:raise]) or return number
-
- options = defaults_translations(options[:locale]).merge(options)
-
- parts = number.to_s.to_str.split('.')
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- safe_join(parts, options[:separator])
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_delimited(number, options) }
end
# Formats a +number+ with the specified level of
@@ -299,41 +232,11 @@ module ActionView
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
def number_with_precision(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- number = (parse_float(number, options[:raise]) or return number)
-
- defaults = format_translations('precision', options[:locale])
- options = defaults.merge!(options)
-
- precision = options.delete :precision
- significant = options.delete :significant
- strip_insignificant_zeros = options.delete :strip_insignificant_zeros
-
- if significant and precision > 0
- if number == 0
- digits, rounded_number = 1, 0
- else
- digits = (Math.log10(number.abs) + 1).floor
- rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
- digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
- end
- precision -= digits
- precision = precision > 0 ? precision : 0 #don't let it be negative
- else
- rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
- rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
- end
- formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
- if strip_insignificant_zeros
- escaped_separator = Regexp.escape(options[:separator])
- formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '').html_safe
- else
- formatted_number
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_rounded(number, options) }
end
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
# Formats the bytes in +number+ into a more understandable
# representation (e.g., giving it 1500 yields 1.5 KB). This
@@ -383,40 +286,11 @@ module ActionView
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
# number_to_human_size(524288000, :precision => 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options = options.symbolize_keys
-
- number = (parse_float(number, options[:raise]) or return number)
-
- defaults = format_translations('human', options[:locale])
- options = defaults.merge!(options)
-
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
- storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- base = options[:prefix] == :si ? 1000 : 1024
-
- if number.to_i < base
- unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
- storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe
- else
- max_exp = STORAGE_UNITS.size - 1
- exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= base ** exponent
-
- unit_key = STORAGE_UNITS[exponent]
- unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
-
- formatted_number = number_with_precision(number, options)
- storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human_size(number, options) }
end
- DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
- -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
-
# Pretty prints (formats and approximates) a number in a way it
# is more readable by humans (eg.: 1200000000 becomes "1.2
# Billion"). This is useful for numbers that can get very large
@@ -516,60 +390,34 @@ module ActionView
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
#
def number_to_human(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- number = (parse_float(number, options[:raise]) or return number)
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human(number, options) }
+ end
- defaults = format_translations('human', options[:locale])
- options = defaults.merge!(options)
+ private
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
+ def escape_unsafe_delimiters_and_separators(options)
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
+ options
+ end
- inverted_du = DECIMAL_UNITS.invert
+ def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
+ valid_float = valid_float?(number)
+ raise InvalidNumberError, number if raise_on_invalid && !valid_float
- units = options.delete :units
- unit_exponents = case units
- when Hash
- units
- when String, Symbol
- I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
- when nil
- I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true)
- else
- raise ArgumentError, ":units must be a Hash or String translation scope."
- end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
+ formatted_number = yield
- number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
- display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
- number /= 10 ** display_exponent
-
- unit = case units
- when Hash
- units[DECIMAL_UNITS[display_exponent]]
- when String, Symbol
- I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ if valid_float || number.html_safe?
+ formatted_number.html_safe
else
- I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ formatted_number
end
-
- decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
- formatted_number = number_with_precision(number, options)
- decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
- end
-
- private
-
- def format_translations(namespace, locale)
- defaults_translations(locale).merge(translations_for(namespace, locale))
- end
-
- def defaults_translations(locale)
- I18n.translate(:'number.format', :locale => locale, :default => {})
end
- def translations_for(namespace, locale)
- I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ def valid_float?(number)
+ !parse_float(number, false).nil?
end
def parse_float(number, raise_error)
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index 8e9db634fb..8a56f147b8 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -1,102 +1,4 @@
"en":
- number:
- # Used in number_with_delimiter()
- # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
- format:
- # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
- separator: "."
- # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
- delimiter: ","
- # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
- precision: 3
- # If set to true, precision will mean the number of significant digits instead
- # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
- significant: false
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
- strip_insignificant_zeros: false
-
- # Used in number_to_currency()
- currency:
- format:
- # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
- format: "%u%n"
- unit: "$"
- # These five are to override number.format and are optional
- separator: "."
- delimiter: ","
- precision: 2
- significant: false
- strip_insignificant_zeros: false
-
- # Used in number_to_percentage()
- percentage:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- # precision:
- # significant: false
- # strip_insignificant_zeros: false
- format: "%n%"
-
- # Used in number_to_precision()
- precision:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- # precision:
- # significant: false
- # strip_insignificant_zeros: false
-
- # Used in number_to_human_size() and number_to_human()
- human:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- precision: 3
- significant: true
- strip_insignificant_zeros: true
- # Used in number_to_human_size()
- storage_units:
- # Storage units output formatting.
- # %u is the storage unit, %n is the number (default: 2 MB)
- format: "%n %u"
- units:
- byte:
- one: "Byte"
- other: "Bytes"
- kb: "KB"
- mb: "MB"
- gb: "GB"
- tb: "TB"
- # Used in number_to_human()
- decimal_units:
- format: "%n %u"
- # Decimal units output formatting
- # By default we will only quantify some of the exponents
- # but the commented ones might be defined or overridden
- # by the user.
- units:
- # femto: Quadrillionth
- # pico: Trillionth
- # nano: Billionth
- # micro: Millionth
- # mili: Thousandth
- # centi: Hundredth
- # deci: Tenth
- unit: ""
- # ten:
- # one: Ten
- # other: Tens
- # hundred: Hundred
- thousand: Thousand
- million: Million
- billion: Billion
- trillion: Trillion
- quadrillion: Quadrillion
-
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:
distance_in_words:
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 757661d8d0..deb234b04f 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -46,12 +46,42 @@ end
class MeTooController < JustMeController
end
+class HelpersPathsController < ActionController::Base
+ paths = ["helpers2_pack", "helpers1_pack"].map do |path|
+ File.join(File.expand_path('../../fixtures', __FILE__), path)
+ end
+ $:.unshift(*paths)
+
+ self.helpers_path = paths
+ helper :all
+
+ def index
+ render :inline => "<%= conflicting_helper %>"
+ end
+end
+
module LocalAbcHelper
def a() end
def b() end
def c() end
end
+class HelperPathsTest < ActiveSupport::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_helpers_paths_priority
+ request = ActionController::TestRequest.new
+ responses = HelpersPathsController.action(:index).call(request.env)
+
+ # helpers1_pack was given as a second path, so pack1_helper should be
+ # included as the second one
+ assert_equal "pack1", responses.last.body
+ end
+end
+
class HelperTest < ActiveSupport::TestCase
class TestController < ActionController::Base
attr_accessor :delegate_attr
diff --git a/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb
new file mode 100644
index 0000000000..9faa427736
--- /dev/null
+++ b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb
@@ -0,0 +1,5 @@
+module Pack1Helper
+ def conflicting_helper
+ "pack1"
+ end
+end
diff --git a/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb
new file mode 100644
index 0000000000..cf56697dfb
--- /dev/null
+++ b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb
@@ -0,0 +1,5 @@
+module Pack2Helper
+ def conflicting_helper
+ "pack2"
+ end
+end
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 14ca6d9879..057cb47f53 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -33,6 +33,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("+18005551212", number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
assert_equal("22-555-1212", number_to_phone(225551212))
assert_equal("+45-22-555-1212", number_to_phone(225551212, :country_code => 45))
+ assert_equal '111&lt;script&gt;&lt;/script&gt;111&lt;script&gt;&lt;/script&gt;1111', number_to_phone(1111111111, :delimiter => "<script></script>")
end
def test_number_to_currency
@@ -47,6 +48,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
assert_equal("1,234,567,890.50 K&#269;", number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
assert_equal("1,234,567,890.50 - K&#269;", number_to_currency("-1234567890.50", {:unit => "K&#269;", :format => "%n %u", :negative_format => "%n - %u"}))
+ assert_equal '$1&lt;script&gt;&lt;/script&gt;01', number_to_currency(1.01, :separator => "<script></script>")
+ assert_equal '$1&lt;script&gt;&lt;/script&gt;000.00', number_to_currency(1000, :delimiter => "<script></script>")
end
def test_number_to_percentage
@@ -58,6 +61,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("123.4%", number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
assert_equal("1.000,000%", number_to_percentage(1000, :delimiter => '.', :separator => ','))
assert_equal("1000.000 %", number_to_percentage(1000, :format => "%n %"))
+ assert_equal '1&lt;script&gt;&lt;/script&gt;010%', number_to_percentage(1.01, :separator => "<script></script>")
+ assert_equal '1&lt;script&gt;&lt;/script&gt;000.000%', number_to_percentage(1000, :delimiter => "<script></script>")
end
def test_number_with_delimiter
@@ -104,6 +109,8 @@ class NumberHelperTest < ActionView::TestCase
def test_number_with_precision_with_custom_delimiter_and_separator
assert_equal '31,83', number_with_precision(31.825, :precision => 2, :separator => ',')
assert_equal '1.231,83', number_with_precision(1231.825, :precision => 2, :separator => ',', :delimiter => '.')
+ assert_equal '1&lt;script&gt;&lt;/script&gt;010', number_with_precision(1.01, :separator => "<script></script>")
+ assert_equal '1&lt;script&gt;&lt;/script&gt;000.000', number_with_precision(1000, :delimiter => "<script></script>")
end
def test_number_with_precision_with_significant_digits
@@ -193,6 +200,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal '1.0 KB', number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false)
assert_equal '1.012 KB', number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false)
assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0
+ assert_equal '9&lt;script&gt;&lt;/script&gt;86 KB', number_to_human_size(10100, :separator => "<script></script>")
end
def test_number_to_human_size_with_custom_delimiter_and_separator
@@ -253,6 +261,9 @@ class NumberHelperTest < ActionView::TestCase
#Spaces are stripped from the resulting string
assert_equal '4', number_to_human(4, :units => {:unit => "", :ten => 'tens '})
assert_equal '4.5 tens', number_to_human(45, :units => {:unit => "", :ten => ' tens '})
+
+ assert_equal '1&lt;script&gt;&lt;/script&gt;01', number_to_human(1.01, :separator => "<script></script>")
+ assert_equal '100&lt;script&gt;&lt;/script&gt;000 Quadrillion', number_to_human(10**20, :delimiter => "<script></script>")
end
def test_number_to_human_with_custom_format
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 789cff0673..eb34edb91b 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell*
+
* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella*
* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran*
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index d94c4e3f4f..6c13d2b4a2 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -88,6 +88,7 @@ module ActiveModel
defaults.merge!(:attributes => attributes)
validations.each do |key, options|
+ next unless options
key = "#{key.to_s.camelize}Validator"
begin
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 1f5023bf76..8ea9745fbf 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -330,6 +330,11 @@ class ValidationsTest < ActiveModel::TestCase
end
end
+ def test_validates_with_false_hash_value
+ Topic.validates :title, :presence => false
+ assert Topic.new.valid?
+ end
+
def test_strict_validation_error_message
Topic.validates :title, :strict => true, :presence => true
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 8f1f315e42..4f4e087acd 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,25 @@
## Rails 4.0.0 (unreleased) ##
+* Allow blocks for `count` with `ActiveRecord::Relation`, to work similar as
+ `Array#count`:
+
+ Person.where("age > 26").count { |person| gender == 'female' }
+
+ *Chris Finne & Carlos Antonio da Silva*
+
+* Added support to `CollectionAssociation#delete` for passing `fixnum`
+ or `string` values as record ids. This finds the records responding
+ to the `id` and executes delete on them.
+
+ class Person < ActiveRecord::Base
+ has_many :pets
+ end
+
+ person.pets.delete("1") # => [#<Pet id: 1>]
+ person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>]
+
+ *Francesco Rodriguez*
+
* Deprecated most of the 'dynamic finder' methods. All dynamic methods
except for `find_by_...` and `find_by_...!` are deprecated. Here's
how you can rewrite the code:
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 210820062b..f8526bb691 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -80,6 +80,7 @@ module ActiveRecord
autoload :Sanitization
autoload :Schema
autoload :SchemaDumper
+ autoload :SchemaMigration
autoload :Scoping
autoload :Serialization
autoload :SessionStore
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 4ec176e641..e94fe35170 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -230,6 +230,7 @@ module ActiveRecord
delete_records(:all, dependent)
end
else
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
delete_or_destroy(records, dependent)
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 7408428493..2176fc4e40 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -469,6 +469,8 @@ module ActiveRecord
#
# :call-seq:
# delete(*records)
+ # delete(*fixnum_ids)
+ # delete(*string_ids)
#
# Deletes the +records+ supplied and removes them from the collection. For
# +has_many+ associations, the deletion is done according to the strategy
@@ -560,6 +562,30 @@ module ActiveRecord
#
# Pet.find(1)
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
+ #
+ # You can pass +Fixnum+ or +String+ values, it finds the records
+ # responding to the +id+ and executes delete on them.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete("1")
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
+ #
+ # person.pets.delete(2, 3)
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
##
# :method: destroy
@@ -637,6 +663,27 @@ module ActiveRecord
# Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
##
+ # :method: uniq
+ #
+ # :call-seq:
+ # uniq()
+ #
+ # Specifies whether the records should be unique or not.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.select(:name)
+ # # => [
+ # # #<Pet name: "Fancy-Fancy">,
+ # # #<Pet name: "Fancy-Fancy">
+ # # ]
+ #
+ # person.pets.select(:name).uniq
+ # # => [#<Pet name: "Fancy-Fancy">]
+
+ ##
# :method: count
#
# :call-seq:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 62b0f51bb2..5758ac4569 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,5 +1,4 @@
require 'active_support/deprecation/reporting'
-require 'active_record/schema_migration'
require 'active_record/migration/join_table'
module ActiveRecord
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index f2833fbf3c..80c6f20b1a 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -10,9 +10,10 @@ module ActiveRecord
included do
##
# :singleton-method:
- # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
- # which is then passed on to any new database connections made and which can be retrieved on both
- # a class and instance level by calling +logger+.
+ #
+ # Accepts a logger conforming to the interface of Log4r which is then
+ # passed on to any new database connections made and which can be
+ # retrieved on both a class and instance level by calling +logger+.
config_attribute :logger, :global => true
##
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 2a9139749d..ac4f53c774 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,7 +1,6 @@
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/class/attribute_accessors"
require 'active_support/deprecation'
-require 'active_record/schema_migration'
require 'set'
module ActiveRecord
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index eb2769f1ef..1e497b2a79 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -30,6 +30,7 @@ module ActiveRecord
)
rake_tasks do
+ require "active_record/base"
load "active_record/railties/databases.rake"
end
@@ -38,10 +39,15 @@ module ActiveRecord
# first time. Also, make it output to STDERR.
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
+ require "active_record/base"
console = ActiveSupport::Logger.new(STDERR)
Rails.logger.extend ActiveSupport::Logger.broadcast console
end
+ runner do |app|
+ require "active_record/base"
+ end
+
initializer "active_record.initialize_timezone" do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 31d99f0192..04b4fcf379 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -16,9 +16,16 @@ module ActiveRecord
#
# Person.count(:age, distinct: true)
# # => counts the number of different age values
+ #
+ # Person.where("age > 26").count { |person| gender == 'female' }
+ # # => queries people where "age > 26" then count the loaded results filtering by gender
def count(column_name = nil, options = {})
- column_name, options = nil, column_name if column_name.is_a?(Hash)
- calculate(:count, column_name, options)
+ if block_given?
+ self.to_a.count { |item| yield item }
+ else
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
+ calculate(:count, column_name, options)
+ end
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -52,9 +59,13 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.sum('age') # => 4562
+ # # => returns the total sum of all people's age
+ #
+ # Person.where('age > 100').sum { |person| person.age - 100 }
+ # # queries people where "age > 100" then perform a sum calculation with the block returns
def sum(*args)
if block_given?
- self.to_a.sum(*args) {|*block_args| yield(*block_args)}
+ self.to_a.sum(*args) { |item| yield item }
else
calculate(:sum, *args)
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 8b384c2513..0d8f311117 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -936,10 +936,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, summit.client_of
end
- def test_deleting_type_mismatch
+ def test_deleting_by_fixnum_id
david = Developer.find(1)
- david.projects.reload
- assert_raise(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) }
+
+ assert_difference 'david.projects.count', -1 do
+ assert_equal 1, david.projects.delete(1).size
+ end
+
+ assert_equal 1, david.projects.size
+ end
+
+ def test_deleting_by_string_id
+ david = Developer.find(1)
+
+ assert_difference 'david.projects.count', -1 do
+ assert_equal 1, david.projects.delete('1').size
+ end
+
+ assert_equal 1, david.projects.size
end
def test_deleting_self_type_mismatch
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index ecc676f300..783b83631c 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -578,7 +578,27 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_deleting_junk_from_has_many_through_should_raise_type_mismatch
- assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete("Uhh what now?") }
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete(Object.new) }
+ end
+
+ def test_deleting_by_fixnum_id_from_has_many_through
+ post = posts(:thinking)
+
+ assert_difference 'post.tags.count', -1 do
+ assert_equal 1, post.tags.delete(1).size
+ end
+
+ assert_equal 0, post.tags.size
+ end
+
+ def test_deleting_by_string_id_from_has_many_through
+ post = posts(:thinking)
+
+ assert_difference 'post.tags.count', -1 do
+ assert_equal 1, post.tags.delete('1').size
+ end
+
+ assert_equal 0, post.tags.size
end
def test_has_many_through_sum_uses_calculations
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 041f8ffb7c..a279b0e77c 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -376,6 +376,22 @@ class CalculationsTest < ActiveRecord::TestCase
Company.where(:type => "Firm").from('companies').count(:type)
end
+ def test_count_with_block_acts_as_array
+ accounts = Account.where('id > 0')
+ assert_equal Account.count, accounts.count { true }
+ assert_equal 0, accounts.count { false }
+ assert_equal Account.where('credit_limit > 50').size, accounts.count { |account| account.credit_limit > 50 }
+ assert_equal Account.count, Account.count { true }
+ assert_equal 0, Account.count { false }
+ end
+
+ def test_sum_with_block_acts_as_array
+ accounts = Account.where('id > 0')
+ assert_equal Account.sum(:credit_limit), accounts.sum { |account| account.credit_limit }
+ assert_equal Account.sum(:credit_limit) + Account.count, accounts.sum{ |account| account.credit_limit + 1 }
+ assert_equal 0, accounts.sum { |account| 0 }
+ end
+
def test_sum_with_from_option
assert_equal Account.sum(:credit_limit), Account.from('accounts').sum(:credit_limit)
assert_equal Account.where("credit_limit > 50").sum(:credit_limit),
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 62b8a789c7..a818ef0c5d 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,9 @@
## Rails 4.0.0 (unreleased) ##
+* ActionView::Helpers::NumberHelper methods have been moved to ActiveSupport::NumberHelper and are now available via
+ Numeric#to_s. Numeric#to_s now accepts the formatting options :phone, :currency, :percentage, :delimited,
+ :rounded, :human, and :human_size. *Andrew Mutz*
+
* Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys`, and `Hash#deep_transform_keys!`. *Mark McSpadden*
* Changed xml type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri*
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 3ec7e576c8..5dc5710c53 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -17,8 +17,13 @@ class BigDecimal
end
DEFAULT_STRING_FORMAT = 'F'
- def to_formatted_s(format = DEFAULT_STRING_FORMAT)
- _original_to_s(format)
+ def to_formatted_s(*args)
+ if args[0].is_a?(Symbol)
+ super
+ else
+ format = args[0] || DEFAULT_STRING_FORMAT
+ _original_to_s(format)
+ end
end
alias_method :_original_to_s, :to_s
alias_method :to_s, :to_formatted_s
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index e5e77bcef4..8e728691c6 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,8 +1,10 @@
class Hash
# Return a new hash with all keys converted using the block operation.
#
- # { :name => 'Rob', :years => '28' }.transform_keys{ |key| key.to_s.upcase }
- # # => { "NAME" => "Rob", "YEARS" => "28" }
+ # hash = { name: 'Rob', age: '28' }
+ #
+ # hash.transform_keys{ |key| key.to_s.upcase }
+ # # => { "NAME" => "Rob", "AGE" => "28" }
def transform_keys
result = {}
keys.each do |key|
@@ -22,8 +24,10 @@ class Hash
# Return a new hash with all keys converted to strings.
#
- # { :name => 'Rob', :years => '28' }.stringify_keys
- # #=> { "name" => "Rob", "years" => "28" }
+ # hash = { name: 'Rob', age: '28' }
+ #
+ # hash.stringify_keys
+ # #=> { "name" => "Rob", "age" => "28" }
def stringify_keys
transform_keys{ |key| key.to_s }
end
@@ -37,8 +41,10 @@ class Hash
# Return a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
#
- # { 'name' => 'Rob', 'years' => '28' }.symbolize_keys
- # #=> { :name => "Rob", :years => "28" }
+ # hash = { 'name' => 'Rob', 'age' => '28' }
+ #
+ # hash.symbolize_keys
+ # #=> { name: "Rob", age: "28" }
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
@@ -69,8 +75,10 @@ class Hash
# This includes the keys from the root hash and from all
# nested hashes.
#
- # { :person => { :name => 'Rob', :years => '28' } }.deep_transform_keys{ |key| key.to_s.upcase }
- # # => { "PERSON" => { "NAME" => "Rob", "YEARS" => "28" } }
+ # hash = { person: { name: 'Rob', age: '28' } }
+ #
+ # hash.deep_transform_keys{ |key| key.to_s.upcase }
+ # # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } }
def deep_transform_keys(&block)
result = {}
each do |key, value|
@@ -93,6 +101,11 @@ class Hash
# Return a new hash with all keys converted to strings.
# This includes the keys from the root hash and from all
# nested hashes.
+ #
+ # hash = { person: { name: 'Rob', age: '28' } }
+ #
+ # hash.deep_stringify_keys
+ # # => { "person" => { "name" => "Rob", "age" => "28" } }
def deep_stringify_keys
deep_transform_keys{ |key| key.to_s }
end
@@ -107,6 +120,11 @@ class Hash
# Return a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+. This includes the keys from the root hash
# and from all nested hashes.
+ #
+ # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
+ #
+ # hash.deep_symbolize_keys
+ # # => { person: { name: "Rob", age: "28" } }
def deep_symbolize_keys
deep_transform_keys{ |key| key.to_sym rescue key }
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index f914425827..b8cb2e347f 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -48,17 +48,17 @@ class Module
#
# module AppConfiguration
# mattr_accessor :google_api_key
- # self.google_api_key = "123456789"
#
- # mattr_accessor :paypal_url
- # self.paypal_url = "www.sandbox.paypal.com"
+ # self.google_api_key = "123456789"
# end
#
+ # AppConfiguration.google_api_key # => "123456789"
# AppConfiguration.google_api_key = "overriding the api key!"
+ # AppConfiguration.google_api_key # => "overriding the api key!"
#
- # To opt out of the instance writer method, pass :instance_writer => false.
- # To opt out of the instance reader method, pass :instance_reader => false.
- # To opt out of both instance methods, pass :instance_accessor => false.
+ # To opt out of the instance writer method, pass instance_writer: false.
+ # To opt out of the instance reader method, pass instance_reader: false.
+ # To opt out of both instance methods, pass instance_accessor: false.
def mattr_accessor(*syms)
mattr_reader(*syms)
mattr_writer(*syms)
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index 3805cf7990..a6bc0624be 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,2 +1,3 @@
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
+require 'active_support/core_ext/numeric/conversions'
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
new file mode 100644
index 0000000000..2bbfa78639
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -0,0 +1,135 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/number_helper'
+
+class Numeric
+
+ # Provides options for converting numbers into formatted strings.
+ # Options are provided for phone numbers, currency, percentage,
+ # precision, positional notation, file size and pretty printing.
+ #
+ # ==== Options
+ #
+ # For details on which formats use which options, see ActiveSupport::NumberHelper
+ #
+ # ==== Examples
+ #
+ # Phone Numbers:
+ # 5551234.to_s(:phone) # => 555-1234
+ # 1235551234.to_s(:phone) # => 123-555-1234
+ # 1235551234.to_s(:phone, :area_code => true) # => (123) 555-1234
+ # 1235551234.to_s(:phone, :delimiter => " ") # => 123 555 1234
+ # 1235551234.to_s(:phone, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
+ # 1235551234.to_s(:phone, :country_code => 1) # => +1-123-555-1234
+ # 1235551234.to_s(:phone, :country_code => 1, :extension => 1343, :delimiter => ".")
+ # # => +1.123.555.1234 x 1343
+ #
+ # Currency:
+ # 1234567890.50.to_s(:currency) # => $1,234,567,890.50
+ # 1234567890.506.to_s(:currency) # => $1,234,567,890.51
+ # 1234567890.506.to_s(:currency, :precision => 3) # => $1,234,567,890.506
+ # 1234567890.506.to_s(:currency, :locale => :fr) # => 1 234 567 890,51 €
+ # -1234567890.50.to_s(:currency, :negative_format => "(%u%n)")
+ # # => ($1,234,567,890.50)
+ # 1234567890.50.to_s(:currency, :unit => "&pound;", :separator => ",", :delimiter => "")
+ # # => &pound;1234567890,50
+ # 1234567890.50.to_s(:currency, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
+ # # => 1234567890,50 &pound;
+ #
+ # Percentage:
+ # 100.to_s(:percentage) # => 100.000%
+ # 100.to_s(:percentage, :precision => 0) # => 100%
+ # 1000.to_s(:percentage, :delimiter => '.', :separator => ',') # => 1.000,000%
+ # 302.24398923423.to_s(:percentage, :precision => 5) # => 302.24399%
+ # 1000.to_s(:percentage, :locale => :fr) # => 1 000,000%
+ # 100.to_s(:percentage, :format => "%n %") # => 100 %
+ #
+ # Delimited:
+ # 12345678.to_s(:delimited) # => 12,345,678
+ # 12345678.05.to_s(:delimited) # => 12,345,678.05
+ # 12345678.to_s(:delimited, :delimiter => ".") # => 12.345.678
+ # 12345678.to_s(:delimited, :delimiter => ",") # => 12,345,678
+ # 12345678.05.to_s(:delimited, :separator => " ") # => 12,345,678 05
+ # 12345678.05.to_s(:delimited, :locale => :fr) # => 12 345 678,05
+ # 98765432.98.to_s(:delimited, :delimiter => " ", :separator => ",")
+ # # => 98 765 432,98
+ #
+ # Rounded:
+ # 111.2345.to_s(:rounded) # => 111.235
+ # 111.2345.to_s(:rounded, :precision => 2) # => 111.23
+ # 13.to_s(:rounded, :precision => 5) # => 13.00000
+ # 389.32314.to_s(:rounded, :precision => 0) # => 389
+ # 111.2345.to_s(:rounded, :significant => true) # => 111
+ # 111.2345.to_s(:rounded, :precision => 1, :significant => true) # => 100
+ # 13.to_s(:rounded, :precision => 5, :significant => true) # => 13.000
+ # 111.234.to_s(:rounded, :locale => :fr) # => 111,234
+ # 13.to_s(:rounded, :precision => 5, :significant => true, :strip_insignificant_zeros => true)
+ # # => 13
+ # 389.32314.to_s(:rounded, :precision => 4, :significant => true) # => 389.3
+ # 1111.2345.to_s(:rounded, :precision => 2, :separator => ',', :delimiter => '.')
+ # # => 1.111,23
+ #
+ # Human-friendly size in Bytes:
+ # 123.to_s(:human_size) # => 123 Bytes
+ # 1234.to_s(:human_size) # => 1.21 KB
+ # 12345.to_s(:human_size) # => 12.1 KB
+ # 1234567.to_s(:human_size) # => 1.18 MB
+ # 1234567890.to_s(:human_size) # => 1.15 GB
+ # 1234567890123.to_s(:human_size) # => 1.12 TB
+ # 1234567.to_s(:human_size, :precision => 2) # => 1.2 MB
+ # 483989.to_s(:human_size, :precision => 2) # => 470 KB
+ # 1234567.to_s(:human_size, :precision => 2, :separator => ',') # => 1,2 MB
+ # 1234567890123.to_s(:human_size, :precision => 5) # => "1.1229 TB"
+ # 524288000.to_s(:human_size, :precision => 5) # => "500 MB"
+ #
+ # Human-friendly format:
+ # 123.to_s(:human) # => "123"
+ # 1234.to_s(:human) # => "1.23 Thousand"
+ # 12345.to_s(:human) # => "12.3 Thousand"
+ # 1234567.to_s(:human) # => "1.23 Million"
+ # 1234567890.to_s(:human) # => "1.23 Billion"
+ # 1234567890123.to_s(:human) # => "1.23 Trillion"
+ # 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
+ # 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
+ # 489939.to_s(:human, :precision => 2) # => "490 Thousand"
+ # 489939.to_s(:human, :precision => 4) # => "489.9 Thousand"
+ # 1234567.to_s(:human, :precision => 4,
+ # :significant => false) # => "1.2346 Million"
+ # 1234567.to_s(:human, :precision => 1,
+ # :separator => ',',
+ # :significant => false) # => "1,2 Million"
+ def to_formatted_s(format = :default, options = {})
+ case format
+ when :phone
+ return ActiveSupport::NumberHelper.number_to_phone(self, options)
+ when :currency
+ return ActiveSupport::NumberHelper.number_to_currency(self, options)
+ when :percentage
+ return ActiveSupport::NumberHelper.number_to_percentage(self, options)
+ when :delimited
+ return ActiveSupport::NumberHelper.number_to_delimited(self, options)
+ when :rounded
+ return ActiveSupport::NumberHelper.number_to_rounded(self, options)
+ when :human
+ return ActiveSupport::NumberHelper.number_to_human(self, options)
+ when :human_size
+ return ActiveSupport::NumberHelper.number_to_human_size(self, options)
+ else
+ self.to_default_s
+ end
+ end
+
+ [Float, Fixnum, Bignum, BigDecimal].each do |klass|
+ klass.send(:alias_method, :to_default_s, :to_s)
+
+ klass.send(:define_method, :to_s) do |*args|
+ if args[0].is_a?(Symbol)
+ format = args[0]
+ options = args[1] || {}
+
+ self.to_formatted_s(format, options)
+ else
+ to_default_s(*args)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index 5c32a2453d..8fa8157d65 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,5 +1,3 @@
-require 'active_support/multibyte'
-
class String
# If you pass a single Fixnum, returns a substring of one character at that
# position. The first character of the string is at position 0, the next at
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 70f2dcb562..8644529806 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/multibyte'
-
class String
# Returns the string, first removing all whitespace on both ends of
# the string, and then changing remaining consecutive whitespace
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index a1499bcc90..18c7d47026 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -34,3 +34,102 @@ en:
words_connector: ", "
two_words_connector: " and "
last_word_connector: ", and "
+ number:
+ # Used in NumberHelper.number_to_delimited()
+ # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
+ format:
+ # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
+ separator: "."
+ # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
+ delimiter: ","
+ # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
+ precision: 3
+ # If set to true, precision will mean the number of significant digits instead
+ # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
+ significant: false
+ # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
+ strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_currency()
+ currency:
+ format:
+ # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
+ format: "%u%n"
+ unit: "$"
+ # These five are to override number.format and are optional
+ separator: "."
+ delimiter: ","
+ precision: 2
+ significant: false
+ strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_percentage()
+ percentage:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ # precision:
+ # significant: false
+ # strip_insignificant_zeros: false
+ format: "%n%"
+
+ # Used in NumberHelper.number_to_rounded()
+ precision:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ # precision:
+ # significant: false
+ # strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()
+ human:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ precision: 3
+ significant: true
+ strip_insignificant_zeros: true
+ # Used in number_to_human_size()
+ storage_units:
+ # Storage units output formatting.
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u"
+ units:
+ byte:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
+ # Used in NumberHelper.number_to_human()
+ decimal_units:
+ format: "%n %u"
+ # Decimal units output formatting
+ # By default we will only quantify some of the exponents
+ # but the commented ones might be defined or overridden
+ # by the user.
+ units:
+ # femto: Quadrillionth
+ # pico: Trillionth
+ # nano: Billionth
+ # micro: Millionth
+ # mili: Thousandth
+ # centi: Hundredth
+ # deci: Tenth
+ unit: ""
+ # ten:
+ # one: Ten
+ # other: Tens
+ # hundred: Hundred
+ thousand: Thousand
+ million: Million
+ billion: Billion
+ trillion: Trillion
+ quadrillion: Quadrillion
+
+ \ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 4fe925f7f4..87b1d76026 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -76,7 +76,7 @@ module ActiveSupport #:nodoc:
#
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
def split(*args)
- @wrapped_string.split(*args).map { |i| i.mb_chars }
+ @wrapped_string.split(*args).map { |i| self.class.new(i) }
end
# Works like like <tt>String#slice!</tt>, but returns an instance of Chars, or nil if the string was not
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
new file mode 100644
index 0000000000..fc97782697
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -0,0 +1,531 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
+require 'active_support/i18n'
+
+module ActiveSupport
+ module NumberHelper
+ extend self
+
+ DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
+ :precision => 2, :significant => false, :strip_insignificant_zeros => false }
+
+ # Formats a +number+ into a US phone number (e.g., (555)
+ # 123-9876). You can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use
+ # (defaults to "-").
+ # * <tt>:extension</tt> - Specifies an extension to add to the
+ # end of the generated number.
+ # * <tt>:country_code</tt> - Sets the country code for the phone
+ # number.
+ # ==== Examples
+ #
+ # number_to_phone(5551234) # => 555-1234
+ # number_to_phone("5551234") # => 555-1234
+ # number_to_phone(1235551234) # => 123-555-1234
+ # number_to_phone(1235551234, :area_code => true) # => (123) 555-1234
+ # number_to_phone(1235551234, :delimiter => " ") # => 123 555 1234
+ # number_to_phone(1235551234, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
+ # number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
+ # number_to_phone("123a456") # => 123a456
+ #
+ # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
+ # # => +1.123.555.1234 x 1343
+ def number_to_phone(number, options = {})
+ return unless number
+ options = options.symbolize_keys
+
+ number = number.to_s.strip
+ area_code = options[:area_code]
+ delimiter = options[:delimiter] || "-"
+ extension = options[:extension]
+ country_code = options[:country_code]
+
+ if area_code
+ number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ else
+ number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
+ end
+
+ str = ''
+ str << "+#{country_code}#{delimiter}" unless country_code.blank?
+ str << number
+ str << " x #{extension}" unless extension.blank?
+ str
+ end
+
+ # Formats a +number+ into a currency string (e.g., $13.65). You
+ # can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the level of precision (defaults
+ # to 2).
+ # * <tt>:unit</tt> - Sets the denomination of the currency
+ # (defaults to "$").
+ # * <tt>:separator</tt> - Sets the separator between the units
+ # (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:format</tt> - Sets the format for non-negative numbers
+ # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
+ # currency, and <tt>%n</tt> for the number.
+ # * <tt>:negative_format</tt> - Sets the format for negative
+ # numbers (defaults to prepending an hyphen to the formatted
+ # number given by <tt>:format</tt>). Accepts the same fields
+ # than <tt>:format</tt>, except <tt>%n</tt> is here the
+ # absolute value of the number.
+ #
+ # ==== Examples
+ #
+ # number_to_currency(1234567890.50) # => $1,234,567,890.50
+ # number_to_currency(1234567890.506) # => $1,234,567,890.51
+ # number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
+ # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,51 €
+ # number_to_currency("123a456") # => $123a456
+ #
+ # number_to_currency(-1234567890.50, :negative_format => "(%u%n)")
+ # # => ($1,234,567,890.50)
+ # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
+ # # => &pound;1234567890,50
+ # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
+ # # => 1234567890,50 &pound;
+ def number_to_currency(number, options = {})
+ return unless number
+ options = options.symbolize_keys
+
+ currency = translations_for('currency', options[:locale])
+ currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
+
+ defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
+ defaults[:negative_format] = "-" + options[:format] if options[:format]
+ options = defaults.merge!(options)
+
+ unit = options.delete(:unit)
+ format = options.delete(:format)
+
+ if number.to_f.phase != 0
+ format = options.delete(:negative_format)
+ number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
+ end
+
+ formatted_number = format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
+ formatted_number
+ end
+
+ # Formats a +number+ as a percentage string (e.g., 65%). You can
+ # customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ # * <tt>:format</tt> - Specifies the format of the percentage
+ # string The number field is <tt>%n</tt> (defaults to "%n%").
+ #
+ # ==== Examples
+ #
+ # number_to_percentage(100) # => 100.000%
+ # number_to_percentage("98") # => 98.000%
+ # number_to_percentage(100, :precision => 0) # => 100%
+ # number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000%
+ # number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
+ # number_to_percentage(1000, :locale => :fr) # => 1 000,000%
+ # number_to_percentage("98a") # => 98a%
+ # number_to_percentage(100, :format => "%n %") # => 100 %
+ #
+ def number_to_percentage(number, options = {})
+ return unless number
+ options = options.symbolize_keys
+
+ defaults = format_translations('percentage', options[:locale])
+ options = defaults.merge!(options)
+
+ format = options[:format] || "%n%"
+
+ formatted_number = format.gsub('%n', self.number_to_rounded(number, options))
+ formatted_number
+ end
+
+ # Formats a +number+ with grouped thousands using +delimiter+
+ # (e.g., 12,324). You can customize the format in the +options+
+ # hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ #
+ # ==== Examples
+ #
+ # number_to_delimited(12345678) # => 12,345,678
+ # number_to_delimited("123456") # => 123,456
+ # number_to_delimited(12345678.05) # => 12,345,678.05
+ # number_to_delimited(12345678, :delimiter => ".") # => 12.345.678
+ # number_to_delimited(12345678, :delimiter => ",") # => 12,345,678
+ # number_to_delimited(12345678.05, :separator => " ") # => 12,345,678 05
+ # number_to_delimited(12345678.05, :locale => :fr) # => 12 345 678,05
+ # number_to_delimited("112a") # => 112a
+ # number_to_delimited(98765432.98, :delimiter => " ", :separator => ",")
+ # # => 98 765 432,98
+ def number_to_delimited(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+
+ options = defaults_translations(options[:locale]).merge(options)
+
+ parts = number.to_s.to_str.split('.')
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
+ parts.join(options[:separator])
+ end
+
+ # Formats a +number+ with the specified level of
+ # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
+ # +:significant+ is +false+, and 5 if +:significant+ is +true+).
+ # You can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ #
+ # ==== Examples
+ #
+ # number_to_rounded(111.2345) # => 111.235
+ # number_to_rounded(111.2345, :precision => 2) # => 111.23
+ # number_to_rounded(13, :precision => 5) # => 13.00000
+ # number_to_rounded(389.32314, :precision => 0) # => 389
+ # number_to_rounded(111.2345, :significant => true) # => 111
+ # number_to_rounded(111.2345, :precision => 1, :significant => true) # => 100
+ # number_to_rounded(13, :precision => 5, :significant => true) # => 13.000
+ # number_to_rounded(111.234, :locale => :fr) # => 111,234
+ #
+ # number_to_rounded(13, :precision => 5, :significant => true, :strip_insignificant_zeros => true)
+ # # => 13
+ #
+ # number_to_rounded(389.32314, :precision => 4, :significant => true) # => 389.3
+ # number_to_rounded(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
+ # # => 1.111,23
+ def number_to_rounded(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+ number = Float(number)
+
+ defaults = format_translations('precision', options[:locale])
+ options = defaults.merge!(options)
+
+ precision = options.delete :precision
+ significant = options.delete :significant
+ strip_insignificant_zeros = options.delete :strip_insignificant_zeros
+
+ if significant and precision > 0
+ if number == 0
+ digits, rounded_number = 1, 0
+ else
+ digits = (Math.log10(number.abs) + 1).floor
+ rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
+ digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
+ end
+ precision -= digits
+ precision = precision > 0 ? precision : 0 #don't let it be negative
+ else
+ rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
+ rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
+ end
+ formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options)
+ if strip_insignificant_zeros
+ escaped_separator = Regexp.escape(options[:separator])
+ formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
+ else
+ formatted_number
+ end
+ end
+
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
+
+ # Formats the bytes in +number+ into a more understandable
+ # representation (e.g., giving it 1500 yields 1.5 KB). This
+ # method is useful for reporting file sizes to users. You can
+ # customize the format in the +options+ hash.
+ #
+ # See <tt>number_to_human</tt> if you want to pretty-print a
+ # generic number.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
+ # prefix (defaults to :binary)
+ #
+ # ==== Examples
+ #
+ # number_to_human_size(123) # => 123 Bytes
+ # number_to_human_size(1234) # => 1.21 KB
+ # number_to_human_size(12345) # => 12.1 KB
+ # number_to_human_size(1234567) # => 1.18 MB
+ # number_to_human_size(1234567890) # => 1.15 GB
+ # number_to_human_size(1234567890123) # => 1.12 TB
+ # number_to_human_size(1234567, :precision => 2) # => 1.2 MB
+ # number_to_human_size(483989, :precision => 2) # => 470 KB
+ # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
+ #
+ # Non-significant zeros after the fractional separator are
+ # stripped out by default (set
+ # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
+ # number_to_human_size(524288000, :precision => 5) # => "500 MB"
+ def number_to_human_size(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+ number = Float(number)
+
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
+
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
+
+ storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
+
+ base = options[:prefix] == :si ? 1000 : 1024
+
+ if number.to_i < base
+ unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
+ storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
+ else
+ max_exp = STORAGE_UNITS.size - 1
+ exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
+ number /= base ** exponent
+
+ unit_key = STORAGE_UNITS[exponent]
+ unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
+
+ formatted_number = self.number_to_rounded(number, options)
+ storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
+ end
+ end
+
+ DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
+
+ # Pretty prints (formats and approximates) a number in a way it
+ # is more readable by humans (eg.: 1200000000 becomes "1.2
+ # Billion"). This is useful for numbers that can get very large
+ # (and too hard to read).
+ #
+ # See <tt>number_to_human_size</tt> if you want to print a file
+ # size.
+ #
+ # You can also define you own unit-quantifier names if you want
+ # to use other decimal units (eg.: 1500 becomes "1.5
+ # kilometers", 0.150 becomes "150 milliliters", etc). You may
+ # define a wide range of unit quantifiers, even fractional ones
+ # (centi, deci, mili, etc).
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <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:
+ # * *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>
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
+ # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
+ # *<tt>:pico</tt>, <tt>:femto</tt>
+ # * <tt>:format</tt> - Sets the format of the output string
+ # (defaults to "%n %u"). The field types are:
+ # * %u - The quantifier (ex.: 'thousand')
+ # * %n - The number
+ #
+ # ==== Examples
+ #
+ # number_to_human(123) # => "123"
+ # number_to_human(1234) # => "1.23 Thousand"
+ # number_to_human(12345) # => "12.3 Thousand"
+ # number_to_human(1234567) # => "1.23 Million"
+ # number_to_human(1234567890) # => "1.23 Billion"
+ # number_to_human(1234567890123) # => "1.23 Trillion"
+ # number_to_human(1234567890123456) # => "1.23 Quadrillion"
+ # number_to_human(1234567890123456789) # => "1230 Quadrillion"
+ # number_to_human(489939, :precision => 2) # => "490 Thousand"
+ # number_to_human(489939, :precision => 4) # => "489.9 Thousand"
+ # number_to_human(1234567, :precision => 4,
+ # :significant => false) # => "1.2346 Million"
+ # number_to_human(1234567, :precision => 1,
+ # :separator => ',',
+ # :significant => false) # => "1,2 Million"
+ #
+ # Non-significant zeros after the decimal separator are stripped
+ # out by default (set <tt>:strip_insignificant_zeros</tt> to
+ # +false+ to change that):
+ # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion"
+ # number_to_human(500000000, :precision => 5) # => "500 Million"
+ #
+ # ==== Custom Unit Quantifiers
+ #
+ # You can also use your own custom unit quantifiers:
+ # number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt"
+ #
+ # If in your I18n locale you have:
+ # distance:
+ # centi:
+ # one: "centimeter"
+ # other: "centimeters"
+ # unit:
+ # one: "meter"
+ # other: "meters"
+ # thousand:
+ # one: "kilometer"
+ # other: "kilometers"
+ # billion: "gazillion-distance"
+ #
+ # Then you could do:
+ #
+ # number_to_human(543934, :units => :distance) # => "544 kilometers"
+ # number_to_human(54393498, :units => :distance) # => "54400 kilometers"
+ # number_to_human(54393498000, :units => :distance) # => "54.4 gazillion-distance"
+ # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters"
+ # number_to_human(1, :units => :distance) # => "1 meter"
+ # number_to_human(0.34, :units => :distance) # => "34 centimeters"
+ def number_to_human(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+ number = Float(number)
+
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
+
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
+
+ inverted_du = DECIMAL_UNITS.invert
+
+ units = options.delete :units
+ unit_exponents = case units
+ when Hash
+ units
+ when String, Symbol
+ I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
+ when nil
+ I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true)
+ else
+ raise ArgumentError, ":units must be a Hash or String translation scope."
+ end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
+
+ number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
+ display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
+ number /= 10 ** display_exponent
+
+ unit = case units
+ when Hash
+ units[DECIMAL_UNITS[display_exponent]]
+ when String, Symbol
+ I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ else
+ I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ end
+
+ decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
+ formatted_number = self.number_to_rounded(number, options)
+ decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip
+ end
+
+ def self.private_module_and_instance_method(method_name)
+ private method_name
+ private_class_method method_name
+ end
+ private_class_method :private_module_and_instance_method
+
+ def format_translations(namespace, locale)
+ defaults_translations(locale).merge(translations_for(namespace, locale))
+ end
+ private_module_and_instance_method :format_translations
+
+ def defaults_translations(locale)
+ I18n.translate(:'number.format', :locale => locale, :default => {})
+ end
+ private_module_and_instance_method :defaults_translations
+
+ def translations_for(namespace, locale)
+ I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ end
+ private_module_and_instance_method :translations_for
+
+ def valid_float?(number)
+ Float(number)
+ rescue ArgumentError, TypeError
+ false
+ end
+ private_module_and_instance_method :valid_float?
+
+ end
+end
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 2bea0f991a..517926c74d 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -3,7 +3,8 @@ require 'rails/version'
require 'active_support/concern'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/string/inflections'
-require 'action_view/helpers/number_helper'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/number_helper'
module ActiveSupport
module Testing
@@ -195,8 +196,7 @@ module ActiveSupport
end
class Base
- include ActionView::Helpers::NumberHelper
- include ActionView::Helpers::OutputSafetyHelper
+ include ActiveSupport::NumberHelper
attr_reader :total
@@ -240,7 +240,7 @@ module ActiveSupport
class Amount < Base
def format(measurement)
- number_with_delimiter(measurement.floor)
+ number_to_delimited(measurement.floor)
end
end
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index 772c7b4209..527fa555b7 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -4,6 +4,14 @@ require 'active_support/callbacks'
module ActiveSupport
module Testing
module SetupAndTeardown
+
+ PASSTHROUGH_EXCEPTIONS = [
+ NoMemoryError,
+ SignalException,
+ Interrupt,
+ SystemExit
+ ]
+
extend ActiveSupport::Concern
included do
@@ -28,11 +36,15 @@ module ActiveSupport
run_callbacks :setup do
result = super
end
+ rescue *PASSTHROUGH_EXCEPTIONS
+ raise
rescue Exception => e
result = runner.puke(self.class, method_name, e)
ensure
begin
run_callbacks :teardown
+ rescue *PASSTHROUGH_EXCEPTIONS
+ raise
rescue Exception => e
result = runner.puke(self.class, method_name, e)
end
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index e24a089650..a5987044b9 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -14,4 +14,9 @@ class BigDecimalTest < ActiveSupport::TestCase
bd = BigDecimal.new '10'
assert_equal bd, bd.to_d
end
+
+ def test_to_s
+ bd = BigDecimal.new '0.01'
+ assert_equal '0.01', bd.to_s
+ end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 6e1b3ca010..bd41311739 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -234,7 +234,7 @@ class ModuleTest < ActiveSupport::TestCase
def test_local_constant_names
ActiveSupport::Deprecation.silence do
- assert_equal %w(Constant1 Constant3), Ab.local_constant_names
+ assert_equal %w(Constant1 Constant3), Ab.local_constant_names.sort.map(&:to_s)
end
end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 1cb1e25d4c..435f4aa5a1 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -186,3 +186,264 @@ class NumericExtSizeTest < ActiveSupport::TestCase
assert_equal 3458764513820540928, 3.exabyte
end
end
+
+class NumericExtFormattingTest < ActiveSupport::TestCase
+ def kilobytes(number)
+ number * 1024
+ end
+
+ def megabytes(number)
+ kilobytes(number) * 1024
+ end
+
+ def gigabytes(number)
+ megabytes(number) * 1024
+ end
+
+ def terabytes(number)
+ gigabytes(number) * 1024
+ end
+
+ def test_to_s__phone
+ assert_equal("555-1234", 5551234.to_s(:phone))
+ assert_equal("800-555-1212", 8005551212.to_s(:phone))
+ assert_equal("(800) 555-1212", 8005551212.to_s(:phone, :area_code => true))
+ assert_equal("800 555 1212", 8005551212.to_s(:phone, :delimiter => " "))
+ assert_equal("(800) 555-1212 x 123", 8005551212.to_s(:phone, :area_code => true, :extension => 123))
+ assert_equal("800-555-1212", 8005551212.to_s(:phone, :extension => " "))
+ assert_equal("555.1212", 5551212.to_s(:phone, :delimiter => '.'))
+ assert_equal("+1-800-555-1212", 8005551212.to_s(:phone, :country_code => 1))
+ assert_equal("+18005551212", 8005551212.to_s(:phone, :country_code => 1, :delimiter => ''))
+ assert_equal("22-555-1212", 225551212.to_s(:phone))
+ assert_equal("+45-22-555-1212", 225551212.to_s(:phone, :country_code => 45))
+ end
+
+ def test_to_s__currency
+ assert_equal("$1,234,567,890.50", 1234567890.50.to_s(:currency))
+ assert_equal("$1,234,567,890.51", 1234567890.506.to_s(:currency))
+ assert_equal("-$1,234,567,890.50", -1234567890.50.to_s(:currency))
+ assert_equal("-$ 1,234,567,890.50", -1234567890.50.to_s(:currency, :format => "%u %n"))
+ assert_equal("($1,234,567,890.50)", -1234567890.50.to_s(:currency, :negative_format => "(%u%n)"))
+ assert_equal("$1,234,567,892", 1234567891.50.to_s(:currency, :precision => 0))
+ assert_equal("$1,234,567,890.5", 1234567890.50.to_s(:currency, :precision => 1))
+ assert_equal("&pound;1234567890,50", 1234567890.50.to_s(:currency, :unit => "&pound;", :separator => ",", :delimiter => ""))
+ end
+
+
+ def test_to_s__rounded
+ assert_equal("-111.235", -111.2346.to_s(:rounded))
+ assert_equal("111.235", 111.2346.to_s(:rounded))
+ assert_equal("31.83", 31.825.to_s(:rounded, :precision => 2))
+ assert_equal("111.23", 111.2346.to_s(:rounded, :precision => 2))
+ assert_equal("111.00", 111.to_s(:rounded, :precision => 2))
+ assert_equal("3268", (32.6751 * 100.00).to_s(:rounded, :precision => 0))
+ assert_equal("112", 111.50.to_s(:rounded, :precision => 0))
+ assert_equal("1234567892", 1234567891.50.to_s(:rounded, :precision => 0))
+ assert_equal("0", 0.to_s(:rounded, :precision => 0))
+ assert_equal("0.00100", 0.001.to_s(:rounded, :precision => 5))
+ assert_equal("0.001", 0.00111.to_s(:rounded, :precision => 3))
+ assert_equal("10.00", 9.995.to_s(:rounded, :precision => 2))
+ assert_equal("11.00", 10.995.to_s(:rounded, :precision => 2))
+ assert_equal("0.00", -0.001.to_s(:rounded, :precision => 2))
+ end
+
+ def test_to_s__percentage
+ assert_equal("100.000%", 100.to_s(:percentage))
+ assert_equal("100%", 100.to_s(:percentage, :precision => 0))
+ assert_equal("302.06%", 302.0574.to_s(:percentage, :precision => 2))
+ assert_equal("123.4%", 123.400.to_s(:percentage, :precision => 3, :strip_insignificant_zeros => true))
+ assert_equal("1.000,000%", 1000.to_s(:percentage, :delimiter => '.', :separator => ','))
+ assert_equal("1000.000 %", 1000.to_s(:percentage, :format => "%n %"))
+ end
+
+ def test_to_s__delimited
+ assert_equal("12,345,678", 12345678.to_s(:delimited))
+ assert_equal("0", 0.to_s(:delimited))
+ assert_equal("123", 123.to_s(:delimited))
+ assert_equal("123,456", 123456.to_s(:delimited))
+ assert_equal("123,456.78", 123456.78.to_s(:delimited))
+ assert_equal("123,456.789", 123456.789.to_s(:delimited))
+ assert_equal("123,456.78901", 123456.78901.to_s(:delimited))
+ assert_equal("123,456,789.78901", 123456789.78901.to_s(:delimited))
+ assert_equal("0.78901", 0.78901.to_s(:delimited))
+ end
+
+ def test_to_s__delimited__with_options_hash
+ assert_equal '12 345 678', 12345678.to_s(:delimited, :delimiter => ' ')
+ assert_equal '12,345,678-05', 12345678.05.to_s(:delimited, :separator => '-')
+ assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :separator => ',', :delimiter => '.')
+ assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :delimiter => '.', :separator => ',')
+ end
+
+
+ def test_to_s__rounded_with_custom_delimiter_and_separator
+ assert_equal '31,83', 31.825.to_s(:rounded, :precision => 2, :separator => ',')
+ assert_equal '1.231,83', 1231.825.to_s(:rounded, :precision => 2, :separator => ',', :delimiter => '.')
+ end
+
+ def test_to_s__rounded__with_significant_digits
+ assert_equal "124000", 123987.to_s(:rounded, :precision => 3, :significant => true)
+ assert_equal "120000000", 123987876.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "9775", 9775.to_s(:rounded, :precision => 4, :significant => true )
+ assert_equal "5.4", 5.3923.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "5", 5.3923.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "1", 1.232.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "7", 7.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "1", 1.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "53", 52.7923.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "9775.00", 9775.to_s(:rounded, :precision => 6, :significant => true )
+ assert_equal "5.392900", 5.3929.to_s(:rounded, :precision => 7, :significant => true )
+ assert_equal "0.0", 0.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "0", 0.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "0.0001", 0.0001.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "0.000100", 0.0001.to_s(:rounded, :precision => 3, :significant => true )
+ assert_equal "0.0001", 0.0001111.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "10.0", 9.995.to_s(:rounded, :precision => 3, :significant => true)
+ assert_equal "9.99", 9.994.to_s(:rounded, :precision => 3, :significant => true)
+ assert_equal "11.0", 10.995.to_s(:rounded, :precision => 3, :significant => true)
+ end
+
+ def test_to_s__rounded__with_strip_insignificant_zeros
+ assert_equal "9775.43", 9775.43.to_s(:rounded, :precision => 4, :strip_insignificant_zeros => true )
+ assert_equal "9775.2", 9775.2.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ assert_equal "0", 0.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ end
+
+ def test_to_s__rounded__with_significant_true_and_zero_precision
+ # Zero precision with significant is a mistake (would always return zero),
+ # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size)
+ assert_equal "124", 123.987.to_s(:rounded, :precision => 0, :significant => true)
+ assert_equal "12", 12.to_s(:rounded, :precision => 0, :significant => true )
+ end
+
+ def test_to_s__human_size
+ assert_equal '0 Bytes', 0.to_s(:human_size)
+ assert_equal '1 Byte', 1.to_s(:human_size)
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size)
+ assert_equal '123 Bytes', 123.0.to_s(:human_size)
+ assert_equal '123 Bytes', 123.to_s(:human_size)
+ assert_equal '1.21 KB', 1234.to_s(:human_size)
+ assert_equal '12.1 KB', 12345.to_s(:human_size)
+ assert_equal '1.18 MB', 1234567.to_s(:human_size)
+ assert_equal '1.15 GB', 1234567890.to_s(:human_size)
+ assert_equal '1.12 TB', 1234567890123.to_s(:human_size)
+ assert_equal '1030 TB', terabytes(1026).to_s(:human_size)
+ assert_equal '444 KB', kilobytes(444).to_s(:human_size)
+ assert_equal '1020 MB', megabytes(1023).to_s(:human_size)
+ assert_equal '3 TB', terabytes(3).to_s(:human_size)
+ assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2)
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4)
+ assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2)
+ assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4)
+ assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4)
+ assert_equal '1 Byte', 1.1.to_s(:human_size)
+ assert_equal '10 Bytes', 10.to_s(:human_size)
+ end
+
+ def test_to_s__human_size_with_si_prefix
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si)
+ assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ end
+
+ def test_to_s__human_size_with_options_hash
+ assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2)
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4)
+ assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2)
+ assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4)
+ assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4)
+ assert_equal '1 TB', 1234567890123.to_s(:human_size, :precision => 1)
+ assert_equal '500 MB', 524288000.to_s(:human_size, :precision=>3)
+ assert_equal '10 MB', 9961472.to_s(:human_size, :precision=>0)
+ assert_equal '40 KB', 41010.to_s(:human_size, :precision => 1)
+ assert_equal '40 KB', 41100.to_s(:human_size, :precision => 2)
+ assert_equal '1.0 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2, :strip_insignificant_zeros => false)
+ assert_equal '1.012 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :significant => false)
+ assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 0, :significant => true) #ignores significant it precision is 0
+ end
+
+ def test_to_s__human_size_with_custom_delimiter_and_separator
+ assert_equal '1,01 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :separator => ',')
+ assert_equal '1,01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4, :separator => ',')
+ assert_equal '1.000,1 TB', terabytes(1000.1).to_s(:human_size, :precision => 5, :delimiter => '.', :separator => ',')
+ end
+
+ def test_number_to_human
+ assert_equal '-123', -123.to_s(:human)
+ assert_equal '-0.5', -0.5.to_s(:human)
+ assert_equal '0', 0.to_s(:human)
+ assert_equal '0.5', 0.5.to_s(:human)
+ assert_equal '123', 123.to_s(:human)
+ assert_equal '1.23 Thousand', 1234.to_s(:human)
+ assert_equal '12.3 Thousand', 12345.to_s(:human)
+ assert_equal '1.23 Million', 1234567.to_s(:human)
+ assert_equal '1.23 Billion', 1234567890.to_s(:human)
+ assert_equal '1.23 Trillion', 1234567890123.to_s(:human)
+ assert_equal '1.23 Quadrillion', 1234567890123456.to_s(:human)
+ assert_equal '1230 Quadrillion', 1234567890123456789.to_s(:human)
+ assert_equal '490 Thousand', 489939.to_s(:human, :precision => 2)
+ assert_equal '489.9 Thousand', 489939.to_s(:human, :precision => 4)
+ assert_equal '489 Thousand', 489000.to_s(:human, :precision => 4)
+ assert_equal '489.0 Thousand', 489000.to_s(:human, :precision => 4, :strip_insignificant_zeros => false)
+ assert_equal '1.2346 Million', 1234567.to_s(:human, :precision => 4, :significant => false)
+ assert_equal '1,2 Million', 1234567.to_s(:human, :precision => 1, :significant => false, :separator => ',')
+ assert_equal '1 Million', 1234567.to_s(:human, :precision => 0, :significant => true, :separator => ',') #significant forced to false
+ end
+
+ def test_number_to_human_with_custom_units
+ #Only integers
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123 lt', 123456.to_s(:human, :units => volume)
+ assert_equal '12 ml', 12.to_s(:human, :units => volume)
+ assert_equal '1.23 m3', 1234567.to_s(:human, :units => volume)
+
+ #Including fractionals
+ distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
+ assert_equal '1.23 mm', 0.00123.to_s(:human, :units => distance)
+ assert_equal '1.23 cm', 0.0123.to_s(:human, :units => distance)
+ assert_equal '1.23 dm', 0.123.to_s(:human, :units => distance)
+ assert_equal '1.23 m', 1.23.to_s(:human, :units => distance)
+ assert_equal '1.23 dam', 12.3.to_s(:human, :units => distance)
+ assert_equal '1.23 hm', 123.to_s(:human, :units => distance)
+ assert_equal '1.23 km', 1230.to_s(:human, :units => distance)
+ assert_equal '1.23 km', 1230.to_s(:human, :units => distance)
+ assert_equal '1.23 km', 1230.to_s(:human, :units => distance)
+ assert_equal '12.3 km', 12300.to_s(:human, :units => distance)
+
+ #The quantifiers don't need to be a continuous sequence
+ gangster = {:hundred => "hundred bucks", :million => "thousand quids"}
+ assert_equal '1 hundred bucks', 100.to_s(:human, :units => gangster)
+ assert_equal '25 hundred bucks', 2500.to_s(:human, :units => gangster)
+ assert_equal '25 thousand quids', 25000000.to_s(:human, :units => gangster)
+ assert_equal '12300 thousand quids', 12345000000.to_s(:human, :units => gangster)
+
+ #Spaces are stripped from the resulting string
+ assert_equal '4', 4.to_s(:human, :units => {:unit => "", :ten => 'tens '})
+ assert_equal '4.5 tens', 45.to_s(:human, :units => {:unit => "", :ten => ' tens '})
+ end
+
+ def test_number_to_human_with_custom_format
+ assert_equal '123 times Thousand', 123456.to_s(:human, :format => "%n times %u")
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123.lt', 123456.to_s(:human, :units => volume, :format => "%n.%u")
+ end
+
+ def test_to_s__injected_on_proper_types
+ assert_equal Fixnum, 1230.class
+ assert_equal '1.23 Thousand', 1230.to_s(:human)
+
+ assert_equal Float, Float(1230).class
+ assert_equal '1.23 Thousand', Float(1230).to_s(:human)
+
+ assert_equal Bignum, (100**10).class
+ assert_equal '100000 Quadrillion', (100**10).to_s(:human)
+
+ assert_equal BigDecimal, BigDecimal("1000010").class
+ assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
+ end
+end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
new file mode 100644
index 0000000000..9b7d7f020c
--- /dev/null
+++ b/activesupport/test/number_helper_test.rb
@@ -0,0 +1,375 @@
+require 'abstract_unit'
+require 'active_support/number_helper'
+
+module ActiveSupport
+ module NumberHelper
+ class NumberHelperTest < ActiveSupport::TestCase
+
+ class TestClassWithInstanceNumberHelpers
+ include ActiveSupport::NumberHelper
+ end
+
+ class TestClassWithClassNumberHelpers
+ extend ActiveSupport::NumberHelper
+ end
+
+ def setup
+ @instance_with_helpers = TestClassWithInstanceNumberHelpers.new
+ end
+
+ def kilobytes(number)
+ number * 1024
+ end
+
+ def megabytes(number)
+ kilobytes(number) * 1024
+ end
+
+ def gigabytes(number)
+ megabytes(number) * 1024
+ end
+
+ def terabytes(number)
+ gigabytes(number) * 1024
+ end
+
+ def test_number_to_phone
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("555-1234", number_helper.number_to_phone(5551234))
+ assert_equal("800-555-1212", number_helper.number_to_phone(8005551212))
+ assert_equal("(800) 555-1212", number_helper.number_to_phone(8005551212, {:area_code => true}))
+ assert_equal("", number_helper.number_to_phone("", {:area_code => true}))
+ assert_equal("800 555 1212", number_helper.number_to_phone(8005551212, {:delimiter => " "}))
+ assert_equal("(800) 555-1212 x 123", number_helper.number_to_phone(8005551212, {:area_code => true, :extension => 123}))
+ assert_equal("800-555-1212", number_helper.number_to_phone(8005551212, :extension => " "))
+ assert_equal("555.1212", number_helper.number_to_phone(5551212, :delimiter => '.'))
+ assert_equal("800-555-1212", number_helper.number_to_phone("8005551212"))
+ assert_equal("+1-800-555-1212", number_helper.number_to_phone(8005551212, :country_code => 1))
+ assert_equal("+18005551212", number_helper.number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
+ assert_equal("22-555-1212", number_helper.number_to_phone(225551212))
+ assert_equal("+45-22-555-1212", number_helper.number_to_phone(225551212, :country_code => 45))
+ end
+ end
+
+ def test_number_to_currency
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("$1,234,567,890.50", number_helper.number_to_currency(1234567890.50))
+ assert_equal("$1,234,567,890.51", number_helper.number_to_currency(1234567890.506))
+ assert_equal("-$1,234,567,890.50", number_helper.number_to_currency(-1234567890.50))
+ assert_equal("-$ 1,234,567,890.50", number_helper.number_to_currency(-1234567890.50, {:format => "%u %n"}))
+ assert_equal("($1,234,567,890.50)", number_helper.number_to_currency(-1234567890.50, {:negative_format => "(%u%n)"}))
+ assert_equal("$1,234,567,892", number_helper.number_to_currency(1234567891.50, {:precision => 0}))
+ assert_equal("$1,234,567,890.5", number_helper.number_to_currency(1234567890.50, {:precision => 1}))
+ assert_equal("&pound;1234567890,50", number_helper.number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
+ assert_equal("$1,234,567,890.50", number_helper.number_to_currency("1234567890.50"))
+ assert_equal("1,234,567,890.50 K&#269;", number_helper.number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
+ assert_equal("1,234,567,890.50 - K&#269;", number_helper.number_to_currency("-1234567890.50", {:unit => "K&#269;", :format => "%n %u", :negative_format => "%n - %u"}))
+ assert_equal("0.00", number_helper.number_to_currency(+0.0, {:unit => "", :negative_format => "(%n)"}))
+ assert_equal("(0.00)", number_helper.number_to_currency(-0.0, {:unit => "", :negative_format => "(%n)"}))
+ end
+ end
+
+ def test_number_to_percentage
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("100.000%", number_helper.number_to_percentage(100))
+ assert_equal("100%", number_helper.number_to_percentage(100, {:precision => 0}))
+ assert_equal("302.06%", number_helper.number_to_percentage(302.0574, {:precision => 2}))
+ assert_equal("100.000%", number_helper.number_to_percentage("100"))
+ assert_equal("1000.000%", number_helper.number_to_percentage("1000"))
+ assert_equal("123.4%", number_helper.number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
+ assert_equal("1.000,000%", number_helper.number_to_percentage(1000, :delimiter => '.', :separator => ','))
+ assert_equal("1000.000 %", number_helper.number_to_percentage(1000, :format => "%n %"))
+ end
+ end
+
+ def test_to_delimited
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("12,345,678", number_helper.number_to_delimited(12345678))
+ assert_equal("0", number_helper.number_to_delimited(0))
+ assert_equal("123", number_helper.number_to_delimited(123))
+ assert_equal("123,456", number_helper.number_to_delimited(123456))
+ assert_equal("123,456.78", number_helper.number_to_delimited(123456.78))
+ assert_equal("123,456.789", number_helper.number_to_delimited(123456.789))
+ assert_equal("123,456.78901", number_helper.number_to_delimited(123456.78901))
+ assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901))
+ assert_equal("0.78901", number_helper.number_to_delimited(0.78901))
+ assert_equal("123,456.78", number_helper.number_to_delimited("123456.78"))
+ end
+ end
+
+ def test_to_delimited_with_options_hash
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '12 345 678', number_helper.number_to_delimited(12345678, :delimiter => ' ')
+ assert_equal '12,345,678-05', number_helper.number_to_delimited(12345678.05, :separator => '-')
+ assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :separator => ',', :delimiter => '.')
+ assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :delimiter => '.', :separator => ',')
+ end
+ end
+
+ def test_to_rounded
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("-111.235", number_helper.number_to_rounded(-111.2346))
+ assert_equal("111.235", number_helper.number_to_rounded(111.2346))
+ assert_equal("31.83", number_helper.number_to_rounded(31.825, :precision => 2))
+ assert_equal("111.23", number_helper.number_to_rounded(111.2346, :precision => 2))
+ assert_equal("111.00", number_helper.number_to_rounded(111, :precision => 2))
+ assert_equal("111.235", number_helper.number_to_rounded("111.2346"))
+ assert_equal("31.83", number_helper.number_to_rounded("31.825", :precision => 2))
+ assert_equal("3268", number_helper.number_to_rounded((32.6751 * 100.00), :precision => 0))
+ assert_equal("112", number_helper.number_to_rounded(111.50, :precision => 0))
+ assert_equal("1234567892", number_helper.number_to_rounded(1234567891.50, :precision => 0))
+ assert_equal("0", number_helper.number_to_rounded(0, :precision => 0))
+ assert_equal("0.00100", number_helper.number_to_rounded(0.001, :precision => 5))
+ assert_equal("0.001", number_helper.number_to_rounded(0.00111, :precision => 3))
+ assert_equal("10.00", number_helper.number_to_rounded(9.995, :precision => 2))
+ assert_equal("11.00", number_helper.number_to_rounded(10.995, :precision => 2))
+ assert_equal("0.00", number_helper.number_to_rounded(-0.001, :precision => 2))
+ end
+ end
+
+ def test_to_rounded_with_custom_delimiter_and_separator
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '31,83', number_helper.number_to_rounded(31.825, :precision => 2, :separator => ',')
+ assert_equal '1.231,83', number_helper.number_to_rounded(1231.825, :precision => 2, :separator => ',', :delimiter => '.')
+ end
+ end
+
+ def test_to_rounded_with_significant_digits
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal "124000", number_helper.number_to_rounded(123987, :precision => 3, :significant => true)
+ assert_equal "120000000", number_helper.number_to_rounded(123987876, :precision => 2, :significant => true )
+ assert_equal "40000", number_helper.number_to_rounded("43523", :precision => 1, :significant => true )
+ assert_equal "9775", number_helper.number_to_rounded(9775, :precision => 4, :significant => true )
+ assert_equal "5.4", number_helper.number_to_rounded(5.3923, :precision => 2, :significant => true )
+ assert_equal "5", number_helper.number_to_rounded(5.3923, :precision => 1, :significant => true )
+ assert_equal "1", number_helper.number_to_rounded(1.232, :precision => 1, :significant => true )
+ assert_equal "7", number_helper.number_to_rounded(7, :precision => 1, :significant => true )
+ assert_equal "1", number_helper.number_to_rounded(1, :precision => 1, :significant => true )
+ assert_equal "53", number_helper.number_to_rounded(52.7923, :precision => 2, :significant => true )
+ assert_equal "9775.00", number_helper.number_to_rounded(9775, :precision => 6, :significant => true )
+ assert_equal "5.392900", number_helper.number_to_rounded(5.3929, :precision => 7, :significant => true )
+ assert_equal "0.0", number_helper.number_to_rounded(0, :precision => 2, :significant => true )
+ assert_equal "0", number_helper.number_to_rounded(0, :precision => 1, :significant => true )
+ assert_equal "0.0001", number_helper.number_to_rounded(0.0001, :precision => 1, :significant => true )
+ assert_equal "0.000100", number_helper.number_to_rounded(0.0001, :precision => 3, :significant => true )
+ assert_equal "0.0001", number_helper.number_to_rounded(0.0001111, :precision => 1, :significant => true )
+ assert_equal "10.0", number_helper.number_to_rounded(9.995, :precision => 3, :significant => true)
+ assert_equal "9.99", number_helper.number_to_rounded(9.994, :precision => 3, :significant => true)
+ assert_equal "11.0", number_helper.number_to_rounded(10.995, :precision => 3, :significant => true)
+ end
+ end
+
+ def test_to_rounded_with_strip_insignificant_zeros
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal "9775.43", number_helper.number_to_rounded(9775.43, :precision => 4, :strip_insignificant_zeros => true )
+ assert_equal "9775.2", number_helper.number_to_rounded(9775.2, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ assert_equal "0", number_helper.number_to_rounded(0, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ end
+ end
+
+ def test_to_rounded_with_significant_true_and_zero_precision
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ # Zero precision with significant is a mistake (would always return zero),
+ # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size)
+ assert_equal "124", number_helper.number_to_rounded(123.987, :precision => 0, :significant => true)
+ assert_equal "12", number_helper.number_to_rounded(12, :precision => 0, :significant => true )
+ assert_equal "12", number_helper.number_to_rounded("12.3", :precision => 0, :significant => true )
+ end
+ end
+
+ def test_number_number_to_human_size
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '0 Bytes', number_helper.number_to_human_size(0)
+ assert_equal '1 Byte', number_helper.number_to_human_size(1)
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123.0)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123)
+ assert_equal '1.21 KB', number_helper.number_to_human_size(1234)
+ assert_equal '12.1 KB', number_helper.number_to_human_size(12345)
+ assert_equal '1.18 MB', number_helper.number_to_human_size(1234567)
+ assert_equal '1.15 GB', number_helper.number_to_human_size(1234567890)
+ assert_equal '1.12 TB', number_helper.number_to_human_size(1234567890123)
+ assert_equal '1030 TB', number_helper.number_to_human_size(terabytes(1026))
+ assert_equal '444 KB', number_helper.number_to_human_size(kilobytes(444))
+ assert_equal '1020 MB', number_helper.number_to_human_size(megabytes(1023))
+ assert_equal '3 TB', number_helper.number_to_human_size(terabytes(3))
+ assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2)
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4)
+ assert_equal '123 Bytes', number_helper.number_to_human_size('123')
+ assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2)
+ assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4)
+ assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4)
+ assert_equal '1 Byte', number_helper.number_to_human_size(1.1)
+ assert_equal '10 Bytes', number_helper.number_to_human_size(10)
+ end
+ end
+
+ def test_number_to_human_size_with_si_prefix
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si)
+ assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si)
+ assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si)
+ assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
+ assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
+ assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ end
+ end
+
+ def test_number_to_human_size_with_options_hash
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2)
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4)
+ assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2)
+ assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4)
+ assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4)
+ assert_equal '1 TB', number_helper.number_to_human_size(1234567890123, :precision => 1)
+ assert_equal '500 MB', number_helper.number_to_human_size(524288000, :precision=>3)
+ assert_equal '10 MB', number_helper.number_to_human_size(9961472, :precision=>0)
+ assert_equal '40 KB', number_helper.number_to_human_size(41010, :precision => 1)
+ assert_equal '40 KB', number_helper.number_to_human_size(41100, :precision => 2)
+ assert_equal '1.0 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false)
+ assert_equal '1.012 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false)
+ assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0
+ end
+ end
+
+ def test_number_to_human_size_with_custom_delimiter_and_separator
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :separator => ',')
+ assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4, :separator => ',')
+ assert_equal '1.000,1 TB', number_helper.number_to_human_size(terabytes(1000.1), :precision => 5, :delimiter => '.', :separator => ',')
+ end
+ end
+
+ def test_number_to_human
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '-123', number_helper.number_to_human(-123)
+ assert_equal '-0.5', number_helper.number_to_human(-0.5)
+ assert_equal '0', number_helper.number_to_human(0)
+ assert_equal '0.5', number_helper.number_to_human(0.5)
+ assert_equal '123', number_helper.number_to_human(123)
+ assert_equal '1.23 Thousand', number_helper.number_to_human(1234)
+ assert_equal '12.3 Thousand', number_helper.number_to_human(12345)
+ assert_equal '1.23 Million', number_helper.number_to_human(1234567)
+ assert_equal '1.23 Billion', number_helper.number_to_human(1234567890)
+ assert_equal '1.23 Trillion', number_helper.number_to_human(1234567890123)
+ assert_equal '1.23 Quadrillion', number_helper.number_to_human(1234567890123456)
+ assert_equal '1230 Quadrillion', number_helper.number_to_human(1234567890123456789)
+ assert_equal '490 Thousand', number_helper.number_to_human(489939, :precision => 2)
+ assert_equal '489.9 Thousand', number_helper.number_to_human(489939, :precision => 4)
+ assert_equal '489 Thousand', number_helper.number_to_human(489000, :precision => 4)
+ assert_equal '489.0 Thousand', number_helper.number_to_human(489000, :precision => 4, :strip_insignificant_zeros => false)
+ assert_equal '1.2346 Million', number_helper.number_to_human(1234567, :precision => 4, :significant => false)
+ assert_equal '1,2 Million', number_helper.number_to_human(1234567, :precision => 1, :significant => false, :separator => ',')
+ assert_equal '1 Million', number_helper.number_to_human(1234567, :precision => 0, :significant => true, :separator => ',') #significant forced to false
+ end
+ end
+
+ def test_number_to_human_with_custom_units
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ #Only integers
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123 lt', number_helper.number_to_human(123456, :units => volume)
+ assert_equal '12 ml', number_helper.number_to_human(12, :units => volume)
+ assert_equal '1.23 m3', number_helper.number_to_human(1234567, :units => volume)
+
+ #Including fractionals
+ distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
+ assert_equal '1.23 mm', number_helper.number_to_human(0.00123, :units => distance)
+ assert_equal '1.23 cm', number_helper.number_to_human(0.0123, :units => distance)
+ assert_equal '1.23 dm', number_helper.number_to_human(0.123, :units => distance)
+ assert_equal '1.23 m', number_helper.number_to_human(1.23, :units => distance)
+ assert_equal '1.23 dam', number_helper.number_to_human(12.3, :units => distance)
+ assert_equal '1.23 hm', number_helper.number_to_human(123, :units => distance)
+ assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance)
+ assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance)
+ assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance)
+ assert_equal '12.3 km', number_helper.number_to_human(12300, :units => distance)
+
+ #The quantifiers don't need to be a continuous sequence
+ gangster = {:hundred => "hundred bucks", :million => "thousand quids"}
+ assert_equal '1 hundred bucks', number_helper.number_to_human(100, :units => gangster)
+ assert_equal '25 hundred bucks', number_helper.number_to_human(2500, :units => gangster)
+ assert_equal '25 thousand quids', number_helper.number_to_human(25000000, :units => gangster)
+ assert_equal '12300 thousand quids', number_helper.number_to_human(12345000000, :units => gangster)
+
+ #Spaces are stripped from the resulting string
+ assert_equal '4', number_helper.number_to_human(4, :units => {:unit => "", :ten => 'tens '})
+ assert_equal '4.5 tens', number_helper.number_to_human(45, :units => {:unit => "", :ten => ' tens '})
+ end
+ end
+
+ def test_number_to_human_with_custom_format
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '123 times Thousand', number_helper.number_to_human(123456, :format => "%n times %u")
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123.lt', number_helper.number_to_human(123456, :units => volume, :format => "%n.%u")
+ end
+ end
+
+ def test_number_helpers_should_return_nil_when_given_nil
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_nil number_helper.number_to_phone(nil)
+ assert_nil number_helper.number_to_currency(nil)
+ assert_nil number_helper.number_to_percentage(nil)
+ assert_nil number_helper.number_to_delimited(nil)
+ assert_nil number_helper.number_to_rounded(nil)
+ assert_nil number_helper.number_to_human_size(nil)
+ assert_nil number_helper.number_to_human(nil)
+ end
+ end
+
+ def test_number_helpers_do_not_mutate_options_hash
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ options = { 'raise' => true }
+
+ number_helper.number_to_phone(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_currency(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_percentage(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_delimited(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_rounded(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_human_size(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_human(1, options)
+ assert_equal({ 'raise' => true }, options)
+ end
+ end
+
+ def test_number_helpers_should_return_non_numeric_param_unchanged
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("+1-x x 123", number_helper.number_to_phone("x", :country_code => 1, :extension => 123))
+ assert_equal("x", number_helper.number_to_phone("x"))
+ assert_equal("$x.", number_helper.number_to_currency("x."))
+ assert_equal("$x", number_helper.number_to_currency("x"))
+ assert_equal("x%", number_helper.number_to_percentage("x"))
+ assert_equal("x", number_helper.number_to_delimited("x"))
+ assert_equal("x.", number_helper.number_to_rounded("x."))
+ assert_equal("x", number_helper.number_to_rounded("x"))
+ assert_equal "x", number_helper.number_to_human_size('x')
+ assert_equal "x", number_helper.number_to_human('x')
+ end
+ end
+
+ def test_extending_or_including_number_helper_correctly_hides_private_methods
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert !number_helper.respond_to?(:valid_float?)
+ assert number_helper.respond_to?(:valid_float?, true)
+ end
+ end
+
+ end
+ end
+end
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index e5b5547478..c02bfa8497 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -18,11 +18,9 @@ module ActiveSupport
end
end
- def test_callback_with_exception
+ def test_standard_error_raised_within_setup_callback_is_puked
tc = Class.new(TestCase) do
- def self.name
- nil
- end
+ def self.name; nil; end
setup :bad_callback
def bad_callback; raise 'oh noes' end
@@ -41,11 +39,9 @@ module ActiveSupport
assert_equal 'oh noes', exception.message
end
- def test_teardown_callback_with_exception
+ def test_standard_error_raised_within_teardown_callback_is_puked
tc = Class.new(TestCase) do
- def self.name
- nil
- end
+ def self.name; nil; end
teardown :bad_callback
def bad_callback; raise 'oh noes' end
@@ -63,5 +59,51 @@ module ActiveSupport
assert_equal test_name, name
assert_equal 'oh noes', exception.message
end
+
+ def test_passthrough_exception_raised_within_test_method_is_not_rescued
+ tc = Class.new(TestCase) do
+ def self.name; nil; end
+
+ def test_which_raises_interrupt; raise Interrupt; end
+ end
+
+ test_name = 'test_which_raises_interrupt'
+ fr = FakeRunner.new
+
+ test = tc.new test_name
+ assert_raises(Interrupt) { test.run fr }
+ end
+
+ def test_passthrough_exception_raised_within_setup_callback_is_not_rescued
+ tc = Class.new(TestCase) do
+ def self.name; nil; end
+
+ setup :callback_which_raises_interrupt
+ def callback_which_raises_interrupt; raise Interrupt; end
+ def test_true; assert true end
+ end
+
+ test_name = 'test_true'
+ fr = FakeRunner.new
+
+ test = tc.new test_name
+ assert_raises(Interrupt) { test.run fr }
+ end
+
+ def test_passthrough_exception_raised_within_teardown_callback_is_not_rescued
+ tc = Class.new(TestCase) do
+ def self.name; nil; end
+
+ teardown :callback_which_raises_interrupt
+ def callback_which_raises_interrupt; raise Interrupt; end
+ def test_true; assert true end
+ end
+
+ test_name = 'test_true'
+ fr = FakeRunner.new
+
+ test = tc.new test_name
+ assert_raises(Interrupt) { test.run fr }
+ end
end
end
diff --git a/activesupport/test/testing/performance_test.rb b/activesupport/test/testing/performance_test.rb
new file mode 100644
index 0000000000..74d7dae9e7
--- /dev/null
+++ b/activesupport/test/testing/performance_test.rb
@@ -0,0 +1,40 @@
+require 'abstract_unit'
+require 'active_support/testing/performance'
+
+
+module ActiveSupport
+ module Testing
+ class PerformanceTest < ActiveSupport::TestCase
+ def test_amount_format
+ amount_metric = ActiveSupport::Testing::Performance::Metrics[:amount].new
+ assert_equal "0", amount_metric.format(0)
+ assert_equal "1", amount_metric.format(1.23)
+ assert_equal "40,000,000", amount_metric.format(40000000)
+ end
+
+ def test_time_format
+ time_metric = ActiveSupport::Testing::Performance::Metrics[:time].new
+ assert_equal "0 ms", time_metric.format(0)
+ assert_equal "40 ms", time_metric.format(0.04)
+ assert_equal "41 ms", time_metric.format(0.0415)
+ assert_equal "1.23 sec", time_metric.format(1.23)
+ assert_equal "40000.00 sec", time_metric.format(40000)
+ assert_equal "-5000 ms", time_metric.format(-5)
+ end
+
+ def test_space_format
+ space_metric = ActiveSupport::Testing::Performance::Metrics[:digital_information_unit].new
+ assert_equal "0 Bytes", space_metric.format(0)
+ assert_equal "0 Bytes", space_metric.format(0.4)
+ assert_equal "1 Byte", space_metric.format(1.23)
+ assert_equal "123 Bytes", space_metric.format(123)
+ assert_equal "123 Bytes", space_metric.format(123.45)
+ assert_equal "12 KB", space_metric.format(12345)
+ assert_equal "1.2 MB", space_metric.format(1234567)
+ assert_equal "9.3 GB", space_metric.format(10**10)
+ assert_equal "91 TB", space_metric.format(10**14)
+ assert_equal "910000 TB", space_metric.format(10**18)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile
index 587f65529e..2addc50d68 100644
--- a/guides/source/active_support_core_extensions.textile
+++ b/guides/source/active_support_core_extensions.textile
@@ -1840,6 +1840,76 @@ date and time arithmetic.
NOTE: Defined in +active_support/core_ext/numeric/time.rb+.
+h4. Formatting
+
+Enables the formatting of numbers in a variety of ways.
+
+Produce a string representation of a number as a telephone number:
+<ruby>
+5551234.to_s(:phone) # => 555-1234
+1235551234.to_s(:phone) # => 123-555-1234
+1235551234.to_s(:phone, :area_code => true) # => (123) 555-1234
+1235551234.to_s(:phone, :delimiter => " ") # => 123 555 1234
+1235551234.to_s(:phone, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
+1235551234.to_s(:phone, :country_code => 1) # => +1-123-555-1234
+</ruby>
+
+Produce a string representation of a number as currency:
+<ruby>
+1234567890.50.to_s(:currency) # => $1,234,567,890.50
+1234567890.506.to_s(:currency) # => $1,234,567,890.51
+1234567890.506.to_s(:currency, :precision => 3) # => $1,234,567,890.506
+</ruby>
+
+Produce a string representation of a number as a percentage:
+<ruby>
+100.to_s(:percentage) # => 100.000%
+100.to_s(:percentage, :precision => 0) # => 100%
+1000.to_s(:percentage, :delimiter => '.', :separator => ',') # => 1.000,000%
+302.24398923423.to_s(:percentage, :precision => 5) # => 302.24399%
+</ruby>
+
+Produce a string representation of a number in delimited form:
+<ruby>
+12345678.to_s(:delimited) # => 12,345,678
+12345678.05.to_s(:delimited) # => 12,345,678.05
+12345678.to_s(:delimited, :delimiter => ".") # => 12.345.678
+12345678.to_s(:delimited, :delimiter => ",") # => 12,345,678
+12345678.05.to_s(:delimited, :separator => " ") # => 12,345,678 05
+</ruby>
+
+Produce a string representation of a number rounded to a precision:
+<ruby>
+111.2345.to_s(:rounded) # => 111.235
+111.2345.to_s(:rounded, :precision => 2) # => 111.23
+13.to_s(:rounded, :precision => 5) # => 13.00000
+389.32314.to_s(:rounded, :precision => 0) # => 389
+111.2345.to_s(:rounded, :significant => true) # => 111
+</ruby>
+
+Produce a string representation of a number as a human-readable number of bytes:
+<ruby>
+123.to_s(:human_size) # => 123 Bytes
+1234.to_s(:human_size) # => 1.21 KB
+12345.to_s(:human_size) # => 12.1 KB
+1234567.to_s(:human_size) # => 1.18 MB
+1234567890.to_s(:human_size) # => 1.15 GB
+1234567890123.to_s(:human_size) # => 1.12 TB
+</ruby>
+
+Produce a string representation of a number in human-readable words:
+<ruby>
+123.to_s(:human) # => "123"
+1234.to_s(:human) # => "1.23 Thousand"
+12345.to_s(:human) # => "12.3 Thousand"
+1234567.to_s(:human) # => "1.23 Million"
+1234567890.to_s(:human) # => "1.23 Billion"
+1234567890123.to_s(:human) # => "1.23 Trillion"
+1234567890123456.to_s(:human) # => "1.23 Quadrillion"
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/numeric/formatting.rb+.
+
h3. Extensions to +Integer+
h4. +multiple_of?+
diff --git a/guides/source/initialization.textile b/guides/source/initialization.textile
index 913ff24290..48d4373afe 100644
--- a/guides/source/initialization.textile
+++ b/guides/source/initialization.textile
@@ -57,7 +57,7 @@ else
end
</ruby>
-The +rbconfig+ file from the Ruby standard library provides us with the +RbConfig+ class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see thisin use in +railties/lib/rails/script_rails_loader+.
+The +rbconfig+ file from the Ruby standard library provides us with the +RbConfig+ class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see this in use in +railties/lib/rails/script_rails_loader+.
<ruby>
require 'pathname'
@@ -157,11 +157,11 @@ The gems that a Rails 4 application depends on are as follows:
TODO: change these when the Rails 4 release is near.
* abstract (1.0.0)
-* actionmailer (3.1.0.beta)
-* actionpack (3.1.0.beta)
-* activemodel (3.1.0.beta)
-* activerecord (3.1.0.beta)
-* activesupport (3.1.0.beta)
+* actionmailer (4.0.0.beta)
+* actionpack (4.0.0.beta)
+* activemodel (4.0.0.beta)
+* activerecord (4.0.0.beta)
+* activesupport (4.0.0.beta)
* arel (2.0.7)
* builder (3.0.0)
* bundler (1.0.6)
@@ -174,8 +174,8 @@ TODO: change these when the Rails 4 release is near.
* rack-cache (0.5.3)
* rack-mount (0.6.13)
* rack-test (0.5.6)
-* rails (3.1.0.beta)
-* railties (3.1.0.beta)
+* rails (4.0.0.beta)
+* railties (4.0.0.beta)
* rake (0.8.7)
* sqlite3-ruby (1.3.2)
* thor (0.14.6)
@@ -191,6 +191,7 @@ ARGV << '--help' if ARGV.empty?
aliases = {
"g" => "generate",
+ "d" => "destroy",
"c" => "console",
"s" => "server",
"db" => "dbconsole",
@@ -579,28 +580,6 @@ this time to the +Array+ and +Hash+ classes. This file defines an
+extract_options!+ method which Rails uses to extract options from
parameters.
-<ruby>
-class Array
- # Extracts options from a set of arguments. Removes and returns the
- # last
- # element in the array if it's a hash, otherwise returns a blank hash.
- #
- # def options(*args)
- # args.extract_options!
- # end
- #
- # options(1, 2) # => {}
- # options(1, 2, :a => :b) # => {:a=>:b}
- def extract_options!
- if last.is_a?(Hash) && last.extractable_options?
- pop
- else
- {}
- end
- end
-end
-</ruby>
-
h4. +railties/lib/rails/application.rb+
The next file required by +railties/lib/rails.rb+ is +application.rb+.
@@ -612,8 +591,7 @@ Before the +Rails::Application+ class is
defined however, +rails/engine+ is also loaded, which is responsible for
handling the behavior and definitions of Rails engines.
-TIP: You can read more about engines in the "Getting Started with Engines":engines.html
-guide.
+TIP: You can read more about engines in the "Getting Started with Engines":engines.html guide.
Among other things, Rails Engine is also responsible for loading the
Railtie class.
@@ -678,7 +656,7 @@ h4. +activesupport/lib/active_support/deprecation/proxy_wrappers.rb+
+proxy_wrappers.rb+ defines deprecation wrappers for methods, instance variables and constants. Previously, this was used for the +RAILS_ENV+ and +RAILS_ROOT+ constants for 3.0 but since then these constants have been removed. The deprecation message that would be raised from these would be something like:
<plain>
- BadConstant is deprecated! Use GoodConstant instead.
+BadConstant is deprecated! Use GoodConstant instead.
</plain>
h4. +active_support/ordered_options+
@@ -689,7 +667,7 @@ The next file required is +active_support/core_ext/hash/deep_dup+ which is cover
h4. +active_support/core_ext/object+
-This file is responsible for requiring many more core extensions:
+This file is responsible for requiring many more Active Support core extensions:
<ruby>
require 'active_support/core_ext/object/acts_like'
@@ -947,7 +925,7 @@ The +initializers_chain+ method referenced in the +initializers_for+ method is d
<ruby>
def initializers_chain
initializers = Collection.new
- ancestors.reverse_each do | klass |
+ ancestors.reverse_each do |klass|
next unless klass.respond_to?(:initializers)
initializers = initializers + klass.initializers
end
@@ -1010,46 +988,35 @@ This file defines the +ActiveSupport::Railtie+ constant which like the +I18n::Ra
Then this Railtie sets up three more initializers:
-* +active_support.initialize_whiny_nils+
* +active_support.deprecation_behavior+
* +active_support.initialize_time_zone+
+* +active_support.set_configs+
We will cover what each of these initializers do when they run.
Once the +active_support/railtie+ file has finished loading the next file required from +railties/lib/rails.rb+ is the +action_dispatch/railtie+.
-h4. +activesupport/lib/action_dispatch/railtie.rb+
+h4. +actionpack/lib/action_dispatch/railtie.rb+
This file defines the +ActionDispatch::Railtie+ class, but not before requiring +action_dispatch+.
-h4. +activesupport/lib/action_dispatch.rb+
-
-This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are.
-
-<ruby>
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-</ruby>
-
-In effect, these lines only define the +activesupport_path+ and +activemodel_path+ variables and nothing more.
+h4. +actionpack/lib/action_dispatch.rb+
-The next two requires in this file are already done, so they are not run:
+This file starts off with the following requires:
<ruby>
require 'active_support'
require 'active_support/dependencies/autoload'
+require 'active_support/core_ext/module/attribute_accessors'
</ruby>
-The following require is to +action_pack+ (+activesupport/lib/action_pack.rb+) which has a 22-line copyright notice at the top of it and ends in a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant:
+The following require is to +action_pack+ (+actionpack/lib/action_pack.rb+) which contains a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant:
<ruby>
module ActionPack
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 1
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
@@ -1067,8 +1034,8 @@ This file makes a require to +active_model/version+ which defines the version fo
<ruby>
module ActiveModel
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 1
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
@@ -1105,7 +1072,7 @@ Once it has finished loading, the +I18n.load_path+ method is used to add the +ac
The loading of this file finishes the loading of +active_model+ and so we go back to +action_dispatch+.
-h4. Back to +activesupport/lib/action_dispatch.rb+
+h4. Back to +actionpack/lib/action_dispatch.rb+
The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here:
@@ -1119,7 +1086,7 @@ The +Assertions+ module is in the +action_dispatch/testing+ folder rather than s
Finally, this file defines a top-level autoload, the +Mime+ constant.
-h4. Back to +activesupport/lib/action_dispatch/railtie.rb+
+h4. Back to +actionpack/lib/action_dispatch/railtie.rb+
After +action_dispatch+ is required in this file, the +ActionDispatch::Railtie+ class is defined and is yet another class that inherits from +Rails::Railtie+. This class defines some initial configuration option defaults for +config.action_dispatch+ before setting up a single initializer called +action_dispatch.configure+.
@@ -1141,22 +1108,21 @@ h4. +activerecord/lib/active_record.rb+
This file begins by detecting if the +lib+ directories of +active_support+ and +active_model+ are not in the load path and if they aren't then adds them. As we saw back in +action_dispatch.rb+, these directories are already there.
-The first three requires have already been done by other files and so aren't loaded here, but the 4th require, the one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module.
+The first couple of requires have already been done by other files and so aren't loaded here, but the next one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module.
<ruby>
require 'active_support'
-require 'active_support/i18n'
require 'active_model'
require 'arel'
</ruby>
-The 5th require in this file is one to +active_record/version+ which defines the +ActiveRecord::VERSION+ constant:
+The file required next is +active_record/version+ which defines the +ActiveRecord::VERSION+ constant:
<ruby>
module ActiveRecord
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 1
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
@@ -1180,7 +1146,9 @@ This will set the engine for +Arel::Table+ to be +ActiveRecord::Base+.
The file then finishes with this line:
<ruby>
-I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+ActiveSupport.on_load(:i18n) do
+ I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+end
</ruby>
This will add the translations from +activerecord/lib/active_record/locale/en.yml+ to the load path for +I18n+, with this file being parsed when all the translations are loaded.
diff --git a/guides/source/security.textile b/guides/source/security.textile
index ac55d60368..cc0894fc77 100644
--- a/guides/source/security.textile
+++ b/guides/source/security.textile
@@ -236,6 +236,17 @@ protect_from_forgery :secret => "123456789012345678901234567890..."
This will automatically include a security token, calculated from the current session and the server-side secret, in all forms and Ajax requests generated by Rails. You won't need the secret, if you use CookieStorage as session storage. If the security token doesn't match what was expected, the session will be reset. *Note:* In Rails versions prior to 3.0.4, this raised an <tt>ActionController::InvalidAuthenticityToken</tt> error.
+It is common to use persistent cookies to store user information, with +cookies.permanent+ for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:
+
+<ruby>
+def handle_unverified_request
+ super
+ sign_out_user # Example method that will destroy the user cookies.
+end
+</ruby>
+
+The above method can be placed in the +ApplicationController+ and will be called when a CSRF token is not present on a non-GET request.
+
Note that _(highlight)cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so he can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later.
h3. Redirection and Files
diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile
index 02407a5fe8..6cdc6ab289 100644
--- a/guides/source/upgrading_ruby_on_rails.textile
+++ b/guides/source/upgrading_ruby_on_rails.textile
@@ -34,17 +34,25 @@ h4(#plugins4_0). vendor/plugins
Rails 4.0 no longer supports loading plugins from <tt>vendor/plugins</tt>. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, <tt>lib/my_plugin/*</tt> and add an appropriate initializer in <tt>config/initializers/my_plugin.rb</tt>.
-h4(#identity_map4_0). IdentityMap
+h4(#identity_map4_0). Identity Map
-Rails 4.0 has removed <tt>IdentityMap</tt> from <tt>ActiveRecord</tt>, due to "some inconsistencies with associations":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: <tt>config.active_record.identity_map</tt>.
+Rails 4.0 has removed the identity map from Active Record, due to "some inconsistencies with associations":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: <tt>config.active_record.identity_map</tt>.
-h4(#active_model4_0). ActiveModel
+h4(#active_record4_0). Active Record
-Rails 4.0 has changed how errors attach with the ConfirmationValidator. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
+The <tt>delete</tt> method in collection associations can now receive <tt>Fixnum</tt> or <tt>String</tt> arguments as record ids, besides records, pretty much like the <tt>destroy</tt> method does. Previously it raised <tt>ActiveRecord::AssociationTypeMismatch</tt> for such arguments. From Rails 4.0 on <tt>delete</tt> automatically tries to find the records matching the given ids before deleting them.
-h4(#action_pack4_0). ActionPack
+h4(#active_model4_0). Active Model
-Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>RoutingError</tt>.
+Rails 4.0 has changed how errors attach with the <tt>ActiveModel::Validations::ConfirmationValidator</tt>. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
+
+h4(#action_pack4_0). Action Pack
+
+Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>ActionController::RoutingError</tt>.
+
+h4(#helpers_order). Helpers Loading Order
+
+The loading order of helpers from more than one directory has changed in Rails 4.0. Previously, helpers from all directories were gathered and then sorted alphabetically. After upgrade to Rails 4.0 helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use <tt>helpers_path</tt> parameter, this change will only impact the way of loading helpers from engines. If you rely on the fact that particular helper from engine loads before or after another helper from application or another engine, you should check if correct methods are available after upgrade. If you would like to change order in which engines are loaded, you can use <tt>config.railties_order=</tt> method.
h3. Upgrading from Rails 3.1 to Rails 3.2
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index d44465e547..32797ee657 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -158,6 +158,14 @@ module Rails
self
end
+ # Load the application runner and invoke the registered hooks.
+ # Check <tt>Rails::Railtie.runner</tt> for more info.
+ def load_runner(app=self)
+ initialize_runner
+ super
+ self
+ end
+
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
def env_config
@@ -185,7 +193,7 @@ module Rails
end
all = (railties.all - order)
- all.push(self) unless all.include?(self)
+ all.push(self) unless (all + order).include?(self)
order.push(:all) unless order.include?(:all)
index = order.index(:all)
@@ -304,6 +312,9 @@ module Rails
require "rails/console/helpers"
end
+ def initialize_runner #:nodoc:
+ end
+
def build_original_fullpath(env)
path_info = env["PATH_INFO"]
query_string = env["QUERY_STRING"]
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 77f1b15fb4..a672258aa6 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -41,6 +41,7 @@ ENV["RAILS_ENV"] = options[:environment]
require APP_PATH
Rails.application.require_environment!
+ Rails.application.load_runner
if code_or_file.nil?
$stderr.puts "Run '#{$0} -h' for help."
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 4c7199a2e2..806b553b81 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -437,6 +437,11 @@ module Rails
super
end
+ def load_runner(app=self)
+ railties.all { |r| r.load_runner(app) }
+ super
+ end
+
def eager_load!
railties.all(&:eager_load!)
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index bf47e66cc4..55a6b3f4f2 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -19,7 +19,7 @@ source 'https://rubygems.org'
# gem 'unicorn'
# Deploy with Capistrano
-# gem 'capistrano', :group => :development
+# gem 'capistrano', group: :development
# To use debugger
# gem 'debugger'
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 2102f8a03c..c3cc65ab31 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -145,6 +145,12 @@ module Rails
@load_console
end
+ def runner(&blk)
+ @load_runner ||= []
+ @load_runner << blk if blk
+ @load_runner
+ end
+
def generators(&blk)
@generators ||= []
@generators << blk if blk
@@ -179,6 +185,10 @@ module Rails
self.class.console.each { |block| block.call(app) }
end
+ def load_runner(app=self)
+ self.class.runner.each { |block| block.call(app) }
+ end
+
def load_tasks(app=self)
extend Rake::DSL if defined? Rake::DSL
self.class.rake_tasks.each { |block| self.instance_exec(app, &block) }
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 27d521485c..8cf867da3c 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -167,5 +167,28 @@ module ApplicationTests
end
assert !File.exists?(File.join(app_path, 'db', 'schema_cache.dump'))
end
+
+ def test_load_activerecord_base_when_we_use_observers
+ Dir.chdir(app_path) do
+ `bundle exec rails g model user;
+ bundle exec rake db:migrate;
+ bundle exec rails g observer user;`
+
+ add_to_config "config.active_record.observers = :user_observer"
+
+ assert_equal "0", `bundle exec rails r "puts User.count"`.strip
+
+ app_file "lib/tasks/count_user.rake", <<-RUBY
+ namespace :user do
+ task :count => :environment do
+ puts User.count
+ end
+ end
+ RUBY
+
+ assert_equal "0", `bundle exec rake user:count`.strip
+ end
+ end
+
end
end
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index e1d283a7fd..81ed5873a5 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -57,5 +57,15 @@ module ApplicationTests
assert_match "script/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/program_name.rb"` }
end
+
+ def test_with_hook
+ add_to_config <<-RUBY
+ runner do |app|
+ app.config.ran = true
+ end
+ RUBY
+
+ assert_match "true", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.application.config.ran"` }
+ end
end
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 7a047ef93a..4437e2c8af 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1098,6 +1098,10 @@ YAML
get("/assets/bar.js")
assert_equal "// App's bar js\n;", last_response.body.strip
+
+ # ensure that railties are not added twice
+ railties = Rails.application.ordered_railties.map(&:class)
+ assert_equal railties, railties.uniq
end
test "railties_order adds :all with lowest priority if not given" do
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index cd495320b5..c80b0f63af 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -163,6 +163,22 @@ module RailtiesTest
assert $ran_block
end
+ test "runner block is executed when MyApp.load_runner is called" do
+ $ran_block = false
+
+ class MyTie < Rails::Railtie
+ runner do
+ $ran_block = true
+ end
+ end
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ AppTemplate::Application.load_runner
+ assert $ran_block
+ end
+
test "railtie can add initializers" do
$ran_block = false