diff options
Diffstat (limited to 'activesupport/lib/active_support')
39 files changed, 495 insertions, 130 deletions
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index 26412cd7f4..136e245859 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -25,22 +25,28 @@ module ActiveSupport      # Silences the logger for the duration of the block.      def silence(temporary_level = ERROR)        if silencer +        old_logger_level = @tmp_levels[Thread.current]          begin -          old_logger_level, self.level = level, temporary_level +          @tmp_levels[Thread.current] = temporary_level            yield self          ensure -          self.level = old_logger_level +          if old_logger_level +            @tmp_levels[Thread.current] = old_logger_level +          else +            @tmp_levels.delete(Thread.current) +          end          end        else          yield self        end      end -    attr_accessor :level +    attr_writer :level      attr_reader :auto_flushing      def initialize(log, level = DEBUG)        @level         = level +      @tmp_levels    = {}        @buffer        = Hash.new { |h,k| h[k] = [] }        @auto_flushing = 1        @guard = Mutex.new @@ -62,8 +68,12 @@ module ActiveSupport        end      end +    def level +      @tmp_levels[Thread.current] || @level +    end +      def add(severity, message = nil, progname = nil, &block) -      return if @level > severity +      return if level > severity        message = (message || (block && block.call) || progname).to_s        # If a newline is necessary then create a new message ending with a newline.        # Ensures that the original message is not mutated. @@ -84,7 +94,7 @@ module ActiveSupport          end                                                             # end          def #{severity.downcase}?                                       # def debug? -          #{severity} >= @level                                         #   DEBUG >= @level +          #{severity} >= level                                         #   DEBUG >= @level          end                                                             # end        EOT      end @@ -105,13 +115,15 @@ module ActiveSupport      def flush        @guard.synchronize do -        buffer.each do |content| -          @log.write(content) -        end +        write_buffer(buffer)          # Important to do this even if buffer was empty or else @buffer will          # accumulate empty arrays for each request where nothing was logged.          clear_buffer + +        # Clear buffers associated with dead threads or else spawned threads +        # that don't call flush will result in a memory leak. +        flush_dead_buffers        end      end @@ -133,5 +145,21 @@ module ActiveSupport        def clear_buffer          @buffer.delete(Thread.current)        end + +      # Find buffers created by threads that are no longer alive and flush them to the log +      # in order to prevent memory leaks from spawned threads. +      def flush_dead_buffers #:nodoc: +        @buffer.keys.reject{|thread| thread.alive?}.each do |thread| +          buffer = @buffer[thread] +          write_buffer(buffer) +          @buffer.delete(thread) +        end +      end + +      def write_buffer(buffer) +        buffer.each do |content| +          @log.write(content) +        end +      end    end  end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 2d2264e58a..6535cc1eb5 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -16,8 +16,6 @@ module ActiveSupport      autoload :FileStore, 'active_support/cache/file_store'      autoload :MemoryStore, 'active_support/cache/memory_store'      autoload :MemCacheStore, 'active_support/cache/mem_cache_store' -    autoload :SynchronizedMemoryStore, 'active_support/cache/synchronized_memory_store' -    autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store'      # These options mean something to all cache implementations. Individual cache      # implementations may support additional options. @@ -141,7 +139,7 @@ module ActiveSupport      # large enough to warrant compression. To turn on compression either pass      # <tt>:compress => true</tt> in the initializer or as an option to +fetch+      # or +write+. To specify the threshold at which to compress values, set the -    # <tt>:compress_threshold</tt> option. The default threshold is 32K. +    # <tt>:compress_threshold</tt> option. The default threshold is 16K.      class Store        cattr_accessor :logger, :instance_writer => true @@ -232,7 +230,7 @@ module ActiveSupport        # <tt>:race_condition_ttl</tt> does not play any role.        #        #   # Set all values to expire after one minute. -      #   cache = ActiveSupport::Cache::MemoryCache.new(:expires_in => 1.minute) +      #   cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 1.minute)        #        #   cache.write("foo", "original value")        #   val_1 = nil @@ -347,7 +345,7 @@ module ActiveSupport            entry = read_entry(key, options)            if entry              if entry.expired? -              delete_entry(key) +              delete_entry(key, options)              else                results[name] = entry.value              end @@ -561,7 +559,7 @@ module ActiveSupport            @value = nil          else            @value = Marshal.dump(value) -          if should_compress?(value, options) +          if should_compress?(@value, options)              @value = Zlib::Deflate.deflate(@value)              @compressed = true            end @@ -615,13 +613,10 @@ module ActiveSupport        end        private -        def should_compress?(value, options) -          if options[:compress] && value -            unless value.is_a?(Numeric) -              compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT -              serialized_value = value.is_a?(String) ? value : Marshal.dump(value) -              return true if serialized_value.size >= compress_threshold -            end +        def should_compress?(serialized_value, options) +          if options[:compress] +            compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT +            return true if serialized_value.size >= compress_threshold            end            false          end diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index f7c01948b4..85e7e21624 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -13,6 +13,8 @@ module ActiveSupport        attr_reader :cache_path        DIR_FORMATTER = "%03X" +      FILENAME_MAX_SIZE = 230 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write) +      EXCLUDED_DIRS = ['.', '..'].freeze        def initialize(cache_path, options = nil)          super(options) @@ -21,7 +23,7 @@ module ActiveSupport        end        def clear(options = nil) -        root_dirs = Dir.entries(cache_path).reject{|f| f.in?(['.', '..'])} +        root_dirs = Dir.entries(cache_path).reject{|f| f.in?(EXCLUDED_DIRS)}          FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})        end @@ -129,15 +131,13 @@ module ActiveSupport            hash, dir_1 = hash.divmod(0x1000)            dir_2 = hash.modulo(0x1000)            fname_paths = [] -          # Make sure file name is < 255 characters so it doesn't exceed file system limits. -          if fname.size <= 255 -            fname_paths << fname -          else -            while fname.size <= 255 -              fname_path << fname[0, 255] -              fname = fname[255, -1] -            end -          end + +          # Make sure file name doesn't exceed file system limits. +          begin +            fname_paths << fname[0, FILENAME_MAX_SIZE] +            fname = fname[FILENAME_MAX_SIZE..-1] +          end until fname.blank? +            File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)          end @@ -150,7 +150,7 @@ module ActiveSupport          # Delete empty directories in the cache.          def delete_empty_directories(dir)            return if dir == cache_path -          if Dir.entries(dir).reject{|f| f.in?(['.', '..'])}.empty? +          if Dir.entries(dir).reject{|f| f.in?(EXCLUDED_DIRS)}.empty?              File.delete(dir) rescue nil              delete_empty_directories(File.dirname(dir))            end @@ -162,8 +162,9 @@ module ActiveSupport          end          def search_dir(dir, &callback) +          return if !File.exist?(dir)            Dir.foreach(dir) do |d| -            next if d == "." || d == ".." +            next if d.in?(EXCLUDED_DIRS)              name = File.join(dir, d)              if File.directory?(name)                search_dir(name, &callback) diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb index 4688468a8f..268c9bed4c 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -5,3 +5,4 @@ require 'active_support/core_ext/array/conversions'  require 'active_support/core_ext/array/extract_options'  require 'active_support/core_ext/array/grouping'  require 'active_support/core_ext/array/random_access' +require 'active_support/core_ext/array/prepend_and_append' diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 3b22e8b4f9..f3d06ecb2f 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -39,10 +39,10 @@ class Array    #    #   Blog.all.to_formatted_s # => "First PostSecond PostThird Post"    # -  # Adding in the <tt>:db</tt> argument as the format yields a prettier -  # output: +  # Adding in the <tt>:db</tt> argument as the format yields a comma separated +  # id list:    # -  #   Blog.all.to_formatted_s(:db) # => "First Post,Second Post,Third Post" +  #   Blog.all.to_formatted_s(:db) # => "1,2,3"    def to_formatted_s(format = :default)      case format        when :db diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb new file mode 100644 index 0000000000..27718f19d4 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb @@ -0,0 +1,7 @@ +class Array +  # The human way of thinking about adding stuff to the end of a list is with append +  alias_method :append,  :<< + +  # The human way of thinking about adding stuff to the beginning of a list is with prepend +  alias_method :prepend, :unshift +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 080604147d..391bdc925d 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -29,8 +29,11 @@ class BigDecimal      coder.represent_scalar(nil, YAML_MAPPING[string] || string)    end -  def to_d -    self +  # Backport this method if it doesn't exist +  unless method_defined?(:to_d) +    def to_d +      self +    end    end    DEFAULT_STRING_FORMAT = 'F' diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index ddb4f3012f..9343bb7106 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -97,7 +97,7 @@ module Enumerable    end    # Returns true if the enumerable has more than 1 element. Functionally equivalent to enum.to_a.size > 1. -  # Can be called with a block too, much like any?, so people.many? { |p| p.age > 26 } returns true if more than 1 person is over 26. +  # Can be called with a block too, much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns true if more than one person is over 26.    def many?      cnt = 0      if block_given? @@ -110,7 +110,7 @@ module Enumerable      end    end -  # The negative of the Enumerable#include?. Returns true if the collection does not include the object. +  # The negative of the <tt>Enumerable#include?</tt>. Returns true if the collection does not include the object.    def exclude?(object)      !include?(object)    end diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index 0b368fe7b7..f4cb445444 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -2,7 +2,7 @@ require 'active_support/hash_with_indifferent_access'  class Hash -  # Returns an +ActiveSupport::HashWithIndifferentAccess+ out of its receiver: +  # Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:    #    #   {:a => 1}.with_indifferent_access["a"] # => 1    # diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index d7fb2da0fb..0484d8e5d8 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -30,6 +30,8 @@ class Hash      omit    end +  # Removes and returns the key/value pairs matching the given keys. +  #   {:a => 1, :b => 2, :c => 3, :d => 4}.extract!(:a, :b) # => {:a => 1, :b => 2}    def extract!(*keys)      result = {}      keys.each {|key| result[key] = delete(key) } diff --git a/activesupport/lib/active_support/core_ext/io.rb b/activesupport/lib/active_support/core_ext/io.rb new file mode 100644 index 0000000000..75f1055191 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/io.rb @@ -0,0 +1,15 @@ +if RUBY_VERSION < '1.9.2' + +# :stopdoc: +class IO +  def self.binread(name, length = nil, offset = nil) +    return File.read name unless length || offset +    File.open(name, 'rb') { |f| +      f.seek offset if offset +      f.read length +    } +  end +end +# :startdoc: + +end diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index 9fed346b7c..f399fce410 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -8,4 +8,5 @@ require 'active_support/core_ext/module/delegation'  require 'active_support/core_ext/module/synchronization'  require 'active_support/core_ext/module/deprecation'  require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/method_names'
\ No newline at end of file +require 'active_support/core_ext/module/method_names' +require 'active_support/core_ext/module/qualified_const'
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 41f9a76b13..7de824a77f 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,5 +1,3 @@ -require "active_support/core_ext/module/remove_method" -  class Module    # Provides a delegate class method to easily expose contained objects' methods    # as your own. Pass one or more methods (specified as symbols or strings) @@ -108,35 +106,48 @@ class Module      unless options.is_a?(Hash) && to = options[:to]        raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."      end +    prefix, to, allow_nil = options[:prefix], options[:to], options[:allow_nil] -    if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/ +    if prefix == true && to.to_s =~ /^[^a-z_]/        raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."      end -    prefix = options[:prefix] ? "#{options[:prefix] == true ? to : options[:prefix]}_" : '' +    method_prefix = +      if prefix +        "#{prefix == true ? to : prefix}_" +      else +        '' +      end      file, line = caller.first.split(':', 2)      line = line.to_i      methods.each do |method| -      on_nil = -        if options[:allow_nil] -          'return' -        else -          %(raise "#{self}##{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") -        end +      method = method.to_s + +      if allow_nil +        module_eval(<<-EOS, file, line - 2) +          def #{method_prefix}#{method}(*args, &block)        # def customer_name(*args, &block) +            if #{to} || #{to}.respond_to?(:#{method})         #   if client || client.respond_to?(:name) +              #{to}.__send__(:#{method}, *args, &block)       #     client.__send__(:name, *args, &block) +            end                                               #   end +          end                                                 # end +        EOS +      else +        exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") -      module_eval(<<-EOS, file, line - 5) -        def #{prefix}#{method}(*args, &block)               # def customer_name(*args, &block) -          #{to}.__send__(#{method.inspect}, *args, &block)  #   client.__send__(:name, *args, &block) -        rescue NoMethodError                                # rescue NoMethodError -          if #{to}.nil?                                     #   if client.nil? -            #{on_nil}                                       #     return # depends on :allow_nil -          else                                              #   else -            raise                                           #     raise -          end                                               #   end -        end                                                 # end -      EOS +        module_eval(<<-EOS, file, line - 1) +          def #{method_prefix}#{method}(*args, &block)        # def customer_name(*args, &block) +            #{to}.__send__(:#{method}, *args, &block)         #   client.__send__(:name, *args, &block) +          rescue NoMethodError                                # rescue NoMethodError +            if #{to}.nil?                                     #   if client.nil? +              #{exception}                                    #     # add helpful message to the exception +            else                                              #   else +              raise                                           #     raise +            end                                               #   end +          end                                                 # end +        EOS +      end      end    end  end diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb new file mode 100644 index 0000000000..d1a0ee2f83 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb @@ -0,0 +1,64 @@ +require 'active_support/core_ext/string/inflections' + +#-- +# Allows code reuse in the methods below without polluting Module. +#++ +module QualifiedConstUtils +  def self.raise_if_absolute(path) +    raise NameError, "wrong constant name #$&" if path =~ /\A::[^:]+/ +  end + +  def self.names(path) +    path.split('::') +  end +end + +## +# Extends the API for constants to be able to deal with qualified names. Arguments +# are assumed to be relative to the receiver. +# +#-- +# Qualified names are required to be relative because we are extending existing +# methods that expect constant names, ie, relative paths of length 1. For example, +# Object.const_get("::String") raises NameError and so does qualified_const_get. +#++ +class Module +  if method(:const_defined?).arity == 1 +    def qualified_const_defined?(path) +      QualifiedConstUtils.raise_if_absolute(path) + +      QualifiedConstUtils.names(path).inject(self) do |mod, name| +        return unless mod.const_defined?(name) +        mod.const_get(name) +      end +      return true +    end +  else +    def qualified_const_defined?(path, search_parents=true) +      QualifiedConstUtils.raise_if_absolute(path) + +      QualifiedConstUtils.names(path).inject(self) do |mod, name| +        return unless mod.const_defined?(name, search_parents) +        mod.const_get(name) +      end +      return true +    end +  end + +  def qualified_const_get(path) +    QualifiedConstUtils.raise_if_absolute(path) + +    QualifiedConstUtils.names(path).inject(self) do |mod, name| +      mod.const_get(name) +    end +  end + +  def qualified_const_set(path, value) +    QualifiedConstUtils.raise_if_absolute(path) + +    const_name = path.demodulize +    mod_name = path.deconstantize +    mod = mod_name.empty? ? self : qualified_const_get(mod_name) +    mod.const_set(const_name, value) +  end +end diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb index 443d2c3d53..5d3d0e9851 100644 --- a/activesupport/lib/active_support/core_ext/module/reachable.rb +++ b/activesupport/lib/active_support/core_ext/module/reachable.rb @@ -3,8 +3,6 @@ require 'active_support/core_ext/string/inflections'  class Module    def reachable? #:nodoc: -    !anonymous? && name.constantize.equal?(self) -  rescue NameError -    false +    !anonymous? && name.safe_constantize.equal?(self)    end  end diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index 3f1540f685..5d5fcf00e0 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -7,7 +7,7 @@ class Object    # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work.    def to_query(key)      require 'cgi' unless defined?(CGI) && defined?(CGI::escape) -    "#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}" +    "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"    end  end diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 4797c93e63..e77a9da0ec 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -28,8 +28,6 @@ class Object    def try(*a, &b)      if a.empty? && block_given?        yield self -    elsif !a.empty? && !respond_to?(a.first) -      nil      else        __send__(*a, &b)      end diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb index 544e63132d..43134b4314 100644 --- a/activesupport/lib/active_support/core_ext/range/conversions.rb +++ b/activesupport/lib/active_support/core_ext/range/conversions.rb @@ -7,7 +7,7 @@ class Range    #    # ==== Example    # -  #   [1..100].to_formatted_s # => "1..100" +  #   (1..100).to_formatted_s # => "1..100"    def to_formatted_s(format = :default)      if formatter = RANGE_FORMATS[format]        formatter.call(first, last) diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 002688d6c0..1e57b586d9 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -9,14 +9,25 @@ require 'active_support/inflector/transliterate'  class String    # Returns the plural form of the word in the string.    # +  # If the optional parameter +count+ is specified, +  # the singular form will be returned if <tt>count == 1</tt>. +  # For any other value of +count+ the plural will be returned. +  # +  # ==== Examples    #   "post".pluralize             # => "posts"    #   "octopus".pluralize          # => "octopi"    #   "sheep".pluralize            # => "sheep"    #   "words".pluralize            # => "words"    #   "the blue mailman".pluralize # => "the blue mailmen"    #   "CamelOctopus".pluralize     # => "CamelOctopi" -  def pluralize -    ActiveSupport::Inflector.pluralize(self) +  #   "apple".pluralize(1)         # => "apple" +  #   "apple".pluralize(2)         # => "apples" +  def pluralize(count = nil) +    if count == 1 +      self +    else +      ActiveSupport::Inflector.pluralize(self) +    end    end    # The reverse of +pluralize+, returns the singular form of a word in a string. @@ -33,15 +44,28 @@ class String    # +constantize+ tries to find a declared constant with the name specified    # in the string. It raises a NameError when the name is not in CamelCase -  # or is not initialized. +  # or is not initialized.  See ActiveSupport::Inflector.constantize    #    # Examples -  #   "Module".constantize # => Module -  #   "Class".constantize  # => Class +  #   "Module".constantize  # => Module +  #   "Class".constantize   # => Class +  #   "blargle".constantize # => NameError: wrong constant name blargle    def constantize      ActiveSupport::Inflector.constantize(self)    end +  # +safe_constantize+ tries to find a declared constant with the name specified +  # in the string. It returns nil when the name is not in CamelCase +  # or is not initialized.  See ActiveSupport::Inflector.safe_constantize +  # +  # Examples +  #   "Module".safe_constantize  # => Module +  #   "Class".safe_constantize   # => Class +  #   "blargle".safe_constantize # => nil +  def safe_constantize +    ActiveSupport::Inflector.safe_constantize(self) +  end +    # By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize    # is set to <tt>:lower</tt> then camelize produces lowerCamelCase.    # @@ -93,10 +117,25 @@ class String    #    #   "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"    #   "Inflections".demodulize                                       # => "Inflections" +  # +  # See also +deconstantize+.    def demodulize      ActiveSupport::Inflector.demodulize(self)    end +  # Removes the rightmost segment from the constant expression in the string. +  # +  #   "Net::HTTP".deconstantize   # => "Net" +  #   "::Net::HTTP".deconstantize # => "::Net" +  #   "String".deconstantize      # => "" +  #   "::String".deconstantize    # => "" +  #   "".deconstantize            # => "" +  # +  # See also +demodulize+. +  def deconstantize +    ActiveSupport::Inflector.deconstantize(self) +  end +    # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.    #    # ==== Examples diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 6d6c4912bb..5d7f74bb65 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -20,7 +20,7 @@ class ERB        if s.html_safe?          s        else -        s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }.html_safe +        s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<").html_safe        end      end @@ -75,7 +75,7 @@ end  module ActiveSupport #:nodoc:    class SafeBuffer < String -    UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase"].freeze +    UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase", "prepend"].freeze      alias_method :original_concat, :concat      private :original_concat @@ -142,16 +142,18 @@ module ActiveSupport #:nodoc:      end      UNSAFE_STRING_METHODS.each do |unsafe_method| -      class_eval <<-EOT, __FILE__, __LINE__ -        def #{unsafe_method}(*args, &block)       # def gsub(*args, &block) -          to_str.#{unsafe_method}(*args, &block)  #   to_str.gsub(*args, &block) -        end                                       # end - -        def #{unsafe_method}!(*args)              # def gsub!(*args) -          @dirty = true                           #   @dirty = true -          super                                   #   super -        end                                       # end -      EOT +      if 'String'.respond_to?(unsafe_method) +        class_eval <<-EOT, __FILE__, __LINE__ + 1 +          def #{unsafe_method}(*args, &block)       # def capitalize(*args, &block) +            to_str.#{unsafe_method}(*args, &block)  #   to_str.capitalize(*args, &block) +          end                                       # end + +          def #{unsafe_method}!(*args)              # def capitalize!(*args) +            @dirty = true                           #   @dirty = true +            super                                   #   super +          end                                       # end +        EOT +      end      end      protected diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index c5fbbcf890..372dd69212 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,5 +1,6 @@  require 'active_support/duration'  require 'active_support/core_ext/time/zones' +require 'active_support/core_ext/time/conversions'  class Time    COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] @@ -8,7 +9,7 @@ class Time    class << self      # Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances      def ===(other) -      other.is_a?(::Time) +      super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone))      end      # Return the number of days in the given month. diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index d9d5e02778..49ac18d245 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -55,9 +55,31 @@ class Time      utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)    end +  # Converts a Time object to a Date, dropping hour, minute, and second precision. +  # +  #   my_time = Time.now  # => Mon Nov 12 22:59:51 -0500 2007 +  #   my_time.to_date     # => Mon, 12 Nov 2007 +  # +  #   your_time = Time.parse("1/13/2009 1:13:03 P.M.")  # => Tue Jan 13 13:13:03 -0500 2009 +  #   your_time.to_date                                 # => Tue, 13 Jan 2009 +  def to_date +    ::Date.new(year, month, day) +  end unless method_defined?(:to_date) +    # A method to keep Time, Date and DateTime instances interchangeable on conversions.    # In this case, it simply returns +self+.    def to_time      self    end unless method_defined?(:to_time) + +  # Converts a Time instance to a Ruby DateTime instance, preserving UTC offset. +  # +  #   my_time = Time.now    # => Mon Nov 12 23:04:21 -0500 2007 +  #   my_time.to_datetime   # => Mon, 12 Nov 2007 23:04:21 -0500 +  # +  #   your_time = Time.parse("1/13/2009 1:13:03 P.M.")  # => Tue Jan 13 13:13:03 -0500 2009 +  #   your_time.to_datetime                             # => Tue, 13 Jan 2009 13:13:03 -0500 +  def to_datetime +    ::DateTime.civil(year, month, day, hour, min, sec, Rational(utc_offset, 86400)) +  end unless method_defined?(:to_datetime)  end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 3f6c93e860..b3ac271778 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -5,6 +5,7 @@ require 'active_support/core_ext/module/aliasing'  require 'active_support/core_ext/module/attribute_accessors'  require 'active_support/core_ext/module/introspection'  require 'active_support/core_ext/module/anonymous' +require 'active_support/core_ext/module/qualified_const'  require 'active_support/core_ext/object/blank'  require 'active_support/core_ext/load_error'  require 'active_support/core_ext/name_error' @@ -229,11 +230,15 @@ module ActiveSupport #:nodoc:        end        def load(file, *) -        load_dependency(file) { super } +        result = false +        load_dependency(file) { result = super } +        result        end        def require(file, *) -        load_dependency(file) { super } +        result = false +        load_dependency(file) { result = super } +        result        end        # Mark the given constant as unloadable. Unloadable constants are removed each @@ -353,12 +358,13 @@ module ActiveSupport #:nodoc:      end      # Is the provided constant path defined? -    def qualified_const_defined?(path) -      names = path.sub(/^::/, '').to_s.split('::') - -      names.inject(Object) do |mod, name| -        return false unless local_const_defined?(mod, name) -        mod.const_get name +    if Module.method(:const_defined?).arity == 1 +      def qualified_const_defined?(path) +        Object.qualified_const_defined?(path.sub(/^::/, '')) +      end +    else +      def qualified_const_defined?(path) +        Object.qualified_const_defined?(path.sub(/^::/, ''), false)        end      end @@ -417,7 +423,8 @@ module ActiveSupport #:nodoc:      end      def load_once_path?(path) -      autoload_once_paths.any? { |base| path.starts_with? base } +      # to_s works around a ruby1.9 issue where #starts_with?(Pathname) will always return false +      autoload_once_paths.any? { |base| path.starts_with? base.to_s }      end      # Attempt to autoload the provided module name by searching for a directory diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index 62f9c9aa2e..9651f02c73 100644 --- a/activesupport/lib/active_support/gzip.rb +++ b/activesupport/lib/active_support/gzip.rb @@ -1,5 +1,6 @@  require 'zlib'  require 'stringio' +require 'active_support/core_ext/string/encoding'  module ActiveSupport    # A convenient wrapper for the zlib standard library that allows compression/decompression of strings with gzip. diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 59ffd24698..636f019cd5 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -112,7 +112,7 @@ module ActiveSupport        end      end -    # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash +    # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash.      # Does not overwrite the existing hash.      def merge(hash)        self.dup.update(hash) diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index a25e951080..4c59fe9ac9 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -1,5 +1,4 @@  require "active_support" -require "rails"  require "active_support/file_update_checker"  require "active_support/core_ext/array/wrap" @@ -38,6 +37,8 @@ module I18n    protected +    @i18n_inited = false +      # Setup i18n configuration      def self.initialize_i18n(app)        return if @i18n_inited diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index 06ceccdb22..daf2a1e1d9 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -54,6 +54,7 @@ module ActiveSupport      inflect.irregular('sex', 'sexes')      inflect.irregular('move', 'moves')      inflect.irregular('cow', 'kine') +    inflect.irregular('zombie', 'zombies')      inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))    end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 423b5abd20..e76ee60dd7 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -160,13 +160,32 @@ module ActiveSupport        underscored_word.gsub(/_/, '-')      end -    # Removes the module part from the expression in the string. +    # Removes the module part from the expression in the string:      # -    # Examples:      #   "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"      #   "Inflections".demodulize                                       # => "Inflections" -    def demodulize(class_name_in_module) -      class_name_in_module.to_s.gsub(/^.*::/, '') +    # +    # See also +deconstantize+. +    def demodulize(path) +      path = path.to_s +      if i = path.rindex('::') +        path[(i+2)..-1] +      else +        path +      end +    end + +    # Removes the rightmost segment from the constant expression in the string: +    # +    #   "Net::HTTP".deconstantize   # => "Net" +    #   "::Net::HTTP".deconstantize # => "::Net" +    #   "String".deconstantize      # => "" +    #   "::String".deconstantize    # => "" +    #   "".deconstantize            # => "" +    # +    # See also +demodulize+. +    def deconstantize(path) +      path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename      end      # Creates a foreign key name from a class name. @@ -224,6 +243,39 @@ module ActiveSupport        end      end +    # Tries to find a constant with the name specified in the argument string: +    # +    #   "Module".safe_constantize     # => Module +    #   "Test::Unit".safe_constantize # => Test::Unit +    # +    # The name is assumed to be the one of a top-level constant, no matter whether +    # it starts with "::" or not. No lexical context is taken into account: +    # +    #   C = 'outside' +    #   module M +    #     C = 'inside' +    #     C                    # => 'inside' +    #     "C".safe_constantize # => 'outside', same as ::C +    #   end +    # +    # nil is returned when the name is not in CamelCase or the constant (or part of it) is +    # unknown. +    # +    #   "blargle".safe_constantize  # => nil +    #   "UnknownModule".safe_constantize  # => nil +    #   "UnknownModule::Foo::Bar".safe_constantize  # => nil +    # +    def safe_constantize(camel_cased_word) +      begin +        constantize(camel_cased_word) +      rescue NameError => e +        raise unless e.message =~ /uninitialized constant #{const_regexp(camel_cased_word)}$/ || +          e.name.to_s == camel_cased_word.to_s +      rescue ArgumentError => e +        raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ +      end +    end +      # Turns a number into an ordinal string used to denote the position in an      # ordered sequence such as 1st, 2nd, 3rd, 4th.      # @@ -246,5 +298,18 @@ module ActiveSupport          end        end      end + +    private + +    # Mount a regular expression that will match part by part of the constant. +    # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)? +    def const_regexp(camel_cased_word) #:nodoc: +      parts = camel_cased_word.split("::") +      last  = parts.pop + +      parts.reverse.inject(last) do |acc, part| +        part.empty? ? acc : "#{part}(::#{acc})?" +      end +    end    end  end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 67698c1cff..469ae69258 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -38,7 +38,7 @@ module ActiveSupport          attr_reader :options          def initialize(options = nil) -          @options = options +          @options = options || {}            @seen = Set.new          end @@ -59,7 +59,7 @@ module ActiveSupport          def options_for(value)            if value.is_a?(Array) || value.is_a?(Hash)              # hashes and arrays need to get encoder in the options, so that they can detect circular references -            (options || {}).merge(:encoder => self) +            options.merge(:encoder => self)            else              options            end diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 3e54134c5c..dcfcf0b63c 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -18,8 +18,8 @@ module ActiveSupport      #       Developer.all      #       wait      #       assert_equal 1, @logger.logged(:debug).size -    #       assert_match /Developer Load/, @logger.logged(:debug).last -    #       assert_match /SELECT \* FROM "developers"/, @logger.logged(:debug).last +    #       assert_match(/Developer Load/, @logger.logged(:debug).last) +    #       assert_match(/SELECT \* FROM "developers"/, @logger.logged(:debug).last)      #     end      #   end      # diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 4f7cd12d48..e14386a85d 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -13,9 +13,15 @@ module ActiveSupport      class InvalidMessage < StandardError; end      OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError -    def initialize(secret, cipher = 'aes-256-cbc') +    def initialize(secret, options = {}) +      unless options.is_a?(Hash) +        ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :cipher => 'algorithm' to specify the cipher algorithm." +        options = { :cipher => options } +      end +              @secret = secret -      @cipher = cipher +      @cipher = options[:cipher] || 'aes-256-cbc' +      @serializer = options[:serializer] || Marshal      end      def encrypt(value) @@ -27,7 +33,7 @@ module ActiveSupport        cipher.key = @secret        cipher.iv  = iv -      encrypted_data = cipher.update(Marshal.dump(value)) +      encrypted_data = cipher.update(@serializer.dump(value))        encrypted_data << cipher.final        [encrypted_data, iv].map {|v| ActiveSupport::Base64.encode64s(v)}.join("--") @@ -44,7 +50,7 @@ module ActiveSupport        decrypted_data = cipher.update(encrypted_data)        decrypted_data << cipher.final -      Marshal.load(decrypted_data) +      @serializer.load(decrypted_data)      rescue OpenSSLCipherError, TypeError        raise InvalidMessage      end diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 8f3946325a..9d7c81142a 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -18,12 +18,23 @@ module ActiveSupport    #     self.current_user = User.find(id)    #   end    # +  # By default it uses Marshal to serialize the message. If you want to use another  +  # serialization method, you can set the serializer attribute to something that responds +  # to dump and load, e.g.: +  # +  #   @verifier.serializer = YAML    class MessageVerifier      class InvalidSignature < StandardError; end -    def initialize(secret, digest = 'SHA1') +    def initialize(secret, options = {}) +      unless options.is_a?(Hash) +        ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :digest => 'algorithm' to specify the digest algorithm." +        options = { :digest => options } +      end +        @secret = secret -      @digest = digest +      @digest = options[:digest] || 'SHA1' +      @serializer = options[:serializer] || Marshal      end      def verify(signed_message) @@ -31,14 +42,14 @@ module ActiveSupport        data, digest = signed_message.split("--")        if data.present? && digest.present? && secure_compare(digest, generate_digest(data)) -        Marshal.load(ActiveSupport::Base64.decode64(data)) +        @serializer.load(ActiveSupport::Base64.decode64(data))        else          raise InvalidSignature        end      end      def generate(value) -      data = ActiveSupport::Base64.encode64s(Marshal.dump(value)) +      data = ActiveSupport::Base64.encode64s(@serializer.dump(value))        "#{data}--#{generate_digest(data)}"      end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 77696eb1db..b5a70d5933 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/module/delegation' -  module ActiveSupport    # Notifications provides an instrumentation API for Ruby. To instrument an    # action in Ruby you just need to do: diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 7f70628933..0264133581 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -47,6 +47,11 @@ module ActiveSupport        self      end +    # Returns true to make sure that this hash is extractable via <tt>Array#extract_options!</tt> +    def extractable_options? +      true +    end +      # Hash is ordered in Ruby 1.9!      if RUBY_VERSION < '1.9' diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 04df2ea562..1638512af0 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -1,5 +1,4 @@  require "active_support" -require "rails"  require "active_support/i18n_railtie"  module ActiveSupport diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb new file mode 100644 index 0000000000..a59fc26d5d --- /dev/null +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -0,0 +1,63 @@ +require 'active_support/core_ext/object/blank' +require 'logger' + +module ActiveSupport +  # Wraps any standard Logger class to provide tagging capabilities. Examples: +  # +  #   Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) +  #   Logger.tagged("BCX") { Logger.info "Stuff" }                            # Logs "[BCX] Stuff" +  #   Logger.tagged("BCX", "Jason") { Logger.info "Stuff" }                   # Logs "[BCX] [Jason] Stuff" +  #   Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff" +  # +  # This is used by the default Rails.logger as configured by Railties to make it easy to stamp log lines +  # with subdomains, request ids, and anything else to aid debugging of multi-user production applications. +  class TaggedLogging +    def initialize(logger) +      @logger = logger +      @tags   = Hash.new { |h,k| h[k] = [] } +    end + +    def tagged(*new_tags) +      tags     = current_tags +      new_tags = Array.wrap(new_tags).flatten.reject(&:blank?) +      tags.concat new_tags +      yield +    ensure +      new_tags.size.times { tags.pop } +    end + +    def add(severity, message = nil, progname = nil, &block) +      @logger.add(severity, "#{tags_text}#{message}", progname, &block) +    end + +    %w( fatal error warn info debug unkown ).each do |severity| +      eval <<-EOM, nil, __FILE__, __LINE__ + 1 +        def #{severity}(progname = nil, &block) +          add(Logger::#{severity.upcase}, progname, &block) +        end +      EOM +    end + +    def flush(*args) +      @tags.delete(Thread.current) +      @logger.flush(*args) if @logger.respond_to?(:flush) +    end + +    def method_missing(method, *args) +      @logger.send(method, *args) +    end + +    protected + +    def tags_text +      tags = current_tags +      if tags.any? +        tags.collect { |tag| "[#{tag}]" }.join(" ") + " " +      end +    end + +    def current_tags +      @tags[Thread.current] +    end +  end +end diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 3864b1f5a6..f3629ada5b 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -45,15 +45,17 @@ module ActiveSupport        #     post :delete, :id => ...        #   end        def assert_difference(expression, difference = 1, message = nil, &block) -        exps = Array.wrap(expression).map { |e| +        expressions = Array.wrap expression + +        exps = expressions.map { |e|            e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }          }          before = exps.map { |e| e.call }          yield -        exps.each_with_index do |e, i| -          error  = "#{e.inspect} didn't change by #{difference}" +        expressions.zip(exps).each_with_index do |(code, e), i| +          error  = "#{code.inspect} didn't change by #{difference}"            error  = "#{message}.\n#{error}" if message            assert_equal(before[i] + difference, e.call, error)          end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index ec2c717942..63279d0e6d 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -12,7 +12,7 @@ module ActiveSupport    #    #   Time.zone = 'Eastern Time (US & Canada)'        # => 'Eastern Time (US & Canada)'    #   Time.zone.local(2007, 2, 10, 15, 30, 45)        # => Sat, 10 Feb 2007 15:30:45 EST -05:00 -  #   Time.zone.parse('2007-02-01 15:30:45')          # => Sat, 10 Feb 2007 15:30:45 EST -05:00 +  #   Time.zone.parse('2007-02-10 15:30:45')          # => Sat, 10 Feb 2007 15:30:45 EST -05:00    #   Time.zone.at(1170361845)                        # => Sat, 10 Feb 2007 15:30:45 EST -05:00    #   Time.zone.now                                   # => Sun, 18 May 2008 13:07:55 EDT -04:00    #   Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone  # => Sat, 10 Feb 2007 15:30:45 EST -05:00 diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 728921a069..35f400f9df 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -195,12 +195,8 @@ module ActiveSupport      # (GMT). Seconds were chosen as the offset unit because that is the unit that      # Ruby uses to represent time zone offsets (see Time#utc_offset).      def initialize(name, utc_offset = nil, tzinfo = nil) -      begin -        require 'tzinfo' -      rescue LoadError => e -        $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install" -        raise e -      end +      self.class.send(:require_tzinfo) +        @name = name        @utc_offset = utc_offset        @tzinfo = tzinfo || TimeZone.find_tzinfo(name) @@ -337,7 +333,12 @@ module ActiveSupport        end        def zones_map -        @zones_map ||= Hash[MAPPING.map { |place, _| [place, create(place)] }] +        @zones_map ||= begin +          new_zones_names = MAPPING.keys - lazy_zones_map.keys +          new_zones       = Hash[new_zones_names.map { |place| [place, create(place)] }] + +          lazy_zones_map.merge(new_zones) +        end        end        # Locate a specific time zone object. If the argument is a string, it @@ -349,7 +350,7 @@ module ActiveSupport          case arg            when String            begin -            zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset } +            lazy_zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }            rescue TZInfo::InvalidTimezoneIdentifier              nil            end @@ -367,11 +368,28 @@ module ActiveSupport          @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }        end +      protected + +        def require_tzinfo +          require 'tzinfo' unless defined?(::TZInfo) +        rescue LoadError +          $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install" +          raise +        end +        private          def lookup(name)            (tzinfo = find_tzinfo(name)) && create(tzinfo.name.freeze)          end + +        def lazy_zones_map +          require_tzinfo + +          @lazy_zones_map ||= Hash.new do |hash, place| +            hash[place] = create(place) if MAPPING.has_key?(place) +          end +        end      end    end  end  | 
