From a17027d13a48e1e64b14a28e7d58e341812f8cb4 Mon Sep 17 00:00:00 2001
From: Pratik Naik <pratiknaik@gmail.com>
Date: Sat, 13 Sep 2008 20:28:01 +0100
Subject: Merge docrails

---
 activesupport/lib/active_support/core_ext/array/grouping.rb | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index dd1484f8fa..f782f8facf 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -7,16 +7,16 @@ module ActiveSupport #:nodoc:
         # 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) {|g| p g}
+        #   %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group}
         #   ["1", "2", "3"]
         #   ["4", "5", "6"]
         #   ["7", nil, nil]
         #
-        #   %w(1 2 3).in_groups_of(2, '&nbsp;') {|g| p g}
+        #   %w(1 2 3).in_groups_of(2, '&nbsp;') {|group| p group}
         #   ["1", "2"]
         #   ["3", "&nbsp;"]
         #
-        #   %w(1 2 3).in_groups_of(2, false) {|g| p g}
+        #   %w(1 2 3).in_groups_of(2, false) {|group| p group}
         #   ["1", "2"]
         #   ["3"]
         def in_groups_of(number, fill_with = nil)
@@ -42,17 +42,17 @@ module ActiveSupport #:nodoc:
         # Splits or iterates over the array in +number+ of groups, padding any
         # remaining slots with +fill_with+ unless it is +false+.
         #
-        #   %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|g| p g}
+        #   %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
         #   ["1", "2", "3", "4"]
         #   ["5", "6", "7", nil]
         #   ["8", "9", "10", nil]
         #
-        #   %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|g| p g}
+        #   %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|group| p group}
         #   ["1", "2", "3"]
         #   ["4", "5", "&nbsp;"]
         #   ["6", "7", "&nbsp;"]
         #
-        #   %w(1 2 3 4 5 6 7).in_groups(3, false) {|g| p g}
+        #   %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
         #   ["1", "2", "3"]
         #   ["4", "5"]
         #   ["6", "7"]
-- 
cgit v1.2.3


From d28c724b4074605445d2834888ad280778f0af6a Mon Sep 17 00:00:00 2001
From: Michael Koziarski <michael@koziarski.com>
Date: Sun, 14 Sep 2008 09:44:29 +0200
Subject: Make sure the permissions check file is closed before being unlinked.

[#1035 state:committed]
---
 activesupport/lib/active_support/core_ext/file/atomic.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index 4d3cf5423f..f988eff3d9 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -28,7 +28,7 @@ module ActiveSupport #:nodoc:
           rescue Errno::ENOENT
             # No old permissions, write a temp file to determine the defaults
             check_name = ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}"
-            new(check_name, "w")
+            open(check_name, "w") { }
             old_stat = stat(check_name)
             unlink(check_name)
           end
-- 
cgit v1.2.3


From bfa12d7a02ce0e84fcd2b83f2ce6fee1386757e3 Mon Sep 17 00:00:00 2001
From: Clemens Kofler <clemens@railway.at>
Date: Thu, 11 Sep 2008 12:51:16 +0200
Subject: Introduce convenience methods past?, today? and future? for Date and
 Time classes to facilitate Date/Time comparisons.

---
 .../active_support/core_ext/date/calculations.rb   | 33 +++++---
 .../core_ext/date_time/calculations.rb             | 24 ++++--
 .../active_support/core_ext/time/calculations.rb   | 35 +++++---
 activesupport/lib/active_support/time_with_zone.rb | 92 ++++++++++++----------
 4 files changed, 120 insertions(+), 64 deletions(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index b5180c9592..43d70c7013 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -20,18 +20,33 @@ module ActiveSupport #:nodoc:
           def yesterday
             ::Date.today.yesterday
           end
-          
+
           # Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
           def tomorrow
             ::Date.today.tomorrow
           end
-          
+
           # Returns Time.zone.today when config.time_zone is set, otherwise just returns Date.today.
           def current
             ::Time.zone_default ? ::Time.zone.today : ::Date.today
           end
         end
-        
+
+        # Tells whether the Date object's date lies in the past
+        def past?
+          self < ::Date.current
+        end
+
+        # Tells whether the Date object's date is today
+        def today?
+          self.to_date == ::Date.current # we need the to_date because of DateTime
+        end
+
+        # Tells whether the Date object's date lies in the future
+        def future?
+          self > ::Date.current
+        end
+
         # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
         # and then subtracts the specified number of seconds
         def ago(seconds)
@@ -57,7 +72,7 @@ module ActiveSupport #:nodoc:
         def end_of_day
           to_time.end_of_day
         end
-        
+
         def plus_with_duration(other) #:nodoc:
           if ActiveSupport::Duration === other
             other.since(self)
@@ -65,7 +80,7 @@ module ActiveSupport #:nodoc:
             plus_without_duration(other)
           end
         end
-        
+
         def minus_with_duration(other) #:nodoc:
           if ActiveSupport::Duration === other
             plus_with_duration(-other)
@@ -73,8 +88,8 @@ module ActiveSupport #:nodoc:
             minus_without_duration(other)
           end
         end
-        
-        # Provides precise Date calculations for years, months, and days.  The +options+ parameter takes a hash with 
+
+        # Provides precise Date calculations for years, months, and days.  The +options+ parameter takes a hash with
         # any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
         def advance(options)
           d = self
@@ -98,7 +113,7 @@ module ActiveSupport #:nodoc:
             options[:day]   || self.day
           )
         end
-        
+
         # Returns a new Date/DateTime representing the time a number of specified months ago
         def months_ago(months)
           advance(:months => -months)
@@ -161,7 +176,7 @@ module ActiveSupport #:nodoc:
           days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6}
           result = (self + 7).beginning_of_week + days_into_week[day]
           self.acts_like?(:time) ? result.change(:hour => 0) : result
-        end          
+        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)
         def beginning_of_month
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 155c961a91..0099431e9d 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -7,7 +7,7 @@ module ActiveSupport #:nodoc:
       module Calculations
         def self.included(base) #:nodoc:
           base.extend ClassMethods
-          
+
           base.class_eval do
             alias_method :compare_without_coercion, :<=>
             alias_method :<=>, :compare_with_coercion
@@ -19,6 +19,20 @@ module ActiveSupport #:nodoc:
           def local_offset
             ::Time.local(2007).utc_offset.to_r / 86400
           end
+
+          def current
+            ::Time.zone_default ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
+          end
+        end
+
+        # Tells whether the DateTime object's datetime lies in the past
+        def past?
+          self < ::DateTime.current
+        end
+
+        # Tells whether the DateTime object's datetime lies in the future
+        def future?
+          self > ::DateTime.current
         end
 
         # Seconds since midnight: DateTime.now.seconds_since_midnight
@@ -78,7 +92,7 @@ module ActiveSupport #:nodoc:
         def end_of_day
           change(:hour => 23, :min => 59, :sec => 59)
         end
-        
+
         # Adjusts DateTime to UTC by adding its offset value; offset is set to 0
         #
         # Example:
@@ -89,17 +103,17 @@ module ActiveSupport #:nodoc:
           new_offset(0)
         end
         alias_method :getutc, :utc
-        
+
         # Returns true if offset == 0
         def utc?
           offset == 0
         end
-        
+
         # Returns the offset value in seconds
         def utc_offset
           (offset * 86400).to_i
         end
-        
+
         # Layers additional behavior on DateTime#<=> so that Time and ActiveSupport::TimeWithZone instances can be compared with a DateTime
         def compare_with_coercion(other)
           other = other.comparable_time if other.respond_to?(:comparable_time)
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index cd234c9b89..070f72c854 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -9,13 +9,13 @@ module ActiveSupport #:nodoc:
           base.class_eval do
             alias_method :plus_without_duration, :+
             alias_method :+, :plus_with_duration
-            
+
             alias_method :minus_without_duration, :-
             alias_method :-, :minus_with_duration
-            
+
             alias_method :minus_without_coercion, :-
             alias_method :-, :minus_with_coercion
-            
+
             alias_method :compare_without_coercion, :<=>
             alias_method :<=>, :compare_with_coercion
           end
@@ -28,9 +28,9 @@ module ActiveSupport #:nodoc:
           def ===(other)
             other.is_a?(::Time)
           end
-          
-          # Return the number of days in the given month. 
-          # If no year is specified, it will use the current year. 
+
+          # Return the number of days in the given month.
+          # If no year is specified, it will use the current year.
           def days_in_month(month, year = now.year)
             return 29 if month == 2 && ::Date.gregorian_leap?(year)
             COMMON_YEAR_DAYS_IN_MONTH[month]
@@ -57,6 +57,21 @@ module ActiveSupport #:nodoc:
           end
         end
 
+        # Tells whether the Time object's time lies in the past
+        def past?
+          self < ::Time.current
+        end
+
+        # Tells whether the Time object's time is today
+        def today?
+          self.to_date == ::Date.current
+        end
+
+        # Tells whether the Time object's time lies in the future
+        def future?
+          self > ::Time.current
+        end
+
         # Seconds since midnight: Time.now.seconds_since_midnight
         def seconds_since_midnight
           self.to_i - self.change(:hour => 0).to_i + (self.usec/1.0e+6)
@@ -106,7 +121,7 @@ module ActiveSupport #:nodoc:
             (seconds.abs >= 86400 && initial_dst != final_dst) ? f + (initial_dst - final_dst).hours : f
           end
         rescue
-          self.to_datetime.since(seconds)          
+          self.to_datetime.since(seconds)
         end
         alias :in :since
 
@@ -199,7 +214,7 @@ module ActiveSupport #:nodoc:
           change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 0)
         end
         alias :at_end_of_month :end_of_month
-		
+
         # Returns  a new Time representing the start of the quarter (1st of january, april, july, october, 0:00)
         def beginning_of_quarter
           beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
@@ -249,7 +264,7 @@ module ActiveSupport #:nodoc:
             minus_without_duration(other)
           end
         end
-        
+
         # Time#- can also be used to determine the number of seconds between two Time instances.
         # We're layering on additional behavior so that ActiveSupport::TimeWithZone instances
         # are coerced into values that Time#- will recognize
@@ -257,7 +272,7 @@ module ActiveSupport #:nodoc:
           other = other.comparable_time if other.respond_to?(:comparable_time)
           minus_without_coercion(other)
         end
-        
+
         # Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances
         # can be chronologically compared with a Time
         def compare_with_coercion(other)
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 75591b7c34..44088f436e 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,6 +1,6 @@
 require 'tzinfo'
 module ActiveSupport
-  # A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are 
+  # A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are
   # limited to UTC and the system's <tt>ENV['TZ']</tt> zone.
   #
   # You shouldn't ever need to create a TimeWithZone instance directly via <tt>new</tt> -- instead, Rails provides the methods
@@ -32,12 +32,12 @@ module ActiveSupport
   class TimeWithZone
     include Comparable
     attr_reader :time_zone
-  
+
     def initialize(utc_time, time_zone, local_time = nil, period = nil)
       @utc, @time_zone, @time = utc_time, time_zone, local_time
       @period = @utc ? period : get_period_and_ensure_valid_local_time
     end
-  
+
     # Returns a Time or DateTime instance that represents the time in +time_zone+.
     def time
       @time ||= period.to_local(@utc)
@@ -51,7 +51,7 @@ module ActiveSupport
     alias_method :getgm, :utc
     alias_method :getutc, :utc
     alias_method :gmtime, :utc
-  
+
     # Returns the underlying TZInfo::TimezonePeriod.
     def period
       @period ||= time_zone.period_for_utc(@utc)
@@ -62,38 +62,38 @@ module ActiveSupport
       return self if time_zone == new_zone
       utc.in_time_zone(new_zone)
     end
-  
+
     # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your system's <tt>ENV['TZ']</tt> zone
     def localtime
       utc.getlocal
     end
     alias_method :getlocal, :localtime
-  
+
     def dst?
       period.dst?
     end
     alias_method :isdst, :dst?
-  
+
     def utc?
       time_zone.name == 'UTC'
     end
     alias_method :gmt?, :utc?
-  
+
     def utc_offset
       period.utc_total_offset
     end
     alias_method :gmt_offset, :utc_offset
     alias_method :gmtoff, :utc_offset
-  
+
     def formatted_offset(colon = true, alternate_utc_string = nil)
       utc? && alternate_utc_string || utc_offset.to_utc_offset_s(colon)
     end
-  
+
     # Time uses +zone+ to display the time zone abbreviation, so we're duck-typing it.
     def zone
       period.zone_identifier.to_s
     end
-  
+
     def inspect
       "#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
     end
@@ -122,7 +122,7 @@ module ActiveSupport
         %("#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}")
       end
     end
-    
+
     def to_yaml(options = {})
       if options.kind_of?(YAML::Emitter)
         utc.to_yaml(options)
@@ -130,19 +130,19 @@ module ActiveSupport
         time.to_yaml(options).gsub('Z', formatted_offset(true, 'Z'))
       end
     end
-    
+
     def httpdate
       utc.httpdate
     end
-  
+
     def rfc2822
       to_s(:rfc822)
     end
     alias_method :rfc822, :rfc2822
-  
+
     # <tt>:db</tt> format outputs time in UTC; all others output time in local.
     # Uses TimeWithZone's +strftime+, so <tt>%Z</tt> and <tt>%z</tt> work correctly.
-    def to_s(format = :default) 
+    def to_s(format = :default)
       return utc.to_s(format) if format == :db
       if formatter = ::Time::DATE_FORMATS[format]
         formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
@@ -150,27 +150,39 @@ module ActiveSupport
         "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format
       end
     end
-    
+
     # Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and +formatted_offset+, respectively, before passing to
     # Time#strftime, so that zone information is correct
     def strftime(format)
       format = format.gsub('%Z', zone).gsub('%z', formatted_offset(false))
       time.strftime(format)
     end
-  
+
     # Use the time in UTC for comparisons.
     def <=>(other)
       utc <=> other
     end
-    
+
     def between?(min, max)
       utc.between?(min, max)
     end
-    
+
+    def past?
+      utc.past?
+    end
+
+    def today?
+      utc.today?
+    end
+
+    def future?
+      utc.future?
+    end
+
     def eql?(other)
       utc == other
     end
-    
+
     def +(other)
       # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
       # otherwise move forward from #utc, for accuracy when moving across DST boundaries
@@ -194,7 +206,7 @@ module ActiveSupport
         result.in_time_zone(time_zone)
       end
     end
-    
+
     def since(other)
       # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
       # otherwise move forward from #utc, for accuracy when moving across DST boundaries
@@ -204,7 +216,7 @@ module ActiveSupport
         utc.since(other).in_time_zone(time_zone)
       end
     end
-    
+
     def ago(other)
       since(-other)
     end
@@ -218,7 +230,7 @@ module ActiveSupport
         utc.advance(options).in_time_zone(time_zone)
       end
     end
-    
+
     %w(year mon month day mday hour min sec).each do |method_name|
       class_eval <<-EOV
         def #{method_name}
@@ -226,45 +238,45 @@ module ActiveSupport
         end
       EOV
     end
-    
+
     def usec
       time.respond_to?(:usec) ? time.usec : 0
     end
-    
+
     def to_a
       [time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
     end
-    
+
     def to_f
       utc.to_f
-    end    
-    
+    end
+
     def to_i
       utc.to_i
     end
     alias_method :hash, :to_i
     alias_method :tv_sec, :to_i
-  
+
     # A TimeWithZone acts like a Time, so just return +self+.
     def to_time
       self
     end
-    
+
     def to_datetime
       utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
     end
-    
+
     # So that +self+ <tt>acts_like?(:time)</tt>.
     def acts_like_time?
       true
     end
-  
+
     # Say we're a Time to thwart type checking.
     def is_a?(klass)
       klass == ::Time || super
     end
     alias_method :kind_of?, :is_a?
-  
+
     # Neuter freeze because freezing can cause problems with lazy loading of attributes.
     def freeze
       self
@@ -273,7 +285,7 @@ module ActiveSupport
     def marshal_dump
       [utc, time_zone.name, time]
     end
-    
+
     def marshal_load(variables)
       initialize(variables[0].utc, ::Time.__send__(:get_zone, variables[1]), variables[2].utc)
     end
@@ -290,10 +302,10 @@ module ActiveSupport
       result = time.__send__(sym, *args, &block)
       result.acts_like?(:time) ? self.class.new(nil, time_zone, result) : result
     end
-    
-    private      
+
+    private
       def get_period_and_ensure_valid_local_time
-        # we don't want a Time.local instance enforcing its own DST rules as well, 
+        # we don't want a Time.local instance enforcing its own DST rules as well,
         # so transfer time values to a utc constructor if necessary
         @time = transfer_time_values_to_utc_constructor(@time) unless @time.utc?
         begin
@@ -304,11 +316,11 @@ module ActiveSupport
           retry
         end
       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)
       end
-      
+
       def duration_of_variable_length?(obj)
         ActiveSupport::Duration === obj && obj.parts.flatten.detect {|p| [:years, :months, :days].include? p }
       end
-- 
cgit v1.2.3


From cce7ae54663243a224e9871f1aac2388842b0c3a Mon Sep 17 00:00:00 2001
From: gbuesing <gbuesing@gmail.com>
Date: Sun, 14 Sep 2008 22:56:32 -0500
Subject: Add thorough tests for Time-object #past?, #future? and #today. Fix
 TimeWithZone #today? to use #time instead of #utc for date comparison. Update
 changelog. [#720 state:resolved]

---
 activesupport/lib/active_support/time_with_zone.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 44088f436e..54cf945251 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -172,7 +172,7 @@ module ActiveSupport
     end
 
     def today?
-      utc.today?
+      time.today?
     end
 
     def future?
-- 
cgit v1.2.3


From 157141b2949b845e372ee703bfd6fba3ffb00415 Mon Sep 17 00:00:00 2001
From: gbuesing <gbuesing@gmail.com>
Date: Sun, 14 Sep 2008 23:07:48 -0500
Subject: TimeWithZone #wday, #yday and #to_date avoid trip through
 #method_missing

---
 activesupport/lib/active_support/time_with_zone.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 54cf945251..b7b8807c6d 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -231,7 +231,7 @@ module ActiveSupport
       end
     end
 
-    %w(year mon month day mday hour min sec).each do |method_name|
+    %w(year mon month day mday wday yday hour min sec to_date).each do |method_name|
       class_eval <<-EOV
         def #{method_name}
           time.#{method_name}
-- 
cgit v1.2.3


From 4dae3649f062137347bac43cd0708207d2a94d66 Mon Sep 17 00:00:00 2001
From: Michael Koziarski <michael@koziarski.com>
Date: Tue, 16 Sep 2008 16:50:14 +0200
Subject: Enhance the test "some string" method to support creating 'pending'
 tests.

If no block is provided to the test method, a default test will be generated which simply flunks.  This makes it easy for you to generate a list of what you intend to do, then flesh it out with actual tests.
---
 activesupport/lib/active_support/test_case.rb | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 0f531b0c79..197e73b3e8 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -12,7 +12,13 @@ module ActiveSupport
       test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
       defined = instance_method(test_name) rescue false
       raise "#{test_name} is already defined in #{self}" if defined
-      define_method(test_name, &block)
+      if block_given?
+        define_method(test_name, &block)
+      else
+        define_method(test_name) do
+          flunk "No implementation provided for #{name}"
+        end
+      end
     end
   end
 end
-- 
cgit v1.2.3


From e7cb8c844ad9a5b3260c7e369b288d0792576765 Mon Sep 17 00:00:00 2001
From: Duff OMelia <duff@omelia.org>
Date: Thu, 18 Sep 2008 08:51:19 -0500
Subject: Ensure old buffers get properly cleared to avoid leaking memory

Signed-off-by: Joshua Peek <josh@joshpeek.com>
---
 activesupport/lib/active_support/buffered_logger.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index 6553d72b4f..77e0b1d33f 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -116,7 +116,7 @@ module ActiveSupport
       end
 
       def clear_buffer
-        @buffer[Thread.current] = []
+        @buffer.delete(Thread.current)
       end
   end
 end
-- 
cgit v1.2.3


From 79f55de9c5e3ff1f8d9e767c5af21ba31be4cfba Mon Sep 17 00:00:00 2001
From: Carlos Brando <eduardobrando@gmail.com>
Date: Fri, 19 Sep 2008 09:06:35 -0500
Subject: Fixed Time#end_of_quarter to not blow up on May 31st [#313
 state:resolved]

Signed-off-by: Joshua Peek <josh@joshpeek.com>
---
 activesupport/lib/active_support/core_ext/time/calculations.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 070f72c854..3cc6d59907 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -223,7 +223,7 @@ module ActiveSupport #:nodoc:
 
         # Returns a new Time representing the end of the quarter (last day of march, june, september, december, 23:59:59)
         def end_of_quarter
-          change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
+          beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
         end
         alias :at_end_of_quarter :end_of_quarter
 
-- 
cgit v1.2.3


From 8cb7d460439a9b20a80a77b6370c1107233d1cbd Mon Sep 17 00:00:00 2001
From: Sven Fuchs <svenfuchs@artweb-design.de>
Date: Sun, 14 Sep 2008 13:36:06 +0200
Subject: I18n: move old-style interpolation syntax deprecation to Active
 Record. [#1044 state:resolved]

Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
---
 .../lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb    | 7 -------
 1 file changed, 7 deletions(-)

(limited to 'activesupport/lib')

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 e2d19cdd45..ff15d83f4e 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
@@ -4,7 +4,6 @@ module I18n
   module Backend
     class Simple
       INTERPOLATION_RESERVED_KEYS = %w(scope default)
-      DEPRECATED_INTERPOLATORS = { '%d' => '{{count}}', '%s' => '{{value}}' }
       MATCH = /(\\\\)?\{\{([^\}]+)\}\}/
 
       # Accepts a list of paths to translation files. Loads translations from 
@@ -120,12 +119,6 @@ module I18n
         def interpolate(locale, string, values = {})
           return string unless string.is_a?(String)
 
-          string = string.gsub(/%d|%s/) do |s|
-            instead = DEPRECATED_INTERPOLATORS[s]
-            ActiveSupport::Deprecation.warn "using #{s} in messages is deprecated; use #{instead} instead."
-            instead
-          end
-
           if string.respond_to?(:force_encoding)
             original_encoding = string.encoding
             string.force_encoding(Encoding::BINARY)
-- 
cgit v1.2.3


From a3b7fa78bfdc33e45e39c095b67e02d50a2c7bea Mon Sep 17 00:00:00 2001
From: Sven Fuchs <svenfuchs@artweb-design.de>
Date: Mon, 15 Sep 2008 10:26:50 +0200
Subject: I18n: Introduce I18n.load_path in favor of I18n.load_translations and
 change Simple backend to load translations lazily. [#1048 state:resolved]

Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
---
 activesupport/lib/active_support.rb                |  2 +-
 .../lib/active_support/vendor/i18n-0.0.1/i18n.rb   | 27 ++++++++++++++--------
 .../vendor/i18n-0.0.1/i18n/backend/simple.rb       | 14 +++++++++--
 3 files changed, 30 insertions(+), 13 deletions(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 7e34a871e3..b30faff06d 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -56,7 +56,7 @@ require 'active_support/time_with_zone'
 
 require 'active_support/secure_random'
 
-I18n.load_translations File.dirname(__FILE__) + '/active_support/locale/en-US.yml'
+I18n.load_path << File.dirname(__FILE__) + '/active_support/locale/en-US.yml'
 
 Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector')
 Dependencies = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Dependencies', 'ActiveSupport::Dependencies')
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb
index 0988ea8f44..344c77aecf 100755
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb
@@ -10,7 +10,8 @@ require 'i18n/exceptions'
 
 module I18n  
   @@backend = nil
-  @@default_locale = 'en-US'
+  @@load_path = nil
+  @@default_locale = :'en-US'
   @@exception_handler = :default_exception_handler
     
   class << self
@@ -49,14 +50,22 @@ module I18n
       @@exception_handler = exception_handler
     end
     
-    # Allows client libraries to pass arguments that specify a source for 
-    # translation data to be loaded by the backend. The backend defines
-    # acceptable sources. 
+    # Allow clients to register paths providing translation data sources. The
+    # backend defines acceptable sources.
+    #
     # E.g. the provided SimpleBackend accepts a list of paths to translation
     # files which are either named *.rb and contain plain Ruby Hashes or are
-    # named *.yml and contain YAML data.)
-    def load_translations(*args)
-      backend.load_translations(*args)
+    # named *.yml and contain YAML data. So for the SimpleBackend clients may
+    # register translation files like this:
+    #   I18n.load_path << 'path/to/locale/en-US.yml'
+    def load_path
+      @@load_path ||= []
+    end
+
+    # Sets the load path instance. Custom implementations are expected to
+    # behave like a Ruby Array.
+    def load_path=(load_path)
+      @@load_path = load_path
     end
     
     # Translates, pluralizes and interpolates a given key using a given locale, 
@@ -175,6 +184,4 @@ module I18n
       keys.flatten.map{|k| k.to_sym}
     end
   end
-end
-
-
+end
\ No newline at end of file
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 ff15d83f4e..2dbaf8a405 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
@@ -1,4 +1,4 @@
-require 'strscan'
+require 'yaml'
 
 module I18n
   module Backend
@@ -59,7 +59,16 @@ module I18n
         object.strftime(format)
       end
       
+      def initialized?
+        @initialized ||= false
+      end
+
       protected
+
+        def init_translations
+          load_translations(*I18n.load_path)
+          @initialized = true
+        end
         
         def translations
           @translations ||= {}
@@ -72,6 +81,7 @@ module I18n
         # <tt>%w(currency format)</tt>.
         def lookup(locale, key, scope = [])
           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 }
         end
@@ -94,7 +104,7 @@ module I18n
         rescue MissingTranslationData
           nil
         end
-      
+
         # Picks a translation from an array according to English pluralization
         # rules. It will pick the first translation if count is not equal to 1
         # and the second translation if it is equal to 1. Other backends can
-- 
cgit v1.2.3


From 22f75d539dca7b6f33cbf86e4e9d1944bb22731f Mon Sep 17 00:00:00 2001
From: Manfred Stienstra <manfred@fngtps.com>
Date: Sun, 21 Sep 2008 17:21:30 +0200
Subject: Simplify ActiveSupport::Multibyte and make it run on Ruby 1.9.

* Unicode methods are now defined directly on Chars instead of a handler
* Updated Unicode database to Unicode 5.1.0
* Improved documentation
---
 .../lib/active_support/core_ext/string.rb          |   6 +-
 .../active_support/core_ext/string/multibyte.rb    |  81 +++
 .../lib/active_support/core_ext/string/unicode.rb  |  66 --
 activesupport/lib/active_support/multibyte.rb      |  36 +-
 .../lib/active_support/multibyte/chars.rb          | 782 +++++++++++++++++----
 .../lib/active_support/multibyte/exceptions.rb     |   7 +
 .../multibyte/generators/generate_tables.rb        | 149 ----
 .../multibyte/handlers/passthru_handler.rb         |   9 -
 .../multibyte/handlers/utf8_handler.rb             | 564 ---------------
 .../multibyte/handlers/utf8_handler_proc.rb        |  43 --
 .../active_support/multibyte/unicode_database.rb   |  71 ++
 .../lib/active_support/values/unicode_tables.dat   | Bin 656156 -> 710734 bytes
 12 files changed, 848 insertions(+), 966 deletions(-)
 create mode 100644 activesupport/lib/active_support/core_ext/string/multibyte.rb
 delete mode 100644 activesupport/lib/active_support/core_ext/string/unicode.rb
 create mode 100644 activesupport/lib/active_support/multibyte/exceptions.rb
 delete mode 100755 activesupport/lib/active_support/multibyte/generators/generate_tables.rb
 delete mode 100644 activesupport/lib/active_support/multibyte/handlers/passthru_handler.rb
 delete mode 100644 activesupport/lib/active_support/multibyte/handlers/utf8_handler.rb
 delete mode 100644 activesupport/lib/active_support/multibyte/handlers/utf8_handler_proc.rb
 create mode 100644 activesupport/lib/active_support/multibyte/unicode_database.rb

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 7ff2f11eff..16c544a577 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -1,9 +1,11 @@
+# encoding: utf-8
+
 require 'active_support/core_ext/string/inflections'
 require 'active_support/core_ext/string/conversions'
 require 'active_support/core_ext/string/access'
 require 'active_support/core_ext/string/starts_ends_with'
 require 'active_support/core_ext/string/iterators'
-require 'active_support/core_ext/string/unicode'
+require 'active_support/core_ext/string/multibyte'
 require 'active_support/core_ext/string/xchar'
 require 'active_support/core_ext/string/filters'
 require 'active_support/core_ext/string/behavior'
@@ -15,6 +17,6 @@ class String #:nodoc:
   include ActiveSupport::CoreExtensions::String::Inflections
   include ActiveSupport::CoreExtensions::String::StartsEndsWith
   include ActiveSupport::CoreExtensions::String::Iterators
-  include ActiveSupport::CoreExtensions::String::Unicode
   include ActiveSupport::CoreExtensions::String::Behavior
+  include ActiveSupport::CoreExtensions::String::Multibyte
 end
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
new file mode 100644
index 0000000000..5a2dc36f72
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -0,0 +1,81 @@
+# encoding: utf-8
+
+module ActiveSupport #:nodoc:
+  module CoreExtensions #:nodoc:
+    module String #:nodoc:
+      # Implements multibyte methods for easier access to multibyte characters in a String instance.
+      module Multibyte
+        unless '1.9'.respond_to?(:force_encoding)
+          # +mb_chars+ is a multibyte safe proxy method for string methods.
+          #
+          # In Ruby 1.8 and older it creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
+          # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
+          # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsuled string.
+          #
+          #   name = 'Claus Müller'
+          #   name.reverse  #=> "rell??M sualC"
+          #   name.length   #=> 13
+          #
+          #   name.mb_chars.reverse.to_s   #=> "rellüM sualC"
+          #   name.mb_chars.length         #=> 12
+          #
+          # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware so we don't need
+          # a proxy class any more. This means that +mb_chars+ makes it easier to write code that runs on multiple Ruby
+          # versions.
+          #
+          # == Method chaining 
+          #
+          # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
+          # method chaining on the result of any of these methods.
+          #
+          #   name.mb_chars.reverse.length #=> 12
+          #
+          # == Interoperability and configuration
+          #
+          # The Char object tries to be as interchangeable with String objects as possible: sorting and comparing between
+          # String and Char work like expected. The bang! methods change the internal string representation in the Chars
+          # object. Interoperability problems can be resolved easily with a +to_s+ call.
+          #
+          # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
+          # information about how to change the default Multibyte behaviour, see ActiveSupport::Multibyte.
+          def mb_chars
+            if ActiveSupport::Multibyte.proxy_class.wants?(self)
+              ActiveSupport::Multibyte.proxy_class.new(self)
+            else
+              self
+            end
+          end
+          
+          # Returns true if the string has UTF-8 semantics (a String used for purely byte resources is unlikely to have
+          # them), returns false otherwise.
+          def is_utf8?
+            ActiveSupport::Multibyte::Chars.consumes?(self)
+          end
+
+          unless '1.8.7 and later'.respond_to?(:chars)
+            alias chars mb_chars
+          end
+        else
+          # In Ruby 1.9 and newer +mb_chars+ returns self. In Ruby 1.8 and older +mb_chars+ creates and returns an
+          # Unicode safe proxy for string operations, this makes it easier to write code that runs on multiple Ruby
+          # versions.
+          def mb_chars
+            self
+          end
+          
+          # Returns true if the string has valid UTF-8 encoding.
+          def is_utf8?
+            case encoding
+            when Encoding::UTF_8
+              valid_encoding?
+            when Encoding::ASCII_8BIT, Encoding::US_ASCII
+              dup.force_encoding(Encoding::UTF_8).valid_encoding?
+            else
+              false
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/unicode.rb b/activesupport/lib/active_support/core_ext/string/unicode.rb
deleted file mode 100644
index 666f7bcb65..0000000000
--- a/activesupport/lib/active_support/core_ext/string/unicode.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-module ActiveSupport #:nodoc:
-  module CoreExtensions #:nodoc:
-    module String #:nodoc:
-      # Define methods for handling unicode data.
-      module Unicode
-        def self.append_features(base)
-          if '1.8.7 and later'.respond_to?(:chars)
-            base.class_eval { remove_method :chars }
-          end
-          super
-        end
-
-        unless '1.9'.respond_to?(:force_encoding)
-          # +chars+ is a Unicode safe proxy for string methods. It creates and returns an instance of the
-          # ActiveSupport::Multibyte::Chars class which encapsulates the original string. A Unicode safe version of all
-          # the String methods are defined on this proxy class. Undefined methods are forwarded to String, so all of the
-          # string overrides can also be called through the +chars+ proxy.
-          #
-          #   name = 'Claus Müller'
-          #   name.reverse              # => "rell??M sualC"
-          #   name.length               # => 13
-          #
-          #   name.chars.reverse.to_s   # => "rellüM sualC"
-          #   name.chars.length         # => 12
-          #   
-          #
-          # All the methods on the chars proxy which normally return a string will return a Chars object. This allows
-          # method chaining on the result of any of these methods.
-          #
-          #   name.chars.reverse.length # => 12
-          #
-          # The Char object tries to be as interchangeable with String objects as possible: sorting and comparing between
-          # String and Char work like expected. The bang! methods change the internal string representation in the Chars
-          # object. Interoperability problems can be resolved easily with a +to_s+ call.
-          #
-          # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars and
-          # ActiveSupport::Multibyte::Handlers::UTF8Handler.
-          def chars
-            ActiveSupport::Multibyte::Chars.new(self)
-          end
-
-          # Returns true if the string has UTF-8 semantics (a String used for purely byte resources is unlikely to have
-          # them), returns false otherwise.
-          def is_utf8?
-            ActiveSupport::Multibyte::Handlers::UTF8Handler.consumes?(self)
-          end
-        else
-          def chars #:nodoc:
-            self
-          end
-
-          def is_utf8? #:nodoc:
-            case encoding
-              when Encoding::UTF_8
-                valid_encoding?
-              when Encoding::ASCII_8BIT
-                dup.force_encoding('UTF-8').valid_encoding?
-              else
-                false
-            end
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb
index 27c0d180a5..63c0d50166 100644
--- a/activesupport/lib/active_support/multibyte.rb
+++ b/activesupport/lib/active_support/multibyte.rb
@@ -1,9 +1,33 @@
-module ActiveSupport
+# encoding: utf-8
+
+require 'active_support/multibyte/chars'
+require 'active_support/multibyte/exceptions'
+require 'active_support/multibyte/unicode_database'
+
+module ActiveSupport #:nodoc:
   module Multibyte #:nodoc:
-    DEFAULT_NORMALIZATION_FORM = :kc
+    # 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]
-    UNICODE_VERSION = '5.0.0'
-  end
-end
 
-require 'active_support/multibyte/chars'
+    # 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.
+    #
+    # Example:
+    #   ActiveSupport::Multibyte.default_normalization_form = :c
+    mattr_accessor :default_normalization_form
+    self.default_normalization_form = :kc
+
+    # The proxy class returned when calling mb_chars. You can use this accessor to configure your own proxy
+    # class so you can support other encodings. See the ActiveSupport::Multibyte::Chars implementation for
+    # an example how to do this.
+    #
+    # Example:
+    #   ActiveSupport::Multibyte.proxy_class = CharsForUTF32
+    mattr_accessor :proxy_class
+    self.proxy_class = ActiveSupport::Multibyte::Chars
+  end
+end
\ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index de2c83f8d1..c05419bfbf 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -1,142 +1,670 @@
-require 'active_support/multibyte/handlers/utf8_handler'
-require 'active_support/multibyte/handlers/passthru_handler'
-
-# Encapsulates all the functionality related to the Chars proxy.
-module ActiveSupport::Multibyte #:nodoc:
-  # Chars enables you to work transparently with multibyte encodings in the Ruby String class without having extensive
-  # knowledge about the encoding. A Chars object accepts a string upon initialization and proxies String methods in an
-  # encoding safe manner. All the normal String methods are also implemented on the proxy.
-  #
-  # String methods are proxied through the Chars object, and can be accessed through the +chars+ method. Methods
-  # which would normally return a String object now return a Chars object so methods can be chained.
-  #
-  #   "The Perfect String  ".chars.downcase.strip.normalize # => "the perfect string"
-  #
-  # Chars objects are perfectly interchangeable with String objects as long as no explicit class checks are made.
-  # If certain methods do explicitly check the class, call +to_s+ before you pass chars objects to them.
-  #
-  #   bad.explicit_checking_method "T".chars.downcase.to_s
-  #
-  # The actual operations on the string are delegated to handlers. Theoretically handlers can be implemented for
-  # any encoding, but the default handler handles UTF-8. This handler is set during initialization, if you want to
-  # use you own handler, you can set it on the Chars class. Look at the UTF8Handler source for an example how to
-  # implement your own handler. If you your own handler to work on anything but UTF-8 you probably also
-  # want to override Chars#handler.
-  #
-  #   ActiveSupport::Multibyte::Chars.handler = MyHandler
-  #
-  # Note that a few methods are defined on Chars instead of the handler because they are defined on Object or Kernel
-  # and method_missing can't catch them.
-  class Chars
-    
-    attr_reader :string # The contained string
-    alias_method :to_s, :string
-    
-    include Comparable
-    
-    # The magic method to make String and Chars comparable
-    def to_str
-      # Using any other ways of overriding the String itself will lead you all the way from infinite loops to
-      # core dumps. Don't go there.
-      @string
-    end
+# encoding: utf-8
+
+module ActiveSupport #:nodoc:
+  module Multibyte #:nodoc:
+    # Chars enables you to work transparently with multibyte encodings in the Ruby String class without having extensive
+    # knowledge about the encoding. A Chars object accepts a string upon initialization and proxies String methods in an
+    # encoding safe manner. All the normal String methods are also implemented on the proxy.
+    #
+    # String methods are proxied through the Chars object, and can be accessed through the +mb_chars+ method. Methods
+    # which would normally return a String object now return a Chars object so methods can be chained.
+    #
+    #   "The Perfect String  ".chars.downcase.strip.normalize #=> "the perfect string"
+    #
+    # Chars objects are perfectly interchangeable with String objects as long as no explicit class checks are made.
+    # If certain methods do explicitly check the class, call +to_s+ before you pass chars objects to them.
+    #
+    #   bad.explicit_checking_method "T".chars.downcase.to_s
+    #
+    # The default Chars implementation assumes that the encoding of the string is UTF-8, if you want to handle different
+    # encodings you can write your own multibyte string handler and configure it through 
+    # ActiveSupport::Multibyte.proxy_class.
+    #
+    #   class CharsForUTF32
+    #     def size
+    #       @wrapped_string.size / 4
+    #     end
+    #
+    #     def self.accepts?(string)
+    #       string.length % 4 == 0
+    #     end
+    #   end
+    #
+    #   ActiveSupport::Multibyte.proxy_class = CharsForUTF32
+    class Chars
+      # Hangul character boundaries and properties
+      HANGUL_SBASE = 0xAC00
+      HANGUL_LBASE = 0x1100
+      HANGUL_VBASE = 0x1161
+      HANGUL_TBASE = 0x11A7
+      HANGUL_LCOUNT = 19
+      HANGUL_VCOUNT = 21
+      HANGUL_TCOUNT = 28
+      HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT
+      HANGUL_SCOUNT = 11172
+      HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT
+      HANGUL_JAMO_FIRST = 0x1100
+      HANGUL_JAMO_LAST = 0x11FF
+
+      # All the unicode whitespace
+      UNICODE_WHITESPACE = [
+        (0x0009..0x000D).to_a, # White_Space # Cc   [5] <control-0009>..<control-000D>
+        0x0020,                # White_Space # Zs       SPACE
+        0x0085,                # White_Space # Cc       <control-0085>
+        0x00A0,                # White_Space # Zs       NO-BREAK SPACE
+        0x1680,                # White_Space # Zs       OGHAM SPACE MARK
+        0x180E,                # White_Space # Zs       MONGOLIAN VOWEL SEPARATOR
+        (0x2000..0x200A).to_a, # White_Space # Zs  [11] EN QUAD..HAIR SPACE
+        0x2028,                # White_Space # Zl       LINE SEPARATOR
+        0x2029,                # White_Space # Zp       PARAGRAPH SEPARATOR
+        0x202F,                # White_Space # Zs       NARROW NO-BREAK SPACE
+        0x205F,                # White_Space # Zs       MEDIUM MATHEMATICAL SPACE
+        0x3000,                # White_Space # Zs       IDEOGRAPHIC SPACE
+      ].flatten.freeze
+
+      # BOM (byte order mark) can also be seen as whitespace, it's a non-rendering character used to distinguish
+      # between little and big endian. This is not an issue in utf-8, so it must be ignored.
+      UNICODE_LEADERS_AND_TRAILERS = UNICODE_WHITESPACE + [65279] # ZERO-WIDTH NO-BREAK SPACE aka BOM
+
+      # Returns a regular expression pattern that matches the passed Unicode codepoints
+      def self.codepoints_to_pattern(array_of_codepoints) #:nodoc:
+        array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|')
+      end
+      UNICODE_TRAILERS_PAT = /(#{codepoints_to_pattern(UNICODE_LEADERS_AND_TRAILERS)})+\Z/
+      UNICODE_LEADERS_PAT = /\A(#{codepoints_to_pattern(UNICODE_LEADERS_AND_TRAILERS)})+/
+
+      # Borrowed from the Kconv library by Shinji KONO - (also as seen on the W3C site)
+      UTF8_PAT = /\A(?:
+                     [\x00-\x7f]                                     |
+                     [\xc2-\xdf] [\x80-\xbf]                         |
+                     \xe0        [\xa0-\xbf] [\x80-\xbf]             |
+                     [\xe1-\xef] [\x80-\xbf] [\x80-\xbf]             |
+                     \xf0        [\x90-\xbf] [\x80-\xbf] [\x80-\xbf] |
+                     [\xf1-\xf3] [\x80-\xbf] [\x80-\xbf] [\x80-\xbf] |
+                     \xf4        [\x80-\x8f] [\x80-\xbf] [\x80-\xbf]
+                    )*\z/xn
+
+      attr_reader :wrapped_string
+      alias to_s wrapped_string
+      alias to_str wrapped_string
+
+      # Creates a new Chars instance. +string+ is the wrapped string.
+      if '1.9'.respond_to?(:force_encoding)
+        def initialize(string)
+          @wrapped_string = string
+          @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
+        end
+      else
+        def initialize(string)
+          @wrapped_string = string
+        end
+      end
+
+      # Forward all undefined methods to the wrapped string.
+      def method_missing(method, *args, &block)
+        if method.to_s =~ /!$/
+          @wrapped_string.__send__(method, *args, &block)
+          self
+        else
+          chars(@wrapped_string.__send__(method, *args, &block))
+        end
+      end
+      
+      # Returns +true+ if _obj_ responds to the given method. Private methods are included in the search
+      # only if the optional second parameter evaluates to +true+.
+      def respond_to?(method, include_private=false)
+        super || @wrapped_string.respond_to?(method, include_private) || false
+      end
+
+      # Enable more predictable duck-typing on String-like classes. See Object#acts_like?.
+      def acts_like_string?
+        true
+      end
+
+      # Returns +true+ if the Chars class can and should act as a proxy for the string +string+. Returns
+      # +false+ otherwise.
+      def self.wants?(string)
+        RUBY_VERSION < '1.9' && $KCODE == 'UTF8' && consumes?(string)
+      end
 
-    # Make duck-typing with String possible
-    def respond_to?(method, include_priv = false)
-      super || @string.respond_to?(method, include_priv) ||
-        handler.respond_to?(method, include_priv) ||
-        (method.to_s =~ /(.*)!/ && handler.respond_to?($1, include_priv)) ||
+      # Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise.
+      def self.consumes?(string)
+        # Unpack is a little bit faster than regular expressions.
+        string.unpack('U*')
+        true
+      rescue ArgumentError
         false
-    end
+      end
 
-    # Enable more predictable duck-typing on String-like classes. See Object#acts_like?.
-    def acts_like_string?
-      true
-    end
+      include Comparable
 
-    # Create a new Chars instance.
-    def initialize(str)
-      @string = str.respond_to?(:string) ? str.string : str
-    end
-    
-    # Returns -1, 0 or +1 depending on whether the Chars object is to be sorted before, equal or after the
-    # object on the right side of the operation. It accepts any object that implements +to_s+. See String.<=>
-    # for more details.
-    def <=>(other); @string <=> other.to_s; end
-    
-    # Works just like String#split, with the exception that the items in the resulting list are Chars
-    # instances instead of String. This makes chaining methods easier.
-    def split(*args)
-      @string.split(*args).map { |i| i.chars }
-    end
-    
-    # Gsub works exactly the same as gsub on a normal string.
-    def gsub(*a, &b); @string.gsub(*a, &b).chars; end
-    
-    # Like String.=~ only it returns the character offset (in codepoints) instead of the byte offset.
-    def =~(other)
-      handler.translate_offset(@string, @string =~ other)
-    end
-    
-    # Try to forward all undefined methods to the handler, when a method is not defined on the handler, send it to
-    # the contained string. Method_missing is also responsible for making the bang! methods destructive.
-    def method_missing(m, *a, &b)
-      begin
-        # Simulate methods with a ! at the end because we can't touch the enclosed string from the handlers.
-        if m.to_s =~ /^(.*)\!$/ && handler.respond_to?($1)
-          result = handler.send($1, @string, *a, &b)
-          if result == @string
-            result = nil
+      # Returns -1, 0 or +1 depending on whether the Chars object is to be sorted before, equal or after the
+      # object on the right side of the operation. It accepts any object that implements +to_s+. See String.<=>
+      # for more details.
+      #
+      # Example:
+      #   'é'.mb_chars <=> 'ü'.mb_chars #=> -1
+      def <=>(other)
+        @wrapped_string <=> other.to_s
+      end
+
+      # Returns a new Chars object containing the other object concatenated to the string.
+      #
+      # Example:
+      #   ('Café'.mb_chars + ' périferôl').to_s #=> "Café périferôl"
+      def +(other)
+        self << other
+      end
+
+      # Like String.=~ only it returns the character offset (in codepoints) instead of the byte offset.
+      #
+      # Example:
+      #   'Café périferôl'.mb_chars =~ /ô/ #=> 12
+      def =~(other)
+        translate_offset(@wrapped_string =~ other)
+      end
+
+      # Works just like String#split, with the exception that the items in the resulting list are Chars
+      # instances instead of String. This makes chaining methods easier.
+      #
+      # Example:
+      #   '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 }
+      end
+
+      # Inserts the passed string at specified codepoint offsets
+      #
+      # Example:
+      #   'Café'.mb_chars.insert(4, ' périferôl').to_s #=> "Café périferôl"
+      def insert(offset, fragment)
+        unpacked = self.class.u_unpack(@wrapped_string)
+        unless offset > unpacked.length
+          @wrapped_string.replace(
+            self.class.u_unpack(@wrapped_string).insert(offset, *self.class.u_unpack(fragment)).pack('U*')
+          )
+        else
+          raise IndexError, "index #{offset} out of string"
+        end
+        self
+      end
+
+      # Returns true if contained string contains +other+. Returns false otherwise.
+      #
+      # Example:
+      #   'Café'.mb_chars.include?('é') #=> true
+      def include?(other)
+        # We have to redefine this method because Enumerable defines it.
+        @wrapped_string.include?(other)
+      end
+
+      # Returns the position of the passed argument in the string, counting in codepoints
+      #
+      # Example:
+      #   'Café périferôl'.mb_chars.index('ô') #=> 12
+      def index(*args)
+        index = @wrapped_string.index(*args)
+        index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil
+      end
+
+      # Works just like the indexed replace method on string, except instead of byte offsets you specify
+      # character offsets.
+      #
+      # Example:
+      #
+      #   s = "Müller"
+      #   s.chars[2] = "e" # Replace character with offset 2
+      #   s
+      #   #=> "Müeler"
+      #
+      #   s = "Müller"
+      #   s.chars[1, 2] = "ö" # Replace 2 characters at character offset 1
+      #   s
+      #   #=> "Möler"
+      def []=(*args)
+        replace_by = args.pop
+        # Indexed replace with regular expressions already works
+        if args.first.is_a?(Regexp)
+          @wrapped_string[*args] = replace_by
+        else
+          result = self.class.u_unpack(@wrapped_string)
+          if args[0].is_a?(Fixnum)
+            raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
+            min = args[0]
+            max = args[1].nil? ? min : (min + args[1] - 1)
+            range = Range.new(min, max)
+            replace_by = [replace_by].pack('U') if replace_by.is_a?(Fixnum)
+          elsif args.first.is_a?(Range)
+            raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
+            range = args[0]
           else
-            @string.replace result
+            needle = args[0].to_s
+            min = index(needle)
+            max = min + self.class.u_unpack(needle).length - 1
+            range = Range.new(min, max)
           end
-        elsif handler.respond_to?(m)
-          result = handler.send(m, @string, *a, &b)
-        else
-          result = @string.send(m, *a, &b)
+          result[range] = self.class.u_unpack(replace_by)
+          @wrapped_string.replace(result.pack('U*'))
         end
-      rescue Handlers::EncodingError
-        @string.replace handler.tidy_bytes(@string)
-        retry
+        self
       end
-      
-      if result.kind_of?(String)
-        result.chars
-      else
-        result
+
+      # Works just like String#rjust, only integer specifies characters instead of bytes.
+      #
+      # Example:
+      #
+      #   "¾ cup".chars.rjust(8).to_s
+      #   #=> "   ¾ cup"
+      #
+      #   "¾ cup".chars.rjust(8, " ").to_s # Use non-breaking whitespace
+      #   #=> "   ¾ cup"
+      def rjust(integer, padstr=' ')
+        justify(integer, :right, padstr)
       end
-    end
-    
-    # Set the handler class for the Char objects.
-    def self.handler=(klass)
-      @@handler = klass
-    end
 
-    # Returns the proper handler for the contained string depending on $KCODE and the encoding of the string. This
-    # method is used internally to always redirect messages to the proper classes depending on the context.
-    def handler
-      if utf8_pragma?
-        @@handler
-      else
-        ActiveSupport::Multibyte::Handlers::PassthruHandler
+      # Works just like String#ljust, only integer specifies characters instead of bytes.
+      #
+      # Example:
+      #
+      #   "¾ cup".chars.rjust(8).to_s
+      #   #=> "¾ cup   "
+      #
+      #   "¾ cup".chars.rjust(8, " ").to_s # Use non-breaking whitespace
+      #   #=> "¾ cup   "
+      def ljust(integer, padstr=' ')
+        justify(integer, :left, padstr)
+      end
+
+      # Works just like String#center, only integer specifies characters instead of bytes.
+      #
+      # Example:
+      #
+      #   "¾ cup".chars.center(8).to_s
+      #   #=> " ¾ cup  "
+      #
+      #   "¾ cup".chars.center(8, " ").to_s # Use non-breaking whitespace
+      #   #=> " ¾ cup  "
+      def center(integer, padstr=' ')
+        justify(integer, :center, padstr)
       end
-    end
 
-    private
+      # Strips entire range of Unicode whitespace from the right of the string.
+      def rstrip
+        chars(@wrapped_string.gsub(UNICODE_TRAILERS_PAT, ''))
+      end
+      
+      # Strips entire range of Unicode whitespace from the left of the string.
+      def lstrip
+        chars(@wrapped_string.gsub(UNICODE_LEADERS_PAT, ''))
+      end
+      
+      # Strips entire range of Unicode whitespace from the right and left of the string.
+      def strip
+        rstrip.lstrip
+      end
+      
+      # Returns the number of codepoints in the string
+      def size
+        self.class.u_unpack(@wrapped_string).size
+      end
+      alias_method :length, :size
       
-      # +utf8_pragma+ checks if it can send this string to the handlers. It makes sure @string isn't nil and $KCODE is
-      # set to 'UTF8'.
-      def utf8_pragma?
-        !@string.nil? && ($KCODE == 'UTF8')
+      # Reverses all characters in the string
+      #
+      # Example:
+      #   'Café'.mb_chars.reverse.to_s #=> 'éfaC'
+      def reverse
+        chars(self.class.u_unpack(@wrapped_string).reverse.pack('U*'))
       end
+      
+      # Implements Unicode-aware slice with codepoints. Slicing on one point returns the codepoints for that
+      # character.
+      #
+      # Example:
+      #   'こにちわ'.mb_chars.slice(2..3).to_s #=> "ちわ"
+      def slice(*args)
+        if args.size > 2
+          raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
+        elsif (args.size == 2 && !(args.first.is_a?(Numeric) || args.first.is_a?(Regexp)))
+          raise TypeError, "cannot convert #{args.first.class} into Integer" # Do as if we were native
+        elsif (args.size == 2 && !args[1].is_a?(Numeric))
+          raise TypeError, "cannot convert #{args[1].class} into Integer" # Do as if we were native
+        elsif args[0].kind_of? Range
+          cps = self.class.u_unpack(@wrapped_string).slice(*args)
+          result = cps.nil? ? nil : cps.pack('U*')
+        elsif args[0].kind_of? Regexp
+          result = @wrapped_string.slice(*args)
+        elsif args.size == 1 && args[0].kind_of?(Numeric)
+          character = self.class.u_unpack(@wrapped_string)[args[0]]
+          result = character.nil? ? nil : [character].pack('U')
+        else
+          result = self.class.u_unpack(@wrapped_string).slice(*args).pack('U*')
+        end
+        result.nil? ? nil : chars(result)
+      end
+      alias_method :[], :slice
+
+      # Convert characters in the string to uppercase
+      #
+      # Example:
+      #   'Laurent, òu sont les tests?'.mb_chars.upcase.to_s #=> "LAURENT, ÒU SONT LES TESTS?"
+      def upcase
+        apply_mapping :uppercase_mapping
+      end
+
+      # Convert characters in the string to lowercase
+      #
+      # Example:
+      #   'VĚDA A VÝZKUM'.mb_chars.downcase.to_s #=> "věda a výzkum"
+      def downcase
+        apply_mapping :lowercase_mapping
+      end
+
+      # Converts the first character to uppercase and the remainder to lowercase
+      #
+      # Example:
+      #  'über'.mb_chars.capitalize.to_s #=> "Über"
+      def capitalize
+        (slice(0) || '').upcase + (slice(1..-1) || '').downcase
+      end
+
+      # Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
+      # passing strings to databases and validations.
+      #
+      # * <tt>str</tt> - The string to perform normalization on.
+      # * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
+      #   <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
+      #   ActiveSupport::Multibyte.default_normalization_form
+      def normalize(form=ActiveSupport::Multibyte.default_normalization_form)
+        # See http://www.unicode.org/reports/tr15, Table 1
+        codepoints = self.class.u_unpack(@wrapped_string)
+        chars(case form
+          when :d
+            self.class.reorder_characters(self.class.decompose_codepoints(:canonical, codepoints))
+          when :c
+            self.class.compose_codepoints(self.class.reorder_characters(self.class.decompose_codepoints(:canonical, codepoints)))
+          when :kd
+            self.class.reorder_characters(self.class.decompose_codepoints(:compatability, codepoints))
+          when :kc
+            self.class.compose_codepoints(self.class.reorder_characters(self.class.decompose_codepoints(:compatability, codepoints)))
+          else
+            raise ArgumentError, "#{form} is not a valid normalization variant", caller
+        end.pack('U*'))
+      end
+
+      # Performs canonical decomposition on all the characters.
+      #
+      # Example:
+      #   'é'.length #=> 2
+      #   'é'.mb_chars.decompose.to_s.length #=> 3
+      def decompose
+        chars(self.class.decompose_codepoints(:canonical, self.class.u_unpack(@wrapped_string)).pack('U*'))
+      end
+
+      # Performs composition on all the characters.
+      #
+      # Example:
+      #   'é'.length #=> 3
+      #   'é'.mb_chars.compose.to_s.length #=> 2
+      def compose
+        chars(self.class.compose_codepoints(self.class.u_unpack(@wrapped_string)).pack('U*'))
+      end
+
+      # Returns the number of grapheme clusters in the string.
+      #
+      # Example:
+      #   'क्षि'.mb_chars.length #=> 4
+      #   'क्षि'.mb_chars.g_length #=> 3
+      def g_length
+        self.class.g_unpack(@wrapped_string).length
+      end
+
+      def tidy_bytes
+        chars(self.class.tidy_bytes(@wrapped_string))
+      end
+
+      %w(lstrip rstrip strip reverse upcase downcase slice tidy_bytes capitalize).each do |method|
+        define_method("#{method}!") do |*args|
+          unless args.nil?
+            @wrapped_string = send(method, *args).to_s
+          else
+            @wrapped_string = send(method).to_s
+          end
+          self
+        end
+      end
+
+      class << self
+
+        # Unpack the string at codepoints boundaries
+        def u_unpack(str)
+          begin
+            str.unpack 'U*'
+          rescue ArgumentError
+            raise EncodingError.new('malformed UTF-8 character')
+          end
+        end
+
+        # Detect whether the codepoint is in a certain character class. Primarily used by the
+        # grapheme cluster support.
+        def in_char_class?(codepoint, classes)
+          classes.detect { |c| UCD.boundary[c] === codepoint } ? true : false
+        end
+
+        # Unpack the string at grapheme boundaries
+        def g_unpack(str)
+          codepoints = u_unpack(str)
+          unpacked = []
+          pos = 0
+          marker = 0
+          eoc = codepoints.length
+          while(pos < eoc)
+            pos += 1
+            previous = codepoints[pos-1]
+            current = codepoints[pos]
+            if (
+                # CR X LF
+                one = ( previous == UCD.boundary[:cr] and current == UCD.boundary[:lf] ) or
+                # L X (L|V|LV|LVT)
+                two = ( UCD.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or
+                # (LV|V) X (V|T)
+                three = ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or
+                # (LVT|T) X (T)
+                four = ( in_char_class?(previous, [:lvt,:t]) and UCD.boundary[:t] === current ) or
+                # X Extend
+                five = (UCD.boundary[:extend] === current)
+              )
+            else
+              unpacked << codepoints[marker..pos-1]
+              marker = pos
+            end
+          end 
+          unpacked
+        end
+
+        # Reverse operation of g_unpack
+        def g_pack(unpacked)
+          (unpacked.flatten).pack('U*')
+        end
+
+        # Generates a padding string of a certain size.
+        def padding(padsize, padstr=' ')
+          if padsize != 0
+            new(padstr * ((padsize / u_unpack(padstr).size) + 1)).slice(0, padsize)
+          else
+            ''
+          end
+        end
+
+        # Re-order codepoints so the string becomes canonical
+        def reorder_characters(codepoints)
+          length = codepoints.length- 1
+          pos = 0
+          while pos < length do
+            cp1, cp2 = UCD.codepoints[codepoints[pos]], UCD.codepoints[codepoints[pos+1]]
+            if (cp1.combining_class > cp2.combining_class) && (cp2.combining_class > 0)
+              codepoints[pos..pos+1] = cp2.code, cp1.code
+              pos += (pos > 0 ? -1 : 1)
+            else
+              pos += 1
+            end
+          end
+          codepoints
+        end
+
+        # Decompose composed characters to the decomposed form
+        def decompose_codepoints(type, codepoints)
+          codepoints.inject([]) do |decomposed, cp|
+            # if it's a hangul syllable starter character
+            if HANGUL_SBASE <= cp and cp < HANGUL_SLAST
+              sindex = cp - HANGUL_SBASE
+              ncp = [] # new codepoints
+              ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT
+              ncp << HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
+              tindex = sindex % HANGUL_TCOUNT
+              ncp << (HANGUL_TBASE + tindex) unless tindex == 0
+              decomposed.concat ncp
+            # if the codepoint is decomposable in with the current decomposition type
+            elsif (ncp = UCD.codepoints[cp].decomp_mapping) and (!UCD.codepoints[cp].decomp_type || type == :compatability)
+              decomposed.concat decompose_codepoints(type, ncp.dup)
+            else
+              decomposed << cp
+            end
+          end
+        end
+
+        # Compose decomposed characters to the composed form
+        def compose_codepoints(codepoints)
+          pos = 0
+          eoa = codepoints.length - 1
+          starter_pos = 0
+          starter_char = codepoints[0]
+          previous_combining_class = -1
+          while pos < eoa
+            pos += 1
+            lindex = starter_char - HANGUL_LBASE
+            # -- Hangul
+            if 0 <= lindex and lindex < HANGUL_LCOUNT
+              vindex = codepoints[starter_pos+1] - HANGUL_VBASE rescue vindex = -1
+              if 0 <= vindex and vindex < HANGUL_VCOUNT
+                tindex = codepoints[starter_pos+2] - HANGUL_TBASE rescue tindex = -1
+                if 0 <= tindex and tindex < HANGUL_TCOUNT
+                  j = starter_pos + 2
+                  eoa -= 2
+                else
+                  tindex = 0
+                  j = starter_pos + 1
+                  eoa -= 1
+                end
+                codepoints[starter_pos..j] = (lindex * HANGUL_VCOUNT + vindex) * HANGUL_TCOUNT + tindex + HANGUL_SBASE
+              end
+              starter_pos += 1
+              starter_char = codepoints[starter_pos]
+            # -- Other characters
+            else
+              current_char = codepoints[pos]
+              current = UCD.codepoints[current_char]
+              if current.combining_class > previous_combining_class
+                if ref = UCD.composition_map[starter_char]
+                  composition = ref[current_char]
+                else
+                  composition = nil
+                end
+                unless composition.nil?
+                  codepoints[starter_pos] = composition
+                  starter_char = composition
+                  codepoints.delete_at pos
+                  eoa -= 1
+                  pos -= 1
+                  previous_combining_class = -1
+                else
+                  previous_combining_class = current.combining_class
+                end
+              else
+                previous_combining_class = current.combining_class
+              end
+              if current.combining_class == 0
+                starter_pos = pos
+                starter_char = codepoints[pos]
+              end
+            end
+          end
+          codepoints
+        end
+
+        # Replaces all the non-UTF-8 bytes by their iso-8859-1 or cp1252 equivalent resulting in a valid UTF-8 string
+        def tidy_bytes(str)
+          str.split(//u).map do |c|
+            if !UTF8_PAT.match(c)
+              n = c.unpack('C')[0]
+              n < 128 ? n.chr :
+              n < 160 ? [UCD.cp1252[n] || n].pack('U') :
+              n < 192 ? "\xC2" + n.chr : "\xC3" + (n-64).chr
+            else
+              c
+            end
+          end.join
+        end
+      end
+
+      protected
+
+        # Translate a byte offset in the wrapped string to a character offset by looking for the character boundary
+        def translate_offset(byte_offset)
+          return nil if byte_offset.nil?
+          return 0   if @wrapped_string == ''
+          chunk = @wrapped_string[0..byte_offset]
+          begin
+            begin
+              chunk.unpack('U*').length - 1
+            rescue ArgumentError => e
+              chunk = @wrapped_string[0..(byte_offset+=1)]
+              # Stop retrying at the end of the string
+              raise e unless byte_offset < chunk.length 
+              # We damaged a character, retry
+              retry
+            end
+          # Catch the ArgumentError so we can throw our own
+          rescue ArgumentError 
+            raise EncodingError, 'malformed UTF-8 character'
+          end
+        end
+
+        # Justifies a string in a certain way. Valid values for <tt>way</tt> are <tt>:right</tt>, <tt>:left</tt> and
+        # <tt>:center</tt>.
+        def justify(integer, way, padstr=' ')
+          raise ArgumentError, "zero width padding" if padstr.length == 0
+          padsize = integer - size
+          padsize = padsize > 0 ? padsize : 0
+          case way
+          when :right
+            result = @wrapped_string.dup.insert(0, self.class.padding(padsize, padstr))
+          when :left
+            result = @wrapped_string.dup.insert(-1, self.class.padding(padsize, padstr))
+          when :center
+            lpad = self.class.padding((padsize / 2.0).floor, padstr)
+            rpad = self.class.padding((padsize / 2.0).ceil, padstr)
+            result = @wrapped_string.dup.insert(0, lpad).insert(-1, rpad)
+          end
+          chars(result)
+        end
+
+        # Map codepoints to one of it's attributes.
+        def apply_mapping(mapping)
+          chars(self.class.u_unpack(@wrapped_string).map do |codepoint|
+            cp = UCD.codepoints[codepoint]
+            if cp and (ncp = cp.send(mapping)) and ncp > 0
+              ncp
+            else
+              codepoint
+            end
+          end.pack('U*'))
+        end
+
+        # Creates a new instance
+        def chars(str)
+          self.class.new(str)
+        end
+    end
   end
-end
-
-# When we can load the utf8proc library, override normalization with the faster methods
-begin
-  require 'utf8proc_native'
-  require 'active_support/multibyte/handlers/utf8_handler_proc'
-  ActiveSupport::Multibyte::Chars.handler = ActiveSupport::Multibyte::Handlers::UTF8HandlerProc
-rescue LoadError
-  ActiveSupport::Multibyte::Chars.handler = ActiveSupport::Multibyte::Handlers::UTF8Handler
-end
+end
\ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/exceptions.rb b/activesupport/lib/active_support/multibyte/exceptions.rb
new file mode 100644
index 0000000000..af760cc561
--- /dev/null
+++ b/activesupport/lib/active_support/multibyte/exceptions.rb
@@ -0,0 +1,7 @@
+# encoding: utf-8
+
+module ActiveSupport #:nodoc:
+  module Multibyte #:nodoc:
+    class EncodingError < StandardError; end
+  end
+end
\ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/generators/generate_tables.rb b/activesupport/lib/active_support/multibyte/generators/generate_tables.rb
deleted file mode 100755
index 7f807585c5..0000000000
--- a/activesupport/lib/active_support/multibyte/generators/generate_tables.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-#!/usr/bin/env ruby
-begin
-  require File.dirname(__FILE__) + '/../../../active_support'
-rescue IOError
-end
-require 'open-uri'
-require 'tmpdir'
-
-module ActiveSupport::Multibyte::Handlers #:nodoc:
-  class UnicodeDatabase #:nodoc:
-    def self.load
-      [Hash.new(Codepoint.new),[],{},{}]
-    end
-  end
-  
-  class UnicodeTableGenerator #:nodoc:
-    BASE_URI = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::UNICODE_VERSION}/ucd/"
-    SOURCES = {
-      :codepoints => BASE_URI + 'UnicodeData.txt',
-      :composition_exclusion => BASE_URI + 'CompositionExclusions.txt',
-      :grapheme_break_property => BASE_URI + 'auxiliary/GraphemeBreakProperty.txt',
-      :cp1252 => 'http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT'
-    }
-    
-    def initialize
-      @ucd = UnicodeDatabase.new
-      
-      default = Codepoint.new
-      default.combining_class = 0
-      default.uppercase_mapping = 0
-      default.lowercase_mapping = 0
-      @ucd.codepoints = Hash.new(default)
-      
-      @ucd.composition_exclusion = []
-      @ucd.composition_map = {}
-      @ucd.boundary = {}
-      @ucd.cp1252 = {}
-    end
-    
-    def parse_codepoints(line)
-      codepoint = Codepoint.new
-      raise "Could not parse input." unless line =~ /^
-        ([0-9A-F]+);        # code
-        ([^;]+);            # name
-        ([A-Z]+);           # general category
-        ([0-9]+);           # canonical combining class
-        ([A-Z]+);           # bidi class
-        (<([A-Z]*)>)?       # decomposition type
-        ((\ ?[0-9A-F]+)*);  # decompomposition mapping
-        ([0-9]*);           # decimal digit
-        ([0-9]*);           # digit
-        ([^;]*);            # numeric
-        ([YN]*);            # bidi mirrored
-        ([^;]*);            # unicode 1.0 name
-        ([^;]*);            # iso comment
-        ([0-9A-F]*);        # simple uppercase mapping
-        ([0-9A-F]*);        # simple lowercase mapping
-        ([0-9A-F]*)$/ix     # simple titlecase mapping
-      codepoint.code              = $1.hex
-      #codepoint.name              = $2
-      #codepoint.category          = $3
-      codepoint.combining_class   = Integer($4)
-      #codepoint.bidi_class        = $5
-      codepoint.decomp_type       = $7
-      codepoint.decomp_mapping    = ($8=='') ? nil : $8.split.collect { |element| element.hex }
-      #codepoint.bidi_mirrored     = ($13=='Y') ? true : false
-      codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex
-      codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex
-      #codepoint.titlecase_mapping = ($18=='') ? nil : $18.hex
-      @ucd.codepoints[codepoint.code] = codepoint
-    end
-
-    def parse_grapheme_break_property(line)
-      if line =~ /^([0-9A-F\.]+)\s*;\s*([\w]+)\s*#/
-        type = $2.downcase.intern
-        @ucd.boundary[type] ||= []
-        if $1.include? '..'
-          parts = $1.split '..'
-          @ucd.boundary[type] << (parts[0].hex..parts[1].hex)
-        else
-          @ucd.boundary[type] << $1.hex
-        end
-      end
-    end
-    
-    def parse_composition_exclusion(line)
-      if line =~ /^([0-9A-F]+)/i
-        @ucd.composition_exclusion << $1.hex
-      end
-    end
-    
-    def parse_cp1252(line)
-      if line =~ /^([0-9A-Fx]+)\s([0-9A-Fx]+)/i
-        @ucd.cp1252[$1.hex] = $2.hex
-      end
-    end
-    
-    def create_composition_map
-      @ucd.codepoints.each do |_, cp|
-        if !cp.nil? and cp.combining_class == 0 and cp.decomp_type.nil? and !cp.decomp_mapping.nil? and cp.decomp_mapping.length == 2 and @ucd[cp.decomp_mapping[0]].combining_class == 0 and !@ucd.composition_exclusion.include?(cp.code)
-          @ucd.composition_map[cp.decomp_mapping[0]] ||= {}
-          @ucd.composition_map[cp.decomp_mapping[0]][cp.decomp_mapping[1]] = cp.code
-        end
-      end
-    end
-
-    def normalize_boundary_map
-      @ucd.boundary.each do |k,v|
-        if [:lf, :cr].include? k
-          @ucd.boundary[k] = v[0]
-        end
-      end
-    end
-  
-    def parse
-      SOURCES.each do |type, url|
-        filename =  File.join(Dir.tmpdir, "#{url.split('/').last}")
-        unless File.exist?(filename)
-          $stderr.puts "Downloading #{url.split('/').last}"
-          File.open(filename, 'wb') do |target|
-            open(url) do |source|
-              source.each_line { |line| target.write line }
-            end
-          end
-        end
-        File.open(filename) do |file|
-          file.each_line { |line| send "parse_#{type}".intern, line }
-        end        
-      end
-      create_composition_map
-      normalize_boundary_map
-    end
-    
-    def dump_to(filename)
-      File.open(filename, 'wb') do |f|
-        f.write Marshal.dump([@ucd.codepoints, @ucd.composition_exclusion, @ucd.composition_map, @ucd.boundary, @ucd.cp1252])
-      end
-    end
-  end
-end
-
-if __FILE__ == $0
-  filename = ActiveSupport::Multibyte::Handlers::UnicodeDatabase.filename
-  generator = ActiveSupport::Multibyte::Handlers::UnicodeTableGenerator.new
-  generator.parse
-  print "Writing to: #{filename}"
-  generator.dump_to filename
-  puts " (#{File.size(filename)} bytes)"
-end
diff --git a/activesupport/lib/active_support/multibyte/handlers/passthru_handler.rb b/activesupport/lib/active_support/multibyte/handlers/passthru_handler.rb
deleted file mode 100644
index 916215c2ce..0000000000
--- a/activesupport/lib/active_support/multibyte/handlers/passthru_handler.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# Chars uses this handler when $KCODE is not set to 'UTF8'. Because this handler doesn't define any methods all call
-# will be forwarded to String.
-class ActiveSupport::Multibyte::Handlers::PassthruHandler #:nodoc:
-  
-  # Return the original byteoffset
-  def self.translate_offset(string, byte_offset) #:nodoc:
-    byte_offset
-  end
-end
\ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/handlers/utf8_handler.rb b/activesupport/lib/active_support/multibyte/handlers/utf8_handler.rb
deleted file mode 100644
index aa9c16f575..0000000000
--- a/activesupport/lib/active_support/multibyte/handlers/utf8_handler.rb
+++ /dev/null
@@ -1,564 +0,0 @@
-# Contains all the handlers and helper classes
-module ActiveSupport::Multibyte::Handlers #:nodoc:
-  class EncodingError < ArgumentError #:nodoc:
-  end
-  
-  class Codepoint #:nodoc:
-    attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
-  end
-  
-  class UnicodeDatabase #:nodoc:
-    attr_writer :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252
-    
-    # self-expiring methods that lazily load the Unicode database and then return the value.
-    [:codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252].each do |attr_name|
-      class_eval(<<-EOS, __FILE__, __LINE__)
-        def #{attr_name}
-          load
-          @#{attr_name}
-        end
-      EOS
-    end
-    
-    # Shortcut to ucd.codepoints[]
-    def [](index); codepoints[index]; end
-    
-    # Returns the directory in which the data files are stored
-    def self.dirname
-      File.dirname(__FILE__) + '/../../values/'
-    end
-    
-    # Returns the filename for the data file for this version
-    def self.filename
-      File.expand_path File.join(dirname, "unicode_tables.dat")
-    end
-    
-    # Loads the unicode database and returns all the internal objects of UnicodeDatabase
-    # Once the values have been loaded, define attr_reader methods for the instance variables.
-    def load
-      begin
-        @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
-      rescue Exception => e
-          raise IOError.new("Couldn't load the unicode tables for UTF8Handler (#{e.message}), handler is unusable")
-      end
-      @codepoints ||= Hash.new(Codepoint.new)
-      @composition_exclusion ||= []
-      @composition_map ||= {}
-      @boundary ||= {}
-      @cp1252 ||= {}
-      
-      # Redefine the === method so we can write shorter rules for grapheme cluster breaks
-      @boundary.each do |k,_|
-        @boundary[k].instance_eval do
-          def ===(other)
-            detect { |i| i === other } ? true : false
-          end
-        end if @boundary[k].kind_of?(Array)
-      end
-
-      # define attr_reader methods for the instance variables
-      class << self
-        attr_reader :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252
-      end
-    end
-  end
-  
-  # UTF8Handler implements Unicode aware operations for strings, these operations will be used by the Chars
-  # proxy when $KCODE is set to 'UTF8'.
-  class UTF8Handler
-    # Hangul character boundaries and properties
-    HANGUL_SBASE = 0xAC00
-    HANGUL_LBASE = 0x1100
-    HANGUL_VBASE = 0x1161
-    HANGUL_TBASE = 0x11A7
-    HANGUL_LCOUNT = 19
-    HANGUL_VCOUNT = 21
-    HANGUL_TCOUNT = 28
-    HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT
-    HANGUL_SCOUNT = 11172
-    HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT
-    HANGUL_JAMO_FIRST = 0x1100
-    HANGUL_JAMO_LAST = 0x11FF
-    
-    # All the unicode whitespace
-    UNICODE_WHITESPACE = [
-      (0x0009..0x000D).to_a,  # White_Space # Cc   [5] <control-0009>..<control-000D>
-      0x0020,          # White_Space # Zs       SPACE
-      0x0085,          # White_Space # Cc       <control-0085>
-      0x00A0,          # White_Space # Zs       NO-BREAK SPACE
-      0x1680,          # White_Space # Zs       OGHAM SPACE MARK
-      0x180E,          # White_Space # Zs       MONGOLIAN VOWEL SEPARATOR
-      (0x2000..0x200A).to_a, # White_Space # Zs  [11] EN QUAD..HAIR SPACE
-      0x2028,          # White_Space # Zl       LINE SEPARATOR
-      0x2029,          # White_Space # Zp       PARAGRAPH SEPARATOR
-      0x202F,          # White_Space # Zs       NARROW NO-BREAK SPACE
-      0x205F,          # White_Space # Zs       MEDIUM MATHEMATICAL SPACE
-      0x3000,          # White_Space # Zs       IDEOGRAPHIC SPACE
-    ].flatten.freeze
-    
-    # BOM (byte order mark) can also be seen as whitespace, it's a non-rendering character used to distinguish
-    # between little and big endian. This is not an issue in utf-8, so it must be ignored.
-    UNICODE_LEADERS_AND_TRAILERS = UNICODE_WHITESPACE + [65279] # ZERO-WIDTH NO-BREAK SPACE aka BOM
-    
-    # Borrowed from the Kconv library by Shinji KONO - (also as seen on the W3C site)
-     UTF8_PAT = /\A(?:
-                   [\x00-\x7f]                                     |
-                   [\xc2-\xdf] [\x80-\xbf]                         |
-                   \xe0        [\xa0-\xbf] [\x80-\xbf]             |
-                   [\xe1-\xef] [\x80-\xbf] [\x80-\xbf]             |
-                   \xf0        [\x90-\xbf] [\x80-\xbf] [\x80-\xbf] |
-                   [\xf1-\xf3] [\x80-\xbf] [\x80-\xbf] [\x80-\xbf] |
-                   \xf4        [\x80-\x8f] [\x80-\xbf] [\x80-\xbf]
-                  )*\z/xn
-    
-    # Returns a regular expression pattern that matches the passed Unicode codepoints
-    def self.codepoints_to_pattern(array_of_codepoints) #:nodoc:
-      array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|') 
-    end
-    UNICODE_TRAILERS_PAT = /(#{codepoints_to_pattern(UNICODE_LEADERS_AND_TRAILERS)})+\Z/
-    UNICODE_LEADERS_PAT = /\A(#{codepoints_to_pattern(UNICODE_LEADERS_AND_TRAILERS)})+/
-    
-    class << self
-      
-      # ///
-      # /// BEGIN String method overrides
-      # ///
-      
-      # Inserts the passed string at specified codepoint offsets
-      def insert(str, offset, fragment)
-        str.replace(
-          u_unpack(str).insert(
-            offset,
-            u_unpack(fragment)
-          ).flatten.pack('U*')
-        )
-      end
-      
-      # Returns the position of the passed argument in the string, counting in codepoints
-      def index(str, *args)
-        bidx = str.index(*args)
-        bidx ? (u_unpack(str.slice(0...bidx)).size) : nil
-      end
-      
-      # Works just like the indexed replace method on string, except instead of byte offsets you specify
-      # character offsets.
-      #
-      # Example:
-      #
-      #   s = "Müller"
-      #   s.chars[2] = "e" # Replace character with offset 2
-      #   s # => "Müeler"
-      #
-      #   s = "Müller"
-      #   s.chars[1, 2] = "ö" # Replace 2 characters at character offset 1
-      #   s # => "Möler"
-      def []=(str, *args)
-        replace_by = args.pop
-        # Indexed replace with regular expressions already works
-        return str[*args] = replace_by if args.first.is_a?(Regexp)
-        result = u_unpack(str)
-        if args[0].is_a?(Fixnum)
-          raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
-          min = args[0]
-          max = args[1].nil? ? min : (min + args[1] - 1)
-          range = Range.new(min, max)
-          replace_by = [replace_by].pack('U') if replace_by.is_a?(Fixnum)
-        elsif args.first.is_a?(Range)
-          raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
-          range = args[0]
-        else
-          needle = args[0].to_s
-          min = index(str, needle)
-          max = min + length(needle) - 1
-          range = Range.new(min, max)
-        end
-        result[range] = u_unpack(replace_by)
-        str.replace(result.pack('U*'))
-      end
-      
-      # Works just like String#rjust, only integer specifies characters instead of bytes.
-      #
-      # Example:
-      #
-      #   "¾ cup".chars.rjust(8).to_s
-      #   # => "   ¾ cup"
-      #
-      #   "¾ cup".chars.rjust(8, " ").to_s # Use non-breaking whitespace
-      #   # => "   ¾ cup"
-      def rjust(str, integer, padstr=' ')
-        justify(str, integer, :right, padstr)
-      end
-      
-      # Works just like String#ljust, only integer specifies characters instead of bytes.
-      #
-      # Example:
-      #
-      #   "¾ cup".chars.rjust(8).to_s
-      #   # => "¾ cup   "
-      #
-      #   "¾ cup".chars.rjust(8, " ").to_s # Use non-breaking whitespace
-      #   # => "¾ cup   "
-      def ljust(str, integer, padstr=' ')
-        justify(str, integer, :left, padstr)
-      end
-      
-      # Works just like String#center, only integer specifies characters instead of bytes.
-      #
-      # Example:
-      #
-      #   "¾ cup".chars.center(8).to_s
-      #   # => " ¾ cup  "
-      #
-      #   "¾ cup".chars.center(8, " ").to_s # Use non-breaking whitespace
-      #   # => " ¾ cup  "
-      def center(str, integer, padstr=' ')
-        justify(str, integer, :center, padstr)
-      end
-      
-      # Does Unicode-aware rstrip
-      def rstrip(str)
-        str.gsub(UNICODE_TRAILERS_PAT, '')
-      end
-      
-      # Does Unicode-aware lstrip
-      def lstrip(str)
-        str.gsub(UNICODE_LEADERS_PAT, '')
-      end
-      
-      # Removed leading and trailing whitespace
-      def strip(str)
-        str.gsub(UNICODE_LEADERS_PAT, '').gsub(UNICODE_TRAILERS_PAT, '')
-      end
-      
-      # Returns the number of codepoints in the string
-      def size(str)
-        u_unpack(str).size
-      end
-      alias_method :length, :size
-      
-      # Reverses codepoints in the string.
-      def reverse(str)
-        u_unpack(str).reverse.pack('U*')
-      end
-      
-      # Implements Unicode-aware slice with codepoints. Slicing on one point returns the codepoints for that
-      # character.
-      def slice(str, *args)
-        if args.size > 2
-          raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
-        elsif (args.size == 2 && !(args.first.is_a?(Numeric) || args.first.is_a?(Regexp)))
-          raise TypeError, "cannot convert #{args.first.class} into Integer" # Do as if we were native
-        elsif (args.size == 2 && !args[1].is_a?(Numeric))
-          raise TypeError, "cannot convert #{args[1].class} into Integer" # Do as if we were native
-        elsif args[0].kind_of? Range
-          cps = u_unpack(str).slice(*args)
-          cps.nil? ? nil : cps.pack('U*')
-        elsif args[0].kind_of? Regexp
-          str.slice(*args)
-        elsif args.size == 1 && args[0].kind_of?(Numeric)
-          u_unpack(str)[args[0]]
-        else
-          u_unpack(str).slice(*args).pack('U*')
-        end
-      end
-      alias_method :[], :slice
-      
-      # Convert characters in the string to uppercase
-      def upcase(str); to_case :uppercase_mapping, str; end
-      
-      # Convert characters in the string to lowercase
-      def downcase(str); to_case :lowercase_mapping, str; end
-      
-      # Returns a copy of +str+ with the first character converted to uppercase and the remainder to lowercase
-      def capitalize(str)
-        upcase(slice(str, 0..0)) + downcase(slice(str, 1..-1) || '')
-      end
-      
-      # ///
-      # /// Extra String methods for unicode operations
-      # ///
-      
-      # Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
-      # passing strings to databases and validations.
-      #
-      # * <tt>str</tt> - The string to perform normalization on.
-      # * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
-      #   <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
-      #   ActiveSupport::Multibyte::DEFAULT_NORMALIZATION_FORM.
-      def normalize(str, form=ActiveSupport::Multibyte::DEFAULT_NORMALIZATION_FORM)
-        # See http://www.unicode.org/reports/tr15, Table 1
-        codepoints = u_unpack(str)
-        case form
-          when :d
-            reorder_characters(decompose_codepoints(:canonical, codepoints))
-          when :c
-            compose_codepoints reorder_characters(decompose_codepoints(:canonical, codepoints))
-          when :kd
-            reorder_characters(decompose_codepoints(:compatability, codepoints))
-          when :kc
-            compose_codepoints reorder_characters(decompose_codepoints(:compatability, codepoints))
-          else
-            raise ArgumentError, "#{form} is not a valid normalization variant", caller
-        end.pack('U*')
-      end
-      
-      # Perform decomposition on the characters in the string
-      def decompose(str)
-        decompose_codepoints(:canonical, u_unpack(str)).pack('U*')
-      end
-      
-      # Perform composition on the characters in the string
-      def compose(str)
-        compose_codepoints u_unpack(str).pack('U*')
-      end
-      
-      # ///
-      # /// BEGIN Helper methods for unicode operation
-      # ///
-      
-      # Used to translate an offset from bytes to characters, for instance one received from a regular expression match
-      def translate_offset(str, byte_offset)
-        return nil if byte_offset.nil?
-        return 0 if str == ''
-        chunk = str[0..byte_offset]
-        begin
-          begin
-            chunk.unpack('U*').length - 1
-          rescue ArgumentError => e
-            chunk = str[0..(byte_offset+=1)]
-            # Stop retrying at the end of the string
-            raise e unless byte_offset < chunk.length 
-            # We damaged a character, retry
-            retry
-          end
-        # Catch the ArgumentError so we can throw our own
-        rescue ArgumentError 
-          raise EncodingError.new('malformed UTF-8 character')
-        end
-      end
-      
-      # Checks if the string is valid UTF8.
-      def consumes?(str)
-        # Unpack is a little bit faster than regular expressions
-        begin
-          str.unpack('U*')
-          true
-        rescue ArgumentError
-          false
-        end
-      end
-      
-      # Returns the number of grapheme clusters in the string. This method is very likely to be moved or renamed
-      # in future versions.
-      def g_length(str)
-        g_unpack(str).length
-      end
-      
-      # Replaces all the non-utf-8 bytes by their iso-8859-1 or cp1252 equivalent resulting in a valid utf-8 string
-      def tidy_bytes(str)
-        str.split(//u).map do |c|
-          if !UTF8_PAT.match(c)
-            n = c.unpack('C')[0]
-            n < 128 ? n.chr :
-            n < 160 ? [UCD.cp1252[n] || n].pack('U') :
-            n < 192 ? "\xC2" + n.chr : "\xC3" + (n-64).chr
-          else
-            c
-          end
-        end.join
-      end
-      
-      protected
-      
-      # Detect whether the codepoint is in a certain character class. Primarily used by the
-      # grapheme cluster support.
-      def in_char_class?(codepoint, classes)
-        classes.detect { |c| UCD.boundary[c] === codepoint } ? true : false
-      end
-      
-      # Unpack the string at codepoints boundaries
-      def u_unpack(str)
-        begin
-          str.unpack 'U*'
-        rescue ArgumentError
-          raise EncodingError.new('malformed UTF-8 character')
-        end
-      end
-      
-      # Unpack the string at grapheme boundaries instead of codepoint boundaries
-      def g_unpack(str)
-        codepoints = u_unpack(str)
-        unpacked = []
-        pos = 0
-        marker = 0
-        eoc = codepoints.length
-        while(pos < eoc)
-          pos += 1
-          previous = codepoints[pos-1]
-          current = codepoints[pos]
-          if (
-              # CR X LF
-              one = ( previous == UCD.boundary[:cr] and current == UCD.boundary[:lf] ) or
-              # L X (L|V|LV|LVT)
-              two = ( UCD.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or
-              # (LV|V) X (V|T)
-              three = ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or
-              # (LVT|T) X (T)
-              four = ( in_char_class?(previous, [:lvt,:t]) and UCD.boundary[:t] === current ) or
-              # X Extend
-              five = (UCD.boundary[:extend] === current)
-            )
-          else
-            unpacked << codepoints[marker..pos-1]
-            marker = pos
-          end
-        end 
-        unpacked
-      end
-      
-      # Reverse operation of g_unpack
-      def g_pack(unpacked)
-        unpacked.flatten
-      end
-      
-      # Justifies a string in a certain way. Valid values for <tt>way</tt> are <tt>:right</tt>, <tt>:left</tt> and
-      # <tt>:center</tt>. Is primarily used as a helper method by <tt>rjust</tt>, <tt>ljust</tt> and <tt>center</tt>.
-      def justify(str, integer, way, padstr=' ')
-        raise ArgumentError, "zero width padding" if padstr.length == 0
-        padsize = integer - size(str)
-        padsize = padsize > 0 ? padsize : 0
-        case way
-        when :right
-          str.dup.insert(0, padding(padsize, padstr))
-        when :left
-          str.dup.insert(-1, padding(padsize, padstr))
-        when :center
-          lpad = padding((padsize / 2.0).floor, padstr)
-          rpad = padding((padsize / 2.0).ceil, padstr)
-          str.dup.insert(0, lpad).insert(-1, rpad)
-        end
-      end
-      
-      # Generates a padding string of a certain size.
-      def padding(padsize, padstr=' ')
-        if padsize != 0
-          slice(padstr * ((padsize / size(padstr)) + 1), 0, padsize)
-        else
-          ''
-        end
-      end
-      
-      # Convert characters to a different case
-      def to_case(way, str)
-        u_unpack(str).map do |codepoint|
-          cp = UCD[codepoint] 
-          unless cp.nil?
-            ncp = cp.send(way)
-            ncp > 0 ? ncp : codepoint
-          else
-            codepoint
-          end
-        end.pack('U*')
-      end
-      
-      # Re-order codepoints so the string becomes canonical
-      def reorder_characters(codepoints)
-        length = codepoints.length- 1
-        pos = 0
-        while pos < length do
-          cp1, cp2 = UCD[codepoints[pos]], UCD[codepoints[pos+1]]
-          if (cp1.combining_class > cp2.combining_class) && (cp2.combining_class > 0)
-            codepoints[pos..pos+1] = cp2.code, cp1.code
-            pos += (pos > 0 ? -1 : 1)
-          else
-            pos += 1
-          end
-        end
-        codepoints
-      end
-      
-      # Decompose composed characters to the decomposed form
-      def decompose_codepoints(type, codepoints)
-        codepoints.inject([]) do |decomposed, cp|
-          # if it's a hangul syllable starter character
-          if HANGUL_SBASE <= cp and cp < HANGUL_SLAST
-            sindex = cp - HANGUL_SBASE
-            ncp = [] # new codepoints
-            ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT
-            ncp << HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
-            tindex = sindex % HANGUL_TCOUNT
-            ncp << (HANGUL_TBASE + tindex) unless tindex == 0
-            decomposed.concat ncp
-          # if the codepoint is decomposable in with the current decomposition type
-          elsif (ncp = UCD[cp].decomp_mapping) and (!UCD[cp].decomp_type || type == :compatability)
-            decomposed.concat decompose_codepoints(type, ncp.dup)
-          else
-            decomposed << cp
-          end
-        end
-      end
-      
-      # Compose decomposed characters to the composed form
-      def compose_codepoints(codepoints)
-        pos = 0
-        eoa = codepoints.length - 1
-        starter_pos = 0
-        starter_char = codepoints[0]
-        previous_combining_class = -1
-        while pos < eoa
-          pos += 1
-          lindex = starter_char - HANGUL_LBASE
-          # -- Hangul
-          if 0 <= lindex and lindex < HANGUL_LCOUNT
-            vindex = codepoints[starter_pos+1] - HANGUL_VBASE rescue vindex = -1
-            if 0 <= vindex and vindex < HANGUL_VCOUNT
-              tindex = codepoints[starter_pos+2] - HANGUL_TBASE rescue tindex = -1
-              if 0 <= tindex and tindex < HANGUL_TCOUNT
-                j = starter_pos + 2
-                eoa -= 2
-              else
-                tindex = 0
-                j = starter_pos + 1
-                eoa -= 1
-              end
-              codepoints[starter_pos..j] = (lindex * HANGUL_VCOUNT + vindex) * HANGUL_TCOUNT + tindex + HANGUL_SBASE
-            end
-            starter_pos += 1
-            starter_char = codepoints[starter_pos]
-          # -- Other characters
-          else
-            current_char = codepoints[pos]
-            current = UCD[current_char]
-            if current.combining_class > previous_combining_class
-              if ref = UCD.composition_map[starter_char]
-                composition = ref[current_char]
-              else
-                composition = nil
-              end
-              unless composition.nil?
-                codepoints[starter_pos] = composition
-                starter_char = composition
-                codepoints.delete_at pos
-                eoa -= 1
-                pos -= 1
-                previous_combining_class = -1
-              else
-                previous_combining_class = current.combining_class
-              end
-            else
-              previous_combining_class = current.combining_class
-            end
-            if current.combining_class == 0
-              starter_pos = pos
-              starter_char = codepoints[pos]
-            end
-          end
-        end
-        codepoints
-      end
-      
-      # UniCode Database
-      UCD = UnicodeDatabase.new
-    end
-  end
-end
diff --git a/activesupport/lib/active_support/multibyte/handlers/utf8_handler_proc.rb b/activesupport/lib/active_support/multibyte/handlers/utf8_handler_proc.rb
deleted file mode 100644
index f10eecc622..0000000000
--- a/activesupport/lib/active_support/multibyte/handlers/utf8_handler_proc.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# Methods in this handler call functions in the utf8proc ruby extension. These are significantly faster than the
-# pure ruby versions. Chars automatically uses this handler when it can load the utf8proc extension. For
-# documentation on handler methods see UTF8Handler.
-class ActiveSupport::Multibyte::Handlers::UTF8HandlerProc < ActiveSupport::Multibyte::Handlers::UTF8Handler #:nodoc:
-  class << self
-    def normalize(str, form=ActiveSupport::Multibyte::DEFAULT_NORMALIZATION_FORM) #:nodoc:
-      codepoints = str.unpack('U*')
-      case form
-        when :d
-          utf8map(str, :stable)
-        when :c
-          utf8map(str, :stable, :compose)
-        when :kd
-          utf8map(str, :stable, :compat)
-        when :kc
-          utf8map(str, :stable, :compose, :compat)
-        else
-          raise ArgumentError, "#{form} is not a valid normalization variant", caller
-      end
-    end
-    
-    def decompose(str) #:nodoc:
-      utf8map(str, :stable)
-    end
-    
-    def downcase(str) #:nodoc:c
-      utf8map(str, :casefold)
-    end
-    
-    protected
-    
-    def utf8map(str, *option_array) #:nodoc:
-      options = 0
-      option_array.each do |option|
-        flag = Utf8Proc::Options[option]
-        raise ArgumentError, "Unknown argument given to utf8map." unless
-          flag
-        options |= flag
-      end
-      return Utf8Proc::utf8map(str, options)
-    end
-  end
-end
diff --git a/activesupport/lib/active_support/multibyte/unicode_database.rb b/activesupport/lib/active_support/multibyte/unicode_database.rb
new file mode 100644
index 0000000000..3b8cf8f9eb
--- /dev/null
+++ b/activesupport/lib/active_support/multibyte/unicode_database.rb
@@ -0,0 +1,71 @@
+# encoding: utf-8
+
+module ActiveSupport #:nodoc:
+  module Multibyte #:nodoc:
+    # Holds data about a codepoint in the Unicode database
+    class Codepoint
+      attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
+    end
+
+    # Holds static data from the Unicode database
+    class UnicodeDatabase
+      ATTRIBUTES = :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252
+
+      attr_writer(*ATTRIBUTES)
+
+      def initialize
+        @codepoints = Hash.new(Codepoint.new)
+        @composition_exclusion = []
+        @composition_map = {}
+        @boundary = {}
+        @cp1252 = {}
+      end
+
+      # Lazy load the Unicode database so it's only loaded when it's actually used
+      ATTRIBUTES.each do |attr_name|
+        class_eval(<<-EOS, __FILE__, __LINE__)
+          def #{attr_name}
+            load
+            @#{attr_name}
+          end
+        EOS
+      end
+
+      # Loads the Unicode database and returns all the internal objects of UnicodeDatabase.
+      def load
+        begin
+          @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
+        rescue Exception => e
+            raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable")
+        end
+
+        # Redefine the === method so we can write shorter rules for grapheme cluster breaks
+        @boundary.each do |k,_|
+          @boundary[k].instance_eval do
+            def ===(other)
+              detect { |i| i === other } ? true : false
+            end
+          end if @boundary[k].kind_of?(Array)
+        end
+
+        # define attr_reader methods for the instance variables
+        class << self
+          attr_reader(*ATTRIBUTES)
+        end
+      end
+
+      # Returns the directory in which the data files are stored
+      def self.dirname
+        File.dirname(__FILE__) + '/../values/'
+      end
+
+      # Returns the filename for the data file for this version
+      def self.filename
+        File.expand_path File.join(dirname, "unicode_tables.dat")
+      end
+    end
+
+    # UniCode Database
+    UCD = UnicodeDatabase.new
+  end
+end
\ No newline at end of file
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 35edb148c3..74b333d416 100644
Binary files a/activesupport/lib/active_support/values/unicode_tables.dat and b/activesupport/lib/active_support/values/unicode_tables.dat differ
-- 
cgit v1.2.3


From 520c3f33c3f642ccab3a860cf5ee0b5530c7c4f1 Mon Sep 17 00:00:00 2001
From: Manfred Stienstra <manfred@fngtps.com>
Date: Sun, 21 Sep 2008 17:23:37 +0200
Subject: Change all calls to String#chars to String#mb_chars.

---
 .../lib/active_support/core_ext/string/access.rb     | 10 +++++-----
 activesupport/lib/active_support/multibyte/chars.rb  | 20 ++++++++++----------
 2 files changed, 15 insertions(+), 15 deletions(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index 1a949c0b77..7fb21fa4dd 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -11,7 +11,7 @@ module ActiveSupport #:nodoc:
           #   "hello".at(4)  # => "o"
           #   "hello".at(10) # => nil
           def at(position)
-            chars[position, 1].to_s
+            mb_chars[position, 1].to_s
           end
           
           # Returns the remaining of the string from the +position+ treating the string as an array (where 0 is the first character).
@@ -21,7 +21,7 @@ module ActiveSupport #:nodoc:
           #   "hello".from(2)  # => "llo"
           #   "hello".from(10) # => nil
           def from(position)
-            chars[position..-1].to_s
+            mb_chars[position..-1].to_s
           end
           
           # Returns the beginning of the string up to the +position+ treating the string as an array (where 0 is the first character).
@@ -31,7 +31,7 @@ module ActiveSupport #:nodoc:
           #   "hello".to(2)  # => "hel"
           #   "hello".to(10) # => "hello"
           def to(position)
-            chars[0..position].to_s
+            mb_chars[0..position].to_s
           end
 
           # Returns the first character of the string or the first +limit+ characters.
@@ -41,7 +41,7 @@ module ActiveSupport #:nodoc:
           #   "hello".first(2)  # => "he"
           #   "hello".first(10) # => "hello"
           def first(limit = 1)
-            chars[0..(limit - 1)].to_s
+            mb_chars[0..(limit - 1)].to_s
           end
           
           # Returns the last character of the string or the last +limit+ characters.
@@ -51,7 +51,7 @@ module ActiveSupport #:nodoc:
           #   "hello".last(2)  # => "lo"
           #   "hello".last(10) # => "hello"
           def last(limit = 1)
-            (chars[(-limit)..-1] || self).to_s
+            (mb_chars[(-limit)..-1] || self).to_s
           end
         end
       else
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index c05419bfbf..cd0993d56b 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -9,12 +9,12 @@ module ActiveSupport #:nodoc:
     # String methods are proxied through the Chars object, and can be accessed through the +mb_chars+ method. Methods
     # which would normally return a String object now return a Chars object so methods can be chained.
     #
-    #   "The Perfect String  ".chars.downcase.strip.normalize #=> "the perfect string"
+    #   "The Perfect String  ".mb_chars.downcase.strip.normalize #=> "the perfect string"
     #
     # Chars objects are perfectly interchangeable with String objects as long as no explicit class checks are made.
     # If certain methods do explicitly check the class, call +to_s+ before you pass chars objects to them.
     #
-    #   bad.explicit_checking_method "T".chars.downcase.to_s
+    #   bad.explicit_checking_method "T".mb_chars.downcase.to_s
     #
     # The default Chars implementation assumes that the encoding of the string is UTF-8, if you want to handle different
     # encodings you can write your own multibyte string handler and configure it through 
@@ -213,12 +213,12 @@ module ActiveSupport #:nodoc:
       # Example:
       #
       #   s = "Müller"
-      #   s.chars[2] = "e" # Replace character with offset 2
+      #   s.mb_chars[2] = "e" # Replace character with offset 2
       #   s
       #   #=> "Müeler"
       #
       #   s = "Müller"
-      #   s.chars[1, 2] = "ö" # Replace 2 characters at character offset 1
+      #   s.mb_chars[1, 2] = "ö" # Replace 2 characters at character offset 1
       #   s
       #   #=> "Möler"
       def []=(*args)
@@ -253,10 +253,10 @@ module ActiveSupport #:nodoc:
       #
       # Example:
       #
-      #   "¾ cup".chars.rjust(8).to_s
+      #   "¾ cup".mb_chars.rjust(8).to_s
       #   #=> "   ¾ cup"
       #
-      #   "¾ cup".chars.rjust(8, " ").to_s # Use non-breaking whitespace
+      #   "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
       #   #=> "   ¾ cup"
       def rjust(integer, padstr=' ')
         justify(integer, :right, padstr)
@@ -266,10 +266,10 @@ module ActiveSupport #:nodoc:
       #
       # Example:
       #
-      #   "¾ cup".chars.rjust(8).to_s
+      #   "¾ cup".mb_chars.rjust(8).to_s
       #   #=> "¾ cup   "
       #
-      #   "¾ cup".chars.rjust(8, " ").to_s # Use non-breaking whitespace
+      #   "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
       #   #=> "¾ cup   "
       def ljust(integer, padstr=' ')
         justify(integer, :left, padstr)
@@ -279,10 +279,10 @@ module ActiveSupport #:nodoc:
       #
       # Example:
       #
-      #   "¾ cup".chars.center(8).to_s
+      #   "¾ cup".mb_chars.center(8).to_s
       #   #=> " ¾ cup  "
       #
-      #   "¾ cup".chars.center(8, " ").to_s # Use non-breaking whitespace
+      #   "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
       #   #=> " ¾ cup  "
       def center(integer, padstr=' ')
         justify(integer, :center, padstr)
-- 
cgit v1.2.3


From 8abef4fd0df828e79be6b9fadd8f45c575ab817c Mon Sep 17 00:00:00 2001
From: Manfred Stienstra <manfred@fngtps.com>
Date: Sun, 21 Sep 2008 17:25:36 +0200
Subject: All methods which normally return a string now return a proxy
 instance.

---
 activesupport/lib/active_support/multibyte/chars.rb | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index cd0993d56b..27cc3c65a2 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -246,7 +246,6 @@ module ActiveSupport #:nodoc:
           result[range] = self.class.u_unpack(replace_by)
           @wrapped_string.replace(result.pack('U*'))
         end
-        self
       end
 
       # Works just like String#rjust, only integer specifies characters instead of bytes.
@@ -365,7 +364,7 @@ module ActiveSupport #:nodoc:
       # Example:
       #  'über'.mb_chars.capitalize.to_s #=> "Über"
       def capitalize
-        (slice(0) || '').upcase + (slice(1..-1) || '').downcase
+        (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
       end
 
       # Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
-- 
cgit v1.2.3


From bfc73852b1f03d8dee405cdeb0f2883d89c78b2d Mon Sep 17 00:00:00 2001
From: Manfred Stienstra <manfred@fngtps.com>
Date: Sun, 21 Sep 2008 17:28:05 +0200
Subject: Improve documentation.

---
 .../active_support/core_ext/string/multibyte.rb    |  23 ++---
 activesupport/lib/active_support/multibyte.rb      |   4 +-
 .../lib/active_support/multibyte/chars.rb          | 113 +++++++++++----------
 .../lib/active_support/multibyte/exceptions.rb     |   1 +
 4 files changed, 74 insertions(+), 67 deletions(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 5a2dc36f72..3bf79bc7e1 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -6,7 +6,9 @@ module ActiveSupport #:nodoc:
       # Implements multibyte methods for easier access to multibyte characters in a String instance.
       module Multibyte
         unless '1.9'.respond_to?(:force_encoding)
-          # +mb_chars+ is a multibyte safe proxy method for string methods.
+          # == Multibyte proxy
+          #
+          # +mb_chars+ is a multibyte safe proxy for string methods.
           #
           # In Ruby 1.8 and older it creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
           # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
@@ -19,11 +21,10 @@ module ActiveSupport #:nodoc:
           #   name.mb_chars.reverse.to_s   #=> "rellüM sualC"
           #   name.mb_chars.length         #=> 12
           #
-          # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware so we don't need
-          # a proxy class any more. This means that +mb_chars+ makes it easier to write code that runs on multiple Ruby
-          # versions.
+          # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
+          # it becomes easy to run one version of your code on multiple Ruby versions.
           #
-          # == Method chaining 
+          # == Method chaining
           #
           # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
           # method chaining on the result of any of these methods.
@@ -32,12 +33,12 @@ module ActiveSupport #:nodoc:
           #
           # == Interoperability and configuration
           #
-          # The Char object tries to be as interchangeable with String objects as possible: sorting and comparing between
+          # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
           # String and Char work like expected. The bang! methods change the internal string representation in the Chars
           # object. Interoperability problems can be resolved easily with a +to_s+ call.
           #
           # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
-          # information about how to change the default Multibyte behaviour, see ActiveSupport::Multibyte.
+          # information about how to change the default Multibyte behaviour see ActiveSupport::Multibyte.
           def mb_chars
             if ActiveSupport::Multibyte.proxy_class.wants?(self)
               ActiveSupport::Multibyte.proxy_class.new(self)
@@ -56,15 +57,11 @@ module ActiveSupport #:nodoc:
             alias chars mb_chars
           end
         else
-          # In Ruby 1.9 and newer +mb_chars+ returns self. In Ruby 1.8 and older +mb_chars+ creates and returns an
-          # Unicode safe proxy for string operations, this makes it easier to write code that runs on multiple Ruby
-          # versions.
-          def mb_chars
+          def mb_chars #:nodoc
             self
           end
           
-          # Returns true if the string has valid UTF-8 encoding.
-          def is_utf8?
+          def is_utf8? #:nodoc
             case encoding
             when Encoding::UTF_8
               valid_encoding?
diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb
index 63c0d50166..018aafe607 100644
--- a/activesupport/lib/active_support/multibyte.rb
+++ b/activesupport/lib/active_support/multibyte.rb
@@ -5,7 +5,7 @@ require 'active_support/multibyte/exceptions'
 require 'active_support/multibyte/unicode_database'
 
 module ActiveSupport #:nodoc:
-  module Multibyte #: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]
@@ -30,4 +30,4 @@ module ActiveSupport #:nodoc:
     mattr_accessor :proxy_class
     self.proxy_class = ActiveSupport::Multibyte::Chars
   end
-end
\ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 27cc3c65a2..c61367968e 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -2,7 +2,7 @@
 
 module ActiveSupport #:nodoc:
   module Multibyte #:nodoc:
-    # Chars enables you to work transparently with multibyte encodings in the Ruby String class without having extensive
+    # Chars enables you to work transparently with UTF-8 encoding in the Ruby String class without having extensive
     # knowledge about the encoding. A Chars object accepts a string upon initialization and proxies String methods in an
     # encoding safe manner. All the normal String methods are also implemented on the proxy.
     #
@@ -88,14 +88,14 @@ module ActiveSupport #:nodoc:
       alias to_s wrapped_string
       alias to_str wrapped_string
 
-      # Creates a new Chars instance. +string+ is the wrapped string.
       if '1.9'.respond_to?(:force_encoding)
+        # Creates a new Chars instance by wrapping _string_.
         def initialize(string)
           @wrapped_string = string
           @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
         end
       else
-        def initialize(string)
+        def initialize(string) #:nodoc:
           @wrapped_string = string
         end
       end
@@ -121,10 +121,10 @@ module ActiveSupport #:nodoc:
         true
       end
 
-      # Returns +true+ if the Chars class can and should act as a proxy for the string +string+. Returns
+      # Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns
       # +false+ otherwise.
       def self.wants?(string)
-        RUBY_VERSION < '1.9' && $KCODE == 'UTF8' && consumes?(string)
+        $KCODE == 'UTF8' && consumes?(string)
       end
 
       # Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise.
@@ -138,9 +138,9 @@ module ActiveSupport #:nodoc:
 
       include Comparable
 
-      # Returns -1, 0 or +1 depending on whether the Chars object is to be sorted before, equal or after the
-      # object on the right side of the operation. It accepts any object that implements +to_s+. See String.<=>
-      # for more details.
+      # Returns <tt>-1</tt>, <tt>0</tt> or <tt>+1</tt> depending on whether the Chars object is to be sorted before,
+      # equal or after the object on the right side of the operation. It accepts any object that implements +to_s+.
+      # See <tt>String#<=></tt> for more details.
       #
       # Example:
       #   'é'.mb_chars <=> 'ü'.mb_chars #=> -1
@@ -148,7 +148,7 @@ module ActiveSupport #:nodoc:
         @wrapped_string <=> other.to_s
       end
 
-      # Returns a new Chars object containing the other object concatenated to the string.
+      # Returns a new Chars object containing the _other_ object concatenated to the string.
       #
       # Example:
       #   ('Café'.mb_chars + ' périferôl').to_s #=> "Café périferôl"
@@ -156,7 +156,7 @@ module ActiveSupport #:nodoc:
         self << other
       end
 
-      # Like String.=~ only it returns the character offset (in codepoints) instead of the byte offset.
+      # Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
       #
       # Example:
       #   'Café périferôl'.mb_chars =~ /ô/ #=> 12
@@ -164,7 +164,7 @@ module ActiveSupport #:nodoc:
         translate_offset(@wrapped_string =~ other)
       end
 
-      # Works just like String#split, with the exception that the items in the resulting list are Chars
+      # Works just like <tt>String#split</tt>, with the exception that the items in the resulting list are Chars
       # instances instead of String. This makes chaining methods easier.
       #
       # Example:
@@ -173,7 +173,7 @@ module ActiveSupport #:nodoc:
         @wrapped_string.split(*args).map { |i| i.mb_chars }
       end
 
-      # Inserts the passed string at specified codepoint offsets
+      # Inserts the passed string at specified codepoint offsets.
       #
       # Example:
       #   'Café'.mb_chars.insert(4, ' périferôl').to_s #=> "Café périferôl"
@@ -189,7 +189,7 @@ module ActiveSupport #:nodoc:
         self
       end
 
-      # Returns true if contained string contains +other+. Returns false otherwise.
+      # Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
       #
       # Example:
       #   'Café'.mb_chars.include?('é') #=> true
@@ -198,17 +198,17 @@ module ActiveSupport #:nodoc:
         @wrapped_string.include?(other)
       end
 
-      # Returns the position of the passed argument in the string, counting in codepoints
+      # Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
       #
       # Example:
       #   'Café périferôl'.mb_chars.index('ô') #=> 12
-      def index(*args)
-        index = @wrapped_string.index(*args)
+      #   'Café périferôl'.mb_chars.index(/\w/u) #=> 0
+      def index(needle, offset=0)
+        index = @wrapped_string.index(needle, offset)
         index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil
       end
 
-      # Works just like the indexed replace method on string, except instead of byte offsets you specify
-      # character offsets.
+      # Like <tt>String#[]=</tt>, except instead of byte offsets you specify character offsets.
       #
       # Example:
       #
@@ -248,7 +248,7 @@ module ActiveSupport #:nodoc:
         end
       end
 
-      # Works just like String#rjust, only integer specifies characters instead of bytes.
+      # Works just like <tt>String#rjust</tt>, only integer specifies characters instead of bytes.
       #
       # Example:
       #
@@ -261,7 +261,7 @@ module ActiveSupport #:nodoc:
         justify(integer, :right, padstr)
       end
 
-      # Works just like String#ljust, only integer specifies characters instead of bytes.
+      # Works just like <tt>String#ljust</tt>, only integer specifies characters instead of bytes.
       #
       # Example:
       #
@@ -274,7 +274,7 @@ module ActiveSupport #:nodoc:
         justify(integer, :left, padstr)
       end
 
-      # Works just like String#center, only integer specifies characters instead of bytes.
+      # Works just like <tt>String#center</tt>, only integer specifies characters instead of bytes.
       #
       # Example:
       #
@@ -308,7 +308,7 @@ module ActiveSupport #:nodoc:
       end
       alias_method :length, :size
       
-      # Reverses all characters in the string
+      # Reverses all characters in the string.
       #
       # Example:
       #   'Café'.mb_chars.reverse.to_s #=> 'éfaC'
@@ -343,7 +343,7 @@ module ActiveSupport #:nodoc:
       end
       alias_method :[], :slice
 
-      # Convert characters in the string to uppercase
+      # Convert characters in the string to uppercase.
       #
       # Example:
       #   'Laurent, òu sont les tests?'.mb_chars.upcase.to_s #=> "LAURENT, ÒU SONT LES TESTS?"
@@ -351,7 +351,7 @@ module ActiveSupport #:nodoc:
         apply_mapping :uppercase_mapping
       end
 
-      # Convert characters in the string to lowercase
+      # Convert characters in the string to lowercase.
       #
       # Example:
       #   'VĚDA A VÝZKUM'.mb_chars.downcase.to_s #=> "věda a výzkum"
@@ -359,7 +359,7 @@ module ActiveSupport #:nodoc:
         apply_mapping :lowercase_mapping
       end
 
-      # Converts the first character to uppercase and the remainder to lowercase
+      # Converts the first character to uppercase and the remainder to lowercase.
       #
       # Example:
       #  'über'.mb_chars.capitalize.to_s #=> "Über"
@@ -418,6 +418,7 @@ module ActiveSupport #:nodoc:
         self.class.g_unpack(@wrapped_string).length
       end
 
+      # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
       def tidy_bytes
         chars(self.class.tidy_bytes(@wrapped_string))
       end
@@ -435,24 +436,35 @@ module ActiveSupport #:nodoc:
 
       class << self
 
-        # Unpack the string at codepoints boundaries
-        def u_unpack(str)
+        # Unpack the string at codepoints boundaries. Raises an EncodingError when the encoding of the string isn't
+        # valid UTF-8.
+        #
+        # Example:
+        #   Chars.u_unpack('Café') #=> [67, 97, 102, 233]
+        def u_unpack(string)
           begin
-            str.unpack 'U*'
+            string.unpack 'U*'
           rescue ArgumentError
             raise EncodingError.new('malformed UTF-8 character')
           end
         end
 
-        # Detect whether the codepoint is in a certain character class. Primarily used by the
-        # grapheme cluster support.
+        # Detect whether the codepoint is in a certain character class. Returns +true+ when it's in the specified
+        # character class and +false+ otherwise. Valid character classes are: <tt>:cr</tt>, <tt>:lf</tt>, <tt>:l</tt>,
+        # <tt>:v</tt>, <tt>:lv</tt>, <tt>:lvt</tt> and <tt>:t</tt>.
+        #
+        # Primarily used by the grapheme cluster support.
         def in_char_class?(codepoint, classes)
           classes.detect { |c| UCD.boundary[c] === codepoint } ? true : false
         end
 
-        # Unpack the string at grapheme boundaries
-        def g_unpack(str)
-          codepoints = u_unpack(str)
+        # Unpack the string at grapheme boundaries. Returns a list of character lists.
+        #
+        # Example:
+        #   Chars.g_unpack('क्षि') #=> [[2325, 2381], [2359], [2367]]
+        #   Chars.g_unpack('Café') #=> [[67], [97], [102], [233]]
+        def g_unpack(string)
+          codepoints = u_unpack(string)
           unpacked = []
           pos = 0
           marker = 0
@@ -481,13 +493,15 @@ module ActiveSupport #:nodoc:
           unpacked
         end
 
-        # Reverse operation of g_unpack
+        # Reverse operation of g_unpack.
+        #
+        # Example:
+        #   Chars.g_pack(Chars.g_unpack('क्षि')) #=> 'क्षि'
         def g_pack(unpacked)
           (unpacked.flatten).pack('U*')
         end
 
-        # Generates a padding string of a certain size.
-        def padding(padsize, padstr=' ')
+        def padding(padsize, padstr=' ') #:nodoc:
           if padsize != 0
             new(padstr * ((padsize / u_unpack(padstr).size) + 1)).slice(0, padsize)
           else
@@ -495,7 +509,7 @@ module ActiveSupport #:nodoc:
           end
         end
 
-        # Re-order codepoints so the string becomes canonical
+        # Re-order codepoints so the string becomes canonical.
         def reorder_characters(codepoints)
           length = codepoints.length- 1
           pos = 0
@@ -511,7 +525,7 @@ module ActiveSupport #:nodoc:
           codepoints
         end
 
-        # Decompose composed characters to the decomposed form
+        # Decompose composed characters to the decomposed form.
         def decompose_codepoints(type, codepoints)
           codepoints.inject([]) do |decomposed, cp|
             # if it's a hangul syllable starter character
@@ -532,7 +546,7 @@ module ActiveSupport #:nodoc:
           end
         end
 
-        # Compose decomposed characters to the composed form
+        # Compose decomposed characters to the composed form.
         def compose_codepoints(codepoints)
           pos = 0
           eoa = codepoints.length - 1
@@ -591,9 +605,9 @@ module ActiveSupport #:nodoc:
           codepoints
         end
 
-        # Replaces all the non-UTF-8 bytes by their iso-8859-1 or cp1252 equivalent resulting in a valid UTF-8 string
-        def tidy_bytes(str)
-          str.split(//u).map do |c|
+        # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
+        def tidy_bytes(string)
+          string.split(//u).map do |c|
             if !UTF8_PAT.match(c)
               n = c.unpack('C')[0]
               n < 128 ? n.chr :
@@ -608,8 +622,7 @@ module ActiveSupport #:nodoc:
 
       protected
 
-        # Translate a byte offset in the wrapped string to a character offset by looking for the character boundary
-        def translate_offset(byte_offset)
+        def translate_offset(byte_offset) #:nodoc:
           return nil if byte_offset.nil?
           return 0   if @wrapped_string == ''
           chunk = @wrapped_string[0..byte_offset]
@@ -629,9 +642,7 @@ module ActiveSupport #:nodoc:
           end
         end
 
-        # Justifies a string in a certain way. Valid values for <tt>way</tt> are <tt>:right</tt>, <tt>:left</tt> and
-        # <tt>:center</tt>.
-        def justify(integer, way, padstr=' ')
+        def justify(integer, way, padstr=' ') #:nodoc:
           raise ArgumentError, "zero width padding" if padstr.length == 0
           padsize = integer - size
           padsize = padsize > 0 ? padsize : 0
@@ -648,8 +659,7 @@ module ActiveSupport #:nodoc:
           chars(result)
         end
 
-        # Map codepoints to one of it's attributes.
-        def apply_mapping(mapping)
+        def apply_mapping(mapping) #:nodoc:
           chars(self.class.u_unpack(@wrapped_string).map do |codepoint|
             cp = UCD.codepoints[codepoint]
             if cp and (ncp = cp.send(mapping)) and ncp > 0
@@ -660,9 +670,8 @@ module ActiveSupport #:nodoc:
           end.pack('U*'))
         end
 
-        # Creates a new instance
-        def chars(str)
-          self.class.new(str)
+        def chars(string) #:nodoc:
+          self.class.new(string)
         end
     end
   end
diff --git a/activesupport/lib/active_support/multibyte/exceptions.rb b/activesupport/lib/active_support/multibyte/exceptions.rb
index af760cc561..62066e3c71 100644
--- a/activesupport/lib/active_support/multibyte/exceptions.rb
+++ b/activesupport/lib/active_support/multibyte/exceptions.rb
@@ -2,6 +2,7 @@
 
 module ActiveSupport #:nodoc:
   module Multibyte #:nodoc:
+    # Raised when a problem with the encoding was found.
     class EncodingError < StandardError; end
   end
 end
\ No newline at end of file
-- 
cgit v1.2.3


From 809af7f5586cb3f2f913b21be168fbf72d58cbfe Mon Sep 17 00:00:00 2001
From: Manfred Stienstra <manfred@fngtps.com>
Date: Sun, 21 Sep 2008 17:29:22 +0200
Subject: Non-string results from forwarded methods should be returned
 vertabim.

---
 activesupport/lib/active_support/multibyte/chars.rb | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index c61367968e..b3fdcdc650 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -106,10 +106,11 @@ module ActiveSupport #:nodoc:
           @wrapped_string.__send__(method, *args, &block)
           self
         else
-          chars(@wrapped_string.__send__(method, *args, &block))
+          result = @wrapped_string.__send__(method, *args, &block)
+          result.kind_of?(String) ? chars(result) : result
         end
       end
-      
+
       # Returns +true+ if _obj_ responds to the given method. Private methods are included in the search
       # only if the optional second parameter evaluates to +true+.
       def respond_to?(method, include_private=false)
-- 
cgit v1.2.3


From 85c05b53948a64ab0e246239d18e01d317a74d7d Mon Sep 17 00:00:00 2001
From: Manfred Stienstra <manfred@fngtps.com>
Date: Sun, 21 Sep 2008 17:30:45 +0200
Subject: Add tests for u_unpack to make sure it raises an EncodingError on
 invalid UTF-8 strings.

---
 activesupport/lib/active_support/multibyte/chars.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index b3fdcdc650..5184026c63 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -446,7 +446,7 @@ module ActiveSupport #:nodoc:
           begin
             string.unpack 'U*'
           rescue ArgumentError
-            raise EncodingError.new('malformed UTF-8 character')
+            raise EncodingError, 'malformed UTF-8 character'
           end
         end
 
-- 
cgit v1.2.3


From 44e44b42d9226c089f00970ced796c83f193f262 Mon Sep 17 00:00:00 2001
From: Manfred Stienstra <manfred@fngtps.com>
Date: Sun, 21 Sep 2008 17:47:19 +0200
Subject: Deprecated String#chars in favor of String#mb_chars.

---
 activesupport/lib/active_support/core_ext/string/multibyte.rb | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 3bf79bc7e1..a4caa83b74 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -54,7 +54,10 @@ module ActiveSupport #:nodoc:
           end
 
           unless '1.8.7 and later'.respond_to?(:chars)
-            alias chars mb_chars
+            def chars
+              ActiveSupport::Deprecation.warn('String#chars has been deprecated in favor of String#mb_chars.', caller)
+              mb_chars
+            end
           end
         else
           def mb_chars #:nodoc
-- 
cgit v1.2.3


From 00a428655149c349d29b9351dbc345a3b8025a58 Mon Sep 17 00:00:00 2001
From: Manfred Stienstra <manfred@fngtps.com>
Date: Sun, 21 Sep 2008 17:51:01 +0200
Subject: Change call to String#chars in inflector to String#mb_chars.

---
 activesupport/lib/active_support/inflector.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb
index 8a917a9eb2..b2046f26de 100644
--- a/activesupport/lib/active_support/inflector.rb
+++ b/activesupport/lib/active_support/inflector.rb
@@ -257,7 +257,7 @@ module ActiveSupport
     #   <%= link_to(@person.name, person_path %>
     #   # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
     def parameterize(string, sep = '-')
-      string.chars.normalize(:kd).to_s.gsub(/[^\x00-\x7F]+/, '').gsub(/[^a-z0-9_\-]+/i, sep).downcase
+      string.mb_chars.normalize(:kd).to_s.gsub(/[^\x00-\x7F]+/, '').gsub(/[^a-z0-9_\-]+/i, sep).downcase
     end
 
     # Create the name of a table like Rails does for models to table names. This method
-- 
cgit v1.2.3


From 5f86451a4c5d0beca5a746c4708be48b13f665be Mon Sep 17 00:00:00 2001
From: Michael Koziarski <michael@koziarski.com>
Date: Mon, 22 Sep 2008 17:14:54 +0200
Subject: Bump the Version constants to align with the *next* release rather
 than the previous release.

This allows people tracking non-release gems or git submodules to use the constants.
---
 activesupport/lib/active_support/version.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index b346459a9b..8f5395fca6 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,7 +1,7 @@
 module ActiveSupport
   module VERSION #:nodoc:
     MAJOR = 2
-    MINOR = 1
+    MINOR = 2
     TINY  = 0
 
     STRING = [MAJOR, MINOR, TINY].join('.')
-- 
cgit v1.2.3


From a4f2ba8fb3c46ef9f7e31725849efdcb1a22c72d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adam=20Cig=C3=A1nek?= <adam.ciganek@gmail.com>
Date: Fri, 12 Sep 2008 14:45:11 +0200
Subject: Modified ActiveSupport::Inflector#parameterize with code from
 slugalizer (http://github.com/henrik/slugalizer)

Handles trailing and leading slashes, and squashes repeated separators into a single character.

Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#1034 state:committed]
---
 activesupport/lib/active_support/inflector.rb | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb
index b2046f26de..89a93f4a5f 100644
--- a/activesupport/lib/active_support/inflector.rb
+++ b/activesupport/lib/active_support/inflector.rb
@@ -240,9 +240,9 @@ module ActiveSupport
     def demodulize(class_name_in_module)
       class_name_in_module.to_s.gsub(/^.*::/, '')
     end
-    
+
     # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
-    # 
+    #
     # ==== Examples
     #
     #   class Person
@@ -250,14 +250,20 @@ module ActiveSupport
     #       "#{id}-#{name.parameterize}"
     #     end
     #   end
-    # 
+    #
     #   @person = Person.find(1)
     #   # => #<Person id: 1, name: "Donald E. Knuth">
-    # 
+    #
     #   <%= link_to(@person.name, person_path %>
     #   # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
     def parameterize(string, sep = '-')
-      string.mb_chars.normalize(:kd).to_s.gsub(/[^\x00-\x7F]+/, '').gsub(/[^a-z0-9_\-]+/i, sep).downcase
+      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
     end
 
     # Create the name of a table like Rails does for models to table names. This method
-- 
cgit v1.2.3


From 2e75bd0808f4dcac328b690aaad176cbfe96773e Mon Sep 17 00:00:00 2001
From: adam <adam@3scale.net>
Date: Tue, 23 Sep 2008 12:08:24 +0200
Subject: slice now returns indifferent hash if called on one

Signed-off-by: Michael Koziarski <michael@koziarski.com>

[#1096 state:committed]
---
 activesupport/lib/active_support/core_ext/hash/slice.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'activesupport/lib')

diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 3f14470f36..88df49a69f 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -18,7 +18,7 @@ module ActiveSupport #:nodoc:
         # Returns a new hash with only the given keys.
         def slice(*keys)
           keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
-          hash = {}
+          hash = self.class.new
           keys.each { |k| hash[k] = self[k] if has_key?(k) }
           hash
         end
-- 
cgit v1.2.3