diff options
Diffstat (limited to 'activesupport/lib/active_support')
37 files changed, 1024 insertions, 137 deletions
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' |