diff options
Diffstat (limited to 'activesupport/lib')
119 files changed, 1634 insertions, 2303 deletions
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 07f7c5d924..e8f148b972 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -38,10 +38,12 @@ end require "active_support/dependencies/autoload" require "active_support/version" +require "active_support/logger" module ActiveSupport extend ActiveSupport::Autoload + autoload :Concern autoload :DescendantsTracker autoload :FileUpdateChecker autoload :LogSubscriber @@ -53,16 +55,13 @@ module ActiveSupport autoload :Base64 autoload :BasicObject autoload :Benchmarkable - autoload :BufferedLogger autoload :Cache autoload :Callbacks - autoload :Concern autoload :Configurable autoload :Deprecation autoload :Gzip autoload :Inflector autoload :JSON - autoload :Memoizable autoload :MessageEncryptor autoload :MessageVerifier autoload :Multibyte @@ -71,6 +70,7 @@ module ActiveSupport autoload :OrderedOptions autoload :Rescuable autoload :StringInquirer + autoload :TaggedLogging autoload :XmlMini end diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 0e6bc30fa2..8f8deb9692 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -1,12 +1,12 @@ module ActiveSupport - # Many backtraces include too much information that's not relevant for the context. This makes it hard to find the signal - # in the backtrace and adds debugging time. With a BacktraceCleaner, you can setup filters and silencers for your particular - # context, so only the relevant lines are included. + # Backtraces often include many lines that are not relevant for the context under review. This makes it hard to find the + # signal amongst the backtrace noise, and adds debugging time. With a BacktraceCleaner, filters and silencers are used to + # remove the noisy lines, so that only the most relevant lines remain. # - # If you need to reconfigure an existing BacktraceCleaner, like the one in Rails, to show as much as possible, you can always - # call BacktraceCleaner#remove_silencers! Also, if you need to reconfigure an existing BacktraceCleaner so that it does not - # filter or modify the paths of any lines of the backtrace, you can call BacktraceCleaner#remove_filters! These two methods - # will give you a completely untouched backtrace. + # Filters are used to modify lines of data, while silencers are used to remove lines entirely. The typical filter use case + # is to remove lengthy path information from the start of each line, and view file paths relevant to the app directory + # instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the + # backtrace, so that you can focus on the rest. # # ==== Example: # @@ -15,13 +15,18 @@ module ActiveSupport # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems # + # To reconfigure an existing BacktraceCleaner (like the default one in Rails) and show as much data as possible, you can + # always call <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the backtrace to a pristine state. If you + # need to reconfigure an existing BacktraceCleaner so that it does not filter or modify the paths of any lines of the + # backtrace, you can call BacktraceCleaner#remove_filters! These two methods will give you a completely untouched backtrace. + # # Inspired by the Quiet Backtrace gem by Thoughtbot. class BacktraceCleaner def initialize @filters, @silencers = [], [] end - # Returns the backtrace after all filters and silencers has been run against it. Filters run first, then silencers. + # Returns the backtrace after all filters and silencers have been run against it. Filters run first, then silencers. def clean(backtrace, kind = :silent) filtered = filter(backtrace) @@ -45,8 +50,8 @@ module ActiveSupport @filters << block end - # Adds a silencer from the block provided. If the silencer returns true for a given line, it'll be excluded from the - # clean backtrace. + # Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from + # the clean backtrace. # # Example: # @@ -57,7 +62,7 @@ module ActiveSupport end # Will remove all silencers, but leave in the filters. This is useful if your context of debugging suddenly expands as - # you suspect a bug in the libraries you use. + # you suspect a bug in one of the libraries you use. def remove_silencers! @silencers = [] end diff --git a/activesupport/lib/active_support/base64.rb b/activesupport/lib/active_support/base64.rb index 35014cb3d5..41a1a3469d 100644 --- a/activesupport/lib/active_support/base64.rb +++ b/activesupport/lib/active_support/base64.rb @@ -1,42 +1,18 @@ -begin - require 'base64' -rescue LoadError -end +require 'base64' module ActiveSupport - if defined? ::Base64 - Base64 = ::Base64 - else - # Base64 provides utility methods for encoding and de-coding binary data - # using a base 64 representation. A base 64 representation of binary data - # consists entirely of printable US-ASCII characters. The Base64 module - # is included in Ruby 1.8, but has been removed in Ruby 1.9. - module Base64 - # Encodes a string to its base 64 representation. Each 60 characters of - # output is separated by a newline character. - # - # ActiveSupport::Base64.encode64("Original unencoded string") - # # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==\n" - def self.encode64(data) - [data].pack("m") - end - - # Decodes a base 64 encoded string to its original representation. - # - # ActiveSupport::Base64.decode64("T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==") - # # => "Original unencoded string" - def self.decode64(data) - data.unpack("m").first - end - end - end + Base64 = ::Base64 + # *DEPRECATED*: Use +Base64.strict_encode64+ instead. + # # Encodes the value as base64 without the newline breaks. This makes the base64 encoding readily usable as URL parameters # or memcache keys without further processing. # # ActiveSupport::Base64.encode64s("Original unencoded string") # # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==" def Base64.encode64s(value) - encode64(value).gsub(/\n/, '') + ActiveSupport::Deprecation.warn "encode64s " \ + "is deprecated. Use Base64.strict_encode64 instead", caller + strict_encode64(value) end end diff --git a/activesupport/lib/active_support/basic_object.rb b/activesupport/lib/active_support/basic_object.rb index 3b5277c205..c3c7ab0112 100644 --- a/activesupport/lib/active_support/basic_object.rb +++ b/activesupport/lib/active_support/basic_object.rb @@ -1,21 +1,14 @@ module ActiveSupport - if defined? ::BasicObject - # A class with no predefined methods that behaves similarly to Builder's - # BlankSlate. Used for proxy classes. - class BasicObject < ::BasicObject - undef_method :== - undef_method :equal? + # A class with no predefined methods that behaves similarly to Builder's + # BlankSlate. Used for proxy classes. + class BasicObject < ::BasicObject + undef_method :== + undef_method :equal? - # Let ActiveSupport::BasicObject at least raise exceptions. - def raise(*args) - ::Object.send(:raise, *args) - end - end - else - class BasicObject #:nodoc: - instance_methods.each do |m| - undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$)/ - end + # Let ActiveSupport::BasicObject at least raise exceptions. + def raise(*args) + ::Object.send(:raise, *args) end end + end diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index e303eaa5ce..cc94041a1d 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -3,28 +3,28 @@ require 'active_support/core_ext/hash/keys' module ActiveSupport module Benchmarkable - # Allows you to measure the execution time of a block - # in a template and records the result to the log. Wrap this block around - # expensive operations or possible bottlenecks to get a time reading - # for the operation. For example, let's say you thought your file - # processing method was taking too long; you could wrap it in a benchmark block. + # Allows you to measure the execution time of a block in a template and records the result to + # the log. Wrap this block around expensive operations or possible bottlenecks to get a time + # reading for the operation. For example, let's say you thought your file processing method + # was taking too long; you could wrap it in a benchmark block. # # <% benchmark "Process data files" do %> # <%= expensive_files_operation %> # <% end %> # - # That would add something like "Process data files (345.2ms)" to the log, - # which you can then use to compare timings when optimizing your code. + # That would add something like "Process data files (345.2ms)" to the log, which you can then + # use to compare timings when optimizing your code. # - # You may give an optional logger level as the :level option. - # (:debug, :info, :warn, :error); the default value is :info. + # You may give an optional logger level (:debug, :info, :warn, :error) as the :level option. + # The default logger level value is :info. # # <% benchmark "Low-level files", :level => :debug do %> # <%= lowlevel_files_operation %> # <% end %> # - # Finally, you can pass true as the third argument to silence all log activity - # inside the block. This is great for boiling down a noisy block to just a single statement: + # Finally, you can pass true as the third argument to silence all log activity (other than the + # timing information) from inside the block. This is great for boiling down a noisy block to + # just a single statement that produces one log line: # # <% benchmark "Process data files", :level => :info, :silence => true do %> # <%= expensive_and_chatty_files_operation %> diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index 26412cd7f4..36e29644c6 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -1,137 +1,10 @@ require 'thread' require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/deprecation' +require 'active_support/logger' +require 'fileutils' module ActiveSupport - # Inspired by the buffered logger idea by Ezra - class BufferedLogger - module Severity - DEBUG = 0 - INFO = 1 - WARN = 2 - ERROR = 3 - FATAL = 4 - UNKNOWN = 5 - end - include Severity - - MAX_BUFFER_SIZE = 1000 - - ## - # :singleton-method: - # Set to false to disable the silencer - cattr_accessor :silencer - self.silencer = true - - # Silences the logger for the duration of the block. - def silence(temporary_level = ERROR) - if silencer - begin - old_logger_level, self.level = level, temporary_level - yield self - ensure - self.level = old_logger_level - end - else - yield self - end - end - - attr_accessor :level - attr_reader :auto_flushing - - def initialize(log, level = DEBUG) - @level = level - @buffer = Hash.new { |h,k| h[k] = [] } - @auto_flushing = 1 - @guard = Mutex.new - - if log.respond_to?(:write) - @log = log - elsif File.exist?(log) - @log = open_log(log, (File::WRONLY | File::APPEND)) - else - FileUtils.mkdir_p(File.dirname(log)) - @log = open_log(log, (File::WRONLY | File::APPEND | File::CREAT)) - end - end - - def open_log(log, mode) - open(log, mode).tap do |open_log| - open_log.set_encoding(Encoding::BINARY) if open_log.respond_to?(:set_encoding) - open_log.sync = true - end - end - - def add(severity, message = nil, progname = nil, &block) - 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. - message = "#{message}\n" unless message[-1] == ?\n - buffer << message - auto_flush - message - end - - # Dynamically add methods such as: - # def info - # def warn - # def debug - Severity.constants.each do |severity| - class_eval <<-EOT, __FILE__, __LINE__ + 1 - def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block) - add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block) - end # end - - def #{severity.downcase}? # def debug? - #{severity} >= @level # DEBUG >= @level - end # end - EOT - end - - # Set the auto-flush period. Set to true to flush after every log message, - # to an integer to flush every N messages, or to false, nil, or zero to - # never auto-flush. If you turn auto-flushing off, be sure to regularly - # flush the log yourself -- it will eat up memory until you do. - def auto_flushing=(period) - @auto_flushing = - case period - when true; 1 - when false, nil, 0; MAX_BUFFER_SIZE - when Integer; period - else raise ArgumentError, "Unrecognized auto_flushing period: #{period.inspect}" - end - end - - def flush - @guard.synchronize do - buffer.each do |content| - @log.write(content) - end - - # 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 - end - end - - def close - flush - @log.close if @log.respond_to?(:close) - @log = nil - end - - protected - def auto_flush - flush if buffer.size >= @auto_flushing - end - - def buffer - @buffer[Thread.current] - end - - def clear_buffer - @buffer.delete(Thread.current) - end - end + BufferedLogger = ActiveSupport::Deprecation::DeprecatedConstantProxy.new( + 'BufferedLogger', '::ActiveSupport::Logger') end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 10c457bb1d..7d032ca984 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -16,8 +16,7 @@ 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' + autoload :NullStore, 'active_support/cache/null_store' # These options mean something to all cache implementations. Individual cache # implementations may support additional options. @@ -27,75 +26,75 @@ module ActiveSupport autoload :LocalCache, 'active_support/cache/strategy/local_cache' end - # Creates a new CacheStore object according to the given options. - # - # If no arguments are passed to this method, then a new - # ActiveSupport::Cache::MemoryStore object will be returned. - # - # If you pass a Symbol as the first argument, then a corresponding cache - # store class under the ActiveSupport::Cache namespace will be created. - # For example: - # - # ActiveSupport::Cache.lookup_store(:memory_store) - # # => returns a new ActiveSupport::Cache::MemoryStore object - # - # ActiveSupport::Cache.lookup_store(:mem_cache_store) - # # => returns a new ActiveSupport::Cache::MemCacheStore object - # - # Any additional arguments will be passed to the corresponding cache store - # class's constructor: - # - # ActiveSupport::Cache.lookup_store(:file_store, "/tmp/cache") - # # => same as: ActiveSupport::Cache::FileStore.new("/tmp/cache") - # - # If the first argument is not a Symbol, then it will simply be returned: - # - # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new) - # # => returns MyOwnCacheStore.new - def self.lookup_store(*store_option) - store, *parameters = *Array.wrap(store_option).flatten - - case store - when Symbol - store_class_name = store.to_s.camelize - store_class = - begin - require "active_support/cache/#{store}" - rescue LoadError => e - raise "Could not find cache store adapter for #{store} (#{e})" - else - ActiveSupport::Cache.const_get(store_class_name) - end - store_class.new(*parameters) - when nil - ActiveSupport::Cache::MemoryStore.new - else - store + class << self + # Creates a new CacheStore object according to the given options. + # + # If no arguments are passed to this method, then a new + # ActiveSupport::Cache::MemoryStore object will be returned. + # + # If you pass a Symbol as the first argument, then a corresponding cache + # store class under the ActiveSupport::Cache namespace will be created. + # For example: + # + # ActiveSupport::Cache.lookup_store(:memory_store) + # # => returns a new ActiveSupport::Cache::MemoryStore object + # + # ActiveSupport::Cache.lookup_store(:mem_cache_store) + # # => returns a new ActiveSupport::Cache::MemCacheStore object + # + # Any additional arguments will be passed to the corresponding cache store + # class's constructor: + # + # ActiveSupport::Cache.lookup_store(:file_store, "/tmp/cache") + # # => same as: ActiveSupport::Cache::FileStore.new("/tmp/cache") + # + # If the first argument is not a Symbol, then it will simply be returned: + # + # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new) + # # => returns MyOwnCacheStore.new + def lookup_store(*store_option) + store, *parameters = *Array.wrap(store_option).flatten + + case store + when Symbol + store_class_name = store.to_s.camelize + store_class = + begin + require "active_support/cache/#{store}" + rescue LoadError => e + raise "Could not find cache store adapter for #{store} (#{e})" + else + ActiveSupport::Cache.const_get(store_class_name) + end + store_class.new(*parameters) + when nil + ActiveSupport::Cache::MemoryStore.new + else + store + end end - end - def self.expand_cache_key(key, namespace = nil) - expanded_cache_key = namespace ? "#{namespace}/" : "" + def expand_cache_key(key, namespace = nil) + expanded_cache_key = namespace ? "#{namespace}/" : "" + + prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] + if prefix + expanded_cache_key << "#{prefix}/" + end - prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] - if prefix - expanded_cache_key << "#{prefix}/" + expanded_cache_key << retrieve_cache_key(key) + expanded_cache_key end - expanded_cache_key << - if key.respond_to?(:cache_key) - key.cache_key - elsif key.is_a?(Array) - if key.size > 1 - key.collect { |element| expand_cache_key(element) }.to_param - else - key.first.to_param - end - elsif key - key.to_param - end.to_s + private - expanded_cache_key + def retrieve_cache_key(key) + case + when key.respond_to?(:cache_key) then key.cache_key + when key.is_a?(Array) then ['Array', *key.map { |element| retrieve_cache_key(element) }].to_param + else key.to_param + end.to_s + end end # An abstract cache store class. There are multiple cache store @@ -116,31 +115,32 @@ module ActiveSupport # cache.read("city") # => "Duckburgh" # # Keys are always translated into Strings and are case sensitive. When an - # object is specified as a key, its +cache_key+ method will be called if it - # is defined. Otherwise, the +to_param+ method will be called. Hashes and - # Arrays can be used as keys. The elements will be delimited by slashes - # and Hashes elements will be sorted by key so they are consistent. + # object is specified as a key and has a +cache_key+ method defined, this + # method will be called to define the key. Otherwise, the +to_param+ + # method will be called. Hashes and Arrays can also be used as keys. The + # elements will be delimited by slashes, and the elements within a Hash + # will be sorted by key so they are consistent. # # cache.read("city") == cache.read(:city) # => true # # Nil values can be cached. # - # If your cache is on a shared infrastructure, you can define a namespace for - # your cache entries. If a namespace is defined, it will be prefixed on to every - # key. The namespace can be either a static value or a Proc. If it is a Proc, it - # will be invoked when each key is evaluated so that you can use application logic - # to invalidate keys. + # If your cache is on a shared infrastructure, you can define a namespace + # for your cache entries. If a namespace is defined, it will be prefixed on + # to every key. The namespace can be either a static value or a Proc. If it + # is a Proc, it will be invoked when each key is evaluated so that you can + # use application logic to invalidate keys. # # cache.namespace = lambda { @last_mod_time } # Set the namespace to a variable # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace # # - # Caches can also store values in a compressed format to save space and reduce - # time spent sending data. Since there is some overhead, values must be large - # enough to warrant compression. To turn on compression either pass - # <tt>:compress => true</tt> in the initializer or to +fetch+ or +write+. - # To specify the threshold at which to compress values, set - # <tt>:compress_threshold</tt>. The default threshold is 32K. + # Caches can also store values in a compressed format to save space and + # reduce time spent sending data. Since there is overhead, values must be + # 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 16K. class Store cattr_accessor :logger, :instance_writer => true @@ -150,7 +150,7 @@ module ActiveSupport # Create a new cache. The options will be passed to any write method calls except # for :namespace which can be used to set the global namespace for the cache. - def initialize (options = nil) + def initialize(options = nil) @options = options ? options.dup : {} end @@ -180,11 +180,11 @@ module ActiveSupport # Fetches data from the cache, using the given key. If there is data in # the cache with the given key, then that data is returned. # - # If there is no such data in the cache (a cache miss occurred), - # then nil will be returned. However, if a block has been passed, then - # that block will be run in the event of a cache miss. The return value - # of the block will be written to the cache under the given cache key, - # and that return value will be returned. + # If there is no such data in the cache (a cache miss), then nil will be + # returned. However, if a block has been passed, that block will be run + # in the event of a cache miss. The return value of the block will be + # written to the cache under the given cache key, and that return value + # will be returned. # # cache.write("today", "Monday") # cache.fetch("today") # => "Monday" @@ -205,10 +205,11 @@ module ActiveSupport # in a compressed format. # # - # Setting <tt>:expires_in</tt> will set an expiration time on the cache. All caches - # support auto expiring content after a specified number of seconds. This value can - # be specified as an option to the construction in which call all entries will be - # affected. Or it can be supplied to the +fetch+ or +write+ method for just one entry. + # Setting <tt>:expires_in</tt> will set an expiration time on the cache. + # All caches support auto-expiring content after a specified number of + # seconds. This value can be specified as an option to the constructor + # (in which case all entries will be affected), or it can be supplied to + # the +fetch+ or +write+ method to effect just one entry. # # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 5.minutes) # cache.write(key, value, :expires_in => 1.minute) # Set a lower value for one entry @@ -230,7 +231,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 @@ -345,7 +346,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 @@ -538,11 +539,11 @@ module ActiveSupport # Create an entry with internal attributes set. This method is intended to be # used by implementations that store cache entries in a native format instead # of as serialized Ruby objects. - def create (raw_value, created_at, options = {}) + def create(raw_value, created_at, options = {}) entry = new(nil) entry.instance_variable_set(:@value, raw_value) entry.instance_variable_set(:@created_at, created_at.to_f) - entry.instance_variable_set(:@compressed, !!options[:compressed]) + entry.instance_variable_set(:@compressed, options[:compressed]) entry.instance_variable_set(:@expires_in, options[:expires_in]) entry end @@ -555,15 +556,14 @@ module ActiveSupport @expires_in = options[:expires_in] @expires_in = @expires_in.to_f if @expires_in @created_at = Time.now.to_f - if value - if should_compress?(value, options) - @value = Zlib::Deflate.deflate(Marshal.dump(value)) + if value.nil? + @value = nil + else + @value = Marshal.dump(value) + if should_compress?(@value, options) + @value = Zlib::Deflate.deflate(@value) @compressed = true - else - @value = value end - else - @value = nil end end @@ -574,12 +574,11 @@ module ActiveSupport # Get the value stored in the cache. def value + # If the original value was exactly false @value is still true because + # it is marshalled and eventually compressed. Both operations yield + # strings. if @value - val = compressed? ? Marshal.load(Zlib::Inflate.inflate(@value)) : @value - unless val.frozen? - val.freeze rescue nil - end - val + Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value) end end @@ -612,21 +611,16 @@ module ActiveSupport def size if @value.nil? 0 - elsif @value.respond_to?(:bytesize) - @value.bytesize else - Marshal.dump(@value).bytesize + @value.bytesize end 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 c4da04e532..9460532af0 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -8,20 +8,22 @@ module ActiveSupport # A cache store implementation which stores everything on the filesystem. # # FileStore implements the Strategy::LocalCache strategy which implements - # an in memory cache inside of a block. + # an in-memory cache inside of a block. class FileStore < Store 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) - @cache_path = cache_path + @cache_path = cache_path.to_s extend Strategy::LocalCache 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/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 7ef1497ac2..e38a8387b4 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -11,7 +11,7 @@ require 'active_support/core_ext/string/encoding' module ActiveSupport module Cache # A cache store implementation which stores data in Memcached: - # http://www.danga.com/memcached/ + # http://memcached.org/ # # This is currently the most popular cache store for production websites. # @@ -21,7 +21,7 @@ module ActiveSupport # server goes down, then MemCacheStore will ignore it until it comes back up. # # MemCacheStore implements the Strategy::LocalCache strategy which implements - # an in memory cache inside of a block. + # an in-memory cache inside of a block. class MemCacheStore < Store module Response # :nodoc: STORED = "STORED\r\n" @@ -31,7 +31,7 @@ module ActiveSupport DELETED = "DELETED\r\n" end - ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/ + ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n def self.build_mem_cache(*addresses) addresses = addresses.flatten @@ -165,7 +165,7 @@ module ActiveSupport # characters properly. def escape_key(key) key = key.to_s.dup - key = key.force_encoding("BINARY") if key.encoding_aware? + key = key.force_encoding("BINARY") key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" } key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 key @@ -183,6 +183,14 @@ module ActiveSupport # Provide support for raw values in the local cache strategy. module LocalCacheWithRaw # :nodoc: protected + def read_entry(key, options) + entry = super + if options[:raw] && local_cache && entry + entry = deserialize_entry(entry.value) + end + entry + end + def write_entry(key, entry, options) # :nodoc: retval = super if options[:raw] && local_cache && retval diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb new file mode 100644 index 0000000000..4427eaafcd --- /dev/null +++ b/activesupport/lib/active_support/cache/null_store.rb @@ -0,0 +1,44 @@ +module ActiveSupport + module Cache + # A cache store implementation which doesn't actually store anything. Useful in + # development and test environments where you don't want caching turned on but + # need to go through the caching interface. + # + # This cache does implement the local cache strategy, so values will actually + # be cached inside blocks that utilize this strategy. See + # ActiveSupport::Cache::Strategy::LocalCache for more details. + class NullStore < Store + def initialize(options = nil) + super(options) + extend Strategy::LocalCache + end + + def clear(options = nil) + end + + def cleanup(options = nil) + end + + def increment(name, amount = 1, options = nil) + end + + def decrement(name, amount = 1, options = nil) + end + + def delete_matched(matcher, options = nil) + end + + protected + def read_entry(key, options) # :nodoc: + end + + def write_entry(key, entry, options) # :nodoc: + true + end + + def delete_entry(key, options) # :nodoc: + false + end + end + end +end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 0649a058aa..db5f228a70 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -4,9 +4,9 @@ require 'active_support/core_ext/string/inflections' module ActiveSupport module Cache module Strategy - # Caches that implement LocalCache will be backed by an in memory cache for the + # Caches that implement LocalCache will be backed by an in-memory cache for the # duration of a block. Repeated calls to the cache for the same key will hit the - # in memory cache for faster access. + # in-memory cache for faster access. module LocalCache # Simple memory backed cache. This cache is not thread safe and is intended only # for serving as a temporary memory cache for a single thread. diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 656cba625c..df3aeb6b8a 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -77,8 +77,16 @@ module ActiveSupport # save # end # - def run_callbacks(kind, *args, &block) - send("_run_#{kind}_callbacks", *args, &block) + def run_callbacks(kind, key = nil, &block) + self.class.__run_callbacks(key, kind, self, &block) + end + + private + + # A hook invoked everytime a before callback is halted. + # This can be overriden in AS::Callback implementors in order + # to provide better debugging/logging. + def halted_callback_hook(filter) end class Callback #:nodoc:# @@ -153,7 +161,7 @@ module ActiveSupport @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def _one_time_conditions_valid_#{@callback_id}? - true #{key_options[0]} + true if #{key_options} end RUBY_EVAL end @@ -171,18 +179,18 @@ module ActiveSupport # if condition # before_save :filter_name, :if => :condition # filter_name # end - filter = <<-RUBY_EVAL - unless halted - # This double assignment is to prevent warnings in 1.9.3. I would - # remove the `result` variable, but apparently some other - # generated code is depending on this variable being set sometimes - # and sometimes not. + <<-RUBY_EVAL + if !halted && #{@compiled_options} + # This double assignment is to prevent warnings in 1.9.3 as + # the `result` variable is not always used except if the + # terminator code refers to it. result = result = #{@filter} halted = (#{chain.config[:terminator]}) + if halted + halted_callback_hook(#{@raw_filter.inspect.inspect}) + end end RUBY_EVAL - - [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n") when :around # Compile around filters with conditions into proxy methods # that contain the conditions. @@ -202,7 +210,7 @@ module ActiveSupport name = "_conditional_callback_#{@kind}_#{next_id}" @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{name}(halted) - #{@compiled_options[0] || "if true"} && !halted + if #{@compiled_options} && !halted #{@filter} do yield self end @@ -222,10 +230,12 @@ module ActiveSupport case @kind when :after - # if condition # after_save :filter_name, :if => :condition - # filter_name - # end - [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n") + # after_save :filter_name, :if => :condition + <<-RUBY_EVAL + if #{@compiled_options} + #{@filter} + end + RUBY_EVAL when :around <<-RUBY_EVAL value @@ -240,9 +250,7 @@ module ActiveSupport # symbols, string, procs, and objects), so compile a conditional # expression based on the options def _compile_options(options) - return [] if options[:if].empty? && options[:unless].empty? - - conditions = [] + conditions = ["true"] unless options[:if].empty? conditions << Array.wrap(_compile_filter(options[:if])) @@ -252,7 +260,7 @@ module ActiveSupport conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"} end - ["if #{conditions.flatten.join(" && ")}", "end"] + conditions.flatten.join(" && ") end # Filters support: @@ -368,45 +376,30 @@ module ActiveSupport end module ClassMethods - # Generate the internal runner method called by +run_callbacks+. - def __define_runner(symbol) #:nodoc: - body = send("_#{symbol}_callbacks").compile - - silence_warnings do - undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks") - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def _run_#{symbol}_callbacks(key = nil, &blk) - if key - name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks" - unless respond_to?(name) - self.class.__create_keyed_callback(name, :#{symbol}, self, &blk) - end - - send(name, &blk) - else - #{body} - end - end - private :_run_#{symbol}_callbacks - RUBY_EVAL - end - end - - # This is called the first time a callback is called with a particular - # key. It creates a new callback method for the key, calculating - # which callbacks can be omitted because of per_key conditions. + # This method calls the callback method for the given key. + # If this called first time it creates a new callback method for the key, + # calculating which callbacks can be omitted because of per_key conditions. # - def __create_keyed_callback(name, kind, object, &blk) #:nodoc: - @_keyed_callbacks ||= {} - @_keyed_callbacks[name] ||= begin - str = send("_#{kind}_callbacks").compile(name, object) + def __run_callbacks(key, kind, object, &blk) #:nodoc: + name = __callback_runner_name(key, kind) + unless object.respond_to?(name) + str = send("_#{kind}_callbacks").compile(key, object) class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{name}() #{str} end protected :#{name} RUBY_EVAL - true end + object.send(name, &blk) + end + + def __reset_runner(symbol) + name = __callback_runner_name(nil, symbol) + undef_method(name) if method_defined?(name) + end + + def __callback_runner_name(key, kind) + "_run__#{self.name.hash.abs}__#{kind}__#{key.hash.abs}__callbacks" end # This is used internally to append, prepend and skip callbacks to the @@ -420,7 +413,7 @@ module ActiveSupport ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target| chain = target.send("_#{name}_callbacks") yield target, chain.dup, type, filters, options - target.__define_runner(name) + target.__reset_runner(name) end end @@ -534,12 +527,12 @@ module ActiveSupport chain = target.send("_#{symbol}_callbacks").dup callbacks.each { |c| chain.delete(c) } target.send("_#{symbol}_callbacks=", chain) - target.__define_runner(symbol) + target.__reset_runner(symbol) end self.send("_#{symbol}_callbacks=", callbacks.dup.clear) - __define_runner(symbol) + __reset_runner(symbol) end # Define sets of events in the object lifecycle that support callbacks. @@ -613,7 +606,6 @@ module ActiveSupport callbacks.each do |callback| class_attribute "_#{callback}_callbacks" send("_#{callback}_callbacks=", CallbackChain.new(callback, config)) - __define_runner(callback) end end end diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index 81fb859334..c94a8d99f4 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -4,17 +4,12 @@ module ActiveSupport # module M # def self.included(base) # base.extend ClassMethods - # base.send(:include, InstanceMethods) # scope :disabled, where(:disabled => true) # end # # module ClassMethods # ... # end - # - # module InstanceMethods - # ... - # end # end # # By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as: @@ -31,10 +26,6 @@ module ActiveSupport # module ClassMethods # ... # end - # - # module InstanceMethods - # ... - # end # end # # Moreover, it gracefully handles module dependencies. Given a +Foo+ module and a +Bar+ @@ -118,7 +109,6 @@ module ActiveSupport @_dependencies.each { |dep| base.send(:include, dep) } super base.extend const_get("ClassMethods") if const_defined?("ClassMethods") - base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods") base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block") end end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index a94446acde..a2d2719de7 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -12,12 +12,12 @@ module ActiveSupport class Configuration < ActiveSupport::InheritableOptions def compile_methods! - self.class.compile_methods!(keys.reject {|key| respond_to?(key)}) + self.class.compile_methods!(keys) end # compiles reader methods so we don't have to go through method_missing def self.compile_methods!(keys) - keys.each do |key| + keys.reject { |m| method_defined?(m) }.each do |key| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{key}; _get(#{key.inspect}); end RUBY diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb index 46a8609dd7..b48bdf08e8 100644 --- a/activesupport/lib/active_support/core_ext.rb +++ b/activesupport/lib/active_support/core_ext.rb @@ -1,3 +1,4 @@ Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path| + next if File.basename(path, '.rb') == 'logger' require "active_support/core_ext/#{File.basename(path, '.rb')}" end diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb index 4688468a8f..79ba79192a 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -4,4 +4,4 @@ require 'active_support/core_ext/array/uniq_by' 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/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 2df4fd1da1..6162f7af27 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -16,7 +16,7 @@ class Array # %w( a b c d ).to(10) # => %w( a b c d ) # %w().to(0) # => %w() def to(position) - self[0..position] + self.first position + 1 end # Equal to <tt>self[1]</tt>. 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/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index 4cd9bfadac..2b3f639cb1 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -1,5 +1,3 @@ -require 'enumerator' - class Array # Splits or iterates over the array in groups of size +number+, # padding any remaining slots with +fill_with+ unless it is +false+. 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/array/random_access.rb b/activesupport/lib/active_support/core_ext/array/random_access.rb deleted file mode 100644 index bb1807a68a..0000000000 --- a/activesupport/lib/active_support/core_ext/array/random_access.rb +++ /dev/null @@ -1,30 +0,0 @@ -class Array - # Backport of Array#sample based on Marc-Andre Lafortune's https://github.com/marcandre/backports/ - # Returns a random element or +n+ random elements from the array. - # If the array is empty and +n+ is nil, returns <tt>nil</tt>. - # If +n+ is passed and its value is less than 0, it raises an +ArgumentError+ exception. - # If the value of +n+ is equal or greater than 0 it returns <tt>[]</tt>. - # - # [1,2,3,4,5,6].sample # => 4 - # [1,2,3,4,5,6].sample(3) # => [2, 4, 5] - # [1,2,3,4,5,6].sample(-3) # => ArgumentError: negative array size - # [].sample # => nil - # [].sample(3) # => [] - def sample(n=nil) - return self[Kernel.rand(size)] if n.nil? - n = n.to_int - rescue Exception => e - raise TypeError, "Coercion error: #{n.inspect}.to_int => Integer failed:\n(#{e.message})" - else - raise TypeError, "Coercion error: obj.to_int did NOT return an Integer (was #{n.class})" unless n.kind_of? Integer - raise ArgumentError, "negative array size" if n < 0 - n = size if n > size - result = Array.new(self) - n.times do |i| - r = i + Kernel.rand(size - i) - result[i], result[r] = result[r], result[i] - end - result[n..size] = [] - result - end unless method_defined? :sample -end diff --git a/activesupport/lib/active_support/core_ext/array/uniq_by.rb b/activesupport/lib/active_support/core_ext/array/uniq_by.rb index 9c5f97b0e9..ac3dedc0e3 100644 --- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb +++ b/activesupport/lib/active_support/core_ext/array/uniq_by.rb @@ -1,16 +1,22 @@ class Array - # Returns an unique array based on the criteria given as a +Proc+. + # *DEPRECATED*: Use +Array#uniq+ instead. + # + # Returns a unique array based on the criteria in the block. # # [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2] # - def uniq_by - hash, array = {}, [] - each { |i| hash[yield(i)] ||= (array << i) } - array + def uniq_by(&block) + ActiveSupport::Deprecation.warn "uniq_by " \ + "is deprecated. Use Array#uniq instead", caller + uniq(&block) end - # Same as uniq_by, but modifies self. - def uniq_by! - replace(uniq_by{ |i| yield(i) }) + # *DEPRECATED*: Use +Array#uniq!+ instead. + # + # Same as +uniq_by+, but modifies +self+. + def uniq_by!(&block) + ActiveSupport::Deprecation.warn "uniq_by! " \ + "is deprecated. Use Array#uniq! instead", caller + uniq!(&block) end end diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index 7fabae3138..4834eca8b1 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -14,7 +14,7 @@ class Array # This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences: # # * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt> - # moves on to try +to_a+ if the returned value is +nil+, but <tt>Arraw.wrap</tt> returns + # moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns # such a +nil+ right away. # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt> # raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value. @@ -40,7 +40,7 @@ class Array if object.nil? [] elsif object.respond_to?(:to_ary) - object.to_ary + object.to_ary || [object] else [object] end 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/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 7baba75ad3..45bec264ff 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/array/extract_options' class Class # Declare a class-level attribute whose value is inheritable by subclasses. @@ -56,11 +57,18 @@ class Class # object.setting # => false # Base.setting # => true # + # To opt out of the instance reader method, pass :instance_reader => false. + # + # object.setting # => NoMethodError + # object.setting? # => NoMethodError + # # To opt out of the instance writer method, pass :instance_writer => false. # # object.setting = false # => NoMethodError def class_attribute(*attrs) - instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer] + options = attrs.extract_options! + instance_reader = options.fetch(:instance_reader, true) + instance_writer = options.fetch(:instance_writer, true) attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -84,13 +92,15 @@ class Class val end - remove_possible_method :#{name} - def #{name} - defined?(@#{name}) ? @#{name} : self.class.#{name} - end + if instance_reader + remove_possible_method :#{name} + def #{name} + defined?(@#{name}) ? @#{name} : self.class.#{name} + end - def #{name}? - !!#{name} + def #{name}? + !!#{name} + end end RUBY @@ -100,12 +110,6 @@ class Class private def singleton_class? - # in case somebody is crazy enough to overwrite allocate - allocate = Class.instance_method(:allocate) - # object.class always points to a real (non-singleton) class - allocate.bind(self).call.class != self - rescue TypeError - # MRI/YARV/JRuby all disallow creating new instances of a singleton class - true + !name || '' == name end end diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index a903735acf..268303aaf2 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -17,6 +17,7 @@ require 'active_support/core_ext/array/extract_options' # # To opt out of the instance writer method, pass :instance_writer => false. # To opt out of the instance reader method, pass :instance_reader => false. +# To opt out of both instance methods, pass :instance_accessor => false. # # class Person # cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false @@ -38,7 +39,7 @@ class Class end EOS - unless options[:instance_reader] == false + unless options[:instance_reader] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} @@#{sym} @@ -61,7 +62,7 @@ class Class end EOS - unless options[:instance_writer] == false + unless options[:instance_writer] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) @@#{sym} = obj diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 26a99658cc..af78226c21 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -7,24 +7,6 @@ require 'active_support/core_ext/time/zones' class Date DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 } - if RUBY_VERSION < '1.9' - undef :>> - - # Backported from 1.9. The one in 1.8 leads to incorrect next_month and - # friends for dates where the calendar reform is involved. It additionally - # prevents an infinite loop fixed in r27013. - def >>(n) - y, m = (year * 12 + (mon - 1) + n).divmod(12) - m, = (m + 1) .divmod(1) - d = mday - until jd2 = self.class.valid_civil?(y, m, d, start) - d -= 1 - raise ArgumentError, 'invalid date' unless d > 0 - end - self + (jd2 - jd) - end - end - class << self # Returns a new Date representing the date 1 day ago (i.e. yesterday's date). def yesterday @@ -154,51 +136,54 @@ class Date advance(:years => years) end - # Shorthand for years_ago(1) - def prev_year - years_ago(1) - end unless method_defined?(:prev_year) - - # Shorthand for years_since(1) - def next_year - years_since(1) - end unless method_defined?(:next_year) - - # Shorthand for months_ago(1) - def prev_month - months_ago(1) - end unless method_defined?(:prev_month) - - # Shorthand for months_since(1) - def next_month - months_since(1) - end unless method_defined?(:next_month) + # Returns number of days to start of this week. Week is assumed to start on + # +start_day+, default is +:monday+. + def days_to_week_start(start_day = :monday) + start_day_number = DAYS_INTO_WEEK[start_day] + current_day_number = wday != 0 ? wday - 1 : 6 + (current_day_number - start_day_number) % 7 + end - # Returns a new Date/DateTime representing the "start" of this week (i.e, Monday; DateTime objects will have time set to 0:00). - def beginning_of_week - days_to_monday = self.wday!=0 ? self.wday-1 : 6 - result = self - days_to_monday - self.acts_like?(:time) ? result.midnight : result + # Returns a new +Date+/+DateTime+ representing the start of this week. Week is + # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects + # have their time set to 0:00. + def beginning_of_week(start_day = :monday) + days_to_start = days_to_week_start(start_day) + result = self - days_to_start + acts_like?(:time) ? result.midnight : result end - alias :monday :beginning_of_week alias :at_beginning_of_week :beginning_of_week - # Returns a new Date/DateTime representing the end of this week (Sunday, DateTime objects will have time set to 23:59:59). - def end_of_week - days_to_sunday = self.wday!=0 ? 7-self.wday : 0 - result = self + days_to_sunday.days + # Returns a new +Date+/+DateTime+ representing the start of this week. Week is + # assumed to start on a Monday. +DateTime+ objects have their time set to 0:00. + def monday + beginning_of_week + end + + # Returns a new +Date+/+DateTime+ representing the end of this week. Week is + # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects + # have their time set to 23:59:59. + def end_of_week(start_day = :monday) + days_to_end = 6 - days_to_week_start(start_day) + result = self + days_to_end.days self.acts_like?(:time) ? result.end_of_day : result end - alias :sunday :end_of_week alias :at_end_of_week :end_of_week - # Returns a new Date/DateTime representing the start of the given day in the previous week (default is Monday). + # Returns a new +Date+/+DateTime+ representing the end of this week. Week is + # assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59. + def sunday + end_of_week + end + + # Returns a new +Date+/+DateTime+ representing the given +day+ in the previous + # week. Default is +:monday+. +DateTime+ objects have their time set to 0:00. def prev_week(day = :monday) result = (self - 7).beginning_of_week + DAYS_INTO_WEEK[day] self.acts_like?(:time) ? result.change(:hour => 0) : result end - # Returns a new Date/DateTime representing the start of the given day in next week (default is Monday). + # Returns a new Date/DateTime representing the start of the given day in next week (default is :monday). def next_week(day = :monday) result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day] self.acts_like?(:time) ? result.change(:hour => 0) : result diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 338104fd05..3262c254f7 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -63,12 +63,6 @@ class Date alias_method :default_inspect, :inspect alias_method :inspect, :readable_inspect - # A method to keep Time, Date and DateTime instances interchangeable on conversions. - # In this case, it simply returns +self+. - def to_date - self - end if RUBY_VERSION < '1.9' - # Converts a Date instance to a Time, where the time is set to the beginning of the day. # The timezone can be either :local or :utc (default :local). # @@ -83,23 +77,6 @@ class Date ::Time.send("#{form}_time", year, month, day) end - # Converts a Date instance to a DateTime, where the time is set to the beginning of the day - # and UTC offset is set to 0. - # - # ==== Examples - # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 - # - # date.to_datetime # => Sat, 10 Nov 2007 00:00:00 0000 - def to_datetime - ::DateTime.civil(year, month, day, 0, 0, 0, 0) - end if RUBY_VERSION < '1.9' - - def iso8601 - strftime('%F') - end if RUBY_VERSION < '1.9' - - alias_method :rfc3339, :iso8601 if RUBY_VERSION < '1.9' - def xmlschema to_time_in_current_zone.xmlschema end diff --git a/activesupport/lib/active_support/core_ext/date/freeze.rb b/activesupport/lib/active_support/core_ext/date/freeze.rb deleted file mode 100644 index 4edd715ba2..0000000000 --- a/activesupport/lib/active_support/core_ext/date/freeze.rb +++ /dev/null @@ -1,31 +0,0 @@ -# Date memoizes some instance methods using metaprogramming to wrap -# the methods with one that caches the result in an instance variable. -# -# If a Date is frozen but the memoized method hasn't been called, the -# first call will result in a frozen object error since the memo -# instance variable is uninitialized. -# -# Work around by eagerly memoizing before freezing. -# -# Ruby 1.9 uses a preinitialized instance variable so it's unaffected. -# This hack is as close as we can get to feature detection: -if RUBY_VERSION < '1.9' - require 'date' - begin - ::Date.today.freeze.jd - rescue => frozen_object_error - if frozen_object_error.message =~ /frozen/ - class Date #:nodoc: - def freeze - self.class.private_instance_methods(false).each do |m| - if m.to_s =~ /\A__\d+__\Z/ - instance_variable_set(:"@#{m}", [send(m)]) - end - end - - super - end - end - end - end -end 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 48cf1a435d..1a3cf66a1b 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -1,5 +1,3 @@ -require 'rational' unless RUBY_VERSION >= '1.9.2' - class DateTime class << self # DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index ca899c714c..d7b3ad7d8d 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -6,7 +6,7 @@ require 'active_support/values/time_zone' class DateTime # Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows # DateTimes outside the range of what can be created with Time. - remove_method :to_time if instance_methods.include?(:to_time) + remove_method :to_time # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats. # @@ -66,7 +66,7 @@ class DateTime # Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class. # If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time. def to_time - self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * (RUBY_VERSION < '1.9' ? 86400000000 : 1000000)) : self + self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) : self end # To be able to keep Times, Dates and DateTimes interchangeable on conversions. diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 6d7f771b5d..77a5087981 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -1,40 +1,4 @@ -require 'active_support/ordered_hash' - module Enumerable - # Ruby 1.8.7 introduces group_by, but the result isn't ordered. Override it. - remove_method(:group_by) if [].respond_to?(:group_by) && RUBY_VERSION < '1.9' - - # Collect an enumerable into sets, grouped by the result of a block. Useful, - # for example, for grouping records by date. - # - # Example: - # - # latest_transcripts.group_by(&:day).each do |day, transcripts| - # p "#{day} -> #{transcripts.map(&:class).join(', ')}" - # end - # "2006-03-01 -> Transcript" - # "2006-02-28 -> Transcript" - # "2006-02-27 -> Transcript, Transcript" - # "2006-02-26 -> Transcript, Transcript" - # "2006-02-25 -> Transcript" - # "2006-02-24 -> Transcript, Transcript" - # "2006-02-23 -> Transcript" - def group_by - assoc = ActiveSupport::OrderedHash.new - - each do |element| - key = yield(element) - - if assoc.has_key?(key) - assoc[key] << element - else - assoc[key] = [element] - end - end - - assoc - end unless [].respond_to?(:group_by) - # Calculates a sum from the elements. Examples: # # payments.sum { |p| p.price * p.tax_rate } @@ -62,26 +26,6 @@ module Enumerable end end - # Iterates over a collection, passing the current element *and* the - # +memo+ to the block. Handy for building up hashes or - # reducing collections down to one object. Examples: - # - # %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } - # # => {'foo' => 'FOO', 'bar' => 'BAR'} - # - # *Note* that you can't use immutable objects like numbers, true or false as - # the memo. You would think the following returns 120, but since the memo is - # never changed, it does not. - # - # (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1 - # - def each_with_object(memo, &block) - each do |element| - block.call(element, memo) - end - memo - end unless [].respond_to?(:each_with_object) - # Convert an enumerable to a hash. Examples: # # people.index_by(&:login) @@ -90,17 +34,25 @@ module Enumerable # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...} # def index_by + return to_enum :index_by unless block_given? Hash[map { |elem| [yield(elem), elem] }] end - # Returns true if the collection has more than 1 element. Functionally equivalent to collection.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. - def many?(&block) - size = block_given? ? select(&block).size : self.size - size > 1 + # 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 <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? + any? do |element| + cnt += 1 if yield element + cnt > 1 + end + else + any?{ (cnt += 1) > 1 } + 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/exception.rb b/activesupport/lib/active_support/core_ext/exception.rb index ef801e713d..ba7757ea07 100644 --- a/activesupport/lib/active_support/core_ext/exception.rb +++ b/activesupport/lib/active_support/core_ext/exception.rb @@ -1,3 +1,3 @@ module ActiveSupport - FrozenObjectError = RUBY_VERSION < '1.9' ? TypeError : RuntimeError + FrozenObjectError = RuntimeError end diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb index a763447566..dc24afbe7f 100644 --- a/activesupport/lib/active_support/core_ext/file.rb +++ b/activesupport/lib/active_support/core_ext/file.rb @@ -1,2 +1 @@ require 'active_support/core_ext/file/atomic' -require 'active_support/core_ext/file/path' diff --git a/activesupport/lib/active_support/core_ext/file/path.rb b/activesupport/lib/active_support/core_ext/file/path.rb deleted file mode 100644 index b5feab80ae..0000000000 --- a/activesupport/lib/active_support/core_ext/file/path.rb +++ /dev/null @@ -1,5 +0,0 @@ -class File - unless File.allocate.respond_to?(:to_path) - alias to_path path - end -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/float.rb b/activesupport/lib/active_support/core_ext/float.rb deleted file mode 100644 index 7570471b95..0000000000 --- a/activesupport/lib/active_support/core_ext/float.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/float/rounding' diff --git a/activesupport/lib/active_support/core_ext/float/rounding.rb b/activesupport/lib/active_support/core_ext/float/rounding.rb deleted file mode 100644 index 0d4fb87665..0000000000 --- a/activesupport/lib/active_support/core_ext/float/rounding.rb +++ /dev/null @@ -1,19 +0,0 @@ -class Float - alias precisionless_round round - private :precisionless_round - - # Rounds the float with the specified precision. - # - # x = 1.337 - # x.round # => 1 - # x.round(1) # => 1.3 - # x.round(2) # => 1.34 - def round(precision = nil) - if precision - magnitude = 10.0 ** precision - (self * magnitude).round / magnitude - else - precisionless_round - end - end -end if RUBY_VERSION < '1.9' diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 102378a029..5f07bb4f5a 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -95,7 +95,7 @@ class Hash case value.class.to_s when 'Hash' if value['type'] == 'array' - _, entries = Array.wrap(value.detect { |k,v| k != 'type' }) + _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) }) if entries.nil? || (c = value['__content__'] && c.blank?) [] else 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 c2a6476604..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 # @@ -11,7 +11,7 @@ class Hash end # Called when object is nested under an object that receives - # #with_indifferent_access. This method with be called on the current object + # #with_indifferent_access. This method will be called on the current object # by the enclosing object and is aliased to #with_indifferent_access by # default. Subclasses of Hash may overwrite this method to return +self+ if # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be 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/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index c6920098a8..526b8378a5 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -59,7 +59,7 @@ module Kernel raise unless exception_classes.any? { |cls| e.kind_of?(cls) } end end - + # Captures the given stream and returns it: # # stream = capture(:stdout) { puts "Cool" } diff --git a/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb b/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb index 33612155fb..9bbf1bbd73 100644 --- a/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb +++ b/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb @@ -1,11 +1,4 @@ module Kernel - # Returns the object's singleton class. - def singleton_class - class << self - self - end - end unless respond_to?(:singleton_class) # exists in 1.9.2 - # class_eval on an object acts like singleton_class.class_eval. def class_eval(*args, &block) singleton_class.class_eval(*args, &block) diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index e63a0a9ed9..a51818d2b2 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -1,4 +1,7 @@ require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/deprecation' + +ActiveSupport::Deprecation.warn 'this file is deprecated and will be removed' # Adds the 'around_level' method to Logger. class Logger #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index 9fed346b7c..f2d4887df6 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -5,7 +5,6 @@ require 'active_support/core_ext/module/reachable' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/module/attr_internal' 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/qualified_const' diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb index 3982c9c586..0a9e791030 100644 --- a/activesupport/lib/active_support/core_ext/module/anonymous.rb +++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/blank' - class Module # A module may or may not have a name. # @@ -7,7 +5,7 @@ class Module # M.name # => "M" # # m = Module.new - # m.name # => "" + # m.name # => nil # # A module gets a name when it is first assigned to a constant. Either # via the +module+ or +class+ keyword or by an explicit assignment: @@ -17,8 +15,6 @@ class Module # m.name # => "M" # def anonymous? - # Uses blank? because the name of an anonymous class is an empty - # string in 1.8, and nil in 1.9. - name.blank? + name.nil? end end diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 871f5cef3b..be94ae1565 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -12,7 +12,7 @@ class Module end EOS - unless options[:instance_reader] == false + unless options[:instance_reader] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} @@#{sym} @@ -31,7 +31,7 @@ class Module end EOS - unless options[:instance_writer] == false + unless options[:instance_writer] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) @@#{sym} = obj @@ -53,6 +53,10 @@ class Module # end # # AppConfiguration.google_api_key = "overriding the api key!" + # + # To opt out of the instance writer method, pass :instance_writer => false. + # To opt out of the instance reader method, pass :instance_reader => false. + # To opt out of both instance methods, pass :instance_accessor => false. def mattr_accessor(*syms) mattr_reader(*syms) mattr_writer(*syms) diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 3a7652f5bf..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,39 +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 - module_eval(<<-EOS, file, line - 5) - if instance_methods(false).map(&:to_s).include?("#{prefix}#{method}") - remove_possible_method("#{prefix}#{method}") - end + 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}") - 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/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index c08ad251dd..1893a9cfa6 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -57,27 +57,8 @@ class Module parents end - if RUBY_VERSION < '1.9' - # Returns the constants that have been defined locally by this object and - # not in an ancestor. This method is exact if running under Ruby 1.9. In - # previous versions it may miss some constants if their definition in some - # ancestor is identical to their definition in the receiver. - def local_constants - inherited = {} - - ancestors.each do |anc| - next if anc == self - anc.constants.each { |const| inherited[const] = anc.const_get(const) } - end - - constants.select do |const| - !inherited.key?(const) || inherited[const].object_id != const_get(const).object_id - end - end - else - def local_constants #:nodoc: - constants(false) - end + def local_constants #:nodoc: + constants(false) end # Returns the names of the constants defined locally rather than the diff --git a/activesupport/lib/active_support/core_ext/module/method_names.rb b/activesupport/lib/active_support/core_ext/module/method_names.rb deleted file mode 100644 index 2eb40a83ab..0000000000 --- a/activesupport/lib/active_support/core_ext/module/method_names.rb +++ /dev/null @@ -1,14 +0,0 @@ -class Module - if instance_methods[0].is_a?(Symbol) - def instance_method_names(*args) - instance_methods(*args).map(&:to_s) - end - - def method_names(*args) - methods(*args).map(&:to_s) - end - else - alias_method :instance_method_names, :instance_methods - alias_method :method_names, :methods - end -end
\ No newline at end of file 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..8adf050b6b --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb @@ -0,0 +1,52 @@ +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 + 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 + + 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/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb index 07d7c9b018..b76bc16ee1 100644 --- a/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -1,11 +1,16 @@ class Module def remove_possible_method(method) - remove_method(method) + if method_defined?(method) || private_method_defined?(method) + remove_method(method) + end rescue NameError + # If the requested method is defined on a superclass or included module, + # method_defined? returns true but remove_method throws a NameError. + # Ignore this. end def redefine_method(method, &block) remove_possible_method(method) define_method(method, &block) end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb deleted file mode 100644 index ed16c2f71b..0000000000 --- a/activesupport/lib/active_support/core_ext/module/synchronization.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'thread' -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/array/extract_options' - -class Module - # Synchronize access around a method, delegating synchronization to a - # particular mutex. A mutex (either a Mutex, or any object that responds to - # #synchronize and yields to a block) must be provided as a final :with option. - # The :with option should be a symbol or string, and can represent a method, - # constant, or instance or class variable. - # Example: - # class SharedCache - # @@lock = Mutex.new - # def expire - # ... - # end - # synchronize :expire, :with => :@@lock - # end - def synchronize(*methods) - options = methods.extract_options! - unless options.is_a?(Hash) && with = options[:with] - raise ArgumentError, "Synchronization needs a mutex. Supply an options hash with a :with key as the last argument (e.g. synchronize :hello, :with => :@mutex)." - end - - methods.each do |method| - aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 - - if method_defined?("#{aliased_method}_without_synchronization#{punctuation}") - raise ArgumentError, "#{method} is already synchronized. Double synchronization is not currently supported." - end - - module_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{aliased_method}_with_synchronization#{punctuation}(*args, &block) # def expire_with_synchronization(*args, &block) - #{with}.synchronize do # @@lock.synchronize do - #{aliased_method}_without_synchronization#{punctuation}(*args, &block) # expire_without_synchronization(*args, &block) - end # end - end # end - EOS - - alias_method_chain method, :synchronization - end - end -end diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 8221dc4abe..7271671908 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,3 +1,6 @@ +# encoding: utf-8 +require 'active_support/core_ext/string/encoding' + class Object # An object is blank if it's false, empty, or a whitespace string. # For example, "", " ", +nil+, [], and {} are all blank. @@ -90,10 +93,11 @@ class String # # "".blank? # => true # " ".blank? # => true + # " ".blank? # => true # " something here ".blank? # => false # def blank? - self !~ /\S/ + self !~ /[^[:space:]]/ end end diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb index b5671f66d0..f611cdd606 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -1,15 +1,25 @@ class Object - # Returns true if this object is included in the argument. Argument must be - # any object which responds to +#include?+. Usage: + # Returns true if this object is included in the argument(s). Argument must be + # any object which responds to +#include?+ or optionally, multiple arguments can be passed in. Usage: # # characters = ["Konata", "Kagami", "Tsukasa"] # "Konata".in?(characters) # => true + # + # character = "Konata" + # character.in?("Konata", "Kagami", "Tsukasa") # => true # - # This will throw an ArgumentError if the argument doesn't respond + # This will throw an ArgumentError if a single argument is passed in and it doesn't respond # to +#include?+. - def in?(another_object) - another_object.include?(self) - rescue NoMethodError - raise ArgumentError.new("The parameter passed to #in? must respond to #include?") + def in?(*args) + if args.length > 1 + args.include? self + else + another_object = args.first + if another_object.respond_to? :include? + another_object.include? self + else + raise ArgumentError.new("The single parameter passed to #in? must respond to #include?") + end + end end end diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb index eda9694614..66caf9bec8 100644 --- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb @@ -23,11 +23,7 @@ class Object # end # # C.new(0, 1).instance_variable_names # => ["@y", "@x"] - if RUBY_VERSION >= '1.9' - def instance_variable_names - instance_variables.map { |var| var.to_s } - end - else - alias_method :instance_variable_names, :instance_variables + def instance_variable_names + instance_variables.map { |var| var.to_s } end end diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb index 14ef27340e..e7dc60a612 100644 --- a/activesupport/lib/active_support/core_ext/object/to_json.rb +++ b/activesupport/lib/active_support/core_ext/object/to_json.rb @@ -10,10 +10,10 @@ end # several cases (for instance, the JSON implementation for Hash does not work) with inheritance # and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. [Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass| - klass.class_eval <<-RUBY, __FILE__, __LINE__ + klass.class_eval do # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info. def to_json(options = nil) ActiveSupport::JSON.encode(self, options) end - RUBY + 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/process.rb b/activesupport/lib/active_support/core_ext/process.rb deleted file mode 100644 index 0b0bc6dc69..0000000000 --- a/activesupport/lib/active_support/core_ext/process.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/process/daemon' diff --git a/activesupport/lib/active_support/core_ext/process/daemon.rb b/activesupport/lib/active_support/core_ext/process/daemon.rb deleted file mode 100644 index f5202ddee4..0000000000 --- a/activesupport/lib/active_support/core_ext/process/daemon.rb +++ /dev/null @@ -1,23 +0,0 @@ -module Process - def self.daemon(nochdir = nil, noclose = nil) - exit if fork # Parent exits, child continues. - Process.setsid # Become session leader. - exit if fork # Zap session leader. See [1]. - - unless nochdir - Dir.chdir "/" # Release old working directory. - end - - File.umask 0000 # Ensure sensible umask. Adjust as needed. - - unless noclose - STDIN.reopen "/dev/null" # Free file descriptors and - STDOUT.reopen "/dev/null", "a" # point them somewhere sensible. - STDERR.reopen '/dev/null', 'a' - end - - trap("TERM") { exit } - - return 0 - end unless respond_to?(:daemon) -end diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb index 2428a02242..c0736f3a44 100644 --- a/activesupport/lib/active_support/core_ext/range.rb +++ b/activesupport/lib/active_support/core_ext/range.rb @@ -2,4 +2,3 @@ require 'active_support/core_ext/range/blockless_step' require 'active_support/core_ext/range/conversions' require 'active_support/core_ext/range/include_range' require 'active_support/core_ext/range/overlaps' -require 'active_support/core_ext/range/cover' diff --git a/activesupport/lib/active_support/core_ext/range/blockless_step.rb b/activesupport/lib/active_support/core_ext/range/blockless_step.rb index db42ef5c47..f687287f0d 100644 --- a/activesupport/lib/active_support/core_ext/range/blockless_step.rb +++ b/activesupport/lib/active_support/core_ext/range/blockless_step.rb @@ -1,27 +1,11 @@ require 'active_support/core_ext/module/aliasing' class Range - begin - (1..2).step - # Range#step doesn't return an Enumerator - rescue LocalJumpError - # Return an array when step is called without a block. - def step_with_blockless(*args, &block) - if block_given? - step_without_blockless(*args, &block) - else - array = [] - step_without_blockless(*args) { |step| array << step } - array - end - end - else - def step_with_blockless(*args, &block) #:nodoc: - if block_given? - step_without_blockless(*args, &block) - else - step_without_blockless(*args).to_a - end + def step_with_blockless(*args, &block) #:nodoc: + if block_given? + step_without_blockless(*args, &block) + else + step_without_blockless(*args).to_a end 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/range/cover.rb b/activesupport/lib/active_support/core_ext/range/cover.rb deleted file mode 100644 index 3a182cddd2..0000000000 --- a/activesupport/lib/active_support/core_ext/range/cover.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Range - alias_method(:cover?, :include?) unless instance_methods.include?(:cover?) -end diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb index 0246627467..684b7cbc4a 100644 --- a/activesupport/lib/active_support/core_ext/range/include_range.rb +++ b/activesupport/lib/active_support/core_ext/range/include_range.rb @@ -9,9 +9,9 @@ class Range # (5..9).include?(11) # => false def include_with_range?(value) if value.is_a?(::Range) - operator = exclude_end? ? :< : :<= - end_value = value.exclude_end? ? last.succ : last - include_without_range?(value.first) && (value.last <=> end_value).send(operator, 0) + # 1...10 includes 1..9 but it does not include 1..10. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + include_without_range?(value.first) && value.last.send(operator, last) else include_without_range?(value) end diff --git a/activesupport/lib/active_support/core_ext/rexml.rb b/activesupport/lib/active_support/core_ext/rexml.rb deleted file mode 100644 index 0419ebc84b..0000000000 --- a/activesupport/lib/active_support/core_ext/rexml.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'active_support/core_ext/kernel/reporting' - -# Fixes the rexml vulnerability disclosed at: -# http://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml/ -# This fix is identical to rexml-expansion-fix version 1.0.1. -# -# We still need to distribute this fix because albeit the REXML -# in recent 1.8.7s is patched, it wasn't in early patchlevels. -require 'rexml/rexml' - -# Earlier versions of rexml defined REXML::Version, newer ones REXML::VERSION -unless (defined?(REXML::VERSION) ? REXML::VERSION : REXML::Version) > "3.1.7.2" - silence_warnings { require 'rexml/document' } - - # REXML in 1.8.7 has the patch but early patchlevels didn't update Version from 3.1.7.2. - unless REXML::Document.respond_to?(:entity_expansion_limit=) - silence_warnings { require 'rexml/entity' } - - module REXML #:nodoc: - class Entity < Child #:nodoc: - undef_method :unnormalized - def unnormalized - document.record_entity_expansion! if document - v = value() - return nil if v.nil? - @unnormalized = Text::unnormalize(v, parent) - @unnormalized - end - end - class Document < Element #:nodoc: - @@entity_expansion_limit = 10_000 - def self.entity_expansion_limit= val - @@entity_expansion_limit = val - end - - def record_entity_expansion! - @number_of_expansions ||= 0 - @number_of_expansions += 1 - if @number_of_expansions > @@entity_expansion_limit - raise "Number of entity expansions exceeded, processing aborted." - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index c0d5cdf2d5..9b5266c58c 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -1,99 +1,35 @@ require "active_support/multibyte" class String - unless '1.9'.respond_to?(:force_encoding) - # Returns the character at the +position+ treating the string as an array (where 0 is the first character). - # - # Examples: - # "hello".at(0) # => "h" - # "hello".at(4) # => "o" - # "hello".at(10) # => ERROR if < 1.9, nil in 1.9 - def at(position) - 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). - # - # Examples: - # "hello".from(0) # => "hello" - # "hello".from(2) # => "llo" - # "hello".from(10) # => "" if < 1.9, nil in 1.9 - def from(position) - 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). - # - # Examples: - # "hello".to(0) # => "h" - # "hello".to(2) # => "hel" - # "hello".to(10) # => "hello" - def to(position) - mb_chars[0..position].to_s - end - - # Returns the first character of the string or the first +limit+ characters. - # - # Examples: - # "hello".first # => "h" - # "hello".first(2) # => "he" - # "hello".first(10) # => "hello" - def first(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - mb_chars[0...limit].to_s - end - end - - # Returns the last character of the string or the last +limit+ characters. - # - # Examples: - # "hello".last # => "o" - # "hello".last(2) # => "lo" - # "hello".last(10) # => "hello" - def last(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - mb_chars[(-limit)..-1].to_s - end - end - else - def at(position) - self[position] - end + def at(position) + self[position] + end - def from(position) - self[position..-1] - end + def from(position) + self[position..-1] + end - def to(position) - self[0..position] - end + def to(position) + self[0..position] + end - def first(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - to(limit - 1) - end + def first(limit = 1) + if limit == 0 + '' + elsif limit >= size + self + else + to(limit - 1) end + end - def last(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - from(-limit) - end + def last(limit = 1) + if limit == 0 + '' + elsif limit >= size + self + else + from(-limit) end end end diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 5b2cb6e331..541f969faa 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -1,42 +1,13 @@ require 'date' -require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/core_ext/time/calculations' class String - # Returns the codepoint of the first character of the string, assuming a - # single-byte character encoding: - # - # "a".ord # => 97 - # "à".ord # => 224, in ISO-8859-1 - # - # This method is defined in Ruby 1.8 for Ruby 1.9 forward compatibility on - # these character encodings. - # - # <tt>ActiveSupport::Multibyte::Chars#ord</tt> is forward compatible with - # Ruby 1.9 on UTF8 strings: - # - # "a".mb_chars.ord # => 97 - # "à".mb_chars.ord # => 224, in UTF8 - # - # Note that the 224 is different in both examples. In ISO-8859-1 "à" is - # represented as a single byte, 224. In UTF8 it is represented with two - # bytes, namely 195 and 160, but its Unicode codepoint is 224. If we - # call +ord+ on the UTF8 string "à" the return value will be 195. That is - # not an error, because UTF8 is unsupported, the call itself would be - # bogus. - def ord - self[0] - end unless method_defined?(:ord) - - # +getbyte+ backport from Ruby 1.9 - alias_method :getbyte, :[] unless method_defined?(:getbyte) - # Form can be either :utc (default) or :local. def to_time(form = :utc) return nil if self.blank? - d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction).map { |arg| arg || 0 } + d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).map { |arg| arg || 0 } d[6] *= 1000000 - ::Time.send("#{form}_time", *d) + ::Time.send("#{form}_time", *d[0..6]) - d[7] end def to_date diff --git a/activesupport/lib/active_support/core_ext/string/encoding.rb b/activesupport/lib/active_support/core_ext/string/encoding.rb index d4781bfe0c..dc635ed6a5 100644 --- a/activesupport/lib/active_support/core_ext/string/encoding.rb +++ b/activesupport/lib/active_support/core_ext/string/encoding.rb @@ -1,11 +1,8 @@ +require 'active_support/deprecation' + class String - if defined?(Encoding) && "".respond_to?(:encode) - def encoding_aware? - true - end - else - def encoding_aware? - false - end + def encoding_aware? + ActiveSupport::Deprecation.warn 'String#encoding_aware? is deprecated', caller + true end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 2f0676f567..1e57b586d9 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,5 +1,4 @@ require 'active_support/inflector/methods' -require 'active_support/inflector/inflections' require 'active_support/inflector/transliterate' # String inflections define new methods on the String class to transform names for different purposes. @@ -10,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. @@ -34,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. # @@ -94,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/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index 41de4d6435..4e7824ad74 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -2,71 +2,55 @@ require 'active_support/multibyte' class String - if RUBY_VERSION >= "1.9" - # == 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 - # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated 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. This means that - # it becomes easy to run one version of your code 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 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. - def mb_chars - if ActiveSupport::Multibyte.proxy_class.consumes?(self) - ActiveSupport::Multibyte.proxy_class.new(self) - else - self - end - end - - def is_utf8? #:nodoc - 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 - else - def mb_chars - if ActiveSupport::Multibyte.proxy_class.wants?(self) - ActiveSupport::Multibyte.proxy_class.new(self) - else - self - end + # == 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 + # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated 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. This means that + # it becomes easy to run one version of your code 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 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 behavior see ActiveSupport::Multibyte. + def mb_chars + if ActiveSupport::Multibyte.proxy_class.consumes?(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) + 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 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 c27cbc37c5..6cb2ea68b3 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.encode(s.encoding, :xml => :attr)[1...-1].html_safe end end @@ -51,7 +51,8 @@ class ERB # <%=j @person.to_json %> # def json_escape(s) - s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + s.html_safe? ? result.html_safe : result end alias j json_escape @@ -66,7 +67,7 @@ class Object end end -class Fixnum +class Numeric def html_safe? true end @@ -74,10 +75,40 @@ end module ActiveSupport #:nodoc: class SafeBuffer < String - alias safe_concat concat + 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 + + class SafeConcatError < StandardError + def initialize + super "Could not concatenate to the buffer because it is not html safe." + end + end + + def[](*args) + new_safe_buffer = super + new_safe_buffer.instance_eval { @dirty = false } + new_safe_buffer + end + + def safe_concat(value) + raise SafeConcatError if dirty? + original_concat(value) + end + + def initialize(*) + @dirty = false + super + end + + def initialize_copy(other) + super + @dirty = other.dirty? + end def concat(value) - if value.html_safe? + if dirty? || value.html_safe? super(value) else super(ERB::Util.h(value)) @@ -90,15 +121,15 @@ module ActiveSupport #:nodoc: end def html_safe? - true + !dirty? end - def html_safe + def to_s self end - def to_s - self + def to_param + to_str end def encode_with(coder) @@ -107,17 +138,33 @@ module ActiveSupport #:nodoc: def to_yaml(*args) return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck? - to_str.to_yaml(*args) end + + UNSAFE_STRING_METHODS.each do |unsafe_method| + 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 + + def dirty? + @dirty + end end end class String - def html_safe! - raise "You can't call html_safe! on a String" - end - def html_safe ActiveSupport::SafeBuffer.new(self) end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 00fda2b370..f3235d11bb 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. @@ -160,27 +161,46 @@ class Time months_since(1) end - # Returns a new Time representing the "start" of this week (Monday, 0:00) - def beginning_of_week - days_to_monday = wday!=0 ? wday-1 : 6 - (self - days_to_monday.days).midnight + # Returns number of days to start of this week, week starts on start_day (default is :monday). + def days_to_week_start(start_day = :monday) + start_day_number = DAYS_INTO_WEEK[start_day] + current_day_number = wday != 0 ? wday - 1 : 6 + days_span = current_day_number - start_day_number + days_span >= 0 ? days_span : 7 + days_span + end + + # Returns a new Time representing the "start" of this week, week starts on start_day (default is :monday, i.e. Monday, 0:00). + def beginning_of_week(start_day = :monday) + days_to_start = days_to_week_start(start_day) + (self - days_to_start.days).midnight end - alias :monday :beginning_of_week alias :at_beginning_of_week :beginning_of_week - # Returns a new Time representing the end of this week, (end of Sunday) - def end_of_week - days_to_sunday = wday!=0 ? 7-wday : 0 - (self + days_to_sunday.days).end_of_day + # Returns a new +Date+/+DateTime+ representing the start of this week. Week is + # assumed to start on a Monday. +DateTime+ objects have their time set to 0:00. + def monday + beginning_of_week + end + + # Returns a new Time representing the end of this week, week starts on start_day (default is :monday, i.e. end of Sunday). + def end_of_week(start_day = :monday) + days_to_end = 6 - days_to_week_start(start_day) + (self + days_to_end.days).end_of_day end alias :at_end_of_week :end_of_week - # Returns a new Time representing the start of the given day in the previous week (default is Monday). + # Returns a new +Date+/+DateTime+ representing the end of this week. Week is + # assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59. + def sunday + end_of_week + end + + # Returns a new Time representing the start of the given day in the previous week (default is :monday). def prev_week(day = :monday) ago(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0) end - # Returns a new Time representing the start of the given day in next week (default is Monday). + # Returns a new Time representing the start of the given day in next week (default is :monday). def next_week(day = :monday) since(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0) end @@ -226,7 +246,7 @@ class Time end alias :at_end_of_quarter :end_of_quarter - # Returns a new Time representing the start of the year (1st of january, 0:00) + # Returns a new Time representing the start of the year (1st of january, 0:00) def beginning_of_year change(:month => 1, :day => 1, :hour => 0) end @@ -248,6 +268,31 @@ class Time advance(:days => 1) end + # Returns a Range representing the whole day of the current time. + def all_day + beginning_of_day..end_of_day + end + + # Returns a Range representing the whole week of the current time. + def all_week + beginning_of_week..end_of_week + end + + # Returns a Range representing the whole month of the current time. + def all_month + beginning_of_month..end_of_month + end + + # Returns a Range representing the whole quarter of the current time. + def all_quarter + beginning_of_quarter..end_of_quarter + end + + # Returns a Range representing the whole year of the current time. + def all_year + beginning_of_year..end_of_year + end + def plus_with_duration(other) #:nodoc: if ActiveSupport::Duration === other other.since(self) @@ -286,4 +331,14 @@ class Time end alias_method :compare_without_coercion, :<=> alias_method :<=>, :compare_with_coercion + + # Layers additional behavior on Time#eql? so that ActiveSupport::TimeWithZone instances + # can be eql? to an equivalent Time + def eql_with_coercion(other) + # if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do eql? comparison + other = other.comparable_time if other.respond_to?(:comparable_time) + eql_without_coercion(other) + end + alias_method :eql_without_coercion, :eql? + alias_method :eql?, :eql_with_coercion end diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index d9d5e02778..5a17fc1afa 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -1,5 +1,4 @@ require 'active_support/inflector/methods' -require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/values/time_zone' class Time @@ -55,9 +54,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/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb index 457d3f5b62..1bf622d6a6 100644 --- a/activesupport/lib/active_support/core_ext/time/marshal.rb +++ b/activesupport/lib/active_support/core_ext/time/marshal.rb @@ -1,30 +1,3 @@ -# Pre-1.9 versions of Ruby have a bug with marshaling Time instances, where utc instances are -# unmarshalled in the local zone, instead of utc. We're layering behavior on the _dump and _load -# methods so that utc instances can be flagged on dump, and coerced back to utc on load. -if !Marshal.load(Marshal.dump(Time.now.utc)).utc? - class Time - class << self - alias_method :_load_without_utc_flag, :_load - def _load(marshaled_time) - time = _load_without_utc_flag(marshaled_time) - time.instance_eval do - if defined?(@marshal_with_utc_coercion) - val = remove_instance_variable("@marshal_with_utc_coercion") - end - val ? utc : self - end - end - end - - alias_method :_dump_without_utc_flag, :_dump - def _dump(*args) - obj = dup - obj.instance_variable_set('@marshal_with_utc_coercion', utc?) - obj._dump_without_utc_flag(*args) - end - end -end - # Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only # preserves utc_offset. Preserve zone also, even though it may not # work in some edge cases. diff --git a/activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb b/activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb deleted file mode 100644 index e1878d3c20..0000000000 --- a/activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'date' - -class Time - # Ruby 1.8-cvs and early 1.9 series define private Time#to_date - %w(to_date to_datetime).each do |method| - if private_instance_methods.include?(method) || private_instance_methods.include?(method.to_sym) - public method - end - end -end diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb index ee991e3439..0b219ce44a 100644 --- a/activesupport/lib/active_support/core_ext/uri.rb +++ b/activesupport/lib/active_support/core_ext/uri.rb @@ -1,22 +1,18 @@ # encoding: utf-8 -if RUBY_VERSION >= '1.9' - require 'uri' +require 'uri' +str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese. +parser = URI::Parser.new - str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese. - - parser = URI::Parser.new - - unless str == parser.unescape(parser.escape(str)) - URI::Parser.class_eval do - remove_method :unescape - def unescape(str, escaped = /%[a-fA-F\d]{2}/) - # TODO: Are we actually sure that ASCII == UTF-8? - # YK: My initial experiments say yes, but let's be sure please - enc = str.encoding - enc = Encoding::UTF_8 if enc == Encoding::US_ASCII - str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc) - end +unless str == parser.unescape(parser.escape(str)) + URI::Parser.class_eval do + remove_method :unescape + def unescape(str, escaped = /%[a-fA-F\d]{2}/) + # TODO: Are we actually sure that ASCII == UTF-8? + # YK: My initial experiments say yes, but let's be sure please + enc = str.encoding + enc = Encoding::UTF_8 if enc == Encoding::US_ASCII + str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc) end end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index cae68c3c95..e121e452a3 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' @@ -70,14 +71,24 @@ module ActiveSupport #:nodoc: # # This is handled by walking back up the watch stack and adding the constants # found by child.rb to the list of original constants in parent.rb - class WatchStack < Hash + class WatchStack + include Enumerable + # @watching is a stack of lists of constants being watched. For instance, # if parent.rb is autoloaded, the stack will look like [[Object]]. If parent.rb # then requires namespace/child.rb, the stack will look like [[Object], [Namespace]]. def initialize @watching = [] - super { |h,k| h[k] = [] } + @stack = Hash.new { |h,k| h[k] = [] } + end + + def each(&block) + @stack.each(&block) + end + + def watching? + !@watching.empty? end # return a list of new constants found since the last call to watch_namespaces @@ -88,7 +99,7 @@ module ActiveSupport #:nodoc: @watching.last.each do |namespace| # Retrieve the constants that were present under the namespace when watch_namespaces # was originally called - original_constants = self[namespace].last + original_constants = @stack[namespace].last mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace) next unless mod.is_a?(Module) @@ -101,7 +112,7 @@ module ActiveSupport #:nodoc: # element of self[Object] will be an Array of the constants that were present # before parent.rb was required. The second element will be an Array of the # constants that were present before child.rb was required. - self[namespace].each do |namespace_constants| + @stack[namespace].each do |namespace_constants| namespace_constants.concat(new_constants) end @@ -125,13 +136,14 @@ module ActiveSupport #:nodoc: Inflector.constantize(module_name).local_constant_names : [] watching << module_name - self[module_name] << original_constants + @stack[module_name] << original_constants end @watching << watching end + private def pop_modules(modules) - modules.each { |mod| self[mod].pop } + modules.each { |mod| @stack[mod].pop } end end @@ -218,8 +230,8 @@ module ActiveSupport #:nodoc: end def load_dependency(file) - if Dependencies.load? - Dependencies.new_constants_in(Object) { yield }.presence + if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching? + Dependencies.new_constants_in(Object) { yield } else yield end @@ -228,12 +240,16 @@ module ActiveSupport #:nodoc: raise end - def load(file, *) - load_dependency(file) { super } + def load(file, wrap = false) + result = false + load_dependency(file) { result = super } + result end - def require(file, *) - load_dependency(file) { super } + def require(file) + result = false + load_dependency(file) { result = super } + result end # Mark the given constant as unloadable. Unloadable constants are removed each @@ -354,24 +370,11 @@ module ActiveSupport #:nodoc: # 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 - end + Object.qualified_const_defined?(path.sub(/^::/, ''), false) end - if Module.method(:const_defined?).arity == 1 - # Does this module define this constant? - # Wrapper to accommodate changing Module#const_defined? in Ruby 1.9 - def local_const_defined?(mod, const) - mod.const_defined?(const) - end - else - def local_const_defined?(mod, const) #:nodoc: - mod.const_defined?(const, false) - end + def local_const_defined?(mod, const) #:nodoc: + mod.const_defined?(const, false) end # Given +path+, a filesystem path to a ruby file, return an array of constant @@ -417,11 +420,12 @@ 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 - # matching the expect path suffix. If found, the module is created and assigned + # matching the expected path suffix. If found, the module is created and assigned # to +into+'s constants with the name +const_name+. Provided that the directory # was loaded from a reloadable base path, it is added to the set of constants # that are to be unloaded. @@ -473,15 +477,11 @@ module ActiveSupport #:nodoc: raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" end - raise ArgumentError, "#{from_mod} is not missing constant #{const_name}!" if local_const_defined?(from_mod, const_name) + raise NameError, "#{from_mod} is not missing constant #{const_name}!" if local_const_defined?(from_mod, const_name) qualified_name = qualified_name_for from_mod, const_name path_suffix = qualified_name.underscore - trace = caller.reject {|l| l =~ %r{#{Regexp.escape(__FILE__)}}} - name_error = NameError.new("uninitialized constant #{qualified_name}") - name_error.set_backtrace(trace) - file_path = search_for_file(path_suffix) if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load @@ -500,11 +500,12 @@ module ActiveSupport #:nodoc: return parent.const_missing(const_name) rescue NameError => e raise unless e.missing_name? qualified_name_for(parent, const_name) - raise name_error end - else - raise name_error end + + raise NameError, + "uninitialized constant #{qualified_name}", + caller.reject {|l| l.starts_with? __FILE__ } end # Remove the constants that have been autoloaded, and those that have been @@ -523,7 +524,7 @@ module ActiveSupport #:nodoc: class ClassCache def initialize - @store = Hash.new { |h, k| h[k] = Inflector.constantize(k) } + @store = Hash.new end def empty? @@ -534,23 +535,24 @@ module ActiveSupport #:nodoc: @store.key?(key) end - def []=(key, value) - return unless key.respond_to?(:name) - - raise(ArgumentError, 'anonymous classes cannot be cached') if key.name.blank? - - @store[key.name] = value + def get(key) + key = key.name if key.respond_to?(:name) + @store[key] ||= Inflector.constantize(key) end + alias :[] :get - def [](key) + def safe_get(key) key = key.name if key.respond_to?(:name) - - @store[key] + @store[key] || begin + klass = Inflector.safe_constantize(key) + @store[key] = klass + end end - alias :get :[] - def store(name) - self[name] = name + def store(klass) + return self unless klass.respond_to?(:name) + raise(ArgumentError, 'anonymous classes cannot be cached') if klass.name.empty? + @store[klass.name] = klass self end @@ -567,10 +569,17 @@ module ActiveSupport #:nodoc: end # Get the reference for class named +name+. + # Raises an exception if referenced class does not exist. def constantize(name) Reference.get(name) end + # Get the reference for class named +name+ if one exists. + # Otherwise returns nil. + def safe_constantize(name) + Reference.safe_get(name) + end + # Determine if the given constant has been automatically loaded. def autoloaded?(desc) # No name => anonymous module. @@ -628,17 +637,6 @@ module ActiveSupport #:nodoc: return [] end - class LoadingModule #:nodoc: - # Old style environment.rb referenced this method directly. Please note, it doesn't - # actually *do* anything any more. - def self.root(*args) - if defined?(Rails) && Rails.logger - Rails.logger.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases." - Rails.logger.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19" - end - end - end - # Convert the provided const desc to a qualified constant name (as a string). # A module, class, symbol, or string may be provided. def to_constant_name(desc) #:nodoc: diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index da4af339fc..f9505a247c 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -7,12 +7,12 @@ module ActiveSupport # Whether to print a backtrace along with the warning. attr_accessor :debug - # Returns the set behaviour or if one isn't set, defaults to +:stderr+ + # Returns the set behavior or if one isn't set, defaults to +:stderr+ def behavior @behavior ||= [DEFAULT_BEHAVIORS[:stderr]] end - # Sets the behaviour to the specified value. Can be a single value or an array. + # Sets the behavior to the specified value. Can be a single value or an array. # # Examples # diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 89b0923882..00c67a470d 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -10,7 +10,6 @@ module ActiveSupport # 1.month.ago # equivalent to Time.now.advance(:months => -1) class Duration < BasicObject attr_accessor :value, :parts - delegate :duplicable?, :to => :value # required when using ActiveSupport's BasicObject on 1.8 def initialize(value, parts) #:nodoc: @value, @parts = value, parts diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index a97e9d7daf..a4ad2da137 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -1,8 +1,28 @@ +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/array/extract_options" + module ActiveSupport - # This class is responsible to track files and invoke the given block - # whenever one of these files are changed. For example, this class - # is used by Rails to reload the I18n framework whenever they are - # changed upon a new request. + # \FileUpdateChecker specifies the API used by Rails to watch files + # and control reloading. The API depends on four methods: + # + # * +initialize+ which expects two parameters and one block as + # described below; + # + # * +updated?+ which returns a boolean if there were updates in + # the filesystem or not; + # + # * +execute+ which executes the given block on initialization + # and updates the counter to the latest timestamp; + # + # * +execute_if_updated+ which just executes the block if it was updated; + # + # After initialization, a call to +execute_if_updated+ must execute + # the block only if there was really a change in the filesystem. + # + # == Examples + # + # This class is used by Rails to reload the I18n framework whenever + # they are changed upon a new request. # # i18n_reloader = ActiveSupport::FileUpdateChecker.new(paths) do # I18n.reload! @@ -13,24 +33,89 @@ module ActiveSupport # end # class FileUpdateChecker - attr_reader :paths, :last_update_at - - def initialize(paths, calculate=false, &block) - @paths = paths + # It accepts two parameters on initialization. The first is an array + # of files and the second is an optional hash of directories. The hash must + # have directories as keys and the value is an array of extensions to be + # watched under that directory. + # + # This method must also receive a block that will be called once a path changes. + # + # == Implementation details + # + # This particular implementation checks for added and updated files, + # but not removed files. Directories lookup are compiled to a glob for + # performance. Therefore, while someone can add new files to the +files+ + # array after initialization (and parts of Rails do depend on this feature), + # adding new directories after initialization is not allowed. + # + # Notice that other objects that implements FileUpdateChecker API may + # not even allow new files to be added after initialization. If this + # is the case, we recommend freezing the +files+ after initialization to + # avoid changes that won't make effect. + def initialize(files, dirs={}, &block) + @files = files + @glob = compile_glob(dirs) @block = block - @last_update_at = calculate ? updated_at : nil + @updated_at = nil + @last_update_at = updated_at + end + + # Check if any of the entries were updated. If so, the updated_at + # value is cached until the block is executed via +execute+ or +execute_if_updated+ + def updated? + current_updated_at = updated_at + if @last_update_at < current_updated_at + @updated_at = updated_at + true + else + false + end end - def updated_at - paths.map { |path| File.stat(path).mtime }.max + # Executes the given block and updates the counter to latest timestamp. + def execute + @last_update_at = updated_at + @block.call + ensure + @updated_at = nil end + # Execute the block given if updated. def execute_if_updated - current_update_at = self.updated_at - if @last_update_at != current_update_at - @last_update_at = current_update_at - @block.call + if updated? + execute + true + else + false + end + end + + private + + def updated_at #:nodoc: + @updated_at || begin + all = [] + all.concat @files.select { |f| File.exists?(f) } + all.concat Dir[@glob] if @glob + all.map { |path| File.mtime(path) }.max || Time.at(0) end end + + def compile_glob(hash) #:nodoc: + hash.freeze # Freeze so changes aren't accidently pushed + return if hash.empty? + + globs = [] + hash.each do |key, value| + globs << "#{key}/**/*#{compile_ext(value)}" + end + "{#{globs.join(",")}}" + end + + def compile_ext(array) #:nodoc: + array = Array.wrap(array) + return if array.empty? + ".{#{array.join(",")}}" + end end end diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index 62f9c9aa2e..f7036315d6 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. @@ -7,7 +8,7 @@ module ActiveSupport class Stream < StringIO def initialize(*) super - set_encoding "BINARY" if "".encoding_aware? + set_encoding "BINARY" end def close; rewind; end end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 15a3717ea1..674e4acfd6 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -6,6 +6,8 @@ require 'active_support/core_ext/hash/keys' module ActiveSupport class HashWithIndifferentAccess < Hash + + # Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class. def extractable_options? true end @@ -14,6 +16,10 @@ module ActiveSupport dup end + def nested_under_indifferent_access + self + end + def initialize(constructor = {}) if constructor.is_a?(Hash) super() @@ -110,7 +116,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..bbeb8d82c6 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" @@ -11,14 +10,19 @@ module I18n config.i18n.fallbacks = ActiveSupport::OrderedOptions.new def self.reloader - @reloader ||= ActiveSupport::FileUpdateChecker.new([]){ I18n.reload! } + @reloader ||= ActiveSupport::FileUpdateChecker.new(reloader_paths){ I18n.reload! } + end + + def self.reloader_paths + @reloader_paths ||= [] end # Add <tt>I18n::Railtie.reloader</tt> to ActionDispatch callbacks. Since, at this # point, no path was added to the reloader, I18n.reload! is not triggered # on to_prepare callbacks. This will only happen on the config.after_initialize # callback below. - initializer "i18n.callbacks" do + initializer "i18n.callbacks" do |app| + app.reloaders << I18n::Railtie.reloader ActionDispatch::Reloader.to_prepare do I18n::Railtie.reloader.execute_if_updated end @@ -38,6 +42,8 @@ module I18n protected + @i18n_inited = false + # Setup i18n configuration def self.initialize_i18n(app) return if @i18n_inited @@ -57,8 +63,8 @@ module I18n init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks) - reloader.paths.concat I18n.load_path - reloader.execute_if_updated + reloader_paths.concat I18n.load_path + reloader.execute @i18n_inited = true end diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index 06ceccdb22..527cce2594 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -16,8 +16,8 @@ module ActiveSupport inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') inflect.plural(/(x|ch|ss|sh)$/i, '\1es') inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices') - inflect.plural(/([m|l])ouse$/i, '\1ice') - inflect.plural(/([m|l])ice$/i, '\1ice') + inflect.plural(/(m|l)ouse$/i, '\1ice') + inflect.plural(/(m|l)ice$/i, '\1ice') inflect.plural(/^(ox)$/i, '\1en') inflect.plural(/^(oxen)$/i, '\1') inflect.plural(/(quiz)$/i, '\1zes') @@ -35,7 +35,7 @@ module ActiveSupport inflect.singular(/(s)eries$/i, '\1eries') inflect.singular(/(m)ovies$/i, '\1ovie') inflect.singular(/(x|ch|ss|sh)es$/i, '\1') - inflect.singular(/([m|l])ice$/i, '\1ouse') + inflect.singular(/(m|l)ice$/i, '\1ouse') inflect.singular(/(bus)es$/i, '\1') inflect.singular(/(o)es$/i, '\1') inflect.singular(/(shoe)s$/i, '\1') @@ -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/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index d5d55b7207..90bb62f57b 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -20,10 +20,61 @@ module ActiveSupport @__instance__ ||= new end - attr_reader :plurals, :singulars, :uncountables, :humans + attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex def initialize - @plurals, @singulars, @uncountables, @humans = [], [], [], [] + @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/ + end + + # Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore + # string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`. + # A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will + # convert the acronym into a non-delimited single lowercase word when passed to +underscore+. + # + # Examples: + # acronym 'HTML' + # titleize 'html' #=> 'HTML' + # camelize 'html' #=> 'HTML' + # underscore 'MyHTML' #=> 'my_html' + # + # The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it: + # + # acronym 'HTTP' + # camelize 'my_http_delimited' #=> 'MyHTTPDelimited' + # camelize 'https' #=> 'Https', not 'HTTPs' + # underscore 'HTTPS' #=> 'http_s', not 'https' + # + # acronym 'HTTPS' + # camelize 'https' #=> 'HTTPS' + # underscore 'HTTPS' #=> 'https' + # + # Note: Acronyms that are passed to `pluralize` will no longer be recognized, since the acronym will not occur as + # a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an + # acronym as well: + # + # acronym 'API' + # camelize(pluralize('api')) #=> 'Apis' + # + # acronym 'APIs' + # camelize(pluralize('api')) #=> 'APIs' + # + # `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard + # capitalization. The only restriction is that the word must begin with a capital letter. + # + # Examples: + # acronym 'RESTful' + # underscore 'RESTful' #=> 'restful' + # underscore 'RESTfulController' #=> 'restful_controller' + # titleize 'RESTfulController' #=> 'RESTful Controller' + # camelize 'restful' #=> 'RESTful' + # camelize 'restful_controller' #=> 'RESTfulController' + # + # acronym 'McDonald' + # underscore 'McDonald' #=> 'mcdonald' + # camelize 'mcdonald' #=> 'McDonald' + def acronym(word) + @acronyms[word.downcase] = word + @acronym_regex = /#{@acronyms.values.join("|")}/ end # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. @@ -117,95 +168,5 @@ module ActiveSupport Inflections.instance end end - - # Returns the plural form of the word in the string. - # - # Examples: - # "post".pluralize # => "posts" - # "octopus".pluralize # => "octopi" - # "sheep".pluralize # => "sheep" - # "words".pluralize # => "words" - # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize(word) - result = word.to_s.dup - - if word.empty? || inflections.uncountables.include?(result.downcase) - result - else - inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # The reverse of +pluralize+, returns the singular form of a word in a string. - # - # Examples: - # "posts".singularize # => "post" - # "octopi".singularize # => "octopus" - # "sheep".singularize # => "sheep" - # "word".singularize # => "word" - # "CamelOctopi".singularize # => "CamelOctopus" - def singularize(word) - result = word.to_s.dup - - if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } - result - else - inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # Capitalizes the first word and turns underscores into spaces and strips a - # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. - # - # Examples: - # "employee_salary" # => "Employee salary" - # "author_id" # => "Author" - def humanize(lower_case_and_underscored_word) - result = lower_case_and_underscored_word.to_s.dup - - inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end - - # Capitalizes all the words and replaces some characters in the string to create - # a nicer looking title. +titleize+ is meant for creating pretty output. It is not - # used in the Rails internals. - # - # +titleize+ is also aliased as as +titlecase+. - # - # Examples: - # "man from the boondocks".titleize # => "Man From The Boondocks" - # "x-men: the last stand".titleize # => "X Men: The Last Stand" - def titleize(word) - humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } - end - - # Create the name of a table like Rails does for models to table names. This method - # uses the +pluralize+ method on the last word in the string. - # - # Examples - # "RawScaledScorer".tableize # => "raw_scaled_scorers" - # "egg_and_ham".tableize # => "egg_and_hams" - # "fancyCategory".tableize # => "fancy_categories" - def tableize(class_name) - pluralize(underscore(class_name)) - end - - # Create a class name from a plural table name like Rails does for table names to models. - # Note that this returns a string and not a Class. (To convert to an actual class - # follow +classify+ with +constantize+.) - # - # Examples: - # "egg_and_hams".classify # => "EggAndHam" - # "posts".classify # => "Post" - # - # Singular names are not handled correctly: - # "business".classify # => "Busines" - def classify(table_name) - # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) - end end end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index a2c4f7bfda..7f325aee94 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,3 +1,5 @@ +require 'active_support/inflector/inflections' + module ActiveSupport # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept @@ -10,6 +12,30 @@ module ActiveSupport module Inflector extend self + # Returns the plural form of the word in the string. + # + # Examples: + # "post".pluralize # => "posts" + # "octopus".pluralize # => "octopi" + # "sheep".pluralize # => "sheep" + # "words".pluralize # => "words" + # "CamelOctopus".pluralize # => "CamelOctopi" + def pluralize(word) + apply_inflections(word, inflections.plurals) + end + + # The reverse of +pluralize+, returns the singular form of a word in a string. + # + # Examples: + # "posts".singularize # => "post" + # "octopi".singularize # => "octopus" + # "sheep".singularize # => "sheep" + # "word".singularize # => "word" + # "CamelOctopi".singularize # => "CamelOctopus" + def singularize(word) + apply_inflections(word, inflections.singulars) + end + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. # @@ -25,12 +51,14 @@ module ActiveSupport # though there are cases where that does not hold: # # "SSLError".underscore.camelize # => "SslError" - def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) - if first_letter_in_uppercase - lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } + def camelize(term, uppercase_first_letter = true) + string = term.to_s + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize } else - lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] + string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } end + string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::') end # Makes an underscored, lowercase form from the expression in the string. @@ -48,13 +76,68 @@ module ActiveSupport def underscore(camel_cased_word) word = camel_cased_word.to_s.dup word.gsub!(/::/, '/') - word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') word.tr!("-", "_") word.downcase! word end + # Capitalizes the first word and turns underscores into spaces and strips a + # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. + # + # Examples: + # "employee_salary" # => "Employee salary" + # "author_id" # => "Author" + def humanize(lower_case_and_underscored_word) + result = lower_case_and_underscored_word.to_s.dup + inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result.gsub!(/_id$/, "") + result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase } + end + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the Rails internals. + # + # +titleize+ is also aliased as as +titlecase+. + # + # Examples: + # "man from the boondocks".titleize # => "Man From The Boondocks" + # "x-men: the last stand".titleize # => "X Men: The Last Stand" + # "TheManWithoutAPast".titleize # => "The Man Without A Past" + # "raiders_of_the_lost_ark".titleize # => "Raiders Of The Lost Ark" + def titleize(word) + humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } + end + + # Create the name of a table like Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # Examples + # "RawScaledScorer".tableize # => "raw_scaled_scorers" + # "egg_and_ham".tableize # => "egg_and_hams" + # "fancyCategory".tableize # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Create a class name from a plural table name like Rails does for table names to models. + # Note that this returns a string and not a Class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # Examples: + # "egg_and_hams".classify # => "EggAndHam" + # "posts".classify # => "Post" + # + # Singular names are not handled correctly: + # "business".classify # => "Busines" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + end + # Replaces underscores with dashes in the string. # # Example: @@ -63,13 +146,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. @@ -84,46 +186,64 @@ module ActiveSupport underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") end - # Ruby 1.9 introduces an inherit argument for Module#const_get and - # #const_defined? and changes their default behavior. - if Module.method(:const_get).arity == 1 - # Tries to find a constant with the name specified in the argument string: - # - # "Module".constantize # => Module - # "Test::Unit".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".constantize # => 'outside', same as ::C - # end - # - # NameError is raised when the name is not in CamelCase or the constant is - # unknown. - def constantize(camel_cased_word) - names = camel_cased_word.split('::') - names.shift if names.empty? || names.first.empty? - - constant = Object - names.each do |name| - constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) - end - constant + # Tries to find a constant with the name specified in the argument string: + # + # "Module".constantize # => Module + # "Test::Unit".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".constantize # => 'outside', same as ::C + # end + # + # NameError is raised when the name is not in CamelCase or the constant is + # unknown. + def constantize(camel_cased_word) #:nodoc: + names = camel_cased_word.split('::') + names.shift if names.empty? || names.first.empty? + + constant = Object + names.each do |name| + constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name) end - else - def constantize(camel_cased_word) #:nodoc: - names = camel_cased_word.split('::') - names.shift if names.empty? || names.first.empty? - - constant = Object - names.each do |name| - constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name) - end - constant + constant + 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 @@ -149,5 +269,34 @@ 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 + + # Applies inflection rules for +singularize+ and +pluralize+. + # + # Examples: + # apply_inflections("post", inflections.plurals) # => "posts" + # apply_inflections("posts", inflections.singulars) # => "post" + def apply_inflections(word, rules) + result = word.to_s.dup + + if word.empty? || inflections.uncountables.any? { |inflection| result =~ /\b#{inflection}\Z/i } + result + else + rules.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end end end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 67698c1cff..d7181035d3 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 @@ -50,16 +50,16 @@ module ActiveSupport end # like encode, but only calls as_json, without encoding to string - def as_json(value) + def as_json(value, use_options = true) check_for_circular_references(value) do - value.as_json(options_for(value)) + use_options ? value.as_json(options_for(value)) : value.as_json end end 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 @@ -119,9 +119,7 @@ module ActiveSupport end def escape(string) - if string.respond_to?(:force_encoding) - string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY) - end + string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY) json = string. gsub(escape_regex) { |s| ESCAPED_CHARS[s] }. gsub(/([\xC0-\xDF][\x80-\xBF]| @@ -130,7 +128,7 @@ module ActiveSupport s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&') } json = %("#{json}") - json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding) + json.force_encoding(::Encoding::UTF_8) json end end @@ -212,7 +210,7 @@ class Array def as_json(options = nil) #:nodoc: # use encoder as a proxy to call as_json on all elements, to protect from circular references encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options) - map { |v| encoder.as_json(v) } + map { |v| encoder.as_json(v, options) } end def encode_json(encoder) #:nodoc: @@ -239,7 +237,7 @@ class Hash # use encoder as a proxy to call as_json on all values in the subset, to protect from circular references encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options) result = self.is_a?(ActiveSupport::OrderedHash) ? ActiveSupport::OrderedHash : Hash - result[subset.map { |k, v| [k.to_s, encoder.as_json(v)] }] + result[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }] end def encode_json(encoder) diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 1c4dd24227..6296c1d4b8 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -3,8 +3,8 @@ require 'active_support/core_ext/class/attribute' module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications - # with solely purpose of logging. The log subscriber dispatches notifications to a - # registered object based on its given namespace. + # with the sole purpose of logging them. The log subscriber dispatches notifications to + # a registered object based on its given namespace. # # An example would be Active Record log subscriber responsible for logging queries: # @@ -109,8 +109,8 @@ module ActiveSupport # Set color by using a string or one of the defined constants. If a third # option is set to true, it also adds bold to the string. This is based - # on Highline implementation and it automatically appends CLEAR to the end - # of the returned String. + # on the Highline implementation and will automatically append CLEAR to the + # end of the returned String. # def color(text, color, bold=false) return text unless colorize_logging diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 3e54134c5c..7b7fc81e6c 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 # @@ -50,7 +50,7 @@ module ActiveSupport end class MockLogger - include ActiveSupport::BufferedLogger::Severity + include ActiveSupport::Logger::Severity attr_reader :flush_count attr_accessor :level @@ -73,7 +73,7 @@ module ActiveSupport @flush_count += 1 end - ActiveSupport::BufferedLogger::Severity.constants.each do |severity| + ActiveSupport::Logger::Severity.constants.each do |severity| class_eval <<-EOT, __FILE__, __LINE__ + 1 def #{severity.downcase}? #{severity} >= @level diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb new file mode 100644 index 0000000000..66e8fcadb4 --- /dev/null +++ b/activesupport/lib/active_support/logger.rb @@ -0,0 +1,18 @@ +require 'logger' + +module ActiveSupport + class Logger < ::Logger + def initialize(*args) + super + @formatter = SimpleFormatter.new + end + + # Simple formatter which only displays the message. + class SimpleFormatter < ::Logger::Formatter + # This method is invoked when a log event occurs + def call(severity, timestamp, progname, msg) + "#{String === msg ? msg : msg.inspect}\n" + end + end + end +end diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb deleted file mode 100644 index 0a7bcd5bb8..0000000000 --- a/activesupport/lib/active_support/memoizable.rb +++ /dev/null @@ -1,105 +0,0 @@ -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/module/aliasing' - -module ActiveSupport - module Memoizable - def self.memoized_ivar_for(symbol) - "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym - end - - module InstanceMethods - def self.included(base) - base.class_eval do - unless base.method_defined?(:freeze_without_memoizable) - alias_method_chain :freeze, :memoizable - end - end - end - - def freeze_with_memoizable - memoize_all unless frozen? - freeze_without_memoizable - end - - def memoize_all - prime_cache ".*" - end - - def unmemoize_all - flush_cache ".*" - end - - def prime_cache(*syms) - syms.each do |sym| - methods.each do |m| - if m.to_s =~ /^_unmemoized_(#{sym})/ - if method(m).arity == 0 - __send__($1) - else - ivar = ActiveSupport::Memoizable.memoized_ivar_for($1) - instance_variable_set(ivar, {}) - end - end - end - end - end - - def flush_cache(*syms) - syms.each do |sym| - (methods + private_methods + protected_methods).each do |m| - if m.to_s =~ /^_unmemoized_(#{sym.to_s.gsub(/\?\Z/, '\?')})/ - ivar = ActiveSupport::Memoizable.memoized_ivar_for($1) - instance_variable_get(ivar).clear if instance_variable_defined?(ivar) - end - end - end - end - end - - def memoize(*symbols) - symbols.each do |symbol| - original_method = :"_unmemoized_#{symbol}" - memoized_ivar = ActiveSupport::Memoizable.memoized_ivar_for(symbol) - - class_eval <<-EOS, __FILE__, __LINE__ + 1 - include InstanceMethods # include InstanceMethods - # - if method_defined?(:#{original_method}) # if method_defined?(:_unmemoized_mime_type) - raise "Already memoized #{symbol}" # raise "Already memoized mime_type" - end # end - alias #{original_method} #{symbol} # alias _unmemoized_mime_type mime_type - # - if instance_method(:#{symbol}).arity == 0 # if instance_method(:mime_type).arity == 0 - def #{symbol}(reload = false) # def mime_type(reload = false) - if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty? # if reload || !defined?(@_memoized_mime_type) || @_memoized_mime_type.empty? - #{memoized_ivar} = [#{original_method}] # @_memoized_mime_type = [_unmemoized_mime_type] - end # end - #{memoized_ivar}[0] # @_memoized_mime_type[0] - end # end - else # else - def #{symbol}(*args) # def mime_type(*args) - #{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen? - reload = args.pop if args.last == true || args.last == :reload # reload = args.pop if args.last == true || args.last == :reload - # - if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type - if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args) - #{memoized_ivar}[args] # @_memoized_mime_type[args] - elsif #{memoized_ivar} # elsif @_memoized_mime_type - #{memoized_ivar}[args] = #{original_method}(*args) # @_memoized_mime_type[args] = _unmemoized_mime_type(*args) - end # end - else # else - #{original_method}(*args) # _unmemoized_mime_type(*args) - end # end - end # end - end # end - # - if private_method_defined?(#{original_method.inspect}) # if private_method_defined?(:_unmemoized_mime_type) - private #{symbol.inspect} # private :mime_type - elsif protected_method_defined?(#{original_method.inspect}) # elsif protected_method_defined?(:_unmemoized_mime_type) - protected #{symbol.inspect} # protected :mime_type - end # end - EOS - end - end - end -end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 4f7cd12d48..476ba0b3d1 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -10,15 +10,41 @@ module ActiveSupport # This can be used in situations similar to the <tt>MessageVerifier</tt>, but where you don't # want users to be able to determine the value of the payload. class MessageEncryptor + module NullSerializer #:nodoc: + def self.load(value) + value + end + + def self.dump(value) + value + end + end + 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 = {}) @secret = secret - @cipher = cipher + @cipher = options[:cipher] || 'aes-256-cbc' + @verifier = MessageVerifier.new(@secret, :serializer => NullSerializer) + @serializer = options[:serializer] || Marshal + end + + # Encrypt and sign a message. We need to sign the message in order to avoid padding attacks. + # Reference: http://www.limited-entropy.com/padding-oracle-attacks + def encrypt_and_sign(value) + verifier.generate(_encrypt(value)) + end + + # Decrypt and verify a message. We need to verify the message in order to avoid padding attacks. + # Reference: http://www.limited-entropy.com/padding-oracle-attacks + def decrypt_and_verify(value) + _decrypt(verifier.verify(value)) end - def encrypt(value) + private + + def _encrypt(value) cipher = new_cipher # Rely on OpenSSL for the initialization vector iv = cipher.random_iv @@ -27,13 +53,13 @@ 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("--") + [encrypted_data, iv].map {|v| ActiveSupport::Base64.strict_encode64(v)}.join("--") end - def decrypt(encrypted_message) + def _decrypt(encrypted_message) cipher = new_cipher encrypted_data, iv = encrypted_message.split("--").map {|v| ActiveSupport::Base64.decode64(v)} @@ -44,28 +70,17 @@ 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 - def encrypt_and_sign(value) - verifier.generate(encrypt(value)) + def new_cipher + OpenSSL::Cipher::Cipher.new(@cipher) end - def decrypt_and_verify(value) - decrypt(verifier.verify(value)) + def verifier + @verifier end - - - - private - def new_cipher - OpenSSL::Cipher::Cipher.new(@cipher) - end - - def verifier - MessageVerifier.new(@secret) - end end end diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 8f3946325a..7cb5b1e82d 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -18,12 +18,18 @@ 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 = {}) @secret = secret - @digest = digest + @digest = options[:digest] || 'SHA1' + @serializer = options[:serializer] || Marshal end def verify(signed_message) @@ -31,14 +37,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.strict_encode64(@serializer.dump(value)) "#{data}--#{generate_digest(data)}" end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index f9607f1aaf..dcc176e93f 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -38,16 +38,10 @@ module ActiveSupport #:nodoc: alias to_s wrapped_string alias to_str wrapped_string - if RUBY_VERSION >= "1.9" - # 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) #:nodoc: - @wrapped_string = string - end + # 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 # Forward all undefined methods to the wrapped string. @@ -94,151 +88,8 @@ module ActiveSupport #:nodoc: @wrapped_string <=> other.to_s end - if RUBY_VERSION < "1.9" - # Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns - # +false+ otherwise. - def self.wants?(string) - $KCODE == 'UTF8' && consumes?(string) - 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) - chars(@wrapped_string + other) - end - - # 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 - def =~(other) - translate_offset(@wrapped_string =~ other) - 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 = Unicode.u_unpack(@wrapped_string) - unless offset > unpacked.length - @wrapped_string.replace( - Unicode.u_unpack(@wrapped_string).insert(offset, *Unicode.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 _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found. - # - # Example: - # 'Café périferôl'.mb_chars.index('ô') # => 12 - # 'Café périferôl'.mb_chars.index(/\w/u) # => 0 - def index(needle, offset=0) - wrapped_offset = first(offset).wrapped_string.length - index = @wrapped_string.index(needle, wrapped_offset) - index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil - end - - # Returns the position _needle_ in the string, counting in - # codepoints, searching backward from _offset_ or the end of the - # string. Returns +nil+ if _needle_ isn't found. - # - # Example: - # 'Café périferôl'.mb_chars.rindex('é') # => 6 - # 'Café périferôl'.mb_chars.rindex(/\w/u) # => 13 - def rindex(needle, offset=nil) - offset ||= length - wrapped_offset = first(offset).wrapped_string.length - index = @wrapped_string.rindex(needle, wrapped_offset) - index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil - end - - # Returns the number of codepoints in the string - def size - Unicode.u_unpack(@wrapped_string).size - end - alias_method :length, :size - - # 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 codepoint of the first character in the string. - # - # Example: - # 'こんにちは'.mb_chars.ord # => 12371 - def ord - Unicode.u_unpack(@wrapped_string)[0] - end - - # Works just like <tt>String#rjust</tt>, only integer specifies characters instead of bytes. - # - # Example: - # - # "¾ cup".mb_chars.rjust(8).to_s - # # => " ¾ cup" - # - # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace - # # => " ¾ cup" - def rjust(integer, padstr=' ') - justify(integer, :right, padstr) - end - - # Works just like <tt>String#ljust</tt>, only integer specifies characters instead of bytes. - # - # Example: - # - # "¾ cup".mb_chars.rjust(8).to_s - # # => "¾ cup " - # - # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace - # # => "¾ cup " - def ljust(integer, padstr=' ') - justify(integer, :left, padstr) - end - - # Works just like <tt>String#center</tt>, only integer specifies characters instead of bytes. - # - # Example: - # - # "¾ cup".mb_chars.center(8).to_s - # # => " ¾ cup " - # - # "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace - # # => " ¾ cup " - def center(integer, padstr=' ') - justify(integer, :center, padstr) - end - - else - def =~(other) - @wrapped_string =~ other - end + def =~(other) + @wrapped_string =~ other end # Works just like <tt>String#split</tt>, with the exception that the items in the resulting list are Chars @@ -331,8 +182,7 @@ module ActiveSupport #:nodoc: # when the storage for a string is limited for some reason. # # Example: - # s = 'こんにちは' - # s.mb_chars.limit(7) # => "こに" + # 'こんにちは'.mb_chars.limit(7).to_s # => "こん" def limit(limit) slice(0...translate_offset(limit)) end @@ -432,9 +282,7 @@ module ActiveSupport #:nodoc: return nil if byte_offset.nil? return 0 if @wrapped_string == '' - if @wrapped_string.respond_to?(:force_encoding) - @wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT) - end + @wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT) begin @wrapped_string[0...byte_offset].unpack('U*').length diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 513f83e445..754ca9290b 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 module ActiveSupport module Multibyte module Unicode diff --git a/activesupport/lib/active_support/multibyte/utils.rb b/activesupport/lib/active_support/multibyte/utils.rb index 94b393cee2..bd6d4bad41 100644 --- a/activesupport/lib/active_support/multibyte/utils.rb +++ b/activesupport/lib/active_support/multibyte/utils.rb @@ -2,36 +2,14 @@ module ActiveSupport #:nodoc: module Multibyte #:nodoc: - if Kernel.const_defined?(:Encoding) - # Returns a regular expression that matches valid characters in the current encoding - def self.valid_character - VALID_CHARACTER[Encoding.default_external.to_s] - end - else - def self.valid_character - case $KCODE - when 'UTF8' - VALID_CHARACTER['UTF-8'] - when 'SJIS' - VALID_CHARACTER['Shift_JIS'] - end - end + # Returns a regular expression that matches valid characters in the current encoding + def self.valid_character + VALID_CHARACTER[Encoding.default_external.to_s] end - if 'string'.respond_to?(:valid_encoding?) - # Verifies the encoding of a string - def self.verify(string) - string.valid_encoding? - end - else - def self.verify(string) - if expression = valid_character - # Splits the string on character boundaries, which are determined based on $KCODE. - string.split(//).all? { |c| expression =~ c } - else - true - end - end + # Verifies the encoding of a string + def self.verify(string) + string.valid_encoding? end # Verifies the encoding of the string and raises an exception when it's not valid @@ -39,22 +17,11 @@ module ActiveSupport #:nodoc: raise EncodingError.new("Found characters with invalid encoding") unless verify(string) end - if 'string'.respond_to?(:force_encoding) - # Removes all invalid characters from the string. - # - # Note: this method is a no-op in Ruby 1.9 - def self.clean(string) - string - end - else - def self.clean(string) - if expression = valid_character - # Splits the string on character boundaries, which are determined based on $KCODE. - string.split(//).grep(expression).join - else - string - end - end + # Removes all invalid characters from the string. + # + # Note: this method is a no-op in Ruby 1.9 + def self.clean(string) + string end end end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 77696eb1db..f549d2fff3 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -1,38 +1,102 @@ -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: + # = Notifications + # + # +ActiveSupport::Notifications+ provides an instrumentation API for Ruby. + # + # == Instrumenters + # + # To instrument an event you just need to do: # - # ActiveSupport::Notifications.instrument(:render, :extra => :information) do + # ActiveSupport::Notifications.instrument("render", :extra => :information) do # render :text => "Foo" # end # + # That executes the block first and notifies all subscribers once done. + # + # In the example above "render" is the name of the event, and the rest is called + # the _payload_. The payload is a mechanism that allows instrumenters to pass + # extra information to subscribers. Payloads consist of a hash whose contents + # are arbitrary and generally depend on the event. + # + # == Subscribers + # # You can consume those events and the information they provide by registering - # a log subscriber. For instance, let's store all instrumented events in an array: + # a subscriber. For instance, let's store all "render" events in an array: # - # @events = [] + # events = [] # - # ActiveSupport::Notifications.subscribe do |*args| - # @events << ActiveSupport::Notifications::Event.new(*args) + # ActiveSupport::Notifications.subscribe("render") do |*args| + # events << ActiveSupport::Notifications::Event.new(*args) # end # - # ActiveSupport::Notifications.instrument(:render, :extra => :information) do + # That code returns right away, you are just subscribing to "render" events. + # The block will be called asynchronously whenever someone instruments "render": + # + # ActiveSupport::Notifications.instrument("render", :extra => :information) do # render :text => "Foo" # end # - # event = @events.first - # event.name # => :render + # event = events.first + # event.name # => "render" # event.duration # => 10 (in milliseconds) # event.payload # => { :extra => :information } # - # When subscribing to Notifications, you can pass a pattern, to only consume - # events that match the pattern: + # The block in the +subscribe+ call gets the name of the event, start + # timestamp, end timestamp, a string with a unique identifier for that event + # (something like "535801666f04d0298cd6"), and a hash with the payload, in + # that order. + # + # If an exception happens during that particular instrumentation the payload will + # have a key +:exception+ with an array of two elements as value: a string with + # the name of the exception class, and the exception message. + # + # As the previous example depicts, the class +ActiveSupport::Notifications::Event+ + # is able to take the arguments as they come and provide an object-oriented + # interface to that data. + # + # You can also subscribe to all events whose name matches a certain regexp: + # + # ActiveSupport::Notifications.subscribe(/render/) do |*args| + # ... + # end + # + # and even pass no argument to +subscribe+, in which case you are subscribing + # to all events. # - # ActiveSupport::Notifications.subscribe(/render/) do |event| - # @render_events << event + # == Temporary Subscriptions + # + # Sometimes you do not want to subscribe to an event for the entire life of + # the application. There are two ways to unsubscribe. + # + # === Subscribe While a Block Runs + # + # You can subscribe to some event temporarily while some block runs. For + # example, in + # + # callback = lambda {|*args| ... } + # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do + # ... + # end + # + # the callback will be called for all "sql.active_record" events instrumented + # during the execution of the block. The callback is unsubscribed automatically + # after that. + # + # === Manual Unsubscription + # + # The +subscribe+ method returns a subscriber object: + # + # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args| + # ... # end # + # To prevent that block from being called anymore, just unsubscribe passing + # that reference: + # + # ActiveSupport::Notifications.unsubscribe(subscriber) + # + # == Default Queue + # # Notifications ships with a queue implementation that consumes and publish events # to log subscribers in a thread. You can use any queue implementation you want. # @@ -64,6 +128,13 @@ module ActiveSupport end end + def subscribed(callback, *args, &block) + subscriber = subscribe(*args, &callback) + yield + ensure + unsubscribe(subscriber) + end + def unsubscribe(args) notifier.unsubscribe(args) @instrumenters.clear diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 762a64a881..d4f309fbd7 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -6,7 +6,7 @@ end require 'yaml' YAML.add_builtin_type("omap") do |type, val| - ActiveSupport::OrderedHash[val.map(&:to_a).map(&:first)] + ActiveSupport::OrderedHash[val.map{ |v| v.to_a.first }] end module ActiveSupport @@ -20,7 +20,7 @@ module ActiveSupport # oh.keys # => [:a, :b], this order is guaranteed # # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations. - class OrderedHash < ::Hash #:nodoc: + class OrderedHash < ::Hash def to_yaml_type "!tag:yaml.org,2002:omap" end @@ -47,167 +47,9 @@ module ActiveSupport self end - # Hash is ordered in Ruby 1.9! - if RUBY_VERSION < '1.9' - - # In MRI the Hash class is core and written in C. In particular, methods are - # programmed with explicit C function calls and polymorphism is not honored. - # - # For example, []= is crucial in this implementation to maintain the @keys - # array but hash.c invokes rb_hash_aset() originally. This prevents method - # reuse through inheritance and forces us to reimplement stuff. - # - # For instance, we cannot use the inherited #merge! because albeit the algorithm - # itself would work, our []= is not being called at all by the C code. - - def initialize(*args, &block) - super - @keys = [] - end - - def self.[](*args) - ordered_hash = new - - if (args.length == 1 && args.first.is_a?(Array)) - args.first.each do |key_value_pair| - next unless (key_value_pair.is_a?(Array)) - ordered_hash[key_value_pair[0]] = key_value_pair[1] - end - - return ordered_hash - end - - unless (args.size % 2 == 0) - raise ArgumentError.new("odd number of arguments for Hash") - end - - args.each_with_index do |val, ind| - next if (ind % 2 != 0) - ordered_hash[val] = args[ind + 1] - end - - ordered_hash - end - - def initialize_copy(other) - super - # make a deep copy of keys - @keys = other.keys - end - - def []=(key, value) - @keys << key unless has_key?(key) - super - end - - def delete(key) - if has_key? key - index = @keys.index(key) - @keys.delete_at index - end - super - end - - def delete_if - super - sync_keys! - self - end - - def reject! - super - sync_keys! - self - end - - def reject(&block) - dup.reject!(&block) - end - - def keys - @keys.dup - end - - def values - @keys.collect { |key| self[key] } - end - - def to_hash - self - end - - def to_a - @keys.map { |key| [ key, self[key] ] } - end - - def each_key - return to_enum(:each_key) unless block_given? - @keys.each { |key| yield key } - self - end - - def each_value - return to_enum(:each_value) unless block_given? - @keys.each { |key| yield self[key]} - self - end - - def each - return to_enum(:each) unless block_given? - @keys.each {|key| yield [key, self[key]]} - self - end - - alias_method :each_pair, :each - - alias_method :select, :find_all - - def clear - super - @keys.clear - self - end - - def shift - k = @keys.first - v = delete(k) - [k, v] - end - - def merge!(other_hash) - if block_given? - other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v } - else - other_hash.each { |k, v| self[k] = v } - end - self - end - - alias_method :update, :merge! - - def merge(other_hash, &block) - dup.merge!(other_hash, &block) - end - - # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not. - def replace(other) - super - @keys = other.keys - self - end - - def invert - OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}] - end - - def inspect - "#<OrderedHash #{super}>" - end - - private - def sync_keys! - @keys.delete_if {|k| !has_key?(k)} - end + # Returns true to make sure that this hash is extractable via <tt>Array#extract_options!</tt> + def extractable_options? + true end end end diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 8d8e6ebc58..bf81567d22 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -36,6 +36,10 @@ module ActiveSupport #:nodoc: self[name] end end + + def respond_to?(name) + true + end end class InheritableOptions < OrderedOptions diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 04df2ea562..f696716cc8 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -1,17 +1,10 @@ require "active_support" -require "rails" require "active_support/i18n_railtie" module ActiveSupport class Railtie < Rails::Railtie config.active_support = ActiveSupport::OrderedOptions.new - # Loads support for "whiny nil" (noisy warnings when methods are invoked - # on +nil+ values) if Configuration#whiny_nils is true. - initializer "active_support.initialize_whiny_nils" do |app| - require 'active_support/whiny_nil' if app.config.whiny_nils - end - initializer "active_support.deprecation_behavior" do |app| if deprecation = app.config.active_support.deprecation ActiveSupport::Deprecation.behavior = deprecation diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb index 608b3fe4b9..41fd866481 100644 --- a/activesupport/lib/active_support/ruby/shim.rb +++ b/activesupport/lib/active_support/ruby/shim.rb @@ -3,20 +3,14 @@ # # Date next_year, next_month # DateTime to_date, to_datetime, xmlschema -# Enumerable group_by, each_with_object, none? -# Process Process.daemon -# REXML security fix +# Enumerable group_by, none? # String ord # Time to_date, to_time, to_datetime require 'active_support' require 'active_support/core_ext/date/calculations' require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/process/daemon' require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/string/interpolation' require 'active_support/core_ext/string/encoding' -require 'active_support/core_ext/rexml' require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/file/path' -require 'active_support/core_ext/module/method_names'
\ No newline at end of file diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb new file mode 100644 index 0000000000..8eae43188d --- /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 unknown ).each do |severity| + eval <<-EOM, nil, __FILE__, __LINE__ + 1 + def #{severity}(progname = nil, &block) # def warn(progname = nil, &block) + add(Logger::#{severity.upcase}, progname, &block) # add(Logger::WARN, progname, &block) + end # end + EOM + end + + def flush + @tags.delete(Thread.current) + @logger.flush 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/test_case.rb b/activesupport/lib/active_support/test_case.rb index 8d6c27e381..707544e594 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -10,15 +10,9 @@ require 'active_support/core_ext/kernel/reporting' module ActiveSupport class TestCase < ::Test::Unit::TestCase - if defined? MiniTest - Assertion = MiniTest::Assertion - alias_method :method_name, :name if method_defined? :name - alias_method :method_name, :__name__ if method_defined? :__name__ - else - Assertion = Test::Unit::AssertionFailedError - - undef :default_test - end + Assertion = MiniTest::Assertion + alias_method :method_name, :name if method_defined? :name + alias_method :method_name, :__name__ if method_defined? :__name__ $tags = {} def self.for_tag(tag) diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 05da50e150..4e1a58a801 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 @@ -68,7 +70,7 @@ module ActiveSupport # # A error message can be specified. # - # assert_no_difference 'Article.count', "An Article should not be destroyed" do + # assert_no_difference 'Article.count', "An Article should not be created" do # post :create, :article => invalid_attributes # end def assert_no_difference(expression, message = nil, &block) @@ -85,7 +87,7 @@ module ActiveSupport # Test if an expression is not blank. Passes if object.present? is true. # - # assert_present {:data => 'x' } # => true + # assert_present({:data => 'x' }) # => true def assert_present(object, message=nil) message ||= "#{object.inspect} is blank" assert object.present?, message diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 02c19448fd..209bfac19f 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -9,18 +9,13 @@ module ActiveSupport module Testing module Performance extend ActiveSupport::Concern - + included do superclass_delegating_accessor :profile_options self.profile_options = {} - - if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions - include ForMiniTest - else - include ForClassicTestUnit - end + include ForMiniTest end - + # each implementation should define metrics and freeze the defaults DEFAULTS = if ARGV.include?('--benchmark') # HAX for rake test @@ -32,7 +27,7 @@ module ActiveSupport :output => 'tmp/performance', :benchmark => false } end - + def full_profile_options DEFAULTS.merge(profile_options) end @@ -40,7 +35,7 @@ module ActiveSupport def full_test_name "#{self.class.name}##{method_name}" end - + module ForMiniTest def run(runner) @runner = runner @@ -53,7 +48,7 @@ module ActiveSupport end end end - + return end @@ -77,52 +72,10 @@ module ActiveSupport end end - module ForClassicTestUnit - def run(result) - return if method_name =~ /^default_test$/ - - yield(self.class::STARTED, name) - @_result = result - - run_warmup - if full_profile_options && metrics = full_profile_options[:metrics] - metrics.each do |metric_name| - if klass = Metrics[metric_name.to_sym] - run_profile(klass.new) - result.add_run - else - puts '%20s: unsupported' % metric_name - end - end - end - - yield(self.class::FINISHED, name) - end - - def run_test(metric, mode) - run_callbacks :setup - setup - metric.send(mode) { __send__ @method_name } - rescue ::Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError => e - add_error(e) - ensure - begin - teardown - run_callbacks :teardown, :enumerator => :reverse_each - rescue ::Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError => e - add_error(e) - end - end - end - protected # overridden by each implementation def run_gc; end - + def run_warmup run_gc @@ -132,7 +85,7 @@ module ActiveSupport run_gc end - + def run_profile(metric) klass = full_profile_options[:benchmark] ? Benchmarker : Profiler performer = klass.new(self, metric) @@ -163,7 +116,7 @@ module ActiveSupport "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}" end end - + # overridden by each implementation class Profiler < Performer def time_with_block @@ -171,7 +124,7 @@ module ActiveSupport yield Time.now - before end - + def run; end def record; end end @@ -181,10 +134,10 @@ module ActiveSupport super @supported = @metric.respond_to?('measure') end - + def run return unless @supported - + full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } @total = @metric.total end @@ -208,8 +161,7 @@ module ActiveSupport end end - ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' - ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" + ruby = "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" @env = [app, rails, ruby, RUBY_PLATFORM] * ',' end @@ -237,7 +189,7 @@ module ActiveSupport "#{super}.csv" end end - + module Metrics def self.[](name) const_get(name.to_s.camelize) @@ -247,7 +199,7 @@ module ActiveSupport class Base include ActionView::Helpers::NumberHelper - + attr_reader :total def initialize @@ -265,15 +217,15 @@ module ActiveSupport @total += (measure - before) end end - + # overridden by each implementation def profile; end - + protected # overridden by each implementation def with_gc_stats; end end - + class Time < Base def measure ::Time.now.to_f @@ -287,26 +239,25 @@ module ActiveSupport end end end - + class Amount < Base def format(measurement) number_with_delimiter(measurement.floor) end end - + class DigitalInformationUnit < Base def format(measurement) number_to_human_size(measurement, :precision => 2) end end - + # each implementation provides its own metrics like ProcessTime, Memory or GcRuns end end end end -RUBY_ENGINE = 'ruby' unless defined?(RUBY_ENGINE) # mri 1.8 case RUBY_ENGINE when 'ruby' then require 'active_support/testing/performance/ruby' when 'rbx' then require 'active_support/testing/performance/rubinius' diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb index 326904bbac..b347539f13 100644 --- a/activesupport/lib/active_support/testing/performance/jruby.rb +++ b/activesupport/lib/active_support/testing/performance/jruby.rb @@ -1,5 +1,5 @@ require 'jruby/profiler' -require 'java' +require 'java' java_import java.lang.management.ManagementFactory module ActiveSupport @@ -12,21 +12,21 @@ module ActiveSupport { :metrics => [:wall_time], :formats => [:flat, :graph] } end).freeze - + protected def run_gc ManagementFactory.memory_mx_bean.gc - end + end class Profiler < Performer def initialize(*args) super @supported = @metric.is_a?(Metrics::WallTime) end - + def run return unless @supported - + @total = time_with_block do @data = JRuby::Profiler.profile do full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } @@ -36,7 +36,7 @@ module ActiveSupport def record return unless @supported - + klasses = full_profile_options[:formats].map { |f| JRuby::Profiler.const_get("#{f.to_s.camelize}ProfilePrinter") }.compact klasses.each do |klass| @@ -61,7 +61,7 @@ module ActiveSupport end end - module Metrics + module Metrics class Base def profile yield @@ -85,7 +85,7 @@ module ActiveSupport ManagementFactory.thread_mx_bean.get_current_thread_cpu_time / 1000 / 1000 / 1000.0 # seconds end end - + class UserTime < Time def measure ManagementFactory.thread_mx_bean.get_current_thread_user_time / 1000 / 1000 / 1000.0 # seconds @@ -97,7 +97,7 @@ module ActiveSupport ManagementFactory.memory_mx_bean.non_heap_memory_usage.used + ManagementFactory.memory_mx_bean.heap_memory_usage.used end end - + class GcRuns < Amount def measure ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_runs, current_gc| total_runs += current_gc.collection_count } diff --git a/activesupport/lib/active_support/testing/performance/rubinius.rb b/activesupport/lib/active_support/testing/performance/rubinius.rb index 198d235548..d9ebfbe352 100644 --- a/activesupport/lib/active_support/testing/performance/rubinius.rb +++ b/activesupport/lib/active_support/testing/performance/rubinius.rb @@ -10,12 +10,12 @@ module ActiveSupport { :metrics => [:wall_time], :formats => [:flat, :graph] } end).freeze - + protected def run_gc GC.run(true) end - + class Performer; end class Profiler < Performer @@ -23,35 +23,35 @@ module ActiveSupport super @supported = @metric.is_a?(Metrics::WallTime) end - + def run return unless @supported - + @profiler = Rubinius::Profiler::Instrumenter.new - + @total = time_with_block do @profiler.profile(false) do full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } end end end - + def record return unless @supported - + if(full_profile_options[:formats].include?(:flat)) create_path_and_open_file(:flat) do |file| @profiler.show(file) end end - + if(full_profile_options[:formats].include?(:graph)) create_path_and_open_file(:graph) do |file| @profiler.show(file) end end end - + protected def create_path_and_open_file(printer_name) fname = "#{output_filename}_#{printer_name}.txt" @@ -62,10 +62,10 @@ module ActiveSupport end end - module Metrics + module Metrics class Base attr_reader :loopback - + def profile yield end diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb index b29ec6719c..26731c6bd7 100644 --- a/activesupport/lib/active_support/testing/performance/ruby.rb +++ b/activesupport/lib/active_support/testing/performance/ruby.rb @@ -16,7 +16,7 @@ module ActiveSupport :metrics => [:process_time, :memory, :objects], :formats => [:flat, :graph_html, :call_tree, :call_stack] } end).freeze - + protected def run_gc GC.start @@ -77,7 +77,7 @@ module ActiveSupport def measure_mode self.class::Mode end - + def profile RubyProf.resume yield @@ -91,7 +91,7 @@ module ActiveSupport yield end end - + class ProcessTime < Time Mode = RubyProf::PROCESS_TIME if RubyProf.const_defined?(:PROCESS_TIME) @@ -144,8 +144,6 @@ end if RUBY_VERSION.between?('1.9.2', '2.0') require 'active_support/testing/performance/ruby/yarv' -elsif RUBY_VERSION.between?('1.8.6', '1.9') - require 'active_support/testing/performance/ruby/mri' else $stderr.puts 'Update your ruby interpreter to be able to run benchmarks.' exit diff --git a/activesupport/lib/active_support/testing/performance/ruby/mri.rb b/activesupport/lib/active_support/testing/performance/ruby/mri.rb deleted file mode 100644 index 86e650050b..0000000000 --- a/activesupport/lib/active_support/testing/performance/ruby/mri.rb +++ /dev/null @@ -1,59 +0,0 @@ -module ActiveSupport - module Testing - module Performance - module Metrics - class Base - protected - # Ruby 1.8 + ruby-prof wrapper (enable/disable stats for Benchmarker) - if GC.respond_to?(:enable_stats) - def with_gc_stats - GC.enable_stats - GC.start - yield - ensure - GC.disable_stats - end - end - end - - class Memory < DigitalInformationUnit - # Ruby 1.8 + ruby-prof wrapper - if RubyProf.respond_to?(:measure_memory) - def measure - RubyProf.measure_memory - end - end - end - - class Objects < Amount - # Ruby 1.8 + ruby-prof wrapper - if RubyProf.respond_to?(:measure_allocations) - def measure - RubyProf.measure_allocations - end - end - end - - class GcRuns < Amount - # Ruby 1.8 + ruby-prof wrapper - if RubyProf.respond_to?(:measure_gc_runs) - def measure - RubyProf.measure_gc_runs - end - end - end - - class GcTime < Time - # Ruby 1.8 + ruby-prof wrapper - if RubyProf.respond_to?(:measure_gc_time) - def measure - RubyProf.measure_gc_time / 1000.0 / 1000.0 - end - end - end - end - end - end -end - - diff --git a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb index 62095a8fe4..c34d31bf10 100644 --- a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb +++ b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb @@ -4,18 +4,15 @@ module ActiveSupport module Metrics class Base protected - # Ruby 1.9 with GC::Profiler - if defined?(GC::Profiler) - def with_gc_stats - GC::Profiler.enable - GC.start - yield - ensure - GC::Profiler.disable - end + def with_gc_stats + GC::Profiler.enable + GC.start + yield + ensure + GC::Profiler.disable end end - + class Memory < DigitalInformationUnit # Ruby 1.9 + GCdata patch if GC.respond_to?(:malloc_allocated_size) @@ -24,7 +21,7 @@ module ActiveSupport end end end - + class Objects < Amount # Ruby 1.9 + GCdata patch if GC.respond_to?(:malloc_allocations) @@ -33,22 +30,16 @@ module ActiveSupport end end end - + class GcRuns < Amount - # Ruby 1.9 - if GC.respond_to?(:count) - def measure - GC.count - end + def measure + GC.count end end - + class GcTime < Time - # Ruby 1.9 with GC::Profiler - if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time) - def measure - GC::Profiler.total_time - end + def measure + GC::Profiler.total_time end end end diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index 22e41fa905..40b781485e 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -10,11 +10,7 @@ module ActiveSupport include ActiveSupport::Callbacks define_callbacks :setup, :teardown - if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions - include ForMiniTest - else - include ForClassicTestUnit - end + include ForMiniTest end module ClassMethods @@ -47,65 +43,6 @@ module ActiveSupport end end - module ForClassicTestUnit - # For compatibility with Ruby < 1.8.6 - PASSTHROUGH_EXCEPTIONS = Test::Unit::TestCase::PASSTHROUGH_EXCEPTIONS rescue [NoMemoryError, SignalException, Interrupt, SystemExit] - - # This redefinition is unfortunate but test/unit shows us no alternative. - # Doubly unfortunate: hax to support Mocha's hax. - def run(result) - return if @method_name.to_s == "default_test" - - mocha_counter = retrieve_mocha_counter(result) - yield(Test::Unit::TestCase::STARTED, name) - @_result = result - - begin - begin - run_callbacks :setup do - setup - __send__(@method_name) - mocha_verify(mocha_counter) if mocha_counter - end - rescue Mocha::ExpectationError => e - add_failure(e.message, e.backtrace) - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue Exception => e - raise if PASSTHROUGH_EXCEPTIONS.include?(e.class) - add_error(e) - ensure - begin - teardown - run_callbacks :teardown - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue Exception => e - raise if PASSTHROUGH_EXCEPTIONS.include?(e.class) - add_error(e) - end - end - ensure - mocha_teardown if mocha_counter - end - - result.add_run - yield(Test::Unit::TestCase::FINISHED, name) - end - - protected - - def retrieve_mocha_counter(result) #:nodoc: - if respond_to?(:mocha_verify) # using mocha - if defined?(Mocha::TestCaseAdapter::AssertionCounter) - Mocha::TestCaseAdapter::AssertionCounter.new(result) - else - Mocha::Integration::TestUnit::AssertionCounter.new(result) - end - end - end - end - end end end diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb index 86f057d676..9634b52ecf 100644 --- a/activesupport/lib/active_support/time.rb +++ b/activesupport/lib/active_support/time.rb @@ -13,7 +13,6 @@ end require 'date' require 'time' -require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/core_ext/time/marshal' require 'active_support/core_ext/time/acts_like' require 'active_support/core_ext/time/calculations' @@ -21,7 +20,6 @@ require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/time/zones' require 'active_support/core_ext/date/acts_like' -require 'active_support/core_ext/date/freeze' require 'active_support/core_ext/date/calculations' require 'active_support/core_ext/date/conversions' require 'active_support/core_ext/date/zones' diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 3d092529d6..d3adf671a0 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 @@ -109,7 +109,7 @@ module ActiveSupport def xmlschema(fraction_digits = 0) fraction = if fraction_digits > 0 - ".%i" % time.usec.to_s[0, fraction_digits] + (".%06i" % time.usec)[0, fraction_digits + 1] end "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}" @@ -203,7 +203,11 @@ module ActiveSupport end def eql?(other) - utc == other + utc.eql?(other) + end + + def hash + utc.hash end def +(other) @@ -277,7 +281,6 @@ module ActiveSupport 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+. 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 diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index e135872bf6..8a8f8f946d 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,9 +1,9 @@ module ActiveSupport module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 + MAJOR = 4 + MINOR = 0 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb deleted file mode 100644 index 577db5018e..0000000000 --- a/activesupport/lib/active_support/whiny_nil.rb +++ /dev/null @@ -1,60 +0,0 @@ -# Extensions to +nil+ which allow for more helpful error messages for people who -# are new to Rails. -# -# Ruby raises NoMethodError if you invoke a method on an object that does not -# respond to it: -# -# $ ruby -e nil.destroy -# -e:1: undefined method `destroy' for nil:NilClass (NoMethodError) -# -# With these extensions, if the method belongs to the public interface of the -# classes in NilClass::WHINERS the error message suggests which could be the -# actual intended class: -# -# $ rails runner nil.destroy -# ... -# You might have expected an instance of ActiveRecord::Base. -# ... -# -# NilClass#id exists in Ruby 1.8 (though it is deprecated). Since +id+ is a fundamental -# method of Active Record models NilClass#id is redefined as well to raise a RuntimeError -# and warn the user. She probably wanted a model database identifier and the 4 -# returned by the original method could result in obscure bugs. -# -# The flag <tt>config.whiny_nils</tt> determines whether this feature is enabled. -# By default it is on in development and test modes, and it is off in production -# mode. -class NilClass - METHOD_CLASS_MAP = Hash.new - - def self.add_whiner(klass) - methods = klass.public_instance_methods - public_instance_methods - class_name = klass.name - methods.each { |method| METHOD_CLASS_MAP[method.to_sym] = class_name } - end - - add_whiner ::Array - - # Raises a RuntimeError when you attempt to call +id+ on +nil+. - def id - raise RuntimeError, "Called id for nil, which would mistakenly be #{object_id} -- if you really wanted the id of nil, use object_id", caller - end - - private - def method_missing(method, *args) - if klass = METHOD_CLASS_MAP[method] - raise_nil_warning_for klass, method, caller - else - super - end - end - - # Raises a NoMethodError when you attempt to call a method on +nil+. - def raise_nil_warning_for(class_name = nil, selector = nil, with_caller = nil) - message = "You have a nil object when you didn't expect it!" - message << "\nYou might have expected an instance of #{class_name}." if class_name - message << "\nThe error occurred while evaluating nil.#{selector}" if selector - - raise NoMethodError, message, with_caller || caller - end -end diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index 7aefabfdd1..6c222b83ba 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -41,7 +41,7 @@ module ActiveSupport xml_string_reader = StringReader.new(data) xml_input_source = InputSource.new(xml_string_reader) doc = @dbf.new_document_builder.parse(xml_input_source) - merge_element!({}, doc.document_element) + merge_element!({CONTENT_KEY => ''}, doc.document_element) end end @@ -54,9 +54,14 @@ module ActiveSupport # element:: # XML element to merge into hash def merge_element!(hash, element) + delete_empty(hash) merge!(hash, element.tag_name, collapse(element)) end + def delete_empty(hash) + hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == '' + end + # Actually converts an XML document element into a data structure. # # element:: @@ -84,6 +89,7 @@ module ActiveSupport # element:: # XML element whose texts are to me merged into the hash def merge_texts!(hash, element) + delete_empty(hash) text_children = texts(element) if text_children.join.empty? hash @@ -128,6 +134,7 @@ module ActiveSupport attribute_hash = {} attributes = element.attributes for i in 0...attributes.length + attribute_hash[CONTENT_KEY] ||= '' attribute_hash[attributes.item(i).name] = attributes.item(i).value end attribute_hash |