diff options
Diffstat (limited to 'activesupport')
19 files changed, 233 insertions, 24 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 2c6f4ed582..08bb632448 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Time#advance recognizes fractional days and weeks. Deprecate Durations of fractional months and years #970 [Tom Lea] + * Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. [Norbert Crombach, Pratik] * Switch from String#chars to String#mb_chars for the unicode proxy. [Manfred Stienstra] diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index e67b719ddb..11c128da22 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -11,7 +11,8 @@ module ActiveSupport #:nodoc: options.assert_valid_keys(:connector, :skip_last_comma, :locale) default = I18n.translate(:'support.array.sentence_connector', :locale => options[:locale]) - options.reverse_merge! :connector => default, :skip_last_comma => false + default_skip_last_comma = I18n.translate(:'support.array.skip_last_comma', :locale => options[:locale]) + options.reverse_merge! :connector => default, :skip_last_comma => default_skip_last_comma options[:connector] = "#{options[:connector]} " unless options[:connector].nil? || options[:connector].strip == '' case length diff --git a/activesupport/lib/active_support/core_ext/float.rb b/activesupport/lib/active_support/core_ext/float.rb index 86862b7150..af166aa610 100644 --- a/activesupport/lib/active_support/core_ext/float.rb +++ b/activesupport/lib/active_support/core_ext/float.rb @@ -1,5 +1,7 @@ require 'active_support/core_ext/float/rounding' +require 'active_support/core_ext/float/time' class Float #:nodoc: include ActiveSupport::CoreExtensions::Float::Rounding + include ActiveSupport::CoreExtensions::Float::Time end diff --git a/activesupport/lib/active_support/core_ext/float/time.rb b/activesupport/lib/active_support/core_ext/float/time.rb new file mode 100644 index 0000000000..13f2e0ddca --- /dev/null +++ b/activesupport/lib/active_support/core_ext/float/time.rb @@ -0,0 +1,27 @@ +module ActiveSupport #:nodoc: + module CoreExtensions #:nodoc: + module Float #:nodoc: + module Time + # Deprication helper methods not available as core_ext is loaded first. + def years + ::ActiveSupport::Deprecation.warn(self.class.deprecated_method_warning(:years, "Fractional years are not respected. Convert value to integer before calling #years."), caller) + years_without_deprecation + end + def months + ::ActiveSupport::Deprecation.warn(self.class.deprecated_method_warning(:months, "Fractional months are not respected. Convert value to integer before calling #months."), caller) + months_without_deprecation + end + + def months_without_deprecation + ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) + end + alias :month :months + + def years_without_deprecation + ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]]) + end + alias :year :years + end + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/integer.rb b/activesupport/lib/active_support/core_ext/integer.rb index d1e6d76acb..18a7a402db 100644 --- a/activesupport/lib/active_support/core_ext/integer.rb +++ b/activesupport/lib/active_support/core_ext/integer.rb @@ -1,7 +1,9 @@ require 'active_support/core_ext/integer/even_odd' require 'active_support/core_ext/integer/inflections' +require 'active_support/core_ext/integer/time' class Integer #:nodoc: include ActiveSupport::CoreExtensions::Integer::EvenOdd include ActiveSupport::CoreExtensions::Integer::Inflections + include ActiveSupport::CoreExtensions::Integer::Time end diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb new file mode 100644 index 0000000000..356e145b90 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -0,0 +1,45 @@ +module ActiveSupport #:nodoc: + module CoreExtensions #:nodoc: + module Integer #:nodoc: + # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. + # + # These methods use Time#advance for precise date calculations when using from_now, ago, etc. + # as well as adding or subtracting their results from a Time object. For example: + # + # # equivalent to Time.now.advance(:months => 1) + # 1.month.from_now + # + # # equivalent to Time.now.advance(:years => 2) + # 2.years.from_now + # + # # equivalent to Time.now.advance(:months => 4, :years => 5) + # (4.months + 5.years).from_now + # + # While these methods provide precise calculation when used as in the examples above, care + # should be taken to note that this is not true if the result of `months', `years', etc is + # converted before use: + # + # # equivalent to 30.days.to_i.from_now + # 1.month.to_i.from_now + # + # # equivalent to 365.25.days.to_f.from_now + # 1.year.to_f.from_now + # + # In such cases, Ruby's core + # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and + # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision + # date and time arithmetic + module Time + def months + ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) + end + alias :month :months + + def years + ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]]) + end + alias :year :years + end + end + end +end diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index aac698f7a8..bc7f180cd1 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -60,16 +60,6 @@ module ActiveSupport #:nodoc: end alias :fortnight :fortnights - def months - ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) - end - alias :month :months - - def years - ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]]) - end - alias :year :years - # Reads best without arguments: 10.minutes.ago def ago(time = ::Time.now) time - self diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 3cc6d59907..00078de692 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -98,6 +98,16 @@ module ActiveSupport #:nodoc: # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, # <tt>:minutes</tt>, <tt>:seconds</tt>. def advance(options) + unless options[:weeks].nil? + options[:weeks], partial_weeks = options[:weeks].divmod(1) + options[:days] = (options[:days] || 0) + 7 * partial_weeks + end + + unless options[:days].nil? + options[:days], partial_days = options[:days].divmod(1) + options[:hours] = (options[:hours] || 0) + 24 * partial_days + end + d = to_date.advance(options) time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600 diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index 89a93f4a5f..1ccfec4000 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -1,4 +1,5 @@ require 'singleton' +require 'iconv' module ActiveSupport # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, @@ -258,12 +259,31 @@ module ActiveSupport # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> def parameterize(string, sep = '-') re_sep = Regexp.escape(sep) - string.mb_chars.normalize(:kd). # Decompose accented characters - gsub(/[^\x00-\x7F]+/, ''). # Remove anything non-ASCII entirely (e.g. diacritics). - gsub(/[^a-z0-9\-_\+]+/i, sep). # Turn unwanted chars into the separator. - squeeze(sep). # No more than one of the separator in a row. - gsub(/^#{re_sep}|#{re_sep}$/i, ''). # Remove leading/trailing separator. - downcase + # replace accented chars with ther ascii equivalents + parameterized_string = transliterate(string) + # Turn unwanted chars into the seperator + parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep) + # No more than one of the separator in a row. + parameterized_string.squeeze!(sep) + # Remove leading/trailing separator. + parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '') + parameterized_string.downcase + end + + + # Replaces accented characters with their ascii equivalents. + def transliterate(string) + Iconv.iconv('ascii//ignore//translit', 'utf-8', string).to_s + end + + # The iconv transliteration code doesn't function correctly + # on some platforms, but it's very fast where it does function. + if "foo" != Inflector.transliterate("föö") + undef_method :transliterate + def transliterate(string) + string.mb_chars.normalize(:kd). # Decompose accented characters + gsub(/[^\x00-\x7F]+/, '') # Remove anything non-ASCII entirely (e.g. diacritics). + end end # Create the name of a table like Rails does for models to table names. This method diff --git a/activesupport/lib/active_support/locale/en-US.yml b/activesupport/lib/active_support/locale/en-US.yml index 60ecb1d42a..c31694b9d6 100644 --- a/activesupport/lib/active_support/locale/en-US.yml +++ b/activesupport/lib/active_support/locale/en-US.yml @@ -29,3 +29,4 @@ en-US: support: array: sentence_connector: "and" + skip_last_comma: false diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb index 4786fd6e0b..cd5c01cda2 100644 --- a/activesupport/lib/active_support/memoizable.rb +++ b/activesupport/lib/active_support/memoizable.rb @@ -1,5 +1,9 @@ module ActiveSupport module Memoizable + MEMOIZED_IVAR = Proc.new do |symbol| + "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym + end + module Freezable def self.included(base) base.class_eval do @@ -20,7 +24,7 @@ module ActiveSupport if method(m).arity == 0 __send__($1) else - ivar = :"@_memoized_#{$1}" + ivar = MEMOIZED_IVAR.call($1) instance_variable_set(ivar, {}) end end @@ -30,7 +34,7 @@ module ActiveSupport def unmemoize_all methods.each do |m| if m.to_s =~ /^_unmemoized_(.*)/ - ivar = :"@_memoized_#{$1}" + ivar = MEMOIZED_IVAR.call($1) instance_variable_get(ivar).clear if instance_variable_defined?(ivar) end end @@ -40,7 +44,7 @@ module ActiveSupport def memoize(*symbols) symbols.each do |symbol| original_method = :"_unmemoized_#{symbol}" - memoized_ivar = :"@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}" + memoized_ivar = MEMOIZED_IVAR.call(symbol) class_eval <<-EOS, __FILE__, __LINE__ include Freezable diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb index 018aafe607..65a96af49a 100644 --- a/activesupport/lib/active_support/multibyte.rb +++ b/activesupport/lib/active_support/multibyte.rb @@ -8,13 +8,13 @@ module ActiveSupport #:nodoc: module Multibyte # A list of all available normalization forms. See http://www.unicode.org/reports/tr15/tr15-29.html for more # information about normalization. - NORMALIZATIONS_FORMS = [:c, :kc, :d, :kd] + NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation UNICODE_VERSION = '5.1.0' # The default normalization used for operations that require normalization. It can be set to any of the - # normalizations in NORMALIZATIONS_FORMS. + # normalizations in NORMALIZATION_FORMS. # # Example: # ActiveSupport::Multibyte.default_normalization_form = :c diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb index 2dbaf8a405..30e3655b7b 100644 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb @@ -30,7 +30,13 @@ module I18n options.delete(:default) values = options.reject{|name, value| reserved.include? name } - entry = lookup(locale, key, scope) || default(locale, default, options) || raise(I18n::MissingTranslationData.new(locale, key, options)) + entry = lookup(locale, key, scope) + if entry.nil? + entry = default(locale, default, options) + if entry.nil? + raise(I18n::MissingTranslationData.new(locale, key, options)) + end + end entry = pluralize locale, entry, count entry = interpolate locale, entry, values entry @@ -83,7 +89,13 @@ module I18n return unless key init_translations unless initialized? keys = I18n.send :normalize_translation_keys, locale, key, scope - keys.inject(translations){|result, k| result[k.to_sym] or return nil } + keys.inject(translations) do |result, k| + if (x = result[k.to_sym]).nil? + return nil + else + x + end + end end # Evaluates a default translation. diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index f802ed8760..80cc6d25d3 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -30,7 +30,57 @@ class DurationTest < Test::Unit::TestCase end end + def test_fractional_weeks + assert_equal (86400 * 7) * 1.5, 1.5.weeks + assert_equal (86400 * 7) * 1.7, 1.7.weeks + end + + def test_fractional_days + assert_equal 86400 * 1.5, 1.5.days + assert_equal 86400 * 1.7, 1.7.days + end + uses_mocha 'TestDurationSinceAndAgoWithCurrentTime' do + def test_since_and_ago_with_fractional_days + Time.stubs(:now).returns Time.local(2000) + # since + assert_equal 36.hours.since, 1.5.days.since + assert_equal((24 * 1.7).hours.since, 1.7.days.since) + # ago + assert_equal 36.hours.ago, 1.5.days.ago + assert_equal((24 * 1.7).hours.ago, 1.7.days.ago) + end + + def test_since_and_ago_with_fractional_weeks + Time.stubs(:now).returns Time.local(2000) + # since + assert_equal((7 * 36).hours.since, 1.5.weeks.since) + assert_equal((7 * 24 * 1.7).hours.since, 1.7.weeks.since) + # ago + assert_equal((7 * 36).hours.ago, 1.5.weeks.ago) + assert_equal((7 * 24 * 1.7).hours.ago, 1.7.weeks.ago) + end + + def test_deprecated_fractional_years + years_re = /Fractional years are not respected\. Convert value to integer before calling #years\./ + assert_deprecated(years_re){1.0.years} + assert_deprecated(years_re){1.5.years} + assert_not_deprecated{1.years} + assert_deprecated(years_re){1.0.year} + assert_deprecated(years_re){1.5.year} + assert_not_deprecated{1.year} + end + + def test_deprecated_fractional_months + months_re = /Fractional months are not respected\. Convert value to integer before calling #months\./ + assert_deprecated(months_re){1.5.months} + assert_deprecated(months_re){1.0.months} + assert_not_deprecated{1.months} + assert_deprecated(months_re){1.5.month} + assert_deprecated(months_re){1.0.month} + assert_not_deprecated{1.month} + end + def test_since_and_ago_anchored_to_time_now_when_time_zone_default_not_set Time.zone_default = nil with_env_tz 'US/Eastern' do diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 8ceaedc7f4..11ec1c6cd6 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -381,7 +381,11 @@ class TimeExtCalculationsTest < Test::Unit::TestCase assert_equal Time.local(2006,2,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 1) assert_equal Time.local(2005,6,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:months => 4) assert_equal Time.local(2005,3,21,15,15,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3) + assert_equal Time.local(2005,3,25,3,15,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3.5) + assert_equal Time.local(2005,3,26,12,51,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3.7) assert_equal Time.local(2005,3,5,15,15,10), Time.local(2005,2,28,15,15,10).advance(:days => 5) + assert_equal Time.local(2005,3,6,3,15,10), Time.local(2005,2,28,15,15,10).advance(:days => 5.5) + assert_equal Time.local(2005,3,6,8,3,10), Time.local(2005,2,28,15,15,10).advance(:days => 5.7) assert_equal Time.local(2012,9,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 7) assert_equal Time.local(2013,10,3,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) assert_equal Time.local(2013,10,17,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) @@ -399,7 +403,11 @@ class TimeExtCalculationsTest < Test::Unit::TestCase assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 1) assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:months => 4) assert_equal Time.utc(2005,3,21,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3) + assert_equal Time.utc(2005,3,25,3,15,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3.5) + assert_equal Time.utc(2005,3,26,12,51,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3.7) assert_equal Time.utc(2005,3,5,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5) + assert_equal Time.utc(2005,3,6,3,15,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5.5) + assert_equal Time.utc(2005,3,6,8,3,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5.7) assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 7) assert_equal Time.utc(2013,10,3,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 19, :days => 11) assert_equal Time.utc(2013,10,17,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb index 4b17e3c523..db5bd5e088 100644 --- a/activesupport/test/i18n_test.rb +++ b/activesupport/test/i18n_test.rb @@ -72,4 +72,20 @@ class I18nTest < Test::Unit::TestCase def test_time_pm assert_equal 'pm', I18n.translate(:'time.pm') end + + def test_sentence_connector + assert_equal 'and', I18n.translate(:'support.array.sentence_connector') + end + + def test_skip_last_comma + assert_equal false, I18n.translate(:'support.array.skip_last_comma') + end + + def test_to_sentence + assert_equal 'a, b, and c', %w[a b c].to_sentence + I18n.backend.store_translations 'en-US', :support => { :array => { :skip_last_comma => true } } + assert_equal 'a, b and c', %w[a b c].to_sentence + ensure + I18n.backend.store_translations 'en-US', :support => { :array => { :skip_last_comma => false } } + end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index fc7a35f859..3aa18ca781 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + module InflectorTestCases SingularToPlural = { "search" => "searches", diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 497f028369..c070e0d9ed 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -100,6 +100,18 @@ class TestJSONEncoding < Test::Unit::TestCase ActiveSupport.use_standard_json_time_format = false end + def test_nested_hash_with_float + assert_nothing_raised do + hash = { + "CHI" => { + :dislay_name => "chicago", + :latitude => 123.234 + } + } + result = hash.to_json + end + end + protected def object_keys(json_object) diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb index 135d56f14a..a78ebd9425 100644 --- a/activesupport/test/memoizable_test.rb +++ b/activesupport/test/memoizable_test.rb @@ -100,6 +100,11 @@ uses_mocha 'Memoizable' do def test_memoization_with_punctuation assert_equal true, @person.name? + + assert_nothing_raised(NameError) do + @person.memoize_all + @person.unmemoize_all + end end def test_memoization_with_nil_value |