diff options
Diffstat (limited to 'activesupport')
60 files changed, 2133 insertions, 186 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 636aca2b8f..a818ef0c5d 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,21 @@ ## 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* + +* Add `:instance_accessor` option for `class_attribute`. *Alexey Vakhov* + +* `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White* + +* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a +Hash+ instance into strings *Lucas Húngaro* + +* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a +Hash+ instance into symbols *Lucas Húngaro* + * `Object#try` can't call private methods. *Vasiliy Ermolovich* * `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 2c874e932e..30221f2401 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -8,6 +8,7 @@ Gem::Specification.new do |s| s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.' s.required_ruby_version = '>= 1.9.3' + s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 8f018dcbc6..56d6676961 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -25,6 +25,7 @@ require 'securerandom' require "active_support/dependencies/autoload" require "active_support/version" require "active_support/logger" +require "active_support/lazy_load_hooks" module ActiveSupport extend ActiveSupport::Autoload diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 55791bfa56..a62214d604 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -405,7 +405,7 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support increment") end - # Increment an integer value in the cache. + # Decrement an integer value in the cache. # # Options are passed to the underlying cache implementation. # diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 89bdb741d0..5be63af342 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -81,7 +81,8 @@ module ActiveSupport if File.exist?(file_name) File.open(file_name) { |f| Marshal.load(f) } end - rescue + rescue => e + logger.error("FileStoreError (#{e}): #{e.message}") if logger nil end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 4e319b4bba..a9253c186d 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -310,13 +310,13 @@ module ActiveSupport method << "value = nil" method << "halted = false" - callbacks = "value = yield if block_given? && !halted" + callbacks = "value = !halted && (!block_given? || yield)" reverse_each do |callback| callbacks = callback.apply(callbacks) end method << callbacks - method << "halted ? false : (block_given? ? value : true)" + method << "value" method.join("\n") end @@ -328,26 +328,17 @@ module ActiveSupport # if it was not yet defined. # This generated method plays caching role. def __define_callbacks(kind, object) #:nodoc: - name = __callback_runner_name(kind) + chain = object.send("_#{kind}_callbacks") + name = "_run_callbacks_#{chain.object_id.abs}" unless object.respond_to?(name, true) - str = object.send("_#{kind}_callbacks").compile class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{name}() #{str} end + def #{name}() #{chain.compile} end protected :#{name} RUBY_EVAL end name end - def __reset_runner(symbol) - name = __callback_runner_name(symbol) - undef_method(name) if method_defined?(name) - end - - def __callback_runner_name(kind) - "_run__#{self.name.hash.abs}__#{kind}__callbacks" - end - # This is used internally to append, prepend and skip callbacks to the # CallbackChain. # @@ -359,7 +350,6 @@ module ActiveSupport ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target| chain = target.send("_#{name}_callbacks") yield target, chain.dup, type, filters, options - target.__reset_runner(name) end end @@ -447,12 +437,9 @@ module ActiveSupport chain = target.send("_#{symbol}_callbacks").dup callbacks.each { |c| chain.delete(c) } target.send("_#{symbol}_callbacks=", chain) - target.__reset_runner(symbol) end self.send("_#{symbol}_callbacks=", callbacks.dup.clear) - - __reset_runner(symbol) end # Define sets of events in the object lifecycle that support callbacks. diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index ac1ae53db0..a184eb492a 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -2,18 +2,21 @@ class Array # Splits or iterates over the array in groups of size +number+, # padding any remaining slots with +fill_with+ unless it is +false+. # - # %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group} + # %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group} # ["1", "2", "3"] # ["4", "5", "6"] - # ["7", nil, nil] + # ["7", "8", "9"] + # ["10", nil, nil] # - # %w(1 2 3).in_groups_of(2, ' ') {|group| p group} + # %w(1 2 3 4 5).in_groups_of(2, ' ') {|group| p group} # ["1", "2"] - # ["3", " "] + # ["3", "4"] + # ["5", " "] # - # %w(1 2 3).in_groups_of(2, false) {|group| p group} + # %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group} # ["1", "2"] - # ["3"] + # ["3", "4"] + # ["5"] def in_groups_of(number, fill_with = nil) if fill_with == false collection = self @@ -42,10 +45,10 @@ class Array # ["5", "6", "7", nil] # ["8", "9", "10", nil] # - # %w(1 2 3 4 5 6 7).in_groups(3, ' ') {|group| p group} - # ["1", "2", "3"] - # ["4", "5", " "] - # ["6", "7", " "] + # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, ' ') {|group| p group} + # ["1", "2", "3", "4"] + # ["5", "6", "7", " "] + # ["8", "9", "10", " "] # # %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group} # ["1", "2", "3"] 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/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index c64685a694..7b6f8ab0a1 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -65,10 +65,12 @@ class Class # To opt out of the instance writer method, pass :instance_writer => false. # # object.setting = false # => NoMethodError + # + # To opt out of both instance methods, pass :instance_accessor => false. def class_attribute(*attrs) options = attrs.extract_options! - instance_reader = options.fetch(:instance_reader, true) - instance_writer = options.fetch(:instance_writer, true) + instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) + instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 3e36c54eba..8a7eb6bc6b 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -202,7 +202,7 @@ class Date acts_like?(:time) ? result.change(:hour => 0) : result end - # Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00) + # Returns a new Date/DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00) def beginning_of_month acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1) end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 02d5a7080f..03efe6a19a 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -65,11 +65,15 @@ class Range #:nodoc: # Optimize range sum to use arithmetic progression if a block is not given and # we have a range of numeric values. def sum(identity = 0) - if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer)) + if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer)) super else actual_last = exclude_end? ? (last - 1) : last - (actual_last - first + 1) * (actual_last + first) / 2 + if actual_last >= first + (actual_last - first + 1) * (actual_last + first) / 2 + else + identity + end end end end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 469dc41f2d..7c72ead36c 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -57,8 +57,8 @@ class Hash # "TrueClass" => "boolean", # "FalseClass" => "boolean", # "Date" => "date", - # "DateTime" => "datetime", - # "Time" => "datetime" + # "DateTime" => "dateTime", + # "Time" => "dateTime" # } # # By default the root node is "hash", but that's configurable via the <tt>:root</tt> option. @@ -129,7 +129,7 @@ class Hash else xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }] - # Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with + # Turn { :files => { :file => #<StringIO> } } into { :files => #<StringIO> } so it is compatible with # how multipart uploaded files from HTML appear xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value end diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index be4d611ce7..8e728691c6 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -1,46 +1,59 @@ class Hash - # Return a new hash with all keys converted to strings. + # Return a new hash with all keys converted using the block operation. # - # { :name => 'Rob', :years => '28' }.stringify_keys - # #=> { "name" => "Rob", "years" => "28" } - def stringify_keys + # 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| - result[key.to_s] = self[key] + result[yield(key)] = self[key] end result end - # Destructively convert all keys to strings. Same as - # +stringify_keys+, but modifies +self+. - def stringify_keys! + # Destructively convert all keys using the block operations. + # Same as transform_keys but modifies +self+ + def transform_keys! keys.each do |key| - self[key.to_s] = delete(key) + self[yield(key)] = delete(key) end self end + # Return a new hash with all keys converted to strings. + # + # hash = { name: 'Rob', age: '28' } + # + # hash.stringify_keys + # #=> { "name" => "Rob", "age" => "28" } + def stringify_keys + transform_keys{ |key| key.to_s } + end + + # Destructively convert all keys to strings. Same as + # +stringify_keys+, but modifies +self+. + def stringify_keys! + transform_keys!{ |key| key.to_s } + end + # 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 - result = {} - keys.each do |key| - result[(key.to_sym rescue key)] = self[key] - end - result + transform_keys{ |key| key.to_sym rescue key } end alias_method :to_options, :symbolize_keys # Destructively convert all keys to symbols, as long as they respond # to +to_sym+. Same as +symbolize_keys+, but modifies +self+. def symbolize_keys! - keys.each do |key| - self[(key.to_sym rescue key)] = delete(key) - end - self + transform_keys!{ |key| key.to_sym rescue key } end alias_method :to_options!, :symbolize_keys! @@ -57,4 +70,69 @@ class Hash raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k) end end + + # Return a new hash with all keys converted by the block operation. + # This includes the keys from the root hash and from all + # nested hashes. + # + # 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| + result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value + end + result + end + + # Destructively convert all keys by using the block operation. + # This includes the keys from the root hash and from all + # nested hashes. + def deep_transform_keys!(&block) + keys.each do |key| + value = delete(key) + self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value + end + self + end + + # 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 + + # Destructively convert all keys to strings. + # This includes the keys from the root hash and from all + # nested hashes. + def deep_stringify_keys! + deep_transform_keys!{ |key| key.to_s } + end + + # 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 + + # Destructively convert all keys to symbols, as long as they respond + # to +to_sym+. This includes the keys from the root hash and from all + # nested hashes. + def deep_symbolize_keys! + deep_transform_keys!{ |key| key.to_sym rescue key } + end 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 => "£", :separator => ",", :delimiter => "") + # # => £1234567890,50 + # 1234567890.50.to_s(:currency, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") + # # => 1234567890,50 £ + # + # 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/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb index 883f5f556c..f55fbc282e 100644 --- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/duplicable' + class Object # Returns a deep copy of object if it's duplicable. If it's # not duplicable, returns +self+. diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9d1630bb7c..f1b755c2c4 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -19,7 +19,7 @@ class Object # Can you safely dup this object? # - # False for +nil+, +false+, +true+, symbols, numbers, class and module objects; + # False for +nil+, +false+, +true+, symbol, and number objects; # true otherwise. def duplicable? true @@ -81,30 +81,6 @@ class Numeric end end -class Class - # Classes are not duplicable: - # - # c = Class.new # => #<Class:0x10328fd80> - # c.dup # => #<Class:0x10328fd80> - # - # Note +dup+ returned the same class object. - def duplicable? - false - end -end - -class Module - # Modules are not duplicable: - # - # m = Module.new # => #<Module:0x10328b6e0> - # m.dup # => #<Module:0x10328b6e0> - # - # Note +dup+ returned the same module object. - def duplicable? - false - end -end - require 'bigdecimal' class BigDecimal begin diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb index 91fdf93eb2..40821fd619 100644 --- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb @@ -1,6 +1,6 @@ class Object - # Returns a hash that maps instance variable names without "@" to their - # corresponding values. Keys are strings both in Ruby 1.8 and 1.9. + # Returns a hash with string keys that maps instance variable names without "@" to their + # corresponding values. # # class C # def initialize(x, y) @@ -9,12 +9,11 @@ class Object # end # # C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} - def instance_values #:nodoc: + def instance_values Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }] end - # Returns an array of instance variable names including "@". They are strings - # both in Ruby 1.8 and 1.9. + # Returns an array of instance variable names including "@". # # class C # def initialize(x, y) diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 48eb546a7d..30c835f5cd 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -7,6 +7,10 @@ class Object # # If try is called without a method to call, it will yield any given block with the object. # + # Please also note that +try+ is defined on +Object+, therefore it won't work with + # subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will + # delegate +try+ to target instead of calling it on delegator itself. + # # ==== Examples # # Without +try+ 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 2478f42290..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 @@ -33,7 +31,7 @@ class String # # => "Once upon a time in a..." # # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...") - # for a total length not exceeding <tt>:length</tt>: + # for a total length not exceeding <tt>length</tt>: # # 'And they found that many people were sleeping better.'.truncate(25, :omission => '... (continued)') # # => "And they f... (continued)" diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 6bda970e40..5226ff0cbe 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -150,6 +150,18 @@ module ActiveSupport #:nodoc: dup.concat(other) end + def %(args) + args = Array(args).map do |arg| + if !html_safe? || arg.html_safe? + arg + else + ERB::Util.h(arg) + end + end + + self.class.new(super(args)) + end + def html_safe? defined?(@html_safe) && @html_safe end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index a0f610d60c..92b8417150 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -253,7 +253,7 @@ class Time :hour => 23, :min => 59, :sec => 59, - :usec => 999999.999 + :usec => Rational(999999999, 1000) ) end @@ -268,7 +268,7 @@ class Time change( :min => 59, :sec => 59, - :usec => 999999.999 + :usec => Rational(999999999, 1000) ) end @@ -288,7 +288,7 @@ class Time :hour => 23, :min => 59, :sec => 59, - :usec => 999999.999 + :usec => Rational(999999999, 1000) ) end alias :at_end_of_month :end_of_month @@ -321,7 +321,7 @@ class Time :hour => 23, :min => 59, :sec => 59, - :usec => 999999.999 + :usec => Rational(999999999, 1000) ) end alias :at_end_of_year :end_of_year diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 4f852fd780..10ca26acf2 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -5,6 +5,7 @@ class Time DATE_FORMATS = { :db => '%Y-%m-%d %H:%M:%S', :number => '%Y%m%d%H%M%S', + :nsec => '%Y%m%d%H%M%S%9N', :time => '%H:%M', :short => '%d %b %H:%M', :long => '%B %d, %Y %H:%M', diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb index a1626ebeba..4045db3232 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -1,5 +1,4 @@ require "active_support/inflector/methods" -require "active_support/lazy_load_hooks" module ActiveSupport module Autoload diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 9102537810..fc962dcb57 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -16,9 +16,9 @@ module ActiveSupport # # Available behaviors: # - # [+stderr+] Log all deprecation warnings to +$stderr+. + # [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>. # [+log+] Log all deprecation warnings to +Rails.logger+. - # [+notify] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. + # [+notify+] Use <tt>ActiveSupport::Notifications</tt> to notify +deprecation.rails+. # [+silence+] Do nothing. # # Setting behaviors only affects deprecations that happen after boot time. diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 91459f3e5b..6e1c0da991 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -141,9 +141,13 @@ module ActiveSupport end def stringify_keys!; self end + def deep_stringify_keys!; self end def stringify_keys; dup end + def deep_stringify_keys; dup end undef :symbolize_keys! + undef :deep_symbolize_keys! def symbolize_keys; to_hash.symbolize_keys end + def deep_symbolize_keys; to_hash.deep_symbolize_keys end def to_options!; self end # Convert to a Hash with String keys. diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index 7eb61cd1a0..c04c2ed15b 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -26,7 +26,7 @@ module ActiveSupport inflect.singular(/(ss)$/i, '\1') inflect.singular(/(n)ews$/i, '\1ews') inflect.singular(/([ti])a$/i, '\1um') - inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1\2sis') + inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis') inflect.singular(/(^analy)(sis|ses)$/i, '\1sis') inflect.singular(/([^f])ves$/i, '\1fe') inflect.singular(/(hive)s$/i, '\1') diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 48296841aa..2acc6ddee5 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -198,12 +198,29 @@ module ActiveSupport # # NameError is raised when the name is not in CamelCase or the constant is # unknown. - def constantize(camel_cased_word) #:nodoc: + def constantize(camel_cased_word) names = camel_cased_word.split('::') names.shift if names.empty? || names.first.empty? names.inject(Object) do |constant, name| - constant.const_get(name, false) + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check it it's owned + # directly before we reach Object or the end of ancestors. + constant = constant.ancestors.inject do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) + end end end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index a6e4e7ced2..41fe61e790 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -159,18 +159,18 @@ class Struct #:nodoc: end class TrueClass - AS_JSON = ActiveSupport::JSON::Variable.new('true').freeze - def as_json(options = nil) AS_JSON end #:nodoc: + def as_json(options = nil) self end #:nodoc: + def encode_json(encoder) to_s end #:nodoc: end class FalseClass - AS_JSON = ActiveSupport::JSON::Variable.new('false').freeze - def as_json(options = nil) AS_JSON end #:nodoc: + def as_json(options = nil) self end #:nodoc: + def encode_json(encoder) to_s end #:nodoc: end class NilClass - AS_JSON = ActiveSupport::JSON::Variable.new('null').freeze - def as_json(options = nil) AS_JSON end #:nodoc: + def as_json(options = nil) self end #:nodoc: + def encode_json(encoder) 'null' end #:nodoc: end class String @@ -189,8 +189,8 @@ end class Float # Encoding Infinity or NaN to JSON should return "null". The default returns - # "Infinity" or "NaN" what breaks parsing the JSON. E.g. JSON.parse('[NaN]'). - def as_json(options = nil) finite? ? self : NilClass::AS_JSON end #:nodoc: + # "Infinity" or "NaN" breaks parsing the JSON. E.g. JSON.parse('[NaN]'). + def as_json(options = nil) finite? ? self : nil end #:nodoc: end class BigDecimal @@ -208,7 +208,7 @@ class BigDecimal if finite? ActiveSupport.encode_big_decimal_as_string ? to_s : self else - NilClass::AS_JSON + nil end end end 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/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 7b7fc81e6c..b65ea6208c 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -61,8 +61,12 @@ module ActiveSupport @logged = Hash.new { |h,k| h[k] = [] } end - def method_missing(level, message) - @logged[level] << message + def method_missing(level, message = nil) + if block_given? + @logged[level] << yield + else + @logged[level] << message + end end def logged(level) 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 => "£", :separator => ",", :delimiter => "") + # # => £1234567890,50 + # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") + # # => 1234567890,50 £ + 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/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 8edd3960c7..1a3693f766 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -5,16 +5,20 @@ YAML.add_builtin_type("omap") do |type, val| end module ActiveSupport - # The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the - # order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt> - # implements a hash that preserves insertion order, as in Ruby 1.9: + # <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves + # insertion order. # # oh = ActiveSupport::OrderedHash.new # oh[:a] = 1 # oh[:b] = 2 # oh.keys # => [:a, :b], this order is guaranteed # - # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations. + # Also, maps the +omap+ feature for YAML files + # (See http://yaml.org/type/omap.html) to support ordered items + # when loading from yaml. + # + # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts + # with other implementations. class OrderedHash < ::Hash def to_yaml_type "!tag:yaml.org,2002:omap" diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index ec6986654e..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,7 +196,7 @@ module ActiveSupport end class Base - include ActionView::Helpers::NumberHelper + include ActiveSupport::NumberHelper attr_reader :total @@ -239,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/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 67ac1b6ccd..451520ac5c 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -317,8 +317,7 @@ module ActiveSupport # Send the missing method to +time+ instance, and wrap result in a new TimeWithZone with the existing +time_zone+. def method_missing(sym, *args, &block) - result = time.__send__(sym, *args, &block) - result.acts_like?(:time) ? self.class.new(nil, time_zone, result) : result + wrap_with_time_zone time.__send__(sym, *args, &block) end private @@ -336,11 +335,21 @@ module ActiveSupport end def transfer_time_values_to_utc_constructor(time) - ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:usec) ? time.usec : 0) + ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:nsec) ? Rational(time.nsec, 1000) : 0) end def duration_of_variable_length?(obj) ActiveSupport::Duration === obj && obj.parts.any? {|p| p[0].in?([:years, :months, :days]) } end + + def wrap_with_time_zone(time) + if time.acts_like?(:time) + self.class.new(nil, time_zone, time) + elsif time.is_a?(Range) + wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end) + else + time + end + end end end diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 677e9910bb..88e18f6fff 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -39,8 +39,8 @@ module ActiveSupport "TrueClass" => "boolean", "FalseClass" => "boolean", "Date" => "date", - "DateTime" => "datetime", - "Time" => "datetime", + "DateTime" => "dateTime", + "Time" => "dateTime", "Array" => "array", "Hash" => "hash" } unless defined?(TYPE_NAMES) @@ -48,7 +48,7 @@ module ActiveSupport FORMATTING = { "symbol" => Proc.new { |symbol| symbol.to_s }, "date" => Proc.new { |date| date.to_s(:db) }, - "datetime" => Proc.new { |time| time.xmlschema }, + "dateTime" => Proc.new { |time| time.xmlschema }, "binary" => Proc.new { |binary| ::Base64.encode64(binary) }, "yaml" => Proc.new { |yaml| yaml.to_yaml } } unless defined?(FORMATTING) @@ -111,6 +111,7 @@ module ActiveSupport type_name ||= TYPE_NAMES[value.class.name] type_name ||= value.class.name if value && !value.respond_to?(:to_str) type_name = type_name.to_s if type_name + type_name = "dateTime" if type_name == "datetime" key = rename_key(key.to_s, options) @@ -145,7 +146,7 @@ module ActiveSupport "#{left}#{middle.tr('_ ', '--')}#{right}" end - # TODO: Add support for other encodings + # TODO: Add support for other encodings def _parse_binary(bin, entity) #:nodoc: case entity['encoding'] when 'base64' diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index d62b782e2d..a75db47be8 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -553,6 +553,9 @@ class FileStoreTest < ActiveSupport::TestCase @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60) @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60) @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), :expires_in => 60) + + @buffer = StringIO.new + @cache.logger = ActiveSupport::Logger.new(@buffer) end def teardown @@ -612,6 +615,12 @@ class FileStoreTest < ActiveSupport::TestCase ActiveSupport::Cache::FileStore.new('/test/cache/directory').delete_matched(/does_not_exist/) end end + + def test_log_exception_when_cache_read_fails + File.expects(:exist?).raises(StandardError, "failed") + @cache.send(:read_entry, "winston", {}) + assert_present @buffer.string + end end class MemoryStoreTest < ActiveSupport::TestCase diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index 135f894056..ec05213409 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -1,16 +1,41 @@ module Ace module Base class Case + class Dice + end + end + class Fase < Case + end + end + class Gas + include Base + end +end + +class Object + module AddtlGlobalConstants + class Case + class Dice + end end end + include AddtlGlobalConstants end module ConstantizeTestCases def run_constantize_tests_on assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") } assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") } + assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") } + assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") } + assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") } + assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") } + assert_nothing_raised { assert_equal Object, yield("") } + assert_nothing_raised { assert_equal Object, yield("::") } assert_raise(NameError) { yield("UnknownClass") } assert_raise(NameError) { yield("UnknownClass::Ace") } assert_raise(NameError) { yield("UnknownClass::Ace::Base") } @@ -18,13 +43,23 @@ module ConstantizeTestCases assert_raise(NameError) { yield("InvalidClass\n") } assert_raise(NameError) { yield("Ace::ConstantizeTestCases") } assert_raise(NameError) { yield("Ace::Base::ConstantizeTestCases") } + assert_raise(NameError) { yield("Ace::Gas::Base") } + assert_raise(NameError) { yield("Ace::Gas::ConstantizeTestCases") } end def run_safe_constantize_tests_on assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") } assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") } + assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") } + assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") } + assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") } + assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") } + assert_nothing_raised { assert_equal Object, yield("") } + assert_nothing_raised { assert_equal Object, yield("::") } assert_nothing_raised { assert_equal nil, yield("UnknownClass") } assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace") } assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace::Base") } @@ -33,6 +68,8 @@ module ConstantizeTestCases assert_nothing_raised { assert_equal nil, yield("blargle") } assert_nothing_raised { assert_equal nil, yield("Ace::ConstantizeTestCases") } assert_nothing_raised { assert_equal nil, yield("Ace::Base::ConstantizeTestCases") } + assert_nothing_raised { assert_equal nil, yield("Ace::Gas::Base") } + assert_nothing_raised { assert_equal nil, yield("Ace::Gas::ConstantizeTestCases") } assert_nothing_raised { assert_equal nil, yield("#<Class:0x7b8b718b>::Nested_1") } end 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/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index e290a6e012..1c3ba8a7a0 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -66,6 +66,13 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_raise(NoMethodError) { object.setting? } end + test 'disabling both instance writer and reader' do + object = Class.new { class_attribute :setting, :instance_accessor => false }.new + assert_raise(NoMethodError) { object.setting } + assert_raise(NoMethodError) { object.setting? } + assert_raise(NoMethodError) { object.setting = 'boom' } + end + test 'works well with singleton classes' do object = @klass.new object.singleton_class.setting = 'foo' diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 760d138623..e14a137f84 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -374,14 +374,14 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_day - assert_equal Time.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day + assert_equal Time.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day end def test_end_of_day_when_zone_is_set zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'UTC' do with_tz_default zone do - assert_equal zone.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day + assert_equal zone.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day assert_equal zone, Date.new(2005,2,21).end_of_day.time_zone end end diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/duplicable_test.rb index 1105353e45..e0566e012c 100644 --- a/activesupport/test/core_ext/duplicable_test.rb +++ b/activesupport/test/core_ext/duplicable_test.rb @@ -5,8 +5,8 @@ require 'active_support/core_ext/numeric/time' class DuplicableTest < ActiveSupport::TestCase RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds] - YES = ['1', Object.new, /foo/, [], {}, Time.now] - NO = [Class.new, Module.new] + YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] + NO = [] begin bd = BigDecimal.new('4.56') diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 0bf48dd378..0a1abac767 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -84,6 +84,11 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal 10, (1..4.5).sum assert_equal 6, (1...4).sum assert_equal 'abc', ('a'..'c').sum + assert_equal 50_000_005_000_000, (0..10_000_000).sum + assert_equal 0, (10..0).sum + assert_equal 5, (10..0).sum(5) + assert_equal 10, (10..10).sum + assert_equal 42, (10...10).sum(42) end def test_index_by diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 8239054117..5d422ce5ad 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -4,6 +4,7 @@ require 'bigdecimal' require 'active_support/core_ext/string/access' require 'active_support/ordered_hash' require 'active_support/core_ext/object/conversions' +require 'active_support/core_ext/object/deep_dup' require 'active_support/inflections' class HashExtTest < ActiveSupport::TestCase @@ -24,56 +25,207 @@ class HashExtTest < ActiveSupport::TestCase def setup @strings = { 'a' => 1, 'b' => 2 } + @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } } @symbols = { :a => 1, :b => 2 } + @nested_symbols = { :a => { :b => { :c => 3 } } } @mixed = { :a => 1, 'b' => 2 } + @nested_mixed = { 'a' => { :b => { 'c' => 3 } } } @fixnums = { 0 => 1, 1 => 2 } + @nested_fixnums = { 0 => { 1 => { 2 => 3} } } @illegal_symbols = { [] => 3 } + @nested_illegal_symbols = { [] => { [] => 3} } + @upcase_strings = { 'A' => 1, 'B' => 2 } + @nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } } end def test_methods h = {} + assert_respond_to h, :transform_keys + assert_respond_to h, :transform_keys! + assert_respond_to h, :deep_transform_keys + assert_respond_to h, :deep_transform_keys! assert_respond_to h, :symbolize_keys assert_respond_to h, :symbolize_keys! + assert_respond_to h, :deep_symbolize_keys + assert_respond_to h, :deep_symbolize_keys! assert_respond_to h, :stringify_keys assert_respond_to h, :stringify_keys! + assert_respond_to h, :deep_stringify_keys + assert_respond_to h, :deep_stringify_keys! assert_respond_to h, :to_options assert_respond_to h, :to_options! end + def test_transform_keys + assert_equal @upcase_strings, @strings.transform_keys{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @symbols.transform_keys{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @mixed.transform_keys{ |key| key.to_s.upcase } + end + + def test_transform_keys_not_mutates + transformed_hash = @mixed.dup + transformed_hash.transform_keys{ |key| key.to_s.upcase } + assert_equal @mixed, transformed_hash + end + + def test_deep_transform_keys + assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase } + end + + def test_deep_transform_keys_not_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_transform_keys{ |key| key.to_s.upcase } + assert_equal @nested_mixed, transformed_hash + end + + def test_transform_keys! + assert_equal @upcase_strings, @symbols.dup.transform_keys!{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @strings.dup.transform_keys!{ |key| key.to_s.upcase } + assert_equal @upcase_strings, @mixed.dup.transform_keys!{ |key| key.to_s.upcase } + end + + def test_transform_keys_with_bang_mutates + transformed_hash = @mixed.dup + transformed_hash.transform_keys!{ |key| key.to_s.upcase } + assert_equal @upcase_strings, transformed_hash + assert_equal @mixed, { :a => 1, "b" => 2 } + end + + def test_deep_transform_keys! + assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase } + end + + def test_deep_transform_keys_with_bang_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_transform_keys!{ |key| key.to_s.upcase } + assert_equal @nested_upcase_strings, transformed_hash + assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } } + end + def test_symbolize_keys assert_equal @symbols, @symbols.symbolize_keys assert_equal @symbols, @strings.symbolize_keys assert_equal @symbols, @mixed.symbolize_keys end + def test_symbolize_keys_not_mutates + transformed_hash = @mixed.dup + transformed_hash.symbolize_keys + assert_equal @mixed, transformed_hash + end + + def test_deep_symbolize_keys + assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys + assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys + assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys + end + + def test_deep_symbolize_keys_not_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_symbolize_keys + assert_equal @nested_mixed, transformed_hash + end + def test_symbolize_keys! assert_equal @symbols, @symbols.dup.symbolize_keys! assert_equal @symbols, @strings.dup.symbolize_keys! assert_equal @symbols, @mixed.dup.symbolize_keys! end + def test_symbolize_keys_with_bang_mutates + transformed_hash = @mixed.dup + transformed_hash.deep_symbolize_keys! + assert_equal @symbols, transformed_hash + assert_equal @mixed, { :a => 1, "b" => 2 } + end + + def test_deep_symbolize_keys! + assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys! + assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys! + assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys! + end + + def test_deep_symbolize_keys_with_bang_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_symbolize_keys! + assert_equal @nested_symbols, transformed_hash + assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } } + end + def test_symbolize_keys_preserves_keys_that_cant_be_symbolized assert_equal @illegal_symbols, @illegal_symbols.symbolize_keys assert_equal @illegal_symbols, @illegal_symbols.dup.symbolize_keys! end + def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized + assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_symbolize_keys + assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys! + end + def test_symbolize_keys_preserves_fixnum_keys assert_equal @fixnums, @fixnums.symbolize_keys assert_equal @fixnums, @fixnums.dup.symbolize_keys! end + def test_deep_symbolize_keys_preserves_fixnum_keys + assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys + assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys! + end + def test_stringify_keys assert_equal @strings, @symbols.stringify_keys assert_equal @strings, @strings.stringify_keys assert_equal @strings, @mixed.stringify_keys end + def test_stringify_keys_not_mutates + transformed_hash = @mixed.dup + transformed_hash.stringify_keys + assert_equal @mixed, transformed_hash + end + + def test_deep_stringify_keys + assert_equal @nested_strings, @nested_symbols.deep_stringify_keys + assert_equal @nested_strings, @nested_strings.deep_stringify_keys + assert_equal @nested_strings, @nested_mixed.deep_stringify_keys + end + + def test_deep_stringify_keys_not_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_stringify_keys + assert_equal @nested_mixed, transformed_hash + end + def test_stringify_keys! assert_equal @strings, @symbols.dup.stringify_keys! assert_equal @strings, @strings.dup.stringify_keys! assert_equal @strings, @mixed.dup.stringify_keys! end + def test_stringify_keys_with_bang_mutates + transformed_hash = @mixed.dup + transformed_hash.stringify_keys! + assert_equal @strings, transformed_hash + assert_equal @mixed, { :a => 1, "b" => 2 } + end + + def test_deep_stringify_keys! + assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys! + end + + def test_deep_stringify_keys_with_bang_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_stringify_keys! + assert_equal @nested_strings, transformed_hash + assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } } + end + def test_symbolize_keys_for_hash_with_indifferent_access assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys @@ -81,22 +233,46 @@ class HashExtTest < ActiveSupport::TestCase assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys end + def test_deep_symbolize_keys_for_hash_with_indifferent_access + assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys + assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys + assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys + assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys + end + + def test_symbolize_keys_bang_for_hash_with_indifferent_access assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! } assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! } assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! } end + def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access + assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! } + assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! } + assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! } + end + def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! } end + def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access + assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys + assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! } + end + def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! } end + def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access + assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys + assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! } + end + def test_stringify_keys_for_hash_with_indifferent_access assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys assert_equal @strings, @symbols.with_indifferent_access.stringify_keys @@ -104,6 +280,13 @@ class HashExtTest < ActiveSupport::TestCase assert_equal @strings, @mixed.with_indifferent_access.stringify_keys end + def test_deep_stringify_keys_for_hash_with_indifferent_access + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys + assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys + assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys + assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys + end + def test_stringify_keys_bang_for_hash_with_indifferent_access assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys! assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys! @@ -111,6 +294,13 @@ class HashExtTest < ActiveSupport::TestCase assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys! end + def test_deep_stringify_keys_bang_for_hash_with_indifferent_access + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys! + assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys! + end + def test_nested_under_indifferent_access foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"] @@ -297,6 +487,17 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, h[:first] end + def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash + h = HashWithIndifferentAccess.new + h[:first] = 1 + h = h.deep_stringify_keys + assert_equal 1, h['first'] + h = HashWithIndifferentAccess.new + h['first'] = 1 + h = h.deep_symbolize_keys + assert_equal 1, h[:first] + end + def test_to_options_on_indifferent_preserves_hash h = HashWithIndifferentAccess.new h['first'] = 1 @@ -672,8 +873,8 @@ class HashToXmlTest < ActiveSupport::TestCase :created_at => Time.utc(1999,2,2), :local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)') }.to_xml(@xml_options) - assert_match %r{<created-at type=\"datetime\">1999-02-02T00:00:00Z</created-at>}, xml - assert_match %r{<local-created-at type=\"datetime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml + assert_match %r{<created-at type=\"dateTime\">1999-02-02T00:00:00Z</created-at>}, xml + assert_match %r{<local-created-at type=\"dateTime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml end def test_multiple_records_from_xml_with_attributes_other_than_type_ignores_them_without_exploding 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("£1234567890,50", 1234567890.50.to_s(:currency, :unit => "£", :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/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 9010a4a716..e5b774425e 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -290,6 +290,10 @@ class StringInflectionsTest < ActiveSupport::TestCase "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10) end + def test_truncate_should_not_be_html_safe + assert !"Hello World!".truncate(12).html_safe? + end + def test_constantize run_constantize_tests_on do |string| string.constantize @@ -439,6 +443,37 @@ class OutputSafetyTest < ActiveSupport::TestCase assert @other_string.html_safe? end + test "Concatting safe onto unsafe with % yields unsafe" do + @other_string = "other%s" + string = @string.html_safe + + @other_string = @other_string % string + assert !@other_string.html_safe? + end + + test "Concatting unsafe onto safe with % yields escaped safe" do + @other_string = "other%s".html_safe + string = @other_string % "<foo>" + + assert_equal "other<foo>", string + assert string.html_safe? + end + + test "Concatting safe onto safe with % yields safe" do + @other_string = "other%s".html_safe + string = @string.html_safe + + @other_string = @other_string % string + assert @other_string.html_safe? + end + + test "Concatting with % doesn't modify a string" do + @other_string = ["<p>", "<b>", "<h1>"] + _ = "%s %s %s".html_safe % @other_string + + assert_equal ["<p>", "<b>", "<h1>"], @other_string + end + test "Concatting a fixnum to safe always yields safe" do string = @string.html_safe string = string.concat(13) diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 4c1ed4b1ae..15c04bedf7 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -109,49 +109,49 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_day - assert_equal Time.local(2007,8,12,23,59,59,999999.999), Time.local(2007,8,12,10,10,10).end_of_day + assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day with_env_tz 'US/Eastern' do - assert_equal Time.local(2007,4,2,23,59,59,999999.999), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' - assert_equal Time.local(2007,10,29,23,59,59,999999.999), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' + assert_equal Time.local(2007,4,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' + assert_equal Time.local(2007,10,29,23,59,59,Rational(999999999, 1000)), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' end with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,23,59,59,999999.999), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,23,59,59,999999.999), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' + assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' + assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' end end def test_end_of_week - assert_equal Time.local(2008,1,6,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_week - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,27,0,0,0).end_of_week #monday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,28,0,0,0).end_of_week #tuesday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,29,0,0,0).end_of_week #wednesday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,30,0,0,0).end_of_week #thursday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,31,0,0,0).end_of_week #friday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,01,0,0,0).end_of_week #saturday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday + assert_equal Time.local(2008,1,6,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_week + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,27,0,0,0).end_of_week #monday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,28,0,0,0).end_of_week #tuesday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,29,0,0,0).end_of_week #wednesday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,30,0,0,0).end_of_week #thursday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,31,0,0,0).end_of_week #friday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,01,0,0,0).end_of_week #saturday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,02,0,0,0).end_of_week #sunday end def test_end_of_hour - assert_equal Time.local(2005,2,4,19,59,59,999999.999), Time.local(2005,2,4,19,30,10).end_of_hour + assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour end def test_end_of_month - assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month - assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month - assert_equal Time.local(2005,4,30,23,59,59,999999.999), Time.local(2005,4,20,10,10,10).end_of_month + assert_equal Time.local(2005,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2005,3,20,10,10,10).end_of_month + assert_equal Time.local(2005,2,28,23,59,59,Rational(999999999, 1000)), Time.local(2005,2,20,10,10,10).end_of_month + assert_equal Time.local(2005,4,30,23,59,59,Rational(999999999, 1000)), Time.local(2005,4,20,10,10,10).end_of_month end def test_end_of_quarter - assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,2,15,10,10,10).end_of_quarter - assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,3,31,0,0,0).end_of_quarter - assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,21,10,10,10).end_of_quarter - assert_equal Time.local(2007,6,30,23,59,59,999999.999), Time.local(2007,4,1,0,0,0).end_of_quarter - assert_equal Time.local(2008,6,30,23,59,59,999999.999), Time.local(2008,5,31,0,0,0).end_of_quarter + assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,15,10,10,10).end_of_quarter + assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,3,31,0,0,0).end_of_quarter + assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,21,10,10,10).end_of_quarter + assert_equal Time.local(2007,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,1,0,0,0).end_of_quarter + assert_equal Time.local(2008,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2008,5,31,0,0,0).end_of_quarter end def test_end_of_year - assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,2,22,10,10,10).end_of_year - assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_year + assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,22,10,10,10).end_of_year + assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_year end def test_beginning_of_year @@ -517,7 +517,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2006,11,15), Time.local(2006,11,23,0,0,0).prev_week(:wednesday) end end - + def test_last_week with_env_tz 'US/Eastern' do assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week @@ -557,12 +557,14 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_to_s - time = Time.utc(2005, 2, 21, 17, 44, 30) + time = Time.utc(2005, 2, 21, 17, 44, 30.12345678901) assert_equal time.to_default_s, time.to_s assert_equal time.to_default_s, time.to_s(:doesnt_exist) assert_equal "2005-02-21 17:44:30", time.to_s(:db) assert_equal "21 Feb 17:44", time.to_s(:short) assert_equal "17:44", time.to_s(:time) + assert_equal "20050221174430", time.to_s(:number) + assert_equal "20050221174430123456789", time.to_s(:nsec) assert_equal "February 21, 2005 17:44", time.to_s(:long) assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal) with_env_tz "UTC" do @@ -828,24 +830,32 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_all_day - assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_day + assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_day + end + + def test_all_day_with_timezone + beginning_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,0,0,0)) + end_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))) + + assert_equal beginning_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.begin + assert_equal end_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.end end def test_all_week - assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week - assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week(:sunday) + assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week + assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week(:sunday) end def test_all_month - assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_month + assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_month end def test_all_quarter - assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_quarter + assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_quarter end def test_all_year - assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_year + assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_year end protected diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index b62337e31b..1293f104e5 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -80,6 +80,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase ActiveSupport.use_standard_json_time_format = old end + def test_nsec + local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)) + with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local) + + assert_equal local.nsec, with_zone.nsec + assert_equal with_zone.nsec, 999999999 + end + def test_strftime assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z') end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 4d10cfca25..9fa1f417e4 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -47,6 +47,7 @@ module InflectorTestCases "medium" => "media", "stadium" => "stadia", "analysis" => "analyses", + "my_analysis" => "my_analyses", "node_child" => "node_children", "child" => "children", diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 0566ebf291..212ee262a3 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -285,6 +285,12 @@ class TestJSONEncoding < ActiveSupport::TestCase end end + def test_nil_true_and_false_represented_as_themselves + assert_equal nil, nil.as_json + assert_equal true, true.as_json + assert_equal false, false.as_json + end + protected def object_keys(json_object) diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb index 8e160714b1..2a0e8d20ed 100644 --- a/activesupport/test/log_subscriber_test.rb +++ b/activesupport/test/log_subscriber_test.rb @@ -11,7 +11,7 @@ class MyLogSubscriber < ActiveSupport::LogSubscriber def foo(event) debug "debug" - info "info" + info { "info" } warn "warn" 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("£1234567890,50", number_helper.number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""})) + assert_equal("$1,234,567,890.50", number_helper.number_to_currency("1234567890.50")) + assert_equal("1,234,567,890.50 Kč", number_helper.number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"})) + assert_equal("1,234,567,890.50 - Kč", number_helper.number_to_currency("-1234567890.50", {:unit => "Kč", :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/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb index 938bb4ee99..2c217157d3 100644 --- a/activesupport/test/ts_isolated.rb +++ b/activesupport/test/ts_isolated.rb @@ -10,7 +10,7 @@ class TestIsolated < ActiveSupport::TestCase define_method("test #{file}") do command = "#{ruby} -Ilib:test #{file}" result = silence_stderr { `#{command}` } - assert_block("#{command}\n#{result}") { $?.to_i.zero? } + assert $?.to_i.zero?, "#{command}\n#{result}" end end end |