diff options
Diffstat (limited to 'activesupport/lib/active_support')
145 files changed, 3449 insertions, 1645 deletions
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index e97bb25b9f..7c3a41288b 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -8,8 +8,6 @@ module ActiveSupport # 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: - # # bc = BacktraceCleaner.new # bc.add_filter { |line| line.gsub(Rails.root, '') } # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } @@ -42,8 +40,6 @@ module ActiveSupport # Adds a filter from the block provided. Each line in the backtrace will be mapped against this filter. # - # Example: - # # # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb" # backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') } def add_filter(&block) @@ -53,8 +49,6 @@ module ActiveSupport # 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: - # # # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb" # backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ } def add_silencer(&block) diff --git a/activesupport/lib/active_support/basic_object.rb b/activesupport/lib/active_support/basic_object.rb index c3c7ab0112..6ccb0cd525 100644 --- a/activesupport/lib/active_support/basic_object.rb +++ b/activesupport/lib/active_support/basic_object.rb @@ -10,5 +10,4 @@ module ActiveSupport ::Object.send(:raise, *args) end end - end diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index cc94041a1d..f149a7f0ed 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -35,7 +35,7 @@ module ActiveSupport options[:level] ||= :info result = nil - ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield } + ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield } logger.send(options[:level], '%s (%.1fms)' % [ message, ms ]) result else diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 26e737e917..a62214d604 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -91,6 +91,7 @@ module ActiveSupport case when key.respond_to?(:cache_key) then key.cache_key when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param + when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) else key.to_param end.to_s end @@ -279,7 +280,7 @@ module ActiveSupport end end if entry && entry.expired? - race_ttl = options[:race_condition_ttl].to_f + race_ttl = options[:race_condition_ttl].to_i if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl entry.expires_at = Time.now + race_ttl write_entry(key, entry, :expires_in => race_ttl * 2) @@ -382,11 +383,7 @@ module ActiveSupport options = merged_options(options) instrument(:exist?, name) do |payload| entry = read_entry(namespaced_key(name, options), options) - if entry && !entry.expired? - true - else - false - end + entry && !entry.expired? end end @@ -408,7 +405,7 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support increment") end - # Increment an integer value in the cache. + # Decrement an integer value in the cache. # # Options are passed to the underlying cache implementation. # diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 9460532af0..2c1ad60d44 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/file/atomic' require 'active_support/core_ext/string/conversions' -require 'active_support/core_ext/object/inclusion' -require 'rack/utils' +require 'uri/common' module ActiveSupport module Cache @@ -13,7 +12,7 @@ module ActiveSupport attr_reader :cache_path DIR_FORMATTER = "%03X" - FILENAME_MAX_SIZE = 230 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write) + FILENAME_MAX_SIZE = 228 # 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) @@ -23,7 +22,7 @@ module ActiveSupport end def clear(options = nil) - root_dirs = Dir.entries(cache_path).reject{|f| f.in?(EXCLUDED_DIRS)} + root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)} FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) end @@ -81,7 +80,8 @@ module ActiveSupport if File.exist?(file_name) File.open(file_name) { |f| Marshal.load(f) } end - rescue + rescue => e + logger.error("FileStoreError (#{e}): #{e.message}") if logger nil end @@ -126,7 +126,7 @@ module ActiveSupport # Translate a key into a file path. def key_file_path(key) - fname = Rack::Utils.escape(key) + fname = URI.encode_www_form_component(key) hash = Zlib.adler32(fname) hash, dir_1 = hash.divmod(0x1000) dir_2 = hash.modulo(0x1000) @@ -143,14 +143,14 @@ module ActiveSupport # Translate a file path into a key. def file_path_key(path) - fname = path[cache_path.size, path.size].split(File::SEPARATOR, 4).last - Rack::Utils.unescape(fname) + fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last + URI.decode_www_form_component(fname, Encoding::UTF_8) end # Delete empty directories in the cache. def delete_empty_directories(dir) return if dir == cache_path - if Dir.entries(dir).reject{|f| f.in?(EXCLUDED_DIRS)}.empty? + if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty? File.delete(dir) rescue nil delete_empty_directories(File.dirname(dir)) end @@ -164,7 +164,7 @@ module ActiveSupport def search_dir(dir, &callback) return if !File.exist?(dir) Dir.foreach(dir) do |d| - next if d.in?(EXCLUDED_DIRS) + next if EXCLUDED_DIRS.include?(d) 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 e38a8387b4..5aa78cc9f3 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -1,12 +1,11 @@ begin - require 'memcache' + require 'dalli' rescue LoadError => e - $stderr.puts "You don't have memcache-client installed in your application. Please add it to your Gemfile and run bundle install" + $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" raise e end require 'digest/md5' -require 'active_support/core_ext/string/encoding' module ActiveSupport module Cache @@ -23,21 +22,13 @@ module ActiveSupport # MemCacheStore implements the Strategy::LocalCache strategy which implements # an in-memory cache inside of a block. class MemCacheStore < Store - module Response # :nodoc: - STORED = "STORED\r\n" - NOT_STORED = "NOT_STORED\r\n" - EXISTS = "EXISTS\r\n" - NOT_FOUND = "NOT_FOUND\r\n" - DELETED = "DELETED\r\n" - end - ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n def self.build_mem_cache(*addresses) addresses = addresses.flatten options = addresses.extract_options! addresses = ["localhost:11211"] if addresses.empty? - MemCache.new(addresses, options) + Dalli::Client.new(addresses, options) end # Creates a new MemCacheStore object, with the given memcached server @@ -91,11 +82,11 @@ module ActiveSupport # to zero. def increment(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) - response = instrument(:increment, name, :amount => amount) do + instrument(:increment, name, :amount => amount) do @data.incr(escape_key(namespaced_key(name, options)), amount) end - response == Response::NOT_FOUND ? nil : response.to_i - rescue MemCache::MemCacheError + rescue Dalli::DalliError + logger.error("DalliError (#{e}): #{e.message}") if logger nil end @@ -105,11 +96,11 @@ module ActiveSupport # to zero. def decrement(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) - response = instrument(:decrement, name, :amount => amount) do + instrument(:decrement, name, :amount => amount) do @data.decr(escape_key(namespaced_key(name, options)), amount) end - response == Response::NOT_FOUND ? nil : response.to_i - rescue MemCache::MemCacheError + rescue Dalli::DalliError + logger.error("DalliError (#{e}): #{e.message}") if logger nil end @@ -117,6 +108,9 @@ module ActiveSupport # be used with care when shared cache is being used. def clear(options = nil) @data.flush_all + rescue Dalli::DalliError => e + logger.error("DalliError (#{e}): #{e.message}") if logger + nil end # Get the statistics from the memcached servers. @@ -127,9 +121,9 @@ module ActiveSupport protected # Read an entry from the cache. def read_entry(key, options) # :nodoc: - deserialize_entry(@data.get(escape_key(key), true)) - rescue MemCache::MemCacheError => e - logger.error("MemCacheError (#{e}): #{e.message}") if logger + deserialize_entry(@data.get(escape_key(key), options)) + rescue Dalli::DalliError => e + logger.error("DalliError (#{e}): #{e.message}") if logger nil end @@ -138,23 +132,17 @@ module ActiveSupport method = options && options[:unless_exist] ? :add : :set value = options[:raw] ? entry.value.to_s : entry expires_in = options[:expires_in].to_i - if expires_in > 0 && !options[:raw] - # Set the memcache expire a few minutes in the future to support race condition ttls on read - expires_in += 5.minutes - end - response = @data.send(method, escape_key(key), value, expires_in, options[:raw]) - response == Response::STORED - rescue MemCache::MemCacheError => e - logger.error("MemCacheError (#{e}): #{e.message}") if logger + @data.send(method, escape_key(key), value, expires_in, options) + rescue Dalli::DalliError => e + logger.error("DalliError (#{e}): #{e.message}") if logger false end # Delete an entry from the cache. def delete_entry(key, options) # :nodoc: - response = @data.delete(escape_key(key)) - response == Response::DELETED - rescue MemCache::MemCacheError => e - logger.error("MemCacheError (#{e}): #{e.message}") if logger + @data.delete(escape_key(key)) + rescue Dalli::DalliError => e + logger.error("DalliError (#{e}): #{e.message}") if logger false end diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index b15bb42c88..7fd5e3b53d 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -137,6 +137,7 @@ module ActiveSupport def write_entry(key, entry, options) # :nodoc: synchronize do old_entry = @data[key] + return false if @data.key?(key) && options[:unless_exist] @cache_size -= old_entry.size if old_entry @cache_size += entry.size @key_access[key] = Time.now.to_f diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 1834027e7b..a55f68497c 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -3,7 +3,6 @@ require 'active_support/descendants_tracker' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/object/inclusion' module ActiveSupport # \Callbacks are code hooks that are run at key points in an object's lifecycle. @@ -23,8 +22,6 @@ module ActiveSupport # methods, procs or lambdas, or callback objects that respond to certain predetermined # methods. See +ClassMethods.set_callback+ for details. # - # ==== Example - # # class Record # include ActiveSupport::Callbacks # define_callbacks :save @@ -54,7 +51,6 @@ module ActiveSupport # saving... # - save # saved - # module Callbacks extend Concern @@ -73,16 +69,15 @@ module ActiveSupport # run_callbacks :save do # save # end - # - def run_callbacks(kind, key = nil, &block) - #TODO: deprecate key argument - self.class.__run_callbacks(kind, self, &block) + def run_callbacks(kind, &block) + runner_name = self.class.__define_callbacks(kind, self) + send(runner_name, &block) end private # A hook invoked everytime a before callback is halted. - # This can be overriden in AS::Callback implementors in order + # This can be overridden in AS::Callback implementors in order # to provide better debugging/logging. def halted_callback_hook(filter) end @@ -187,7 +182,7 @@ module ActiveSupport # Compile around filters with conditions into proxy methods # that contain the conditions. # - # For `around_save :filter_name, :if => :condition': + # For `set_callback :save, :around, :filter_name, :if => :condition': # # def _conditional_callback_save_17 # if condition @@ -198,7 +193,6 @@ module ActiveSupport # yield self # end # end - # def define_conditional_callback name = "_conditional_callback_#{@kind}_#{next_id}" @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 @@ -252,7 +246,6 @@ module ActiveSupport # Objects:: # a method is created that calls the before_foo method # on the object. - # def _compile_filter(filter) method_name = "_callback_#{@kind}_#{next_id}" case filter @@ -289,7 +282,8 @@ module ActiveSupport filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{kind}(context, &block) filter(context, &block) end RUBY_EVAL - elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around + elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around) + ActiveSupport::Deprecation.warn("Filter object with #before and #after methods is deprecated. Define #around method instead.") def filter.around(context) should_continue = before(context) yield if should_continue @@ -307,7 +301,6 @@ module ActiveSupport @name = name @config = { :terminator => "false", - :rescuable => false, :scope => [ :kind ] }.merge(config) end @@ -317,32 +310,13 @@ module ActiveSupport method << "value = nil" method << "halted = false" - callbacks = yielding + callbacks = "value = !halted && (!block_given? || yield)" reverse_each do |callback| callbacks = callback.apply(callbacks) end method << callbacks - method << "raise rescued_error if rescued_error" if config[:rescuable] - method << "halted ? false : (block_given? ? value : true)" - method.flatten.compact.join("\n") - end - - # Returns part of method that evaluates the callback block - def yielding - method = [] - if config[:rescuable] - method << "rescued_error = nil" - method << "begin" - end - - method << "value = yield if block_given? && !halted" - - if config[:rescuable] - method << "rescue Exception => e" - method << "rescued_error = e" - method << "end" - end + method << "value" method.join("\n") end @@ -350,20 +324,19 @@ module ActiveSupport module ClassMethods - # This method runs callback chain for the given kind. - # If this called first time it creates a new callback method for the kind. + # This method defines callback chain method for the given kind + # if it was not yet defined. # This generated method plays caching role. - # - def __run_callbacks(kind, object, &blk) #:nodoc: + def __define_callbacks(kind, object) #:nodoc: name = __callback_runner_name(kind) - unless object.respond_to?(name) + unless object.respond_to?(name, true) str = object.send("_#{kind}_callbacks").compile class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{name}() #{str} end protected :#{name} RUBY_EVAL end - object.send(name, &blk) + name end def __reset_runner(symbol) @@ -379,7 +352,7 @@ module ActiveSupport # CallbackChain. # def __update_callbacks(name, filters = [], block = nil) #:nodoc: - type = filters.first.in?([:before, :after, :around]) ? filters.shift : :before + type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before options = filters.last.is_a?(Hash) ? filters.pop : {} filters.unshift(block) if block @@ -394,7 +367,7 @@ module ActiveSupport # # set_callback :save, :before, :before_meth # set_callback :save, :after, :after_meth, :if => :condition - # set_callback :save, :around, lambda { |r| stuff; result = yield; stuff } + # set_callback :save, :around, lambda { |r, &block| stuff; result = block.call; stuff } # # The second arguments indicates whether the callback is to be run +:before+, # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This @@ -425,7 +398,6 @@ module ActiveSupport # will be called only when it returns a false value. # * <tt>:prepend</tt> - If true, the callback will be prepended to the existing # chain rather than appended. - # def set_callback(name, *filter_list, &block) mapped = nil @@ -450,7 +422,6 @@ module ActiveSupport # class Writer < Person # skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 } # end - # def skip_callback(name, *filter_list, &block) __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options| filters.each do |filter| @@ -469,7 +440,6 @@ module ActiveSupport end # Remove all set callbacks for the given event. - # def reset_callbacks(symbol) callbacks = send("_#{symbol}_callbacks") @@ -508,11 +478,6 @@ module ActiveSupport # if callback chain was terminated or not. # Option makes sence only when <tt>:terminator</tt> option is specified. # - # * <tt>:rescuable</tt> - By default, after filters are not executed if - # the given block or a before filter raises an error. By setting this option - # to <tt>true</tt> exception raised by given block is stored and after - # executing all the after callbacks the stored exception is raised. - # # * <tt>:scope</tt> - Indicates which methods should be executed when an object # is used as a callback. # @@ -555,7 +520,6 @@ module ActiveSupport # define_callbacks :save, :scope => [:name] # # would call <tt>Audit#save</tt>. - # def define_callbacks(*callbacks) config = callbacks.last.is_a?(Hash) ? callbacks.pop : {} callbacks.each do |callback| diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index c94a8d99f4..b927b58a9a 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -56,7 +56,7 @@ module ActiveSupport # these from +Host+ directly including +Foo+ in +Bar+: # # module Bar - # include Foo + # include Foo # def self.included(base) # base.method_injected_by_foo # end @@ -94,9 +94,8 @@ module ActiveSupport # class Host # include Bar # works, Bar takes care now of its dependencies # end - # module Concern - def self.extended(base) + def self.extended(base) #:nodoc: base.instance_variable_set("@_dependencies", []) end diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb new file mode 100644 index 0000000000..1507de433e --- /dev/null +++ b/activesupport/lib/active_support/concurrency/latch.rb @@ -0,0 +1,27 @@ +require 'thread' +require 'monitor' + +module ActiveSupport + module Concurrency + class Latch + def initialize(count = 1) + @count = count + @lock = Monitor.new + @cv = @lock.new_cond + end + + def release + @lock.synchronize do + @count -= 1 if @count > 0 + @cv.broadcast if @count.zero? + end + end + + def await + @lock.synchronize do + @cv.wait_while { @count > 0 } + end + end + end + end +end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index a8aa53a80f..307ae40398 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -37,29 +37,77 @@ module ActiveSupport yield config end - # Allows you to add shortcut so that you don't have to refer to attribute through config. - # Also look at the example for config to contrast. + # Allows you to add shortcut so that you don't have to refer to attribute + # through config. Also look at the example for config to contrast. + # + # Defines both class and instance config accessors. # # class User # include ActiveSupport::Configurable # config_accessor :allowed_access # end # + # User.allowed_access # => nil + # User.allowed_access = false + # User.allowed_access # => false + # # user = User.new + # user.allowed_access # => false # user.allowed_access = true # user.allowed_access # => true # + # User.allowed_access # => false + # + # The attribute name must be a valid method name in Ruby. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :"1_Badname" + # end + # # => NameError: invalid config attribute name + # + # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>. + # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access, instance_reader: false, instance_writer: false + # end + # + # User.allowed_access = false + # User.allowed_access # => false + # + # User.new.allowed_access = true # => NoMethodError + # User.new.allowed_access # => NoMethodError + # + # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access, instance_accessor: false + # end + # + # User.allowed_access = false + # User.allowed_access # => false + # + # User.new.allowed_access = true # => NoMethodError + # User.new.allowed_access # => NoMethodError def config_accessor(*names) options = names.extract_options! names.each do |name| - reader, line = "def #{name}; config.#{name}; end", __LINE__ - writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__ + raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/ + + reader, reader_line = "def #{name}; config.#{name}; end", __LINE__ + writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__ - singleton_class.class_eval reader, __FILE__, line - singleton_class.class_eval writer, __FILE__, line - class_eval reader, __FILE__, line unless options[:instance_reader] == false - class_eval writer, __FILE__, line unless options[:instance_writer] == false + singleton_class.class_eval reader, __FILE__, reader_line + singleton_class.class_eval writer, __FILE__, writer_line + + unless options[:instance_accessor] == false + class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false + class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false + end end end end @@ -79,7 +127,6 @@ module ActiveSupport # # user.config.allowed_access # => true # user.config.level # => 1 - # def config @_config ||= self.class.config.inheritable_copy end diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 6162f7af27..a8f9dddae5 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -1,40 +1,48 @@ class Array # Returns the tail of the array from +position+. # - # %w( a b c d ).from(0) # => %w( a b c d ) - # %w( a b c d ).from(2) # => %w( c d ) - # %w( a b c d ).from(10) # => %w() - # %w().from(0) # => %w() + # %w( a b c d ).from(0) # => ["a", "b", "c", "d"] + # %w( a b c d ).from(2) # => ["c", "d"] + # %w( a b c d ).from(10) # => [] + # %w().from(0) # => [] def from(position) self[position, length] || [] end # Returns the beginning of the array up to +position+. # - # %w( a b c d ).to(0) # => %w( a ) - # %w( a b c d ).to(2) # => %w( a b c ) - # %w( a b c d ).to(10) # => %w( a b c d ) - # %w().to(0) # => %w() + # %w( a b c d ).to(0) # => ["a"] + # %w( a b c d ).to(2) # => ["a", "b", "c"] + # %w( a b c d ).to(10) # => ["a", "b", "c", "d"] + # %w().to(0) # => [] def to(position) - self.first position + 1 + first position + 1 end # Equal to <tt>self[1]</tt>. + # + # %w( a b c d e).second # => "b" def second self[1] end # Equal to <tt>self[2]</tt>. + # + # %w( a b c d e).third # => "c" def third self[2] end # Equal to <tt>self[3]</tt>. + # + # %w( a b c d e).fourth # => "d" def fourth self[3] end # Equal to <tt>self[4]</tt>. + # + # %w( a b c d e).fifth # => "e" def fifth self[4] end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index f3d06ecb2f..d6ae031c0d 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -1,41 +1,95 @@ require 'active_support/xml_mini' require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/string/inflections' class Array - # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options: - # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ") - # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ") - # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ") + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. + # + # You can pass the following options to change the default behaviour. If you + # pass an option key that doesn't exist in the list below, it will raise an + # <tt>ArgumentError</tt>. + # + # Options: + # + # * <tt>:words_connector</tt> - The sign or word used to join the elements + # in arrays with two or more elements (default: ", "). + # * <tt>:two_words_connector</tt> - The sign or word used to join the elements + # in arrays with two elements (default: " and "). + # * <tt>:last_word_connector</tt> - The sign or word used to join the last element + # in arrays with three or more elements (default: ", and "). + # * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use + # the connector options defined on the 'support.array' namespace in the + # corresponding dictionary file. + # + # [].to_sentence # => "" + # ['one'].to_sentence # => "one" + # ['one', 'two'].to_sentence # => "one and two" + # ['one', 'two', 'three'].to_sentence # => "one, two, and three" + # + # ['one', 'two'].to_sentence(passing: 'invalid option') + # # => ArgumentError: Unknown key :passing + # + # ['one', 'two'].to_sentence(two_words_connector: '-') + # # => "one-two" + # + # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') + # # => "one or two or at least three" + # + # Examples using <tt>:locale</tt> option: + # + # # Given this locale dictionary: + # # + # # es: + # # support: + # # array: + # # words_connector: " o " + # # two_words_connector: " y " + # # last_word_connector: " o al menos " + # + # ['uno', 'dos'].to_sentence(locale: :es) + # # => "uno y dos" + # + # ['uno', 'dos', 'tres'].to_sentence(locale: :es) + # # => "uno o dos o al menos tres" def to_sentence(options = {}) + options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) + + default_connectors = { + :words_connector => ', ', + :two_words_connector => ' and ', + :last_word_connector => ', and ' + } if defined?(I18n) - default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale]) - default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale]) - default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale]) - else - default_words_connector = ", " - default_two_words_connector = " and " - default_last_word_connector = ", and " + i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {}) + default_connectors.merge!(i18n_connectors) end - - options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) - options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector + options = default_connectors.merge!(options) case length - when 0 - "" - when 1 - self[0].to_s.dup - when 2 - "#{self[0]}#{options[:two_words_connector]}#{self[1]}" - else - "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}" + when 0 + '' + when 1 + self[0].to_s.dup + when 2 + "#{self[0]}#{options[:two_words_connector]}#{self[1]}" + else + "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}" end end # Converts a collection of elements into a formatted string by calling - # <tt>to_s</tt> on all elements and joining them: + # <tt>to_s</tt> on all elements and joining them. Having this model: + # + # class Blog < ActiveRecord::Base + # def to_s + # title + # end + # end + # + # Blog.all.map(&:title) #=> ["First Post", "Second Post", "Third post"] + # + # <tt>to_formatted_s</tt> shows us: # # Blog.all.to_formatted_s # => "First PostSecond PostThird Post" # @@ -45,14 +99,14 @@ class Array # Blog.all.to_formatted_s(:db) # => "1,2,3" def to_formatted_s(format = :default) case format - when :db - if respond_to?(:empty?) && self.empty? - "null" - else - collect { |element| element.id }.join(",") - end + when :db + if empty? + 'null' else - to_default_s + collect { |element| element.id }.join(',') + end + else + to_default_s end end alias_method :to_default_s, :to_s @@ -86,20 +140,20 @@ class Array # </project> # </projects> # - # Otherwise the root element is "records": + # Otherwise the root element is "objects": # # [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml # # <?xml version="1.0" encoding="UTF-8"?> - # <records type="array"> - # <record> + # <objects type="array"> + # <object> # <bar type="integer">2</bar> # <foo type="integer">1</foo> - # </record> - # <record> + # </object> + # <object> # <baz type="integer">3</baz> - # </record> - # </records> + # </object> + # </objects> # # If the collection is empty the root element is "nil-classes" by default: # @@ -139,26 +193,28 @@ class Array options = options.dup options[:indent] ||= 2 options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) - options[:root] ||= if first.class.to_s != "Hash" && all? { |e| e.is_a?(first.class) } - underscored = ActiveSupport::Inflector.underscore(first.class.name) - ActiveSupport::Inflector.pluralize(underscored).tr('/', '_') - else - "objects" - end + options[:root] ||= \ + if first.class != Hash && all? { |e| e.is_a?(first.class) } + underscored = ActiveSupport::Inflector.underscore(first.class.name) + ActiveSupport::Inflector.pluralize(underscored).tr('/', '_') + else + 'objects' + end builder = options[:builder] builder.instruct! unless options.delete(:skip_instruct) root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) children = options.delete(:children) || root.singularize + attributes = options[:skip_types] ? {} : {:type => 'array'} - attributes = options[:skip_types] ? {} : {:type => "array"} - return builder.tag!(root, attributes) if empty? - - builder.__send__(:method_missing, root, attributes) do - each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) } - yield builder if block_given? + if empty? + builder.tag!(root, attributes) + else + builder.__send__(:method_missing, root, attributes) do + each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) } + yield builder if block_given? + end end end - end diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index 2b3f639cb1..a184eb492a 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -2,18 +2,21 @@ class Array # Splits or iterates over the array in groups of size +number+, # padding any remaining slots with +fill_with+ unless it is +false+. # - # %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group} + # %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group} # ["1", "2", "3"] # ["4", "5", "6"] - # ["7", nil, nil] + # ["7", "8", "9"] + # ["10", nil, nil] # - # %w(1 2 3).in_groups_of(2, ' ') {|group| p group} + # %w(1 2 3 4 5).in_groups_of(2, ' ') {|group| p group} # ["1", "2"] - # ["3", " "] + # ["3", "4"] + # ["5", " "] # - # %w(1 2 3).in_groups_of(2, false) {|group| p group} + # %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group} # ["1", "2"] - # ["3"] + # ["3", "4"] + # ["5"] def in_groups_of(number, fill_with = nil) if fill_with == false collection = self @@ -42,10 +45,10 @@ class Array # ["5", "6", "7", nil] # ["8", "9", "10", nil] # - # %w(1 2 3 4 5 6 7).in_groups(3, ' ') {|group| p group} - # ["1", "2", "3"] - # ["4", "5", " "] - # ["6", "7", " "] + # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, ' ') {|group| p group} + # ["1", "2", "3", "4"] + # ["5", "6", "7", " "] + # ["8", "9", "10", " "] # # %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group} # ["1", "2", "3"] @@ -82,11 +85,9 @@ class Array # # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] - def split(value = nil) - using_block = block_given? - + def split(value = nil, &block) inject([[]]) do |results, element| - if (using_block && yield(element)) || (value == element) + if block && block.call(element) || value == element results << [] else results.last << element 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 ac3dedc0e3..3bedfa9a61 100644 --- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb +++ b/activesupport/lib/active_support/core_ext/array/uniq_by.rb @@ -6,8 +6,7 @@ class Array # [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2] # def uniq_by(&block) - ActiveSupport::Deprecation.warn "uniq_by " \ - "is deprecated. Use Array#uniq instead", caller + ActiveSupport::Deprecation.warn 'uniq_by is deprecated. Use Array#uniq instead', caller uniq(&block) end @@ -15,8 +14,7 @@ class Array # # Same as +uniq_by+, but modifies +self+. def uniq_by!(&block) - ActiveSupport::Deprecation.warn "uniq_by! " \ - "is deprecated. Use Array#uniq! instead", caller + 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 4834eca8b1..9ea93d7226 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -25,9 +25,6 @@ class Array # Array(:foo => :bar) # => [[:foo, :bar]] # Array.wrap(:foo => :bar) # => [{:foo => :bar}] # - # Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8 - # Array.wrap("foo\nbar") # => ["foo\nbar"] - # # There's also a related idiom that uses the splat operator: # # [*object] diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 3ec7e576c8..5dc5710c53 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -17,8 +17,13 @@ class BigDecimal end DEFAULT_STRING_FORMAT = 'F' - def to_formatted_s(format = DEFAULT_STRING_FORMAT) - _original_to_s(format) + def to_formatted_s(*args) + if args[0].is_a?(Symbol) + super + else + format = args[0] || DEFAULT_STRING_FORMAT + _original_to_s(format) + end end alias_method :_original_to_s, :to_s alias_method :to_s, :to_formatted_s diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 305ed4964b..7b6f8ab0a1 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -65,10 +65,12 @@ class Class # To opt out of the instance writer method, pass :instance_writer => false. # # object.setting = false # => NoMethodError + # + # To opt out of both instance methods, pass :instance_accessor => false. def class_attribute(*attrs) options = attrs.extract_options! - instance_reader = options.fetch(:instance_reader, true) - instance_writer = options.fetch(:instance_writer, true) + instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) + instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -109,7 +111,7 @@ class Class end private - def singleton_class? - ancestors.first != self - end + def singleton_class? + ancestors.first != self + 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 268303aaf2..fa1dbfdf06 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -2,33 +2,37 @@ require 'active_support/core_ext/array/extract_options' # Extends the class object with class and instance accessors for class attributes, # just like the native attr* accessors for instance attributes. -# -# Note that unlike +class_attribute+, if a subclass changes the value then that would -# also change the value for parent class. Similarly if parent class changes the value -# then that would change the value of subclasses too. -# -# class Person -# cattr_accessor :hair_colors -# end -# -# Person.hair_colors = [:brown, :black, :blonde, :red] -# Person.hair_colors # => [:brown, :black, :blonde, :red] -# Person.new.hair_colors # => [:brown, :black, :blonde, :red] -# -# 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 -# end -# -# Person.new.hair_colors = [:brown] # => NoMethodError -# Person.new.hair_colors # => NoMethodError class Class + # Defines a class attribute if it's not defined and creates a reader method that + # returns the attribute value. + # + # class Person + # cattr_reader :hair_colors + # end + # + # Person.class_variable_set("@@hair_colors", [:brown, :black]) + # Person.hair_colors # => [:brown, :black] + # Person.new.hair_colors # => [:brown, :black] + # + # The attribute name must be a valid method name in Ruby. + # + # class Person + # cattr_reader :"1_Badname " + # end + # # => NameError: invalid attribute name + # + # If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt> + # or <tt>instance_accessor: false</tt>. + # + # class Person + # cattr_reader :hair_colors, instance_reader: false + # end + # + # Person.new.hair_colors # => NoMethodError def cattr_reader(*syms) options = syms.extract_options! syms.each do |sym| + raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) unless defined? @@#{sym} @@#{sym} = nil @@ -49,9 +53,47 @@ class Class end end + # Defines a class attribute if it's not defined and creates a writer method to allow + # assignment to the attribute. + # + # class Person + # cattr_writer :hair_colors + # end + # + # Person.hair_colors = [:brown, :black] + # Person.class_variable_get("@@hair_colors") # => [:brown, :black] + # Person.new.hair_colors = [:blonde, :red] + # Person.class_variable_get("@@hair_colors") # => [:blonde, :red] + # + # The attribute name must be a valid method name in Ruby. + # + # class Person + # cattr_writer :"1_Badname " + # end + # # => NameError: invalid attribute name + # + # If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt> + # or <tt>instance_accessor: false</tt>. + # + # class Person + # cattr_writer :hair_colors, instance_writer: false + # end + # + # Person.new.hair_colors = [:blonde, :red] # => NoMethodError + # + # Also, you can pass a block to set up the attribute with a default value. + # + # class Person + # cattr_writer :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] def cattr_writer(*syms) options = syms.extract_options! syms.each do |sym| + raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) unless defined? @@#{sym} @@#{sym} = nil @@ -69,10 +111,58 @@ class Class end EOS end - self.send("#{sym}=", yield) if block_given? + send("#{sym}=", yield) if block_given? end end + # Defines both class and instance accessors for class attributes. + # + # class Person + # cattr_accessor :hair_colors + # end + # + # Person.hair_colors = [:brown, :black, :blonde, :red] + # Person.hair_colors # => [:brown, :black, :blonde, :red] + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + # + # If a subclass changes the value then that would also change the value for + # parent class. Similarly if parent class changes the value then that would + # change the value of subclasses too. + # + # class Male < Person + # end + # + # Male.hair_colors << :blue + # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue] + # + # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>. + # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>. + # + # class Person + # cattr_accessor :hair_colors, instance_writer: false, instance_reader: false + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods. + # + # class Person + # cattr_accessor :hair_colors, instance_accessor: false + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # Also you can pass a block to set up the attribute with a default value. + # + # class Person + # cattr_accessor :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red] def cattr_accessor(*syms, &blk) cattr_reader(*syms) cattr_writer(*syms, &blk) diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index 29bf7c0f3d..ff870f5fd1 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/remove_method' @@ -22,23 +20,21 @@ class Class define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false end -private - - # Take the object being set and store it in a method. This gives us automatic - # inheritance behavior, without having to store the object in an instance - # variable and look up the superclass chain manually. - def _stash_object_in_method(object, method, instance_reader = true) - singleton_class.remove_possible_method(method) - singleton_class.send(:define_method, method) { object } - remove_possible_method(method) - define_method(method) { object } if instance_reader - end - - def _superclass_delegating_accessor(name, options = {}) - singleton_class.send(:define_method, "#{name}=") do |value| - _stash_object_in_method(value, name, options[:instance_reader] != false) + private + # Take the object being set and store it in a method. This gives us automatic + # inheritance behavior, without having to store the object in an instance + # variable and look up the superclass chain manually. + def _stash_object_in_method(object, method, instance_reader = true) + singleton_class.remove_possible_method(method) + singleton_class.send(:define_method, method) { object } + remove_possible_method(method) + define_method(method) { object } if instance_reader end - send("#{name}=", nil) - end + def _superclass_delegating_accessor(name, options = {}) + singleton_class.send(:define_method, "#{name}=") do |value| + _stash_object_in_method(value, name, options[:instance_reader] != false) + end + send("#{name}=", nil) + end end diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 74ea047c24..c2e0ebb3d4 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -1,11 +1,11 @@ require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/reachable' -class Class #:nodoc: +class Class begin ObjectSpace.each_object(Class.new) {} - def descendants + def descendants # :nodoc: descendants = [] ObjectSpace.each_object(singleton_class) do |k| descendants.unshift k unless k == self @@ -13,7 +13,7 @@ class Class #:nodoc: descendants end rescue StandardError # JRuby - def descendants + def descendants # :nodoc: descendants = [] ObjectSpace.each_object(Class) do |k| descendants.unshift k if k < self @@ -25,7 +25,13 @@ class Class #:nodoc: # Returns an array with the direct children of +self+. # - # Integer.subclasses # => [Bignum, Fixnum] + # Integer.subclasses # => [Fixnum, Bignum] + # + # class Foo; end + # class Bar < Foo; end + # class Baz < Foo; end + # + # Foo.subclasses # => [Baz, Bar] def subclasses subclasses, chain = [], descendants chain.each do |k| diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb new file mode 100644 index 0000000000..465fedda80 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date.rb @@ -0,0 +1,5 @@ +require 'active_support/core_ext/date/acts_like' +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/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index af78226c21..86badf4d29 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -3,9 +3,10 @@ require 'active_support/duration' require 'active_support/core_ext/object/acts_like' require 'active_support/core_ext/date/zones' require 'active_support/core_ext/time/zones' +require 'active_support/core_ext/date_and_time/calculations' class Date - DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 } + include DateAndTime::Calculations class << self # Returns a new Date representing the date 1 day ago (i.e. yesterday's date). @@ -24,21 +25,6 @@ class Date end end - # Returns true if the Date object's date lies in the past. Otherwise returns false. - def past? - self < ::Date.current - end - - # Returns true if the Date object's date is today. - def today? - self.to_date == ::Date.current # we need the to_date because of DateTime - end - - # Returns true if the Date object's date lies in the future. - def future? - self > ::Date.current - end - # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) # and then subtracts the specified number of seconds. def ago(seconds) @@ -98,141 +84,15 @@ class Date end # Returns a new Date where one or more of the elements have been changed according to the +options+ parameter. - # - # Examples: + # The +options+ parameter is a hash with a combination of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>. # # Date.new(2007, 5, 12).change(:day => 1) # => Date.new(2007, 5, 1) # Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12) def change(options) ::Date.new( - options[:year] || self.year, - options[:month] || self.month, - options[:day] || self.day + options.fetch(:year, year), + options.fetch(:month, month), + options.fetch(:day, day) ) end - - # Returns a new Date/DateTime representing the time a number of specified weeks ago. - def weeks_ago(weeks) - advance(:weeks => -weeks) - end - - # Returns a new Date/DateTime representing the time a number of specified months ago. - def months_ago(months) - advance(:months => -months) - end - - # Returns a new Date/DateTime representing the time a number of specified months in the future. - def months_since(months) - advance(:months => months) - end - - # Returns a new Date/DateTime representing the time a number of specified years ago. - def years_ago(years) - advance(:years => -years) - end - - # Returns a new Date/DateTime representing the time a number of specified years in the future. - def years_since(years) - advance(:years => years) - end - - # 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. 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 :at_beginning_of_week :beginning_of_week - - # 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 :at_end_of_week :end_of_week - - # 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). - def next_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 ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00) - def beginning_of_month - self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1) - end - alias :at_beginning_of_month :beginning_of_month - - # Returns a new Date/DateTime representing the end of the month (last day of the month; DateTime objects will have time set to 0:00) - def end_of_month - last_day = ::Time.days_in_month( self.month, self.year ) - self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day) - end - alias :at_end_of_month :end_of_month - - # Returns a new Date/DateTime representing the start of the quarter (1st of january, april, july, october; DateTime objects will have time set to 0:00) - def beginning_of_quarter - beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month }) - end - alias :at_beginning_of_quarter :beginning_of_quarter - - # Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59) - def end_of_quarter - beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month - end - alias :at_end_of_quarter :end_of_quarter - - # Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00) - def beginning_of_year - self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1) - end - alias :at_beginning_of_year :beginning_of_year - - # Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59) - def end_of_year - self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31) - end - alias :at_end_of_year :end_of_year - - # Convenience method which returns a new Date/DateTime representing the time 1 day ago - def yesterday - self - 1 - end - - # Convenience method which returns a new Date/DateTime representing the time 1 day since the instance time - def tomorrow - self + 1 - end end diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 3262c254f7..81f969e786 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -5,12 +5,15 @@ require 'active_support/core_ext/module/remove_method' class Date DATE_FORMATS = { - :short => "%e %b", - :long => "%B %e, %Y", - :db => "%Y-%m-%d", - :number => "%Y%m%d", - :long_ordinal => lambda { |date| date.strftime("%B #{ActiveSupport::Inflector.ordinalize(date.day)}, %Y") }, # => "April 25th, 2007" - :rfc822 => "%e %b %Y" + :short => '%e %b', + :long => '%B %e, %Y', + :db => '%Y-%m-%d', + :number => '%Y%m%d', + :long_ordinal => lambda { |date| + day_format = ActiveSupport::Inflector.ordinalize(date.day) + date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" + }, + :rfc822 => '%e %b %Y' } # Ruby 1.9 has Date#to_time which converts to localtime only. @@ -23,7 +26,6 @@ class Date # # This method is aliased to <tt>to_s</tt>. # - # ==== Examples # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 # # date.to_formatted_s(:db) # => "2007-11-10" @@ -40,7 +42,7 @@ class Date # or Proc instance that takes a date argument as the value. # # # config/initializers/time_formats.rb - # Date::DATE_FORMATS[:month_and_year] = "%B %Y" + # Date::DATE_FORMATS[:month_and_year] = '%B %Y' # Date::DATE_FORMATS[:short_ordinal] = lambda { |date| date.strftime("%B #{date.day.ordinalize}") } def to_formatted_s(format = :default) if formatter = DATE_FORMATS[format] @@ -58,7 +60,7 @@ class Date # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005" def readable_inspect - strftime("%a, %d %b %Y") + strftime('%a, %d %b %Y') end alias_method :default_inspect, :inspect alias_method :inspect, :readable_inspect @@ -66,7 +68,6 @@ class Date # 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). # - # ==== Examples # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 # # date.to_time # => Sat Nov 10 00:00:00 0800 2007 diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb new file mode 100644 index 0000000000..e703fca7a7 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb @@ -0,0 +1,213 @@ +module DateAndTime + module Calculations + DAYS_INTO_WEEK = { + :monday => 0, + :tuesday => 1, + :wednesday => 2, + :thursday => 3, + :friday => 4, + :saturday => 5, + :sunday => 6 + } + + # Returns a new date/time representing yesterday. + def yesterday + advance(:days => -1) + end + + # Returns a new date/time representing tomorrow. + def tomorrow + advance(:days => 1) + end + + # Returns true if the date/time is today. + def today? + to_date == ::Date.current + end + + # Returns true if the date/time is in the past. + def past? + self < self.class.current + end + + # Returns true if the date/time is in the future. + def future? + self > self.class.current + end + + # Returns a new date/time the specified number of days ago. + def days_ago(days) + advance(:days => -days) + end + + # Returns a new date/time the specified number of days in the future. + def days_since(days) + advance(:days => days) + end + + # Returns a new date/time the specified number of weeks ago. + def weeks_ago(weeks) + advance(:weeks => -weeks) + end + + # Returns a new date/time the specified number of weeks in the future. + def weeks_since(weeks) + advance(:weeks => weeks) + end + + # Returns a new date/time the specified number of months ago. + def months_ago(months) + advance(:months => -months) + end + + # Returns a new date/time the specified number of months in the future. + def months_since(months) + advance(:months => months) + end + + # Returns a new date/time the specified number of years ago. + def years_ago(years) + advance(:years => -years) + end + + # Returns a new date/time the specified number of years in the future. + def years_since(years) + advance(:years => years) + end + + # Returns a new date/time at the start of the month. + # DateTime objects will have a time set to 0:00. + def beginning_of_month + first_hour{ change(:day => 1) } + end + alias :at_beginning_of_month :beginning_of_month + + # Returns a new date/time at the start of the quarter. + # Example: 1st January, 1st July, 1st October. + # DateTime objects will have a time set to 0:00. + def beginning_of_quarter + first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month } + beginning_of_month.change(:month => first_quarter_month) + end + alias :at_beginning_of_quarter :beginning_of_quarter + + # Returns a new date/time at the end of the quarter. + # Example: 31st March, 30th June, 30th September. + # DateTIme objects will have a time set to 23:59:59. + def end_of_quarter + last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month } + beginning_of_month.change(:month => last_quarter_month).end_of_month + end + alias :at_end_of_quarter :end_of_quarter + + # Return a new date/time at the beginning of the year. + # Example: 1st January. + # DateTime objects will have a time set to 0:00. + def beginning_of_year + change(:month => 1).beginning_of_month + end + alias :at_beginning_of_year :beginning_of_year + + # Returns a new date/time representing the given day in the next week. + # Default is :monday. + # DateTime objects have their time set to 0:00. + def next_week(day = :monday) + first_hour{ weeks_since(1).beginning_of_week.days_since(DAYS_INTO_WEEK[day]) } + end + + # Short-hand for months_since(1). + def next_month + months_since(1) + end + + # Short-hand for months_since(3) + def next_quarter + months_since(3) + end + + # Short-hand for years_since(1). + def next_year + years_since(1) + end + + # Returns a new date/time 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) + first_hour{ weeks_ago(1).beginning_of_week.days_since(DAYS_INTO_WEEK[day]) } + end + alias_method :last_week, :prev_week + + # Short-hand for months_ago(1). + def prev_month + months_ago(1) + end + alias_method :last_month, :prev_month + + # Short-hand for months_ago(3). + def prev_quarter + months_ago(3) + end + alias_method :last_quarter, :prev_quarter + + # Short-hand for years_ago(1). + def prev_year + years_ago(1) + end + alias_method :last_year, :prev_year + + # Returns the number of days to the start of the week on the given 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/time representing the start of this week on the given day. + # Default is :monday. + # DateTime objects have their time set to 0:00. + def beginning_of_week(start_day = :monday) + result = days_ago(days_to_week_start(start_day)) + acts_like?(:time) ? result.midnight : result + end + alias :at_beginning_of_week :beginning_of_week + alias :monday :beginning_of_week + + # Returns a new date/time representing the end of this week on the given day. + # Default is :monday (i.e end of Sunday). + # DateTime objects have their time set to 23:59:59. + def end_of_week(start_day = :monday) + last_hour{ days_since(6 - days_to_week_start(start_day)) } + end + alias :at_end_of_week :end_of_week + alias :sunday :end_of_week + + # Returns a new date/time representing the end of the month. + # DateTime objects will have a time set to 23:59:59. + def end_of_month + last_day = ::Time.days_in_month(month, year) + last_hour{ days_since(last_day - day) } + end + alias :at_end_of_month :end_of_month + + # Returns a new date/time representing the end of the year. + # DateTime objects will have a time set to 23:59:59. + def end_of_year + change(:month => 12).end_of_month + end + alias :at_end_of_year :end_of_year + + private + + def first_hour + result = yield + acts_like?(:time) ? result.change(:hour => 0) : result + end + + def last_hour + result = yield + acts_like?(:time) ? result.end_of_day : result + end + end +end diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb new file mode 100644 index 0000000000..e8a27b9f38 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date_time.rb @@ -0,0 +1,4 @@ +require 'active_support/core_ext/date_time/acts_like' +require 'active_support/core_ext/date_time/calculations' +require 'active_support/core_ext/date_time/conversions' +require 'active_support/core_ext/date_time/zones' 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 6f730e4b6f..5fb19f2e6e 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -4,8 +4,8 @@ class DateTime class << self # *DEPRECATED*: Use +DateTime.civil_from_format+ directly. def local_offset - ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. ' \ - 'Use DateTime.civil_from_format directly.', caller + ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. Use DateTime.civil_from_format directly.', caller + ::Time.local(2012).utc_offset.to_r / 86400 end @@ -31,18 +31,23 @@ class DateTime end # Returns a new DateTime where one or more of the elements have been changed according to the +options+ parameter. The time options - # (hour, minute, sec) reset cascadingly, so if only the hour is passed, then minute and sec is set to 0. If the hour and - # minute is passed, then sec is set to 0. + # (<tt>:hour</tt>, <tt>:minute</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is passed, then minute and sec is set to 0. If the hour and + # minute is passed, then sec is set to 0. The +options+ parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, + # <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:offset</tt>, <tt>:start</tt>. + # + # DateTime.new(2012, 8, 29, 22, 35, 0).change(:day => 1) # => DateTime.new(2012, 8, 1, 22, 35, 0) + # DateTime.new(2012, 8, 29, 22, 35, 0).change(:year => 1981, :day => 1) # => DateTime.new(1981, 8, 1, 22, 35, 0) + # DateTime.new(2012, 8, 29, 22, 35, 0).change(:year => 1981, :hour => 0) # => DateTime.new(1981, 8, 29, 0, 0, 0) def change(options) ::DateTime.civil( - options[:year] || year, - options[:month] || month, - options[:day] || day, - options[:hour] || hour, - options[:min] || (options[:hour] ? 0 : min), - options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec), - options[:offset] || offset, - options[:start] || start + options.fetch(:year, year), + options.fetch(:month, month), + options.fetch(:day, day), + options.fetch(:hour, hour), + options.fetch(:min, options[:hour] ? 0 : min), + options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec), + options.fetch(:offset, offset), + options.fetch(:start, start) ) end @@ -53,8 +58,16 @@ class DateTime def advance(options) d = to_date.advance(options) datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) - seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600 - seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date.since(seconds_to_advance) + seconds_to_advance = \ + options.fetch(:seconds, 0) + + options.fetch(:minutes, 0) * 60 + + options.fetch(:hours, 0) * 3600 + + if seconds_to_advance.zero? + datetime_advanced_by_date + else + datetime_advanced_by_date.since seconds_to_advance + end end # Returns a new DateTime representing the time a number of seconds ago @@ -83,10 +96,19 @@ class DateTime change(:hour => 23, :min => 59, :sec => 59) end + # Returns a new DateTime representing the start of the hour (hh:00:00) + def beginning_of_hour + change(:min => 0) + end + alias :at_beginning_of_hour :beginning_of_hour + + # Returns a new DateTime representing the end of the hour (hh:59:59) + def end_of_hour + change(:min => 59, :sec => 59) + end + # Adjusts DateTime to UTC by adding its offset value; offset is set to 0 # - # Example: - # # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600 # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000 def utc @@ -108,4 +130,5 @@ class DateTime def <=>(other) super other.to_datetime end + end 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 dc55e9c33c..7c3a5eaace 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -4,10 +4,6 @@ require 'active_support/core_ext/date_time/calculations' 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 - # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats. # # This method is aliased to <tt>to_s</tt>. @@ -30,7 +26,7 @@ class DateTime # datetime argument as the value. # # # config/initializers/time_formats.rb - # Time::DATE_FORMATS[:month_and_year] = "%B %Y" + # Time::DATE_FORMATS[:month_and_year] = '%B %Y' # Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") } def to_formatted_s(format = :default) if formatter = ::Time::DATE_FORMATS[format] @@ -39,10 +35,9 @@ class DateTime to_default_s end end - alias_method :to_default_s, :to_s unless (instance_methods(false) & [:to_s, 'to_s']).empty? + alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s) alias_method :to_s, :to_formatted_s - # Returns the +utc_offset+ as an +HH:MM formatted string. Examples: # # datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24)) # datetime.formatted_offset # => "-06:00" @@ -58,12 +53,6 @@ class DateTime alias_method :default_inspect, :inspect alias_method :inspect, :readable_inspect - # 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 * 1000000) : self - end - # Returns DateTime with local offset for given year if format is local else offset is zero # # DateTime.civil_from_format :local, 2012 @@ -91,8 +80,11 @@ class DateTime private + def offset_in_seconds + (offset * 86400).to_i + end + def seconds_since_unix_epoch - seconds_per_day = 86_400 - (self - ::DateTime.civil(1970)) * seconds_per_day + (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight end end diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb index 6fa55a9255..823735d3e2 100644 --- a/activesupport/lib/active_support/core_ext/date_time/zones.rb +++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb @@ -14,8 +14,10 @@ class DateTime # # DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) - return self unless zone - - ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) + if zone + ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) + else + self + end end end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 77a5087981..03efe6a19a 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -1,5 +1,5 @@ module Enumerable - # Calculates a sum from the elements. Examples: + # Calculates a sum from the elements. # # payments.sum { |p| p.price * p.tax_rate } # payments.sum(&:price) @@ -11,7 +11,7 @@ module Enumerable # It can also calculate the sum without the use of a block. # # [5, 15, 10].sum # => 30 - # ["foo", "bar"].sum # => "foobar" + # ['foo', 'bar'].sum # => "foobar" # [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5] # # The default sum of an empty list is zero. You can override this default: @@ -26,7 +26,7 @@ module Enumerable end end - # Convert an enumerable to a hash. Examples: + # Convert an enumerable to a hash. # # people.index_by(&:login) # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...} @@ -34,8 +34,11 @@ 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] }] + if block_given? + Hash[map { |elem| [yield(elem), elem] }] + else + to_enum :index_by + end end # Returns true if the enumerable has more than 1 element. Functionally equivalent to enum.to_a.size > 1. @@ -48,7 +51,7 @@ module Enumerable cnt > 1 end else - any?{ (cnt += 1) > 1 } + any? { (cnt += 1) > 1 } end end @@ -62,8 +65,15 @@ class Range #:nodoc: # Optimize range sum to use arithmetic progression if a block is not given and # we have a range of numeric values. def sum(identity = 0) - return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer)) - actual_last = exclude_end? ? (last - 1) : last - (actual_last - first + 1) * (actual_last + first) / 2 + if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer)) + super + else + actual_last = exclude_end? ? (last - 1) : last + if actual_last >= first + (actual_last - first + 1) * (actual_last + first) / 2 + else + identity + end + end end end diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb index 3645597301..81beb4e85d 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -1,22 +1,25 @@ +require 'fileutils' + class File # Write to a file atomically. Useful for situations where you don't # want other processes or threads to see half-written files. # - # File.atomic_write("important.file") do |file| - # file.write("hello") + # File.atomic_write('important.file') do |file| + # file.write('hello') # end # # If your temp directory is not on the same filesystem as the file you're # trying to write, you can provide a different temporary directory. # - # File.atomic_write("/data/something.important", "/data/tmp") do |file| - # file.write("hello") + # File.atomic_write('/data/something.important', '/data/tmp') do |file| + # file.write('hello') # end def self.atomic_write(file_name, temp_dir = Dir.tmpdir) require 'tempfile' unless defined?(Tempfile) require 'fileutils' unless defined?(FileUtils) temp_file = Tempfile.new(basename(file_name), temp_dir) + temp_file.binmode yield temp_file temp_file.close @@ -24,11 +27,9 @@ class File # Get original file permissions old_stat = stat(file_name) rescue Errno::ENOENT - # No old permissions, write a temp file to determine the defaults - check_name = join(dirname(file_name), ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}") - open(check_name, "w") { } - old_stat = stat(check_name) - unlink(check_name) + # If not possible, probe which are the default permissions in the + # destination directory. + old_stat = probe_stat_in(dirname(file_name)) end # Overwrite original file with temp file @@ -38,4 +39,20 @@ class File chown(old_stat.uid, old_stat.gid, file_name) chmod(old_stat.mode, file_name) end + + # Private utility method. + def self.probe_stat_in(dir) #:nodoc: + basename = [ + '.permissions_check', + Thread.current.object_id, + Process.pid, + rand(1000000) + ].join('.') + + file_name = join(dir, basename) + FileUtils.touch(file_name) + stat(file_name) + ensure + FileUtils.rm_f(file_name) if file_name + end end diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index fd1cda991e..501483498d 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/hash/deep_merge' -require 'active_support/core_ext/hash/deep_dup' require 'active_support/core_ext/hash/diff' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/indifferent_access' diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 5f07bb4f5a..7c72ead36c 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -8,7 +8,7 @@ require 'active_support/core_ext/string/inflections' class Hash # Returns a string containing an XML representation of its receiver: # - # {"foo" => 1, "bar" => 2}.to_xml + # {'foo' => 1, 'bar' => 2}.to_xml # # => # # <?xml version="1.0" encoding="UTF-8"?> # # <hash> @@ -26,20 +26,20 @@ class Hash # # * If +value+ is a callable object it must expect one or two arguments. Depending # on the arity, the callable is invoked with the +options+ hash as first argument - # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The + # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The # callable can add nodes by using <tt>options[:builder]</tt>. # - # "foo".to_xml(lambda { |options, key| options[:builder].b(key) }) + # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) }) # # => "<b>foo</b>" # # * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>. - # + # # class Foo # def to_xml(options) - # options[:builder].bar "fooing!" + # options[:builder].bar 'fooing!' # end # end - # + # # {:foo => Foo.new}.to_xml(:skip_instruct => true) # # => "<hash><bar>fooing!</bar></hash>" # @@ -57,8 +57,8 @@ class Hash # "TrueClass" => "boolean", # "FalseClass" => "boolean", # "Date" => "date", - # "DateTime" => "datetime", - # "Time" => "datetime" + # "DateTime" => "dateTime", + # "Time" => "dateTime" # } # # By default the root node is "hash", but that's configurable via the <tt>:root</tt> option. @@ -71,7 +71,7 @@ class Hash options = options.dup options[:indent] ||= 2 - options[:root] ||= "hash" + options[:root] ||= 'hash' options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) builder = options[:builder] @@ -100,24 +100,24 @@ class Hash [] else case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a? - when "Array" + when 'Array' entries.collect { |v| typecast_xml_value(v) } - when "Hash" + when 'Hash' [typecast_xml_value(entries)] else raise "can't typecast #{entries.inspect}" end end - elsif value['type'] == 'file' || - (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?)) - content = value["__content__"] - if parser = ActiveSupport::XmlMini::PARSING[value["type"]] + elsif value['type'] == 'file' || + (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?)) + content = value['__content__'] + if parser = ActiveSupport::XmlMini::PARSING[value['type']] parser.arity == 1 ? parser.call(content) : parser.call(content, value) else content end elsif value['type'] == 'string' && value['nil'] != 'true' - "" + '' # blank or nil parsed values are represented by nil elsif value.blank? || value['nil'] == 'true' nil @@ -129,9 +129,9 @@ class Hash else xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }] - # Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with + # Turn { :files => { :file => #<StringIO> } } into { :files => #<StringIO> } so it is compatible with # how multipart uploaded files from HTML appear - xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value + xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value end when 'Array' value.map! { |i| typecast_xml_value(i) } @@ -145,9 +145,9 @@ class Hash def unrename_keys(params) case params.class.to_s - when "Hash" - Hash[params.map { |k,v| [k.to_s.tr("-", "_"), unrename_keys(v)] } ] - when "Array" + when 'Hash' + Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ] + when 'Array' params.map { |v| unrename_keys(v) } else params diff --git a/activesupport/lib/active_support/core_ext/hash/deep_dup.rb b/activesupport/lib/active_support/core_ext/hash/deep_dup.rb deleted file mode 100644 index 447142605c..0000000000 --- a/activesupport/lib/active_support/core_ext/hash/deep_dup.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Hash - # Returns a deep copy of hash. - def deep_dup - duplicate = self.dup - duplicate.each_pair do |k,v| - tv = duplicate[k] - duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_dup : v - end - duplicate - end -end diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb index af771c86ff..023bf68a87 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb @@ -1,11 +1,16 @@ class Hash # Returns a new hash with +self+ and +other_hash+ merged recursively. + # + # h1 = {x: {y: [4,5,6]}, z: [7,8,9]} + # h2 = {x: {y: [7,8,9]}, z: "xyz"} + # + # h1.deep_merge(h2) #=> {:x => {:y => [7, 8, 9]}, :z => "xyz"} + # h2.deep_merge(h1) #=> {:x => {:y => [4, 5, 6]}, :z => [7, 8, 9]} def deep_merge(other_hash) dup.deep_merge!(other_hash) end - # Returns a new hash with +self+ and +other_hash+ merged recursively. - # Modifies the receiver in place. + # Same as +deep_merge+, but modifies +self+. def deep_merge!(other_hash) other_hash.each_pair do |k,v| tv = self[k] diff --git a/activesupport/lib/active_support/core_ext/hash/diff.rb b/activesupport/lib/active_support/core_ext/hash/diff.rb index b904f49fa8..831dee8ecb 100644 --- a/activesupport/lib/active_support/core_ext/hash/diff.rb +++ b/activesupport/lib/active_support/core_ext/hash/diff.rb @@ -1,13 +1,13 @@ class Hash # Returns a hash that represents the difference between two hashes. # - # Examples: - # # {1 => 2}.diff(1 => 2) # => {} # {1 => 2}.diff(1 => 3) # => {1 => 2} # {}.diff(1 => 2) # => {1 => 2} # {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4} - def diff(h2) - dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| has_key?(k) }) + def diff(other) + dup. + delete_if { |k, v| other[k] == v }. + merge!(other.dup.delete_if { |k, v| has_key?(k) }) end end diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index 89729df258..c82da3c6c2 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -4,13 +4,6 @@ class Hash # # @person.update_attributes(params[:person].except(:admin)) # - # If the receiver responds to +convert_key+, the method is called on each of the - # arguments. This allows +except+ to play nice with hashes with indifferent access - # for instance: - # - # {:a => 1}.with_indifferent_access.except(:a) # => {} - # {:a => 1}.with_indifferent_access.except("a") # => {} - # def except(*keys) dup.except!(*keys) end diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index d8748b1138..e753e36124 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -1,47 +1,138 @@ class Hash - # Return a new hash with all keys converted to strings. - def stringify_keys - dup.stringify_keys! + # Return a new hash with all keys converted using the block operation. + # + # hash = { name: 'Rob', age: '28' } + # + # hash.transform_keys{ |key| key.to_s.upcase } + # # => { "NAME" => "Rob", "AGE" => "28" } + def transform_keys + result = {} + each_key do |key| + result[yield(key)] = self[key] + end + result end - # Destructively convert all keys to strings. - def stringify_keys! + # Destructively convert all keys using the block operations. + # Same as transform_keys but modifies +self+ + def transform_keys! keys.each do |key| - self[key.to_s] = delete(key) + self[yield(key)] = delete(key) end self end + # Return a new hash with all keys converted to strings. + # + # hash = { name: 'Rob', age: '28' } + # + # hash.stringify_keys + # #=> { "name" => "Rob", "age" => "28" } + def stringify_keys + transform_keys{ |key| key.to_s } + end + + # Destructively convert all keys to strings. Same as + # +stringify_keys+, but modifies +self+. + def stringify_keys! + transform_keys!{ |key| key.to_s } + end + # Return a new hash with all keys converted to symbols, as long as # they respond to +to_sym+. + # + # hash = { 'name' => 'Rob', 'age' => '28' } + # + # hash.symbolize_keys + # #=> { name: "Rob", age: "28" } def symbolize_keys - dup.symbolize_keys! + transform_keys{ |key| key.to_sym rescue key } end + alias_method :to_options, :symbolize_keys # Destructively convert all keys to symbols, as long as they respond - # to +to_sym+. + # to +to_sym+. Same as +symbolize_keys+, but modifies +self+. def symbolize_keys! - keys.each do |key| - self[(key.to_sym rescue key) || key] = delete(key) - end - self + transform_keys!{ |key| key.to_sym rescue key } end - - alias_method :to_options, :symbolize_keys alias_method :to_options!, :symbolize_keys! # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch. # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols # as keys, this will fail. # - # ==== Examples - # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years" - # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key: name" - # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing + # { :name => 'Rob', :years => '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years" + # { :name => 'Rob', :age => '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name" + # { :name => 'Rob', :age => '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing def assert_valid_keys(*valid_keys) valid_keys.flatten! each_key do |k| - raise(ArgumentError, "Unknown key: #{k}") unless valid_keys.include?(k) + raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k) + end + end + + # Return a new hash with all keys converted by the block operation. + # This includes the keys from the root hash and from all + # nested hashes. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_transform_keys{ |key| key.to_s.upcase } + # # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } } + def deep_transform_keys(&block) + result = {} + each do |key, value| + result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value end + result + end + + # Destructively convert all keys by using the block operation. + # This includes the keys from the root hash and from all + # nested hashes. + def deep_transform_keys!(&block) + keys.each do |key| + value = delete(key) + self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value + end + self + end + + # Return a new hash with all keys converted to strings. + # This includes the keys from the root hash and from all + # nested hashes. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_stringify_keys + # # => { "person" => { "name" => "Rob", "age" => "28" } } + def deep_stringify_keys + deep_transform_keys{ |key| key.to_s } + end + + # Destructively convert all keys to strings. + # This includes the keys from the root hash and from all + # nested hashes. + def deep_stringify_keys! + deep_transform_keys!{ |key| key.to_s } + end + + # Return a new hash with all keys converted to symbols, as long as + # they respond to +to_sym+. This includes the keys from the root hash + # and from all nested hashes. + # + # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } } + # + # hash.deep_symbolize_keys + # # => { person: { name: "Rob", age: "28" } } + def deep_symbolize_keys + deep_transform_keys{ |key| key.to_sym rescue key } + end + + # Destructively convert all keys to symbols, as long as they respond + # to +to_sym+. This includes the keys from the root hash and from all + # nested hashes. + def deep_symbolize_keys! + deep_transform_keys!{ |key| key.to_sym rescue key } end end diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb index 01863a162b..6074103484 100644 --- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb @@ -18,6 +18,5 @@ class Hash # right wins if there is no left merge!( other_hash ){|key,left,right| left } end - alias_method :reverse_update, :reverse_merge! end diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 0484d8e5d8..b862b5ae2a 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -13,17 +13,15 @@ class Hash # valid_keys = [:mass, :velocity, :time] # search(options.slice(*valid_keys)) def slice(*keys) - keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key) - hash = self.class.new - keys.each { |k| hash[k] = self[k] if has_key?(k) } - hash + keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true) + keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) } end # Replaces the hash with only the given keys. - # Returns a hash contained the removed key/value pairs + # Returns a hash containing the removed key/value pairs. # {:a => 1, :b => 2, :c => 3, :d => 4}.slice!(:a, :b) # => {:c => 3, :d => 4} def slice!(*keys) - keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key) + keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true) omit = slice(*self.keys - keys) hash = slice(*keys) replace(hash) @@ -33,8 +31,6 @@ class Hash # 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) } - result + keys.each_with_object({}) { |key, result| result[key] = delete(key) } end end diff --git a/activesupport/lib/active_support/core_ext/integer/multiple.rb b/activesupport/lib/active_support/core_ext/integer/multiple.rb index 8dff217ddc..7c6c2f1ca7 100644 --- a/activesupport/lib/active_support/core_ext/integer/multiple.rb +++ b/activesupport/lib/active_support/core_ext/integer/multiple.rb @@ -1,5 +1,9 @@ class Integer # Check whether the integer is evenly divisible by the argument. + # + # 0.multiple_of?(0) #=> true + # 6.multiple_of?(5) #=> false + # 10.multiple_of?(2) #=> true def multiple_of?(number) number != 0 ? self % number == 0 : zero? end diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb index c677400396..894b5d0696 100644 --- a/activesupport/lib/active_support/core_ext/integer/time.rb +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -24,9 +24,9 @@ class Integer # 1.year.to_f.from_now # # In such cases, Ruby's core - # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and - # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision - # date and time arithmetic + # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and + # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision + # date and time arithmetic. def months ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) end diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb index d5b590e9f0..2073cac98d 100644 --- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb @@ -1,8 +1,8 @@ module Kernel unless respond_to?(:debugger) - # Starts a debugging session if ruby-debug has been loaded (call rails server --debugger to do load it). + # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it). def debugger - message = "\n***** Debugger requested, but was not available (ensure ruby-debug19 is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n" + message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n" defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message) end alias breakpoint debugger unless respond_to?(:breakpoint) diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 526b8378a5..ad3f9ebec9 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,4 +1,5 @@ require 'rbconfig' + module Kernel # Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards. # @@ -49,10 +50,10 @@ module Kernel # # suppress(ZeroDivisionError) do # 1/0 - # puts "This code is NOT reached" + # puts 'This code is NOT reached' # end # - # puts "This code gets executed and nothing related to ZeroDivisionError was seen" + # puts 'This code gets executed and nothing related to ZeroDivisionError was seen' def suppress(*exception_classes) begin yield rescue Exception => e @@ -62,7 +63,7 @@ module Kernel # Captures the given stream and returns it: # - # stream = capture(:stdout) { puts "Cool" } + # stream = capture(:stdout) { puts 'Cool' } # stream # => "Cool\n" # def capture(stream) diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index 8bdfa0c5bc..fe24f3716d 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -6,12 +6,14 @@ class LoadError /^cannot load such file -- (.+)$/i, ] - def path - @path ||= begin - REGEXPS.find do |regex| - message =~ regex + unless method_defined?(:path) + def path + @path ||= begin + REGEXPS.find do |regex| + message =~ regex + end + $1 end - $1 end end diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index a51818d2b2..16fce81445 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -56,8 +56,8 @@ class Logger alias :old_datetime_format= :datetime_format= # Logging date-time format (string passed to +strftime+). Ignored if the formatter # does not respond to datetime_format=. - def datetime_format=(datetime_format) - formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=) + def datetime_format=(format) + formatter.datetime_format = format if formatter.respond_to?(:datetime_format=) end alias :old_datetime_format :datetime_format diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb index ce481f0e84..580cb80413 100644 --- a/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -26,26 +26,25 @@ class Module aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 yield(aliased_target, punctuation) if block_given? - with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}" + with_method = "#{aliased_target}_with_#{feature}#{punctuation}" + without_method = "#{aliased_target}_without_#{feature}#{punctuation}" alias_method without_method, target alias_method target, with_method case - when public_method_defined?(without_method) - public target - when protected_method_defined?(without_method) - protected target - when private_method_defined?(without_method) - private target + when public_method_defined?(without_method) + public target + when protected_method_defined?(without_method) + protected target + when private_method_defined?(without_method) + private target end end # Allows you to make aliases for attributes, which includes # getter, setter, and query methods. # - # Example: - # # class Content < ActiveRecord::Base # # has a title attribute # end diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb index 00db75bfec..db07d549b0 100644 --- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb +++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb @@ -15,7 +15,6 @@ class Module attr_internal_reader(*attrs) attr_internal_writer(*attrs) end - alias_method :attr_internal, :attr_internal_accessor class << self; attr_accessor :attr_internal_naming_format 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 be94ae1565..672cc0256f 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -4,6 +4,7 @@ class Module def mattr_reader(*syms) options = syms.extract_options! syms.each do |sym| + raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@#{sym} = nil unless defined? @@#{sym} @@ -25,6 +26,7 @@ class Module def mattr_writer(*syms) options = syms.extract_options! syms.each do |sym| + raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) def self.#{sym}=(obj) @@#{sym} = obj @@ -44,19 +46,19 @@ class Module # Extends the module object with module and instance accessors for class attributes, # just like the native attr* accessors for instance attributes. # - # module AppConfiguration - # mattr_accessor :google_api_key - # self.google_api_key = "123456789" + # module AppConfiguration + # mattr_accessor :google_api_key # - # mattr_accessor :paypal_url - # self.paypal_url = "www.sandbox.paypal.com" - # end + # self.google_api_key = "123456789" + # end # - # AppConfiguration.google_api_key = "overriding the api key!" + # AppConfiguration.google_api_key # => "123456789" + # AppConfiguration.google_api_key = "overriding the api key!" + # AppConfiguration.google_api_key # => "overriding the api key!" # - # To opt out of the instance writer method, pass :instance_writer => false. - # To opt out of the instance reader method, pass :instance_reader => false. - # To opt out of both instance methods, pass :instance_accessor => false. + # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>. + # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>. + # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>. 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 ac2a63d3a1..39a1240c61 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,5 +1,5 @@ class Module - # Provides a delegate class method to easily expose contained objects' methods + # Provides a delegate class method to easily expose contained objects' public methods # as your own. Pass one or more methods (specified as symbols or strings) # and the name of the target object via the <tt>:to</tt> option (also a symbol # or string). At least one method and the <tt>:to</tt> option are required. @@ -8,11 +8,11 @@ class Module # # class Greeter < ActiveRecord::Base # def hello - # "hello" + # 'hello' # end # # def goodbye - # "goodbye" + # 'goodbye' # end # end # @@ -62,7 +62,7 @@ class Module # delegate :name, :address, :to => :client, :prefix => true # end # - # john_doe = Person.new("John Doe", "Vimmersvej 13") + # john_doe = Person.new('John Doe', 'Vimmersvej 13') # invoice = Invoice.new(john_doe) # invoice.client_name # => "John Doe" # invoice.client_address # => "Vimmersvej 13" @@ -74,8 +74,8 @@ class Module # end # # invoice = Invoice.new(john_doe) - # invoice.customer_name # => "John Doe" - # invoice.customer_address # => "Vimmersvej 13" + # invoice.customer_name # => 'John Doe' + # invoice.customer_address # => 'Vimmersvej 13' # # If the delegate object is +nil+ an exception is raised, and that happens # no matter whether +nil+ responds to the delegated method. You can get a @@ -104,15 +104,16 @@ class Module def delegate(*methods) options = methods.pop 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)." + 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 prefix == true && to.to_s =~ /^[^a-z_]/ - raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." + prefix, allow_nil = options.values_at(:prefix, :allow_nil) + + if prefix == true && to =~ /^[^a-z_]/ + raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.' end - method_prefix = + method_prefix = \ if prefix "#{prefix == true ? to : prefix}_" else @@ -123,13 +124,15 @@ class Module line = line.to_i methods.each do |method| - method = method.to_s + # Attribute writer methods only accept one argument. Makes sure []= + # methods still accept two arguments. + definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' if allow_nil module_eval(<<-EOS, file, line - 2) - def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block) + def #{method_prefix}#{method}(#{definition}) # 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) + #{to}.#{method}(#{definition}) # client.name(*args, &block) end # end end # end EOS @@ -137,8 +140,8 @@ class Module exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") 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) + def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) + #{to}.#{method}(#{definition}) # client.name(*args, &block) rescue NoMethodError # rescue NoMethodError if #{to}.nil? # if client.nil? #{exception} # # add helpful message to the exception diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb index 5a5b4e3f80..9e77ac3c45 100644 --- a/activesupport/lib/active_support/core_ext/module/deprecation.rb +++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation/method_wrappers' + class Module # Declare that a method has been deprecated. # deprecate :foo diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 743db47bac..3c8e811fa4 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -5,10 +5,11 @@ class Module # # M::N.parent_name # => "M" def parent_name - unless defined? @parent_name + if defined? @parent_name + @parent_name + else @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil end - @parent_name end # Returns the module which contains this one according to its name. @@ -73,7 +74,7 @@ class Module # This method is useful for forward compatibility, since Ruby 1.8 returns # constant names as strings, whereas 1.9 returns them as symbols. def local_constant_names - ActiveSupport::Deprecation.warn('Module#local_constant_names is deprecated, use Module#local_constants instead', caller) + ActiveSupport::Deprecation.warn 'Module#local_constant_names is deprecated, use Module#local_constants instead', caller local_constants.map { |c| c.to_s } end end diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb index 8adf050b6b..65525013db 100644 --- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb +++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb @@ -5,7 +5,7 @@ require 'active_support/core_ext/string/inflections' #++ module QualifiedConstUtils def self.raise_if_absolute(path) - raise NameError, "wrong constant name #$&" if path =~ /\A::[^:]+/ + raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/ end def self.names(path) @@ -20,7 +20,7 @@ end #-- # 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. +# Object.const_get('::String') raises NameError and so does qualified_const_get. #++ class Module def qualified_const_defined?(path, search_parents=true) 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 b76bc16ee1..719071d1c2 100644 --- a/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -1,12 +1,8 @@ class Module def remove_possible_method(method) if method_defined?(method) || private_method_defined?(method) - remove_method(method) + undef_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) diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb index 3805cf7990..a6bc0624be 100644 --- a/activesupport/lib/active_support/core_ext/numeric.rb +++ b/activesupport/lib/active_support/core_ext/numeric.rb @@ -1,2 +1,3 @@ require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' +require 'active_support/core_ext/numeric/conversions' diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb new file mode 100644 index 0000000000..2bbfa78639 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -0,0 +1,135 @@ +require 'active_support/core_ext/big_decimal/conversions' +require 'active_support/number_helper' + +class Numeric + + # Provides options for converting numbers into formatted strings. + # Options are provided for phone numbers, currency, percentage, + # precision, positional notation, file size and pretty printing. + # + # ==== Options + # + # For details on which formats use which options, see ActiveSupport::NumberHelper + # + # ==== Examples + # + # Phone Numbers: + # 5551234.to_s(:phone) # => 555-1234 + # 1235551234.to_s(:phone) # => 123-555-1234 + # 1235551234.to_s(:phone, :area_code => true) # => (123) 555-1234 + # 1235551234.to_s(:phone, :delimiter => " ") # => 123 555 1234 + # 1235551234.to_s(:phone, :area_code => true, :extension => 555) # => (123) 555-1234 x 555 + # 1235551234.to_s(:phone, :country_code => 1) # => +1-123-555-1234 + # 1235551234.to_s(:phone, :country_code => 1, :extension => 1343, :delimiter => ".") + # # => +1.123.555.1234 x 1343 + # + # Currency: + # 1234567890.50.to_s(:currency) # => $1,234,567,890.50 + # 1234567890.506.to_s(:currency) # => $1,234,567,890.51 + # 1234567890.506.to_s(:currency, :precision => 3) # => $1,234,567,890.506 + # 1234567890.506.to_s(:currency, :locale => :fr) # => 1 234 567 890,51 € + # -1234567890.50.to_s(:currency, :negative_format => "(%u%n)") + # # => ($1,234,567,890.50) + # 1234567890.50.to_s(:currency, :unit => "£", :separator => ",", :delimiter => "") + # # => £1234567890,50 + # 1234567890.50.to_s(:currency, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") + # # => 1234567890,50 £ + # + # Percentage: + # 100.to_s(:percentage) # => 100.000% + # 100.to_s(:percentage, :precision => 0) # => 100% + # 1000.to_s(:percentage, :delimiter => '.', :separator => ',') # => 1.000,000% + # 302.24398923423.to_s(:percentage, :precision => 5) # => 302.24399% + # 1000.to_s(:percentage, :locale => :fr) # => 1 000,000% + # 100.to_s(:percentage, :format => "%n %") # => 100 % + # + # Delimited: + # 12345678.to_s(:delimited) # => 12,345,678 + # 12345678.05.to_s(:delimited) # => 12,345,678.05 + # 12345678.to_s(:delimited, :delimiter => ".") # => 12.345.678 + # 12345678.to_s(:delimited, :delimiter => ",") # => 12,345,678 + # 12345678.05.to_s(:delimited, :separator => " ") # => 12,345,678 05 + # 12345678.05.to_s(:delimited, :locale => :fr) # => 12 345 678,05 + # 98765432.98.to_s(:delimited, :delimiter => " ", :separator => ",") + # # => 98 765 432,98 + # + # Rounded: + # 111.2345.to_s(:rounded) # => 111.235 + # 111.2345.to_s(:rounded, :precision => 2) # => 111.23 + # 13.to_s(:rounded, :precision => 5) # => 13.00000 + # 389.32314.to_s(:rounded, :precision => 0) # => 389 + # 111.2345.to_s(:rounded, :significant => true) # => 111 + # 111.2345.to_s(:rounded, :precision => 1, :significant => true) # => 100 + # 13.to_s(:rounded, :precision => 5, :significant => true) # => 13.000 + # 111.234.to_s(:rounded, :locale => :fr) # => 111,234 + # 13.to_s(:rounded, :precision => 5, :significant => true, :strip_insignificant_zeros => true) + # # => 13 + # 389.32314.to_s(:rounded, :precision => 4, :significant => true) # => 389.3 + # 1111.2345.to_s(:rounded, :precision => 2, :separator => ',', :delimiter => '.') + # # => 1.111,23 + # + # Human-friendly size in Bytes: + # 123.to_s(:human_size) # => 123 Bytes + # 1234.to_s(:human_size) # => 1.21 KB + # 12345.to_s(:human_size) # => 12.1 KB + # 1234567.to_s(:human_size) # => 1.18 MB + # 1234567890.to_s(:human_size) # => 1.15 GB + # 1234567890123.to_s(:human_size) # => 1.12 TB + # 1234567.to_s(:human_size, :precision => 2) # => 1.2 MB + # 483989.to_s(:human_size, :precision => 2) # => 470 KB + # 1234567.to_s(:human_size, :precision => 2, :separator => ',') # => 1,2 MB + # 1234567890123.to_s(:human_size, :precision => 5) # => "1.1229 TB" + # 524288000.to_s(:human_size, :precision => 5) # => "500 MB" + # + # Human-friendly format: + # 123.to_s(:human) # => "123" + # 1234.to_s(:human) # => "1.23 Thousand" + # 12345.to_s(:human) # => "12.3 Thousand" + # 1234567.to_s(:human) # => "1.23 Million" + # 1234567890.to_s(:human) # => "1.23 Billion" + # 1234567890123.to_s(:human) # => "1.23 Trillion" + # 1234567890123456.to_s(:human) # => "1.23 Quadrillion" + # 1234567890123456789.to_s(:human) # => "1230 Quadrillion" + # 489939.to_s(:human, :precision => 2) # => "490 Thousand" + # 489939.to_s(:human, :precision => 4) # => "489.9 Thousand" + # 1234567.to_s(:human, :precision => 4, + # :significant => false) # => "1.2346 Million" + # 1234567.to_s(:human, :precision => 1, + # :separator => ',', + # :significant => false) # => "1,2 Million" + def to_formatted_s(format = :default, options = {}) + case format + when :phone + return ActiveSupport::NumberHelper.number_to_phone(self, options) + when :currency + return ActiveSupport::NumberHelper.number_to_currency(self, options) + when :percentage + return ActiveSupport::NumberHelper.number_to_percentage(self, options) + when :delimited + return ActiveSupport::NumberHelper.number_to_delimited(self, options) + when :rounded + return ActiveSupport::NumberHelper.number_to_rounded(self, options) + when :human + return ActiveSupport::NumberHelper.number_to_human(self, options) + when :human_size + return ActiveSupport::NumberHelper.number_to_human_size(self, options) + else + self.to_default_s + end + end + + [Float, Fixnum, Bignum, BigDecimal].each do |klass| + klass.send(:alias_method, :to_default_s, :to_s) + + klass.send(:define_method, :to_s) do |*args| + if args[0].is_a?(Symbol) + format = args[0] + options = args[1] || {} + + self.to_formatted_s(format, options) + else + to_default_s(*args) + end + end + end +end diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index 58a03d508e..2bf3d1f278 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -8,13 +8,13 @@ class Numeric # These methods use Time#advance for precise date calculations when using from_now, ago, etc. # as well as adding or subtracting their results from a Time object. For example: # - # # equivalent to Time.now.advance(:months => 1) + # # equivalent to Time.current.advance(:months => 1) # 1.month.from_now # - # # equivalent to Time.now.advance(:years => 2) + # # equivalent to Time.current.advance(:years => 2) # 2.years.from_now # - # # equivalent to Time.now.advance(:months => 4, :years => 5) + # # equivalent to Time.current.advance(:months => 4, :years => 5) # (4.months + 5.years).from_now # # While these methods provide precise calculation when used as in the examples above, care @@ -28,9 +28,9 @@ class Numeric # 1.year.to_f.from_now # # In such cases, Ruby's core - # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and - # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision - # date and time arithmetic + # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and + # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision + # date and time arithmetic. def seconds ActiveSupport::Duration.new(self, [[:seconds, self]]) end diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index 9ad1e12699..ec2157221f 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/object/acts_like' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/duplicable' +require 'active_support/core_ext/object/deep_dup' require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/inclusion' diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 7271671908..e238fef5a2 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,9 +1,8 @@ # 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. + # For example, '', ' ', +nil+, [], and {} are all blank. # # This simplifies: # @@ -91,10 +90,10 @@ end class String # A string is blank if it's empty or contains whitespaces only: # - # "".blank? # => true - # " ".blank? # => true - # " ".blank? # => true - # " something here ".blank? # => false + # ''.blank? # => true + # ' '.blank? # => true + # ' '.blank? # => true + # ' something here '.blank? # => false # def blank? self !~ /[^[:space:]]/ diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb new file mode 100644 index 0000000000..f55fbc282e --- /dev/null +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -0,0 +1,46 @@ +require 'active_support/core_ext/object/duplicable' + +class Object + # Returns a deep copy of object if it's duplicable. If it's + # not duplicable, returns +self+. + # + # object = Object.new + # dup = object.deep_dup + # dup.instance_variable_set(:@a, 1) + # + # object.instance_variable_defined?(:@a) #=> false + # dup.instance_variable_defined?(:@a) #=> true + def deep_dup + duplicable? ? dup : self + end +end + +class Array + # Returns a deep copy of array. + # + # array = [1, [2, 3]] + # dup = array.deep_dup + # dup[1][2] = 4 + # + # array[1][2] #=> nil + # dup[1][2] #=> 4 + def deep_dup + map { |it| it.deep_dup } + end +end + +class Hash + # Returns a deep copy of hash. + # + # hash = { a: { b: 'b' } } + # dup = hash.deep_dup + # dup[:a][:c] = 'c' + # + # hash[:a][:c] #=> nil + # dup[:a][:c] #=> "c" + def deep_dup + each_with_object(dup) do |(key, value), hash| + hash[key.deep_dup] = value.deep_dup + end + end +end diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9d044eba71..f1b755c2c4 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -19,7 +19,7 @@ class Object # Can you safely dup this object? # - # False for +nil+, +false+, +true+, symbols, numbers, class and module objects; + # False for +nil+, +false+, +true+, symbol, and number objects; # true otherwise. def duplicable? true @@ -81,26 +81,15 @@ class Numeric end end -class Class - # Classes are not duplicable: - # - # c = Class.new # => #<Class:0x10328fd80> - # c.dup # => #<Class:0x10328fd80> - # - # Note +dup+ returned the same class object. - def duplicable? - false - end -end +require 'bigdecimal' +class BigDecimal + begin + BigDecimal.new('4.56').dup -class Module - # Modules are not duplicable: - # - # m = Module.new # => #<Module:0x10328b6e0> - # m.dup # => #<Module:0x10328b6e0> - # - # Note +dup+ returned the same module object. - def duplicable? - false + def duplicable? + true + end + rescue TypeError + # can't dup, so use superclass implementation 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 f611cdd606..3fec465ec0 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -2,11 +2,11 @@ class Object # 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 + # characters = ['Konata', 'Kagami', 'Tsukasa'] + # 'Konata'.in?(characters) # => true + # + # character = 'Konata' + # character.in?('Konata', 'Kagami', 'Tsukasa') # => true # # This will throw an ArgumentError if a single argument is passed in and it doesn't respond # to +#include?+. @@ -18,7 +18,7 @@ class Object if another_object.respond_to? :include? another_object.include? self else - raise ArgumentError.new("The single parameter passed to #in? must respond to #include?") + raise ArgumentError.new 'The single parameter passed to #in? must respond to #include?' 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 91fdf93eb2..40821fd619 100644 --- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb @@ -1,6 +1,6 @@ class Object - # Returns a hash that maps instance variable names without "@" to their - # corresponding values. Keys are strings both in Ruby 1.8 and 1.9. + # Returns a hash with string keys that maps instance variable names without "@" to their + # corresponding values. # # class C # def initialize(x, y) @@ -9,12 +9,11 @@ class Object # end # # C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} - def instance_values #:nodoc: + def instance_values Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }] end - # Returns an array of instance variable names including "@". They are strings - # both in Ruby 1.8 and 1.9. + # Returns an array of instance variable names including "@". # # class C # def initialize(x, y) diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index e5f81078ee..0d5f3501e5 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -6,18 +6,21 @@ class Object end class NilClass + # Returns +self+. def to_param self end end class TrueClass + # Returns +self+. def to_param self end end class FalseClass + # Returns +self+. def to_param self end @@ -35,12 +38,12 @@ class Hash # Returns a string representation of the receiver suitable for use as a URL # query string: # - # {:name => 'David', :nationality => 'Danish'}.to_param + # {name: 'David', nationality: 'Danish'}.to_param # # => "name=David&nationality=Danish" # # An optional namespace can be passed to enclose the param names: # - # {:name => 'David', :nationality => 'Danish'}.to_param('user') + # {name: 'David', nationality: 'Danish'}.to_param('user') # # => "user[name]=David&user[nationality]=Danish" # # The string pairs "key=value" that conform the query string diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index e77a9da0ec..1079ddde98 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -1,13 +1,18 @@ class Object - # Invokes the method identified by the symbol +method+, passing it any arguments - # and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does. + # Invokes the public method identified by the symbol +method+, passing it any arguments + # and/or the block specified, just like the regular Ruby <tt>Object#public_send</tt> does. # # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass. # + # This is also true if the receiving object does not implemented the tried method. It will + # return +nil+ in that case as well. + # # If try is called without a method to call, it will yield any given block with the object. # - # ==== Examples + # Please also note that +try+ is defined on +Object+, therefore it won't work with + # subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will + # delegate +try+ to target instead of calling it on delegator itself. # # Without +try+ # @person && @person.name @@ -23,13 +28,23 @@ class Object # # Without a method argument try will yield to the block unless the receiver is nil. # @person.try { |p| "#{p.first_name} #{p.last_name}" } - #-- - # +try+ behaves like +Object#send+, unless called on +NilClass+. + # + # +try+ behaves like +Object#public_send+, unless called on +NilClass+. def try(*a, &b) if a.empty? && block_given? yield self else - __send__(*a, &b) + public_send(*a, &b) if respond_to?(a.first) + end + end + + # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and + # does not implemented the tried method. + def try!(*a, &b) + if a.empty? && block_given? + yield self + else + public_send(*a, &b) end end end @@ -38,8 +53,6 @@ class NilClass # Calling +try+ on +nil+ always returns +nil+. # It becomes specially helpful when navigating through associations that may return +nil+. # - # === Examples - # # nil.try(:name) # => nil # # Without +try+ @@ -50,4 +63,8 @@ class NilClass def try(*args) nil end + + def try!(*args) + nil + end end diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index 1397142c04..e058367111 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -29,7 +29,7 @@ class Object # # It can also be used with an explicit receiver: # - # I18n.with_options :locale => user.locale, :scope => "newsletter" do |i18n| + # I18n.with_options :locale => user.locale, :scope => 'newsletter' do |i18n| # subject i18n.t :subject # body i18n.t :body, :user_name => user.name # end diff --git a/activesupport/lib/active_support/core_ext/proc.rb b/activesupport/lib/active_support/core_ext/proc.rb index 94bb5fb0cb..cd63740940 100644 --- a/activesupport/lib/active_support/core_ext/proc.rb +++ b/activesupport/lib/active_support/core_ext/proc.rb @@ -1,7 +1,10 @@ require "active_support/core_ext/kernel/singleton_class" +require "active_support/deprecation" class Proc #:nodoc: def bind(object) + ActiveSupport::Deprecation.warn 'Proc#bind is deprecated and will be removed in future versions', caller + block, time = self, Time.now object.class_eval do method_name = "__bind_#{time.to_i}_#{time.usec}" diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb index c0736f3a44..1d8b1ede5a 100644 --- a/activesupport/lib/active_support/core_ext/range.rb +++ b/activesupport/lib/active_support/core_ext/range.rb @@ -1,4 +1,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' diff --git a/activesupport/lib/active_support/core_ext/range/blockless_step.rb b/activesupport/lib/active_support/core_ext/range/blockless_step.rb deleted file mode 100644 index f687287f0d..0000000000 --- a/activesupport/lib/active_support/core_ext/range/blockless_step.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_support/core_ext/module/aliasing' - -class Range - def step_with_blockless(*args, &block) #:nodoc: - if block_given? - step_without_blockless(*args, &block) - else - step_without_blockless(*args).to_a - end - end - - alias_method_chain :step, :blockless -end diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb index 43134b4314..b1a12781f3 100644 --- a/activesupport/lib/active_support/core_ext/range/conversions.rb +++ b/activesupport/lib/active_support/core_ext/range/conversions.rb @@ -5,8 +5,6 @@ class Range # Gives a human readable format of the range. # - # ==== Example - # # (1..100).to_formatted_s # => "1..100" def to_formatted_s(format = :default) if formatter = RANGE_FORMATS[format] 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 684b7cbc4a..3af66aaf2f 100644 --- a/activesupport/lib/active_support/core_ext/range/include_range.rb +++ b/activesupport/lib/active_support/core_ext/range/include_range.rb @@ -5,7 +5,7 @@ class Range # (1..5).include?(2..6) # => false # # The native Range#include? behavior is untouched. - # ("a".."f").include?("c") # => true + # ('a'..'f').include?('c') # => true # (5..9).include?(11) # => false def include_with_range?(value) if value.is_a?(::Range) diff --git a/activesupport/lib/active_support/core_ext/range/overlaps.rb b/activesupport/lib/active_support/core_ext/range/overlaps.rb index 7df653b53f..603657c180 100644 --- a/activesupport/lib/active_support/core_ext/range/overlaps.rb +++ b/activesupport/lib/active_support/core_ext/range/overlaps.rb @@ -3,6 +3,6 @@ class Range # (1..5).overlaps?(4..6) # => true # (1..5).overlaps?(7..9) # => false def overlaps?(other) - include?(other.first) || other.include?(first) + cover?(other.first) || other.cover?(first) end end diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index 72522d395c..ad864765a3 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -6,9 +6,8 @@ require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/string/access' require 'active_support/core_ext/string/xchar' require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/string/interpolation' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/exclude' -require 'active_support/core_ext/string/encoding' require 'active_support/core_ext/string/strip' require 'active_support/core_ext/string/inquiry' +require 'active_support/core_ext/string/indent' diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 9b5266c58c..8fa8157d65 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -1,18 +1,77 @@ -require "active_support/multibyte" - class String + # If you pass a single Fixnum, returns a substring of one character at that + # position. The first character of the string is at position 0, the next at + # position 1, and so on. If a range is supplied, a substring containing + # characters at offsets given by the range is returned. In both cases, if an + # offset is negative, it is counted from the end of the string. Returns nil + # if the initial offset falls outside the string. Returns an empty string if + # the beginning of the range is greater than the end of the string. + # + # str = "hello" + # str.at(0) #=> "h" + # str.at(1..3) #=> "ell" + # str.at(-2) #=> "l" + # str.at(-2..-1) #=> "lo" + # str.at(5) #=> nil + # str.at(5..-1) #=> "" + # + # If a Regexp is given, the matching portion of the string is returned. + # If a String is given, that given string is returned if it occurs in + # the string. In both cases, nil is returned if there is no match. + # + # str = "hello" + # str.at(/lo/) #=> "lo" + # str.at(/ol/) #=> nil + # str.at("lo") #=> "lo" + # str.at("ol") #=> nil def at(position) self[position] end + # Returns a substring from the given position to the end of the string. + # If the position is negative, it is counted from the end of the string. + # + # str = "hello" + # str.from(0) #=> "hello" + # str.from(3) #=> "lo" + # str.from(-2) #=> "lo" + # + # You can mix it with +to+ method and do fun things like: + # + # str = "hello" + # str.from(0).to(-1) #=> "hello" + # str.from(1).to(-2) #=> "ell" def from(position) self[position..-1] end + # Returns a substring from the beginning of the string to the given position. + # If the position is negative, it is counted from the end of the string. + # + # str = "hello" + # str.to(0) #=> "h" + # str.to(3) #=> "hell" + # str.to(-2) #=> "hell" + # + # You can mix it with +from+ method and do fun things like: + # + # str = "hello" + # str.from(0).to(-1) #=> "hello" + # str.from(1).to(-2) #=> "ell" def to(position) self[0..position] end + # Returns the first character. If a limit is supplied, returns a substring + # from the beginning of the string until it reaches the limit value. If the + # given limit is greater than or equal to the string length, returns self. + # + # str = "hello" + # str.first #=> "h" + # str.first(1) #=> "h" + # str.first(2) #=> "he" + # str.first(0) #=> "" + # str.first(6) #=> "hello" def first(limit = 1) if limit == 0 '' @@ -23,6 +82,16 @@ class String end end + # Returns the last character of the string. If a limit is supplied, returns a substring + # from the end of the string until it reaches the limit value (counting backwards). If + # the given limit is greater than or equal to the string length, returns self. + # + # str = "hello" + # str.last #=> "o" + # str.last(1) #=> "o" + # str.last(2) #=> "lo" + # str.last(0) #=> "" + # str.last(6) #=> "hello" def last(limit = 1) if limit == 0 '' diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 541f969faa..022b376aec 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -4,21 +4,45 @@ require 'active_support/core_ext/time/calculations' class String # 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, :offset).map { |arg| arg || 0 } - d[6] *= 1000000 - ::Time.send("#{form}_time", *d[0..6]) - d[7] + unless blank? + date_values = ::Date._parse(self, false). + values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset). + map! { |arg| arg || 0 } + date_values[6] *= 1000000 + offset = date_values.pop + + ::Time.send("#{form}_time", *date_values) - offset + end end + # Converts a string to a Date value. + # + # "1-1-2012".to_date #=> Sun, 01 Jan 2012 + # "01/01/2012".to_date #=> Sun, 01 Jan 2012 + # "2012-12-13".to_date #=> Thu, 13 Dec 2012 + # "12/13/2012".to_date #=> ArgumentError: invalid date def to_date - return nil if self.blank? - ::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday)) + unless blank? + date_values = ::Date._parse(self, false).values_at(:year, :mon, :mday) + + ::Date.new(*date_values) + end end + # Converts a string to a DateTime value. + # + # "1-1-2012".to_datetime #=> Sun, 01 Jan 2012 00:00:00 +0000 + # "01/01/2012 23:59:59".to_datetime #=> Sun, 01 Jan 2012 23:59:59 +0000 + # "2012-12-13 12:50".to_datetime #=> Thu, 13 Dec 2012 12:50:00 +0000 + # "12/13/2012".to_datetime #=> ArgumentError: invalid date def to_datetime - return nil if self.blank? - d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction).map { |arg| arg || 0 } - d[5] += d.pop - ::DateTime.civil(*d) + unless blank? + date_values = ::Date._parse(self, false). + values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction). + map! { |arg| arg || 0 } + date_values[5] += date_values.pop + + ::DateTime.civil(*date_values) + end end end diff --git a/activesupport/lib/active_support/core_ext/string/exclude.rb b/activesupport/lib/active_support/core_ext/string/exclude.rb index 5e184ec1b3..114bcb87f0 100644 --- a/activesupport/lib/active_support/core_ext/string/exclude.rb +++ b/activesupport/lib/active_support/core_ext/string/exclude.rb @@ -1,5 +1,10 @@ class String - # The inverse of <tt>String#include?</tt>. Returns true if the string does not include the other string. + # The inverse of <tt>String#include?</tt>. Returns true if the string + # does not include the other string. + # + # "hello".exclude? "lo" #=> false + # "hello".exclude? "ol" #=> true + # "hello".exclude? ?h #=> false def exclude?(string) !include?(string) end diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 1a34e88a87..8644529806 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -1,11 +1,8 @@ -require 'active_support/core_ext/string/multibyte' - class String # Returns the string, first removing all whitespace on both ends of # the string, and then changing remaining consecutive whitespace # groups into one space each. # - # Examples: # %{ Multi-line # string }.squish # => "Multi-line string" # " foo bar \n \t boo".squish # => "foo bar boo" @@ -22,26 +19,33 @@ class String # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>: # - # "Once upon a time in a world far far away".truncate(27) + # 'Once upon a time in a world far far away'.truncate(27) # # => "Once upon a time in a wo..." # - # Pass a <tt>:separator</tt> to truncate +text+ at a natural break: + # Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break: + # + # 'Once upon a time in a world far far away'.truncate(27, :separator => ' ') + # # => "Once upon a time in a..." # - # "Once upon a time in a world far far away".truncate(27, :separator => ' ') + # 'Once upon a time in a world far far away'.truncate(27, :separator => /\s/) # # => "Once upon a time in a..." # # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...") - # for a total length not exceeding <tt>:length</tt>: + # for a total length not exceeding <tt>length</tt>: # - # "And they found that many people were sleeping better.".truncate(25, :omission => "... (continued)") + # 'And they found that many people were sleeping better.'.truncate(25, :omission => '... (continued)') # # => "And they f... (continued)" - def truncate(length, options = {}) - return self.dup unless self.length > length + def truncate(truncate_at, options = {}) + return dup unless length > truncate_at - options[:omission] ||= "..." - length_with_room_for_omission = length - options[:omission].length - stop = options[:separator] ? - (rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission) : length_with_room_for_omission + options[:omission] ||= '...' + length_with_room_for_omission = truncate_at - options[:omission].length + stop = \ + if options[:separator] + rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission + else + length_with_room_for_omission + end self[0...stop] + options[:omission] end diff --git a/activesupport/lib/active_support/core_ext/string/indent.rb b/activesupport/lib/active_support/core_ext/string/indent.rb new file mode 100644 index 0000000000..afc3032272 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/string/indent.rb @@ -0,0 +1,43 @@ +class String + # Same as +indent+, except it indents the receiver in-place. + # + # Returns the indented string, or +nil+ if there was nothing to indent. + def indent!(amount, indent_string=nil, indent_empty_lines=false) + indent_string = indent_string || self[/^[ \t]/] || ' ' + re = indent_empty_lines ? /^/ : /^(?!$)/ + gsub!(re, indent_string * amount) + end + + # Indents the lines in the receiver: + # + # <<EOS.indent(2) + # def some_method + # some_code + # end + # EOS + # # => + # def some_method + # some_code + # end + # + # The second argument, +indent_string+, specifies which indent string to + # use. The default is +nil+, which tells the method to make a guess by + # peeking at the first indented line, and fallback to a space if there is + # none. + # + # " foo".indent(2) # => " foo" + # "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar" + # "foo".indent(2, "\t") # => "\t\tfoo" + # + # While +indent_string+ is tipically one space or tab, it may be any string. + # + # The third argument, +indent_empty_lines+, is a flag that says whether + # empty lines should be indented. Default is false. + # + # "foo\n\nbar".indent(2) # => " foo\n\n bar" + # "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar" + # + def indent(amount, indent_string=nil, indent_empty_lines=false) + dup.tap {|_| _.indent!(amount, indent_string, indent_empty_lines)} + end +end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 2194dafe4d..6edfcd7493 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -4,7 +4,7 @@ require 'active_support/inflector/transliterate' # String inflections define new methods on the String class to transform names for different purposes. # For instance, you can figure out the name of a table from the name of a class. # -# "ScaleScore".tableize # => "scale_scores" +# 'ScaleScore'.tableize # => "scale_scores" # class String # Returns the plural form of the word in the string. @@ -13,43 +13,55 @@ class String # 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" - # "apple".pluralize(1) # => "apple" - # "apple".pluralize(2) # => "apples" - def pluralize(count = nil) + # If the optional parameter +locale+ is specified, + # the word will be pluralized as a word of that language. + # By default, this parameter is set to <tt>:en</tt>. + # You must define your own inflection rules for languages other than English. + # + # 'post'.pluralize # => "posts" + # 'octopus'.pluralize # => "octopi" + # 'sheep'.pluralize # => "sheep" + # 'words'.pluralize # => "words" + # 'the blue mailman'.pluralize # => "the blue mailmen" + # 'CamelOctopus'.pluralize # => "CamelOctopi" + # 'apple'.pluralize(1) # => "apple" + # 'apple'.pluralize(2) # => "apples" + # 'ley'.pluralize(:es) # => "leyes" + # 'ley'.pluralize(1, :es) # => "ley" + def pluralize(count = nil, locale = :en) + locale = count if count.is_a?(Symbol) if count == 1 self else - ActiveSupport::Inflector.pluralize(self) + ActiveSupport::Inflector.pluralize(self, locale) end end # The reverse of +pluralize+, returns the singular form of a word in a string. - # - # "posts".singularize # => "post" - # "octopi".singularize # => "octopus" - # "sheep".singularize # => "sheep" - # "word".singularize # => "word" - # "the blue mailmen".singularize # => "the blue mailman" - # "CamelOctopi".singularize # => "CamelOctopus" - def singularize - ActiveSupport::Inflector.singularize(self) + # + # If the optional parameter +locale+ is specified, + # the word will be singularized as a word of that language. + # By default, this paramter is set to <tt>:en</tt>. + # You must define your own inflection rules for languages other than English. + # + # 'posts'.singularize # => "post" + # 'octopi'.singularize # => "octopus" + # 'sheep'.singularize # => "sheep" + # 'word'.singularize # => "word" + # 'the blue mailmen'.singularize # => "the blue mailman" + # 'CamelOctopi'.singularize # => "CamelOctopus" + # 'leyes'.singularize(:es) # => "ley" + def singularize(locale = :en) + ActiveSupport::Inflector.singularize(self, locale) end # +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. See ActiveSupport::Inflector.constantize # - # Examples - # "Module".constantize # => Module - # "Class".constantize # => Class - # "blargle".constantize # => NameError: wrong constant name blargle + # 'Module'.constantize # => Module + # 'Class'.constantize # => Class + # 'blargle'.constantize # => NameError: wrong constant name blargle def constantize ActiveSupport::Inflector.constantize(self) end @@ -58,10 +70,9 @@ class String # 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 + # 'Module'.safe_constantize # => Module + # 'Class'.safe_constantize # => Class + # 'blargle'.safe_constantize # => nil def safe_constantize ActiveSupport::Inflector.safe_constantize(self) end @@ -71,14 +82,16 @@ class String # # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. # - # "active_record".camelize # => "ActiveRecord" - # "active_record".camelize(:lower) # => "activeRecord" - # "active_record/errors".camelize # => "ActiveRecord::Errors" - # "active_record/errors".camelize(:lower) # => "activeRecord::Errors" + # 'active_record'.camelize # => "ActiveRecord" + # 'active_record'.camelize(:lower) # => "activeRecord" + # 'active_record/errors'.camelize # => "ActiveRecord::Errors" + # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors" def camelize(first_letter = :upper) case first_letter - when :upper then ActiveSupport::Inflector.camelize(self, true) - when :lower then ActiveSupport::Inflector.camelize(self, false) + when :upper + ActiveSupport::Inflector.camelize(self, true) + when :lower + ActiveSupport::Inflector.camelize(self, false) end end alias_method :camelcase, :camelize @@ -89,8 +102,8 @@ class String # # +titleize+ is also aliased as +titlecase+. # - # "man from the boondocks".titleize # => "Man From The Boondocks" - # "x-men: the last stand".titleize # => "X Men: The Last Stand" + # 'man from the boondocks'.titleize # => "Man From The Boondocks" + # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" def titleize ActiveSupport::Inflector.titleize(self) end @@ -100,23 +113,23 @@ class String # # +underscore+ will also change '::' to '/' to convert namespaces to paths. # - # "ActiveModel".underscore # => "active_model" - # "ActiveModel::Errors".underscore # => "active_model/errors" + # 'ActiveModel'.underscore # => "active_model" + # 'ActiveModel::Errors'.underscore # => "active_model/errors" def underscore ActiveSupport::Inflector.underscore(self) end # Replaces underscores with dashes in the string. # - # "puni_puni" # => "puni-puni" + # 'puni_puni'.dasherize # => "puni-puni" def dasherize ActiveSupport::Inflector.dasherize(self) end # Removes the module part from the constant expression in the string. # - # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" - # "Inflections".demodulize # => "Inflections" + # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" + # 'Inflections'.demodulize # => "Inflections" # # See also +deconstantize+. def demodulize @@ -125,11 +138,11 @@ class String # Removes the rightmost segment from the constant expression in the string. # - # "Net::HTTP".deconstantize # => "Net" - # "::Net::HTTP".deconstantize # => "::Net" - # "String".deconstantize # => "" - # "::String".deconstantize # => "" - # "".deconstantize # => "" + # 'Net::HTTP'.deconstantize # => "Net" + # '::Net::HTTP'.deconstantize # => "::Net" + # 'String'.deconstantize # => "" + # '::String'.deconstantize # => "" + # ''.deconstantize # => "" # # See also +demodulize+. def deconstantize @@ -138,8 +151,6 @@ class String # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. # - # ==== Examples - # # class Person # def to_param # "#{id}-#{name.parameterize}" @@ -158,9 +169,9 @@ class String # Creates 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. # - # "RawScaledScorer".tableize # => "raw_scaled_scorers" - # "egg_and_ham".tableize # => "egg_and_hams" - # "fancyCategory".tableize # => "fancy_categories" + # 'RawScaledScorer'.tableize # => "raw_scaled_scorers" + # 'egg_and_ham'.tableize # => "egg_and_hams" + # 'fancyCategory'.tableize # => "fancy_categories" def tableize ActiveSupport::Inflector.tableize(self) end @@ -169,12 +180,12 @@ class String # Note that this returns a string and not a class. (To convert to an actual class # follow +classify+ with +constantize+.) # - # "egg_and_hams".classify # => "EggAndHam" - # "posts".classify # => "Post" + # 'egg_and_hams'.classify # => "EggAndHam" + # 'posts'.classify # => "Post" # # Singular names are not handled correctly. # - # "business".classify # => "Busines" + # 'business'.classify # => "Busines" def classify ActiveSupport::Inflector.classify(self) end @@ -182,8 +193,8 @@ class String # Capitalizes the first word, turns underscores into spaces, and strips '_id'. # Like +titleize+, this is meant for creating pretty output. # - # "employee_salary" # => "Employee salary" - # "author_id" # => "Author" + # 'employee_salary' # => "Employee salary" + # 'author_id' # => "Author" def humanize ActiveSupport::Inflector.humanize(self) end @@ -192,10 +203,9 @@ class String # +separate_class_name_and_id_with_underscore+ sets whether # the method should put '_' between the name and 'id'. # - # Examples - # "Message".foreign_key # => "message_id" - # "Message".foreign_key(false) # => "messageid" - # "Admin::Post".foreign_key # => "post_id" + # 'Message'.foreign_key # => "message_id" + # 'Message'.foreign_key(false) # => "messageid" + # 'Admin::Post'.foreign_key # => "post_id" def foreign_key(separate_class_name_and_id_with_underscore = true) ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore) end diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb index 5f0a017de6..1dcd949536 100644 --- a/activesupport/lib/active_support/core_ext/string/inquiry.rb +++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb @@ -2,9 +2,9 @@ require 'active_support/string_inquirer' class String # Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class, - # which gives you a prettier way to test for equality. Example: + # which gives you a prettier way to test for equality. # - # env = "production".inquiry + # env = 'production'.inquiry # env.production? # => true # env.development? # => false def inquiry diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb deleted file mode 100644 index 7f764e9de1..0000000000 --- a/activesupport/lib/active_support/core_ext/string/interpolation.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'active_support/i18n' -require 'i18n/core_ext/string/interpolate' 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 dd780da157..dad4b29d46 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -3,9 +3,9 @@ require 'active_support/core_ext/kernel/singleton_class' class ERB module Util - HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' } + HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } - HTML_ESCAPE_ONCE_REGEXP = /[\"><]|&(?!([a-zA-Z]+|(#\d+));)/ + HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ JSON_ESCAPE_REGEXP = /[&"><]/ # A utility method for escaping HTML tag characters. @@ -14,15 +14,14 @@ class ERB # In your ERB templates, use this method to escape any unsafe content. For example: # <%=h @person.name %> # - # ==== Example: - # puts html_escape("is a > 0 & a < 10?") + # puts html_escape('is a > 0 & a < 10?') # # => is a > 0 & a < 10? def html_escape(s) s = s.to_s if s.html_safe? s else - s.encode(s.encoding, :xml => :attr)[1...-1].html_safe + s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe end end @@ -37,11 +36,10 @@ class ERB # A utility method for escaping HTML without affecting existing escaped entities. # - # ==== Examples - # html_escape_once("1 < 2 & 3") + # html_escape_once('1 < 2 & 3') # # => "1 < 2 & 3" # - # html_escape_once("<< Accept & Checkout") + # html_escape_once('<< Accept & Checkout') # # => "<< Accept & Checkout" def html_escape_once(s) result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] } @@ -53,7 +51,7 @@ class ERB # A utility method for escaping HTML entities in JSON strings # using \uXXXX JavaScript escape sequences for string literals: # - # json_escape("is a > 0 & a < 10?") + # json_escape('is a > 0 & a < 10?') # # => is a \u003E 0 \u0026 a \u003C 10? # # Note that after this operation is performed the output is not @@ -62,18 +60,11 @@ class ERB # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}') # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1} # - # This method is also aliased as +j+, and available as a helper - # in Rails templates: - # - # <%=j @person.to_json %> - # def json_escape(s) result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] } s.html_safe? ? result.html_safe : result end - alias j json_escape - module_function :j module_function :json_escape end end @@ -92,40 +83,55 @@ end module ActiveSupport #:nodoc: class SafeBuffer < String - UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase", "prepend"].freeze + UNSAFE_STRING_METHODS = %w( + capitalize chomp chop delete downcase gsub lstrip next reverse rstrip + slice squeeze strip sub succ swapcase tr tr_s upcase prepend + ) 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." + 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 + def [](*args) + if args.size < 2 + super + else + if html_safe? + new_safe_buffer = super + new_safe_buffer.instance_eval { @html_safe = true } + new_safe_buffer + else + to_str[*args] + end + end end def safe_concat(value) - raise SafeConcatError if dirty? + raise SafeConcatError unless html_safe? original_concat(value) end def initialize(*) - @dirty = false + @html_safe = true super end def initialize_copy(other) super - @dirty = other.dirty? + @html_safe = other.html_safe? + end + + def clone_empty + self[0, 0] end def concat(value) - if dirty? || value.html_safe? + if !html_safe? || value.html_safe? super(value) else super(ERB::Util.h(value)) @@ -137,8 +143,20 @@ module ActiveSupport #:nodoc: dup.concat(other) end + def %(args) + args = Array(args).map do |arg| + if !html_safe? || arg.html_safe? + arg + else + ERB::Util.h(arg) + end + end + + self.class.new(super(args)) + end + def html_safe? - !dirty? + defined?(@html_safe) && @html_safe end def to_s @@ -161,18 +179,12 @@ module ActiveSupport #:nodoc: end # end def #{unsafe_method}!(*args) # def capitalize!(*args) - @dirty = true # @dirty = true + @html_safe = false # @html_safe = false super # super end # end EOT end end - - protected - - def dirty? - @dirty - end end end diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb new file mode 100644 index 0000000000..32cffe237d --- /dev/null +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -0,0 +1,5 @@ +require 'active_support/core_ext/time/acts_like' +require 'active_support/core_ext/time/calculations' +require 'active_support/core_ext/time/conversions' +require 'active_support/core_ext/time/marshal' +require 'active_support/core_ext/time/zones' diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 4f300329f5..148f2c27f1 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,10 +1,13 @@ require 'active_support/duration' -require 'active_support/core_ext/time/zones' require 'active_support/core_ext/time/conversions' +require 'active_support/time_with_zone' +require 'active_support/core_ext/time/zones' +require 'active_support/core_ext/date_and_time/calculations' class Time + include DateAndTime::Calculations + COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 } class << self # Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances @@ -15,8 +18,11 @@ class Time # Return the number of days in the given month. # If no year is specified, it will use the current year. def days_in_month(month, year = now.year) - return 29 if month == 2 && ::Date.gregorian_leap?(year) - COMMON_YEAR_DAYS_IN_MONTH[month] + if month == 2 && ::Date.gregorian_leap?(year) + 29 + else + COMMON_YEAR_DAYS_IN_MONTH[month] + end end # Returns a new Time if requested year can be accommodated by Ruby's Time class @@ -24,8 +30,13 @@ class Time # otherwise returns a DateTime. def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0) time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec) + # This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138. - time.year == year ? time : ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec) + if time.year == year + time + else + ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec) + end rescue ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec) end @@ -46,40 +57,35 @@ class Time end end - # Tells whether the Time object's time lies in the past - def past? - self < ::Time.current - end - - # Tells whether the Time object's time is today - def today? - to_date == ::Date.current - end - - # Tells whether the Time object's time lies in the future - def future? - self > ::Time.current - end - # Seconds since midnight: Time.now.seconds_since_midnight def seconds_since_midnight to_i - change(:hour => 0).to_i + (usec / 1.0e+6) end # Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options - # (hour, minute, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and - # minute is passed, then sec and usec is set to 0. + # (<tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. + # If the hour and minute is passed, then sec and usec is set to 0. The +options+ parameter takes a hash with any of these keys: <tt>:year</tt>, + # <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>. + # + # Time.new(2012, 8, 29, 22, 35, 0).change(:day => 1) # => Time.new(2012, 8, 1, 22, 35, 0) + # Time.new(2012, 8, 29, 22, 35, 0).change(:year => 1981, :day => 1) # => Time.new(1981, 8, 1, 22, 35, 0) + # Time.new(2012, 8, 29, 22, 35, 0).change(:year => 1981, :hour => 0) # => Time.new(1981, 8, 29, 0, 0, 0) def change(options) - ::Time.send( - utc? ? :utc_time : :local_time, - options[:year] || year, - options[:month] || month, - options[:day] || day, - options[:hour] || hour, - options[:min] || (options[:hour] ? 0 : min), - options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec), - options[:usec] || ((options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) - ) + new_year = options.fetch(:year, year) + new_month = options.fetch(:month, month) + new_day = options.fetch(:day, day) + new_hour = options.fetch(:hour, hour) + new_min = options.fetch(:min, options[:hour] ? 0 : min) + new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + + if utc? + ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) + elsif zone + ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) + else + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset) + end end # Uses Date to provide precise Time calculations for years, months, and days. @@ -89,18 +95,26 @@ class Time def advance(options) unless options[:weeks].nil? options[:weeks], partial_weeks = options[:weeks].divmod(1) - options[:days] = (options[:days] || 0) + 7 * partial_weeks + options[:days] = options.fetch(:days, 0) + 7 * partial_weeks end unless options[:days].nil? options[:days], partial_days = options[:days].divmod(1) - options[:hours] = (options[:hours] || 0) + 24 * partial_days + options[:hours] = options.fetch(:hours, 0) + 24 * partial_days end d = to_date.advance(options) time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) - seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600 - seconds_to_advance == 0 ? time_advanced_by_date : time_advanced_by_date.since(seconds_to_advance) + seconds_to_advance = \ + options.fetch(:seconds, 0) + + options.fetch(:minutes, 0) * 60 + + options.fetch(:hours, 0) * 3600 + + if seconds_to_advance.zero? + time_advanced_by_date + else + time_advanced_by_date.since(seconds_to_advance) + end end # Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension @@ -116,95 +130,6 @@ class Time end alias :in :since - # Returns a new Time representing the time a number of specified weeks ago. - def weeks_ago(weeks) - advance(:weeks => -weeks) - end - - # Returns a new Time representing the time a number of specified months ago - def months_ago(months) - advance(:months => -months) - end - - # Returns a new Time representing the time a number of specified months in the future - def months_since(months) - advance(:months => months) - end - - # Returns a new Time representing the time a number of specified years ago - def years_ago(years) - advance(:years => -years) - end - - # Returns a new Time representing the time a number of specified years in the future - def years_since(years) - advance(:years => years) - end - - # Short-hand for years_ago(1) - def prev_year - years_ago(1) - end - - # Short-hand for years_since(1) - def next_year - years_since(1) - end - - # Short-hand for months_ago(1) - def prev_month - months_ago(1) - end - - # Short-hand for months_since(1) - def next_month - months_since(1) - end - - # 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 :at_beginning_of_week :beginning_of_week - - # 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 +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). - def next_week(day = :monday) - since(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0) - end - # Returns a new Time representing the start of the day (0:00) def beginning_of_day #(self - seconds_since_midnight).change(:usec => 0) @@ -216,56 +141,27 @@ class Time # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9) def end_of_day - change(:hour => 23, :min => 59, :sec => 59, :usec => 999999.999) - end - - # Returns a new Time representing the start of the month (1st of the month, 0:00) - def beginning_of_month - #self - ((self.mday-1).days + self.seconds_since_midnight) - change(:day => 1, :hour => 0) - end - alias :at_beginning_of_month :beginning_of_month - - # Returns a new Time representing the end of the month (end of the last day of the month) - def end_of_month - #self - ((self.mday-1).days + self.seconds_since_midnight) - last_day = ::Time.days_in_month(month, year) - change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999) - end - alias :at_end_of_month :end_of_month - - # Returns a new Time representing the start of the quarter (1st of january, april, july, october, 0:00) - def beginning_of_quarter - beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= month }) - end - alias :at_beginning_of_quarter :beginning_of_quarter - - # Returns a new Time representing the end of the quarter (end of the last day of march, june, september, december) - def end_of_quarter - beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= month }).end_of_month - end - alias :at_end_of_quarter :end_of_quarter - - # 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 - alias :at_beginning_of_year :beginning_of_year - - # Returns a new Time representing the end of the year (end of the 31st of december) - def end_of_year - change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999) + change( + :hour => 23, + :min => 59, + :sec => 59, + :usec => Rational(999999999, 1000) + ) end - alias :at_end_of_year :end_of_year - # Convenience method which returns a new Time representing the time 1 day ago - def yesterday - advance(:days => -1) + # Returns a new Time representing the start of the hour (x:00) + def beginning_of_hour + change(:min => 0) end + alias :at_beginning_of_hour :beginning_of_hour - # Convenience method which returns a new Time representing the time 1 day since the instance time - def tomorrow - advance(:days => 1) + # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9) + def end_of_hour + change( + :min => 59, + :sec => 59, + :usec => Rational(999999999, 1000) + ) end # Returns a Range representing the whole day of the current time. @@ -327,7 +223,11 @@ class Time # can be chronologically compared with a Time def compare_with_coercion(other) # we're avoiding Time#to_datetime cause it's expensive - other.is_a?(Time) ? compare_without_coercion(other.to_time) : to_datetime <=> other + if other.is_a?(Time) + compare_without_coercion(other.to_time) + else + to_datetime <=> other + end end alias_method :compare_without_coercion, :<=> alias_method :<=>, :compare_with_coercion @@ -341,4 +241,5 @@ class Time 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 0fdcd383f0..10ca26acf2 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -3,13 +3,20 @@ require 'active_support/values/time_zone' class Time DATE_FORMATS = { - :db => "%Y-%m-%d %H:%M:%S", - :number => "%Y%m%d%H%M%S", - :time => "%H:%M", - :short => "%d %b %H:%M", - :long => "%B %d, %Y %H:%M", - :long_ordinal => lambda { |time| time.strftime("%B #{ActiveSupport::Inflector.ordinalize(time.day)}, %Y %H:%M") }, - :rfc822 => lambda { |time| time.strftime("%a, %d %b %Y %H:%M:%S #{time.formatted_offset(false)}") } + :db => '%Y-%m-%d %H:%M:%S', + :number => '%Y%m%d%H%M%S', + :nsec => '%Y%m%d%H%M%S%9N', + :time => '%H:%M', + :short => '%d %b %H:%M', + :long => '%B %d, %Y %H:%M', + :long_ordinal => lambda { |time| + day_format = ActiveSupport::Inflector.ordinalize(time.day) + time.strftime("%B #{day_format}, %Y %H:%M") + }, + :rfc822 => lambda { |time| + offset_format = time.formatted_offset(false) + time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") + } } # Converts to a formatted string. See DATE_FORMATS for builtin formats. @@ -34,7 +41,7 @@ class Time # or Proc instance that takes a time argument as the value. # # # config/initializers/time_formats.rb - # Time::DATE_FORMATS[:month_and_year] = "%B %Y" + # Time::DATE_FORMATS[:month_and_year] = '%B %Y' # Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") } def to_formatted_s(format = :default) if formatter = DATE_FORMATS[format] diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 0c5962858e..37bc3fae24 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -26,11 +26,11 @@ class Time # around_filter :set_time_zone # # def set_time_zone - # old_time_zone = Time.zone - # Time.zone = current_user.time_zone if logged_in? - # yield - # ensure - # Time.zone = old_time_zone + # if logged_in? + # Time.use_zone(current_user.time_zone) { yield } + # else + # yield + # end # end # end def zone=(time_zone) @@ -50,13 +50,21 @@ class Time # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones. def find_zone!(time_zone) - return time_zone if time_zone.nil? || time_zone.is_a?(ActiveSupport::TimeZone) - # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone) - unless time_zone.respond_to?(:period_for_local) - time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) + if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone) + unless time_zone.respond_to?(:period_for_local) + time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) + end + + # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone + if time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone) + end end - # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone - time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone) rescue TZInfo::InvalidTimezoneIdentifier raise ArgumentError, "Invalid Timezone: #{time_zone}" end @@ -79,8 +87,10 @@ class Time # # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) - return self unless zone - - ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) + if zone + ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) + else + self + end end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 2c5950edf5..48be96f176 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -129,16 +129,14 @@ module ActiveSupport #:nodoc: # Add a set of modules to the watch stack, remembering the initial constants def watch_namespaces(namespaces) - watching = [] - namespaces.map do |namespace| + @watching << namespaces.map do |namespace| module_name = Dependencies.to_constant_name(namespace) original_constants = Dependencies.qualified_const_defined?(module_name) ? Inflector.constantize(module_name).local_constants : [] - watching << module_name @stack[module_name] << original_constants + module_name end - @watching << watching end private @@ -170,36 +168,13 @@ module ActiveSupport #:nodoc: end end - # Use const_missing to autoload associations so we don't have to - # require_association when using single-table inheritance. - def const_missing(const_name, nesting = nil) - klass_name = name.presence || "Object" - - unless nesting - # We'll assume that the nesting of Foo::Bar is ["Foo::Bar", "Foo"] - # even though it might not be, such as in the case of - # class Foo::Bar; Baz; end - nesting = [] - klass_name.to_s.scan(/::|$/) { nesting.unshift $` } - end - - # If there are multiple levels of nesting to search under, the top - # level is the one we want to report as the lookup fail. - error = nil - - nesting.each do |namespace| - begin - return Dependencies.load_missing_constant Inflector.constantize(namespace), const_name - rescue NoMethodError then raise - rescue NameError => e - error ||= e - end - end - - # Raise the first error for this set. If this const_missing came from an - # earlier const_missing, this will result in the real error bubbling - # all the way up - raise error + def const_missing(const_name) + # The interpreter does not pass nesting information, and in the + # case of anonymous modules we cannot even make the trade-off of + # assuming their name reflects the nesting. Resort to Object as + # the only meaningful guess we can make. + from_mod = anonymous? ? ::Object : self + Dependencies.load_missing_constant(from_mod, const_name) end def unloadable(const_desc = self) @@ -222,11 +197,7 @@ module ActiveSupport #:nodoc: raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}" end - Dependencies.depend_on(file_name, false, message) - end - - def require_association(file_name) - Dependencies.associate_with(file_name) + Dependencies.depend_on(file_name, message) end def load_dependency(file) @@ -295,33 +266,26 @@ module ActiveSupport #:nodoc: Object.class_eval { include Loadable } Module.class_eval { include ModuleConstMissing } Exception.class_eval { include Blamable } - true end def unhook! ModuleConstMissing.exclude_from(Module) Loadable.exclude_from(Object) - true end def load? mechanism == :load end - def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb") + def depend_on(file_name, message = "No such file to load -- %s.rb") path = search_for_file(file_name) require_or_load(path || file_name) rescue LoadError => load_error - unless swallow_load_errors - if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] - raise LoadError.new(message % file_name).copy_blame!(load_error) - end - raise + if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] + load_error.message.replace(message % file_name) + load_error.copy_blame!(load_error) end - end - - def associate_with(file_name) - depend_on(file_name, true) + raise end def clear @@ -332,7 +296,7 @@ module ActiveSupport #:nodoc: def require_or_load(file_name, const_path = nil) log_call file_name, const_path - file_name = $1 if file_name =~ /^(.*)\.rb$/ + file_name = $` if file_name =~ /\.rb\z/ expanded = File.expand_path(file_name) return if loaded.include?(expanded) @@ -365,7 +329,7 @@ module ActiveSupport #:nodoc: # Record history *after* loading so first load gets warnings. history << expanded - return result + result end # Is the provided constant path defined? @@ -373,14 +337,10 @@ module ActiveSupport #:nodoc: Object.qualified_const_defined?(path.sub(/^::/, ''), 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 # paths which would cause Dependencies to attempt to load this file. def loadable_constants_for_path(path, bases = autoload_paths) - path = $1 if path =~ /\A(.*)\.rb\Z/ + path = $` if path =~ /\.rb\z/ expanded_path = File.expand_path(path) paths = [] @@ -434,7 +394,7 @@ module ActiveSupport #:nodoc: mod = Module.new into.const_set const_name, mod autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path) - return mod + mod end # Load the file at the provided path. +const_paths+ is a set of qualified @@ -448,7 +408,7 @@ module ActiveSupport #:nodoc: def load_file(path, const_paths = loadable_constants_for_path(path)) log_call path, const_paths const_paths = [const_paths].compact unless const_paths.is_a? Array - parent_paths = const_paths.collect { |const_path| /(.*)::[^:]+\Z/ =~ const_path ? $1 : :Object } + parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || :Object } result = nil newly_defined_paths = new_constants_in(*parent_paths) do @@ -458,7 +418,7 @@ module ActiveSupport #:nodoc: autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) autoloaded_constants.uniq! log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty? - return result + result end # Return the constant path for the provided parent and constant name. @@ -477,26 +437,52 @@ module ActiveSupport #:nodoc: raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" end - raise NameError, "#{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 from_mod.const_defined?(const_name, false) qualified_name = qualified_name_for from_mod, const_name path_suffix = qualified_name.underscore file_path = search_for_file(path_suffix) - if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load - require_or_load file_path - raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless local_const_defined?(from_mod, const_name) - return from_mod.const_get(const_name) + if file_path + expanded = File.expand_path(file_path) + expanded.sub!(/\.rb\z/, '') + + if loaded.include?(expanded) + raise "Circular dependency detected while autoloading constant #{qualified_name}" + else + require_or_load(expanded) + raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false) + return from_mod.const_get(const_name) + end elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) return mod elsif (parent = from_mod.parent) && parent != from_mod && - ! from_mod.parents.any? { |p| local_const_defined?(p, const_name) } + ! from_mod.parents.any? { |p| p.const_defined?(const_name, false) } # If our parents do not have a constant named +const_name+ then we are free # to attempt to load upwards. If they do have such a constant, then this # const_missing must be due to from_mod::const_name, which should not # return constants from from_mod's parents. begin + # Since Ruby does not pass the nesting at the point the unknown + # constant triggered the callback we cannot fully emulate constant + # name lookup and need to make a trade-off: we are going to assume + # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even + # though it might not be. Counterexamples are + # + # class Foo::Bar + # Module.nesting # => [Foo::Bar] + # end + # + # or + # + # module M::N + # module S::T + # Module.nesting # => [S::T, M::N] + # end + # end + # + # for example. return parent.const_missing(const_name) rescue NameError => e raise unless e.missing_name? qualified_name_for(parent, const_name) @@ -505,7 +491,7 @@ module ActiveSupport #:nodoc: raise NameError, "uninitialized constant #{qualified_name}", - caller.reject {|l| l.starts_with? __FILE__ } + caller.reject { |l| l.starts_with? __FILE__ } end # Remove the constants that have been autoloaded, and those that have been @@ -543,10 +529,7 @@ module ActiveSupport #:nodoc: def safe_get(key) key = key.name if key.respond_to?(:name) - @store[key] || begin - klass = Inflector.safe_constantize(key) - @store[key] = klass - end + @store[key] ||= Inflector.safe_constantize(key) end def store(klass) @@ -600,10 +583,10 @@ module ActiveSupport #:nodoc: def mark_for_unload(const_desc) name = to_constant_name const_desc if explicitly_unloadable_constants.include? name - return false + false else explicitly_unloadable_constants << name - return true + true end end @@ -631,10 +614,10 @@ module ActiveSupport #:nodoc: return new_constants unless aborting log "Error during loading, removing partially loaded constants " - new_constants.each {|c| remove_constant(c) }.clear + new_constants.each { |c| remove_constant(c) }.clear end - return [] + [] end # Convert the provided const desc to a qualified constant name (as a string). @@ -663,7 +646,7 @@ module ActiveSupport #:nodoc: constantized.before_remove_const if constantized.respond_to?(:before_remove_const) parent.instance_eval { remove_const to_remove } - return true + true end protected diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb index 4c771da096..df490ae298 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -1,50 +1,78 @@ require "active_support/inflector/methods" -require "active_support/lazy_load_hooks" module ActiveSupport + # Autoload and eager load conveniences for your library. + # + # This module allows you to define autoloads based on + # Rails conventions (i.e. no need to define the path + # it is automatically guessed based on the filename) + # and also define a set of constants that needs to be + # eager loaded: + # + # module MyLib + # extend ActiveSupport::Autoload + # + # autoload :Model + # + # eager_autoload do + # autoload :Cache + # end + # end + # + # Then your library can be eager loaded by simply calling: + # + # MyLib.eager_load! + # module Autoload - @@autoloads = {} - @@under_path = nil - @@at_path = nil - @@eager_autoload = false + def self.extended(base) + base.class_eval do + @_autoloads = {} + @_under_path = nil + @_at_path = nil + @_eager_autoload = false + end + end - def autoload(const_name, path = @@at_path) - full = [self.name, @@under_path, const_name.to_s, path].compact.join("::") - location = path || Inflector.underscore(full) + def autoload(const_name, path = @_at_path) + unless path + full = [name, @_under_path, const_name.to_s, path].compact.join("::") + path = Inflector.underscore(full) + end - if @@eager_autoload - @@autoloads[const_name] = location + if @_eager_autoload + @_autoloads[const_name] = path end - super const_name, location + + super const_name, path end def autoload_under(path) - @@under_path, old_path = path, @@under_path + @_under_path, old_path = path, @_under_path yield ensure - @@under_path = old_path + @_under_path = old_path end def autoload_at(path) - @@at_path, old_path = path, @@at_path + @_at_path, old_path = path, @_at_path yield ensure - @@at_path = old_path + @_at_path = old_path end def eager_autoload - old_eager, @@eager_autoload = @@eager_autoload, true + old_eager, @_eager_autoload = @_eager_autoload, true yield ensure - @@eager_autoload = old_eager + @_eager_autoload = old_eager end - def self.eager_autoload! - @@autoloads.values.each { |file| require file } + def eager_load! + @_autoloads.values.each { |file| require file } end def autoloads - @@autoloads + @_autoloads end end end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 45b9dda5ca..e3b4a7240e 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/module/deprecation' require 'active_support/deprecation/behaviors' require 'active_support/deprecation/reporting' require 'active_support/deprecation/method_wrappers' @@ -9,10 +10,10 @@ module ActiveSupport # The version the deprecated behavior will be removed, by default. attr_accessor :deprecation_horizon end - self.deprecation_horizon = '3.2' + self.deprecation_horizon = '4.1' # By default, warnings are not silenced and debugging is off. self.silenced = false self.debug = false end -end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 94f8d7133e..fc962dcb57 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -6,17 +6,31 @@ module ActiveSupport # Whether to print a backtrace along with the warning. attr_accessor :debug - # Returns the set behavior or if one isn't set, defaults to +:stderr+ + # Returns the current behavior or if one isn't set, defaults to +:stderr+ def behavior @behavior ||= [DEFAULT_BEHAVIORS[:stderr]] end - # Sets the behavior to the specified value. Can be a single value or an array. + # Sets the behavior to the specified value. Can be a single value, array, or + # an object that responds to +call+. # - # Examples + # Available behaviors: + # + # [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>. + # [+log+] Log all deprecation warnings to +Rails.logger+. + # [+notify+] Use <tt>ActiveSupport::Notifications</tt> to notify +deprecation.rails+. + # [+silence+] Do nothing. + # + # Setting behaviors only affects deprecations that happen after boot time. + # Deprecation warnings raised by gems are not affected by this setting because + # they happen before Rails boots up. # # ActiveSupport::Deprecation.behavior = :stderr # ActiveSupport::Deprecation.behavior = [:stderr, :log] + # ActiveSupport::Deprecation.behavior = MyCustomHandler + # ActiveSupport::Deprecation.behavior = proc { |message, callstack| + # # custom stuff + # } def behavior=(behavior) @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b } end @@ -41,8 +55,9 @@ module ActiveSupport }, :notify => Proc.new { |message, callstack| ActiveSupport::Notifications.instrument("deprecation.rails", - :message => message, :callstack => callstack) - } + :message => message, :callstack => callstack) + }, + :silence => Proc.new { |message, callstack| } } end end diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index d0d8b577b3..257b70e34a 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -1,11 +1,30 @@ -require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/array/extract_options' module ActiveSupport - class << Deprecation + module Deprecation # Declare that a method has been deprecated. - def deprecate_methods(target_module, *method_names) + # + # module Fred + # extend self + # + # def foo; end + # def bar; end + # def baz; end + # end + # + # ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead') + # # => [:foo, :bar, :baz] + # + # Fred.foo + # # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1." + # + # Fred.bar + # # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)." + # + # Fred.baz + # # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)." + def self.deprecate_methods(target_module, *method_names) options = method_names.extract_options! method_names += options.keys diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index 5d7e241d1a..a1e9618084 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -3,7 +3,8 @@ module ActiveSupport class << self attr_accessor :silenced - # Outputs a deprecation warning to the output configured by <tt>ActiveSupport::Deprecation.behavior</tt> + # Outputs a deprecation warning to the output configured by + # <tt>ActiveSupport::Deprecation.behavior</tt>. # # ActiveSupport::Deprecation.warn("something broke!") # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" @@ -15,6 +16,14 @@ module ActiveSupport end # Silence deprecation warnings within the block. + # + # ActiveSupport::Deprecation.warn("something broke!") + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + # + # ActiveSupport::Deprecation.silence do + # ActiveSupport::Deprecation.warn("something broke!") + # end + # # => nil def silence old_silenced, @silenced = @silenced, true yield diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 00c67a470d..a0139b7d8e 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -5,7 +5,6 @@ require 'active_support/core_ext/object/acts_like' module ActiveSupport # Provides accurate date and time measurements using Date#advance and # Time#advance, respectively. It mainly supports the methods on Numeric. - # Example: # # 1.month.ago # equivalent to Time.now.advance(:months => -1) class Duration < BasicObject @@ -71,7 +70,7 @@ module ActiveSupport alias :until :ago def inspect #:nodoc: - consolidated = parts.inject(::Hash.new(0)) { |h,part| h[part.first] += part.last; h } + consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h } parts = [:years, :months, :days, :minutes, :seconds].map do |length| n = consolidated[length] "#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero? diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 2ede084e95..1cc852a3e6 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -1,5 +1,3 @@ -require "active_support/core_ext/array/extract_options" - module ActiveSupport # \FileUpdateChecker specifies the API used by Rails to watch files # and control reloading. The API depends on four methods: @@ -11,7 +9,7 @@ module ActiveSupport # the filesystem or not; # # * +execute+ which executes the given block on initialization - # and updates the counter to the latest timestamp; + # and updates the latest watched files and timestamp; # # * +execute_if_updated+ which just executes the block if it was updated; # @@ -37,45 +35,48 @@ module ActiveSupport # 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. + # This method must also receive a block that will be called once a path + # changes. The array of files and list of directories cannot be changed + # after FileUpdateChecker has been initialized. def initialize(files, dirs={}, &block) - @files = files + @files = files.freeze @glob = compile_glob(dirs) @block = block + + @watched = nil @updated_at = nil - @last_update_at = updated_at + + @last_watched = watched + @last_update_at = updated_at(@last_watched) 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+ + # Check if any of the entries were updated. If so, the watched and/or + # updated_at values are 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 + current_watched = watched + if @last_watched.size != current_watched.size + @watched = current_watched true else - false + current_updated_at = updated_at(current_watched) + if @last_update_at < current_updated_at + @watched = current_watched + @updated_at = current_updated_at + true + else + false + end end end - # Executes the given block and updates the counter to latest timestamp. + # Executes the given block and updates the latest watched files and timestamp. def execute - @last_update_at = updated_at + @last_watched = watched + @last_update_at = updated_at(@last_watched) @block.call ensure + @watched = nil @updated_at = nil end @@ -91,27 +92,45 @@ module ActiveSupport 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) + def watched + @watched || begin + all = @files.select { |f| File.exists?(f) } + all.concat(Dir[@glob]) if @glob + all end end - def compile_glob(hash) #:nodoc: + def updated_at(paths) + @updated_at || max_mtime(paths) || Time.at(0) + end + + # This method returns the maximum mtime of the files in +paths+, or +nil+ + # if the array is empty. + # + # Files with a mtime in the future are ignored. Such abnormal situation + # can happen for example if the user changes the clock by hand. It is + # healthy to consider this edge case because with mtimes in the future + # reloading is not triggered. + def max_mtime(paths) + time_now = Time.now + paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max + end + + def compile_glob(hash) hash.freeze # Freeze so changes aren't accidently pushed return if hash.empty? - globs = [] - hash.each do |key, value| - globs << "#{key}/**/*#{compile_ext(value)}" + globs = hash.map do |key, value| + "#{escape(key)}/**/*#{compile_ext(value)}" end "{#{globs.join(",")}}" end - def compile_ext(array) #:nodoc: + def escape(key) + key.gsub(',','\,') + end + + def compile_ext(array) array = Array(array) return if array.empty? ".{#{array.join(",")}}" diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index f7036315d6..6ef33ab683 100644 --- a/activesupport/lib/active_support/gzip.rb +++ b/activesupport/lib/active_support/gzip.rb @@ -1,9 +1,15 @@ 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. + # A convenient wrapper for the zlib standard library that allows + # compression/decompression of strings with gzip. + # + # gzip = ActiveSupport::Gzip.compress('compress me!') + # # => "\x1F\x8B\b\x00o\x8D\xCDO\x00\x03K\xCE\xCF-(J-.V\xC8MU\x04\x00R>n\x83\f\x00\x00\x00" + # + # ActiveSupport::Gzip.decompress(gzip) + # # => "compress me!" module Gzip class Stream < StringIO def initialize(*) diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 674e4acfd6..71713644a7 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -1,13 +1,46 @@ require 'active_support/core_ext/hash/keys' -# This class has dubious semantics and we only have it so that -# people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt> -# and they get the same value for both keys. - module ActiveSupport + # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered to be the same. + # + # rgb = ActiveSupport::HashWithIndifferentAccess.new + # + # rgb[:black] = '#000000' + # rgb[:black] # => '#000000' + # rgb['black'] # => '#000000' + # + # rgb['white'] = '#FFFFFF' + # rgb[:white] # => '#FFFFFF' + # rgb['white'] # => '#FFFFFF' + # + # Internally symbols are mapped to strings when used as keys in the entire + # writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This + # mapping belongs to the public interface. For example, given + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(:a => 1) + # + # you are guaranteed that the key is returned as a string: + # + # hash.keys # => ["a"] + # + # Technically other types of keys are accepted: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(:a => 1) + # hash[0] = 0 + # hash # => {"a"=>1, 0=>0} + # + # but this class is intended for use cases where strings or symbols are the + # expected keys and it is convenient to understand both as the same. For + # example the +params+ hash in Ruby on Rails. + # + # Note that core extensions define <tt>Hash#with_indifferent_access</tt>: + # + # rgb = {:black => '#000000', :white => '#FFFFFF'}.with_indifferent_access + # + # which may be handy. class HashWithIndifferentAccess < Hash - - # Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class. + # Returns true so that <tt>Array#extract_options!</tt> finds members of + # this class. def extractable_options? true end @@ -43,35 +76,61 @@ module ActiveSupport end end + def self.[](*args) + new.merge(Hash[*args]) + end + alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) alias_method :regular_update, :update unless method_defined?(:regular_update) # Assigns a new value to the hash: # - # hash = HashWithIndifferentAccess.new + # hash = ActiveSupport::HashWithIndifferentAccess.new # hash[:key] = "value" # + # This value can be later fetched using either +:key+ or +"key"+. def []=(key, value) regular_writer(convert_key(key), convert_value(value)) end alias_method :store, :[]= - # Updates the instantized hash with values from the second: + # Updates the receiver in-place, merging in the hash passed as argument: # - # hash_1 = HashWithIndifferentAccess.new + # hash_1 = ActiveSupport::HashWithIndifferentAccess.new # hash_1[:key] = "value" # - # hash_2 = HashWithIndifferentAccess.new + # hash_2 = ActiveSupport::HashWithIndifferentAccess.new # hash_2[:key] = "New Value!" # # hash_1.update(hash_2) # => {"key"=>"New Value!"} # + # The argument can be either an + # <tt>ActiveSupport::HashWithIndifferentAccess</tt> or a regular +Hash+. + # In either case the merge respects the semantics of indifferent access. + # + # If the argument is a regular hash with keys +:key+ and +"key"+ only one + # of the values end up in the receiver, but which one is unspecified. + # + # When given a block, the value for duplicated keys will be determined + # by the result of invoking the block with the duplicated key, the value + # in the receiver, and the value in +other_hash+. The rules for duplicated + # keys follow the semantics of indifferent access: + # + # hash_1[:key] = 10 + # hash_2['key'] = 12 + # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22} + # def update(other_hash) if other_hash.is_a? HashWithIndifferentAccess super(other_hash) else - other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) } + other_hash.each_pair do |key, value| + if block_given? && key?(key) + value = yield(convert_key(key), self[key], value) + end + regular_writer(convert_key(key), convert_value(value)) + end self end end @@ -80,10 +139,10 @@ module ActiveSupport # Checks the hash for a key matching the argument passed in: # - # hash = HashWithIndifferentAccess.new + # hash = ActiveSupport::HashWithIndifferentAccess.new # hash["key"] = "value" - # hash.key? :key # => true - # hash.key? "key" # => true + # hash.key?(:key) # => true + # hash.key?("key") # => true # def key?(key) super(convert_key(key)) @@ -93,14 +152,24 @@ module ActiveSupport alias_method :has_key?, :key? alias_method :member?, :key? - # Fetches the value for the specified key, same as doing hash[key] + # Same as <tt>Hash#fetch</tt> where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters.fetch("foo") # => 1 + # counters.fetch(:bar, 0) # => 0 + # counters.fetch(:bar) {|key| 0} # => 0 + # counters.fetch(:zoo) # => KeyError: key not found: "zoo" + # def fetch(key, *extras) super(convert_key(key), *extras) end # Returns an array of the values at the specified indices: # - # hash = HashWithIndifferentAccess.new + # hash = ActiveSupport::HashWithIndifferentAccess.new # hash[:a] = "x" # hash[:b] = "y" # hash.values_at("a", "b") # => ["x", "y"] @@ -116,34 +185,45 @@ module ActiveSupport end end - # 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) + # This method has the same semantics of +update+, except it does not + # modify the receiver but rather returns a new hash with indifferent + # access with the result of the merge. + def merge(hash, &block) + self.dup.update(hash, &block) end - # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second. - # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a <tt>HashWithDifferentAccess</tt>. + # Like +merge+ but the other way around: Merges the receiver into the + # argument and returns a new hash with indifferent access as result: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash['a'] = nil + # hash.reverse_merge(:a => 0, :b => 1) # => {"a"=>nil, "b"=>1} + # def reverse_merge(other_hash) - super self.class.new_from_hash_copying_default(other_hash) + super(self.class.new_from_hash_copying_default(other_hash)) end + # Same semantics as +reverse_merge+ but modifies the receiver in-place. def reverse_merge!(other_hash) replace(reverse_merge( other_hash )) end - # Removes a specified key from the hash. + # Removes the specified key from the hash. def delete(key) super(convert_key(key)) end def stringify_keys!; self end + def deep_stringify_keys!; self end def stringify_keys; dup end + def deep_stringify_keys; dup end undef :symbolize_keys! + undef :deep_symbolize_keys! def symbolize_keys; to_hash.symbolize_keys end + def deep_symbolize_keys; to_hash.deep_symbolize_keys end def to_options!; self end - # Convert to a Hash with String keys. + # Convert to a regular hash with string keys. def to_hash Hash.new(default).merge!(self) end @@ -157,7 +237,8 @@ module ActiveSupport if value.is_a? Hash value.nested_under_indifferent_access elsif value.is_a?(Array) - value.dup.replace(value.map { |e| convert_value(e) }) + value = value.dup if value.frozen? + value.map! { |e| convert_value(e) } else value end diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb index f9c5e5e2f8..188653bd9b 100644 --- a/activesupport/lib/active_support/i18n.rb +++ b/activesupport/lib/active_support/i18n.rb @@ -6,4 +6,5 @@ rescue LoadError => e raise e end +ActiveSupport.run_load_hooks(:i18n) I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index bbeb8d82c6..f67b221024 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -9,25 +9,6 @@ module I18n config.i18n.load_path = [] config.i18n.fallbacks = ActiveSupport::OrderedOptions.new - def self.reloader - @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 |app| - app.reloaders << I18n::Railtie.reloader - ActionDispatch::Reloader.to_prepare do - I18n::Railtie.reloader.execute_if_updated - end - end - # Set the i18n configuration after initialization since a lot of # configuration is still usually done in application initializers. config.after_initialize do |app| @@ -63,7 +44,9 @@ module I18n init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks) - reloader_paths.concat I18n.load_path + reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! } + app.reloaders << reloader + ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated } reloader.execute @i18n_inited = true diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index 527cce2594..ef882ebd09 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -1,8 +1,10 @@ +require 'active_support/inflector/inflections' + module ActiveSupport - Inflector.inflections do |inflect| + Inflector.inflections(:en) do |inflect| inflect.plural(/$/, 's') inflect.plural(/s$/i, 's') - inflect.plural(/(ax|test)is$/i, '\1es') + inflect.plural(/^(ax|test)is$/i, '\1es') inflect.plural(/(octop|vir)us$/i, '\1i') inflect.plural(/(octop|vir)i$/i, '\1i') inflect.plural(/(alias|status)$/i, '\1es') @@ -16,17 +18,18 @@ 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') inflect.singular(/s$/i, '') + inflect.singular(/(ss)$/i, '\1') inflect.singular(/(n)ews$/i, '\1ews') inflect.singular(/([ti])a$/i, '\1um') - inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis') - inflect.singular(/(^analy)ses$/i, '\1sis') + inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis') + inflect.singular(/(^analy)(sis|ses)$/i, '\1sis') inflect.singular(/([^f])ves$/i, '\1fe') inflect.singular(/(hive)s$/i, '\1') inflect.singular(/(tive)s$/i, '\1') @@ -35,13 +38,14 @@ 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(/(bus)es$/i, '\1') + 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') - inflect.singular(/(cris|ax|test)es$/i, '\1is') - inflect.singular(/(octop|vir)i$/i, '\1us') - inflect.singular(/(alias|status)es$/i, '\1') + inflect.singular(/(cris|test)(is|es)$/i, '\1is') + inflect.singular(/^(a)x[ie]s$/i, '\1xis') + inflect.singular(/(octop|vir)(us|i)$/i, '\1us') + inflect.singular(/(alias|status)(es)?$/i, '\1') inflect.singular(/^(ox)en/i, '\1') inflect.singular(/(vert|ind)ices$/i, '\1ex') inflect.singular(/(matr)ices$/i, '\1ix') @@ -56,6 +60,6 @@ module ActiveSupport inflect.irregular('cow', 'kine') inflect.irregular('zombie', 'zombies') - inflect.uncountable(%w(equipment information rice money species series fish sheep jeans)) + inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police)) end end diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 90bb62f57b..091692e5a4 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -1,9 +1,15 @@ +require 'active_support/core_ext/array/prepend_and_append' +require 'active_support/i18n' + module ActiveSupport module Inflector + extend self + # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional - # inflection rules. Examples: + # inflection rules. If passed an optional locale, rules for other languages can be specified. The default locale is + # <tt>:en</tt>. Only rules for English are provided. # - # ActiveSupport::Inflector.inflections do |inflect| + # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, '\1\2en' # inflect.singular /^(ox)en/i, '\1' # @@ -16,8 +22,9 @@ module ActiveSupport # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may # already have been loaded. class Inflections - def self.instance - @__instance__ ||= new + def self.instance(locale = :en) + @__instance__ ||= Hash.new { |h, k| h[k] = new } + @__instance__[locale] end attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex @@ -26,12 +33,18 @@ module ActiveSupport @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/ end + # Private, for the test suite. + def initialize_dup(orig) + %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope| + instance_variable_set("@#{scope}", orig.send(scope).dup) + end + 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' @@ -61,7 +74,6 @@ module ActiveSupport # `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' @@ -82,7 +94,7 @@ module ActiveSupport def plural(rule, replacement) @uncountables.delete(rule) if rule.is_a?(String) @uncountables.delete(replacement) - @plurals.insert(0, [rule, replacement]) + @plurals.prepend([rule, replacement]) end # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression. @@ -90,13 +102,12 @@ module ActiveSupport def singular(rule, replacement) @uncountables.delete(rule) if rule.is_a?(String) @uncountables.delete(replacement) - @singulars.insert(0, [rule, replacement]) + @singulars.prepend([rule, replacement]) end # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used # for strings, not regular expressions. You simply pass the irregular in singular and plural form. # - # Examples: # irregular 'octopus', 'octopi' # irregular 'person', 'people' def irregular(singular, plural) @@ -118,7 +129,6 @@ module ActiveSupport # Add uncountable words that shouldn't be attempted inflected. # - # Examples: # uncountable "money" # uncountable "money", "information" # uncountable %w( money information rice ) @@ -130,18 +140,16 @@ module ActiveSupport # When using a regular expression based replacement, the normal humanize formatting is called after the replacement. # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name') # - # Examples: # human /_cnt$/i, '\1_count' # human "legacy_col_person_name", "Name" def human(rule, replacement) - @humans.insert(0, [rule, replacement]) + @humans.prepend([rule, replacement]) end # Clears the loaded inflections within a given scope (default is <tt>:all</tt>). # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>, # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>. # - # Examples: # clear :all # clear :plurals def clear(scope = :all) @@ -155,17 +163,18 @@ module ActiveSupport end # Yields a singleton instance of Inflector::Inflections so you can specify additional - # inflector rules. + # inflector rules. If passed an optional locale, rules for other languages can be specified. + # If not specified, defaults to <tt>:en</tt>. Only rules for English are provided. + # # - # Example: - # ActiveSupport::Inflector.inflections do |inflect| + # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.uncountable "rails" # end - def inflections + def inflections(locale = :en) if block_given? - yield Inflections.instance + yield Inflections.instance(locale) else - Inflections.instance + Inflections.instance(locale) end end end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 4b7c36f839..44214d16fa 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,4 +1,7 @@ +# encoding: utf-8 + require 'active_support/inflector/inflections' +require 'active_support/inflections' module ActiveSupport # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, @@ -7,33 +10,41 @@ module ActiveSupport # # The Rails core team has stated patches for the inflections library will not be accepted # in order to avoid breaking legacy applications which may be relying on errant inflections. - # If you discover an incorrect inflection and require it for your application, you'll need - # to correct it yourself (explained below). + # If you discover an incorrect inflection and require it for your application or wish to + # define rules for languages other than English, please correct or add them yourself (explained below). module Inflector extend self # Returns the plural form of the word in the string. # - # Examples: + # If passed an optional +locale+ parameter, the word will be + # pluralized using rules defined for that language. By default, + # this parameter is set to <tt>:en</tt>. + # # "post".pluralize # => "posts" # "octopus".pluralize # => "octopi" # "sheep".pluralize # => "sheep" # "words".pluralize # => "words" # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize(word) - apply_inflections(word, inflections.plurals) + # "ley".pluralize(:es) # => "leyes" + def pluralize(word, locale = :en) + apply_inflections(word, inflections(locale).plurals) end # The reverse of +pluralize+, returns the singular form of a word in a string. # - # Examples: + # If passed an optional +locale+ parameter, the word will be + # pluralized using rules defined for that language. By default, + # this parameter is set to <tt>:en</tt>. + # # "posts".singularize # => "post" # "octopi".singularize # => "octopus" # "sheep".singularize # => "sheep" # "word".singularize # => "word" # "CamelOctopi".singularize # => "CamelOctopus" - def singularize(word) - apply_inflections(word, inflections.singulars) + # "leyes".singularize(:es) # => "ley" + def singularize(word, locale = :en) + apply_inflections(word, inflections(locale).singulars) end # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ @@ -41,7 +52,6 @@ module ActiveSupport # # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. # - # Examples: # "active_model".camelize # => "ActiveModel" # "active_model".camelize(:lower) # => "activeModel" # "active_model/errors".camelize # => "ActiveModel::Errors" @@ -65,7 +75,6 @@ module ActiveSupport # # Changes '::' to '/' to convert namespaces to paths. # - # Examples: # "ActiveModel".underscore # => "active_model" # "ActiveModel::Errors".underscore # => "active_model/errors" # @@ -75,7 +84,7 @@ module ActiveSupport # "SSLError".underscore.camelize # => "SslError" def underscore(camel_cased_word) word = camel_cased_word.to_s.dup - word.gsub!(/::/, '/') + word.gsub!('::', '/') 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') @@ -87,14 +96,13 @@ module ActiveSupport # 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) } + inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } result.gsub!(/_id$/, "") - result.gsub!(/_/, ' ') + result.tr!('_', ' ') result.gsub(/([a-z\d]*)/i) { |match| "#{inflections.acronyms[match] || match.downcase}" }.gsub(/^\w/) { $&.upcase } @@ -104,21 +112,19 @@ module ActiveSupport # 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+. + # +titleize+ is also aliased 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 } + humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.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" @@ -130,7 +136,6 @@ module ActiveSupport # 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" # @@ -143,10 +148,9 @@ module ActiveSupport # Replaces underscores with dashes in the string. # - # Example: - # "puni_puni" # => "puni-puni" + # "puni_puni".dasherize # => "puni-puni" def dasherize(underscored_word) - underscored_word.gsub(/_/, '-') + underscored_word.tr('_', '-') end # Removes the module part from the expression in the string: @@ -181,7 +185,6 @@ module ActiveSupport # +separate_class_name_and_id_with_underscore+ sets whether # the method should put '_' between the name and 'id'. # - # Examples: # "Message".foreign_key # => "message_id" # "Message".foreign_key(false) # => "messageid" # "Admin::Post".foreign_key # => "post_id" @@ -206,15 +209,30 @@ module ActiveSupport # # NameError is raised when the name is not in CamelCase or the constant is # unknown. - def constantize(camel_cased_word) #:nodoc: + def constantize(camel_cased_word) names = camel_cased_word.split('::') names.shift if names.empty? || names.first.empty? - constant = Object - names.each do |name| - constant = constant.const_get(name, false) + names.inject(Object) do |constant, name| + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check it it's owned + # directly before we reach Object or the end of ancestors. + constant = constant.ancestors.inject do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) + end end - constant end # Tries to find a constant with the name specified in the argument string: @@ -253,7 +271,6 @@ module ActiveSupport # Returns the suffix that should be added to a number to denote the position # in an ordered sequence such as 1st, 2nd, 3rd, 4th. # - # Examples: # ordinal(1) # => "st" # ordinal(2) # => "nd" # ordinal(1002) # => "nd" @@ -276,7 +293,6 @@ module ActiveSupport # Turns a number into an ordinal string used to denote the position in an # ordered sequence such as 1st, 2nd, 3rd, 4th. # - # Examples: # ordinalize(1) # => "1st" # ordinalize(2) # => "2nd" # ordinalize(1002) # => "1002nd" @@ -302,16 +318,15 @@ module ActiveSupport # 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 } + if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/]) result else - rules.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) } result end end diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 40e7a0e389..a372b6d1f7 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -66,8 +66,6 @@ module ActiveSupport # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. # - # ==== Examples - # # class Person # def to_param # "#{id}-#{name.parameterize}" diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index cbeb6c0a28..e44939e78a 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -8,8 +8,13 @@ module ActiveSupport module JSON class << self + # Parses a JSON string (JavaScript Object Notation) into a hash. + # See www.json.org for more info. + # + # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}") + # => {"team" => "rails", "players" => "36"} def decode(json, options ={}) - data = MultiJson.decode(json, options) + data = MultiJson.load(json, options) if ActiveSupport.parse_json_times convert_dates_from(data) else @@ -18,12 +23,12 @@ module ActiveSupport end def engine - MultiJson.engine + MultiJson.adapter end alias :backend :engine def engine=(name) - MultiJson.engine = name + MultiJson.use(name) end alias :backend= :engine= @@ -34,6 +39,14 @@ module ActiveSupport self.backend = old_backend end + # Returns the class of the error that will be raised when there is an error in decoding JSON. + # Using this method means you won't directly depend on the ActiveSupport's JSON implementation, in case it changes in the future. + # + # begin + # obj = ActiveSupport::JSON.decode(some_string) + # rescue ActiveSupport::JSON.parse_error + # Rails.logger.warn("Attempted to decode invalid JSON: #{some_string}") + # end def parse_error MultiJson::DecodeError end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index b2adfea273..1a95bd63e6 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -17,6 +17,7 @@ module ActiveSupport class << self delegate :use_standard_json_time_format, :use_standard_json_time_format=, :escape_html_entities_in_json, :escape_html_entities_in_json=, + :encode_big_decimal_as_string, :encode_big_decimal_as_string=, :to => :'ActiveSupport::JSON::Encoding' end @@ -24,7 +25,10 @@ module ActiveSupport # matches YAML-formatted dates DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/ - # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info. + # Dumps objects in JSON (JavaScript Object Notation). See www.json.org for more info. + # + # ActiveSupport::JSON.encode({team: 'rails', players: '36'}) + # # => "{\"team\":\"rails\",\"players\":\"36\"}" def self.encode(value, options = nil) Encoding::Encoder.new(options).encode(value) end @@ -104,6 +108,9 @@ module ActiveSupport # If true, use ISO 8601 format for dates and times. Otherwise, fall back to the Active Support legacy format. attr_accessor :use_standard_json_time_format + # If false, serializes BigDecimal objects as numeric instead of wrapping them in a string + attr_accessor :encode_big_decimal_as_string + attr_accessor :escape_regex attr_reader :escape_html_entities_in_json @@ -132,7 +139,8 @@ module ActiveSupport end self.use_standard_json_time_format = true - self.escape_html_entities_in_json = false + self.escape_html_entities_in_json = true + self.encode_big_decimal_as_string = true end end end @@ -154,32 +162,67 @@ class Struct #:nodoc: end class TrueClass - AS_JSON = ActiveSupport::JSON::Variable.new('true').freeze - def as_json(options = nil) AS_JSON end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + to_s + end end class FalseClass - AS_JSON = ActiveSupport::JSON::Variable.new('false').freeze - def as_json(options = nil) AS_JSON end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + to_s + end end class NilClass - AS_JSON = ActiveSupport::JSON::Variable.new('null').freeze - def as_json(options = nil) AS_JSON end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + 'null' + end end class String - def as_json(options = nil) self end #:nodoc: - def encode_json(encoder) encoder.escape(self) end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + encoder.escape(self) + end end class Symbol - def as_json(options = nil) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + to_s + end end class Numeric - def as_json(options = nil) self end #:nodoc: - def encode_json(encoder) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + to_s + end +end + +class Float + # Encoding Infinity or NaN to JSON should return "null". The default returns + # "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]'). + def as_json(options = nil) #:nodoc: + finite? ? self : nil + end end class BigDecimal @@ -191,11 +234,21 @@ class BigDecimal # That's why a JSON string is returned. The JSON literal is not numeric, but if # the other end knows by contract that the data is supposed to be a BigDecimal, # it still has the chance to post-process the string and get the real value. - def as_json(options = nil) to_s end #:nodoc: + # + # Use ActiveSupport.use_standard_json_big_decimal_format = true to override this behaviour + def as_json(options = nil) #:nodoc: + if finite? + ActiveSupport.encode_big_decimal_as_string ? to_s : self + else + nil + end + end end class Regexp - def as_json(options = nil) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + to_s + end end module Enumerable @@ -205,7 +258,9 @@ module Enumerable end class Range - def as_json(options = nil) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + to_s + end end class Array @@ -241,7 +296,7 @@ class Hash Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }] end - def encode_json(encoder) + def encode_json(encoder) #:nodoc: # values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be # processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields); diff --git a/activesupport/lib/active_support/json/variable.rb b/activesupport/lib/active_support/json/variable.rb index 5685ed18b7..8af661a795 100644 --- a/activesupport/lib/active_support/json/variable.rb +++ b/activesupport/lib/active_support/json/variable.rb @@ -1,7 +1,15 @@ +require 'active_support/deprecation' + module ActiveSupport module JSON - # A string that returns itself as its JSON-encoded form. + # Deprecated: A string that returns itself as its JSON-encoded form. class Variable < String + def initialize(*args) + ActiveSupport::Deprecation.warn 'ActiveSupport::JSON::Variable is deprecated and will be removed in Rails 4.1. ' \ + 'For your own custom JSON literals, define #as_json and #encode_json yourself.' + super + end + def as_json(options = nil) self end #:nodoc: def encode_json(encoder) self end #:nodoc: end diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb index 82507c1e03..c167efc1a7 100644 --- a/activesupport/lib/active_support/lazy_load_hooks.rb +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -1,32 +1,32 @@ -# lazy_load_hooks allows rails to lazily load a lot of components and thus making the app boot faster. Because of -# this feature now there is no need to require <tt>ActiveRecord::Base</tt> at boot time purely to apply configuration. Instead -# a hook is registered that applies configuration once <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is used -# as example but this feature can be applied elsewhere too. -# -# Here is an example where +on_load+ method is called to register a hook. -# -# initializer "active_record.initialize_timezone" do -# ActiveSupport.on_load(:active_record) do -# self.time_zone_aware_attributes = true -# self.default_timezone = :utc -# end -# end -# -# When the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated then +run_load_hooks+ is invoked. -# The very last line of +activerecord/lib/active_record/base.rb+ is: -# -# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) -# module ActiveSupport - @load_hooks = Hash.new {|h,k| h[k] = [] } - @loaded = {} + # lazy_load_hooks allows rails to lazily load a lot of components and thus making the app boot faster. Because of + # this feature now there is no need to require <tt>ActiveRecord::Base</tt> at boot time purely to apply configuration. Instead + # a hook is registered that applies configuration once <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is used + # as example but this feature can be applied elsewhere too. + # + # Here is an example where +on_load+ method is called to register a hook. + # + # initializer "active_record.initialize_timezone" do + # ActiveSupport.on_load(:active_record) do + # self.time_zone_aware_attributes = true + # self.default_timezone = :utc + # end + # end + # + # When the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated then +run_load_hooks+ is invoked. + # The very last line of +activerecord/lib/active_record/base.rb+ is: + # + # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) + # + @load_hooks = Hash.new { |h,k| h[k] = [] } + @loaded = Hash.new { |h,k| h[k] = [] } def self.on_load(name, options = {}, &block) - if base = @loaded[name] + @loaded[name].each do |base| execute_hook(base, options, block) - else - @load_hooks[name] << [block, options] end + + @load_hooks[name] << [block, options] end def self.execute_hook(base, options, block) @@ -38,7 +38,7 @@ module ActiveSupport end def self.run_load_hooks(name, base = Object) - @loaded[name] = base + @loaded[name] << base @load_hooks[name].each do |hook, options| execute_hook(base, options, hook) end diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml index a1499bcc90..f4900dc935 100644 --- a/activesupport/lib/active_support/locale/en.yml +++ b/activesupport/lib/active_support/locale/en.yml @@ -34,3 +34,100 @@ en: words_connector: ", " two_words_connector: " and " last_word_connector: ", and " + number: + # Used in NumberHelper.number_to_delimited() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "." + # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "," + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 3 + # If set to true, precision will mean the number of significant digits instead + # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) + significant: false + # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2) + strip_insignificant_zeros: false + + # Used in NumberHelper.number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + format: "%u%n" + unit: "$" + # These five are to override number.format and are optional + separator: "." + delimiter: "," + precision: 2 + significant: false + strip_insignificant_zeros: false + + # Used in NumberHelper.number_to_percentage() + percentage: + format: + # These five are to override number.format and are optional + # separator: + delimiter: "" + # precision: + # significant: false + # strip_insignificant_zeros: false + format: "%n%" + + # Used in NumberHelper.number_to_rounded() + precision: + format: + # These five are to override number.format and are optional + # separator: + delimiter: "" + # precision: + # significant: false + # strip_insignificant_zeros: false + + # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human() + human: + format: + # These five are to override number.format and are optional + # separator: + delimiter: "" + precision: 3 + significant: true + strip_insignificant_zeros: true + # Used in number_to_human_size() + storage_units: + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + # Used in NumberHelper.number_to_human() + decimal_units: + format: "%n %u" + # Decimal units output formatting + # By default we will only quantify some of the exponents + # but the commented ones might be defined or overridden + # by the user. + units: + # femto: Quadrillionth + # pico: Trillionth + # nano: Billionth + # micro: Millionth + # mili: Thousandth + # centi: Hundredth + # deci: Tenth + unit: "" + # ten: + # one: Ten + # other: Tens + # hundred: Hundred + thousand: Thousand + million: Million + billion: Billion + trillion: Trillion + quadrillion: Quadrillion diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 58938cdc3d..e5b4ca2738 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/class/attribute' module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications - # with the sole purpose of logging them. The log subscriber dispatches notifications to + # 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: @@ -48,20 +48,19 @@ module ActiveSupport mattr_accessor :colorize_logging self.colorize_logging = true - class_attribute :logger - class << self - remove_method :logger def logger @logger ||= Rails.logger if defined?(Rails) + @logger end + attr_writer :logger + def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications) log_subscribers << log_subscriber - @@flushable_loggers = nil log_subscriber.public_methods(false).each do |event| - next if 'call' == event.to_s + next if %w{ start finish }.include?(event.to_s) notifier.subscribe("#{event}.#{namespace}", log_subscriber) end @@ -71,28 +70,44 @@ module ActiveSupport @@log_subscribers ||= [] end - def flushable_loggers - @@flushable_loggers ||= begin - loggers = log_subscribers.map(&:logger) - loggers.uniq! - loggers.select { |l| l.respond_to?(:flush) } - end - end - # Flush all log_subscribers' logger. def flush_all! - flushable_loggers.each { |log| log.flush } + logger.flush if logger.respond_to?(:flush) end end - def call(message, *args) + def initialize + @queue_key = [self.class.name, object_id].join "-" + super + end + + def logger + LogSubscriber.logger + end + + def start(name, id, payload) return unless logger - method = message.split('.').first + e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload) + parent = event_stack.last + parent << e if parent + + event_stack.push e + end + + def finish(name, id, payload) + return unless logger + + finished = Time.now + event = event_stack.pop + event.end = finished + event.payload.merge!(payload) + + method = name.split('.').first begin - send(method, ActiveSupport::Notifications::Event.new(message, *args)) + send(method, event) rescue Exception => e - logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" + logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" end end @@ -101,8 +116,7 @@ module ActiveSupport %w(info debug warn error fatal unknown).each do |level| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{level}(progname = nil, &block) - return unless logger - logger.#{level}(progname, &block) + logger.#{level}(progname, &block) if logger end METHOD end @@ -114,9 +128,15 @@ module ActiveSupport # def color(text, color, bold=false) return text unless colorize_logging - color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) + color = self.class.const_get(color.upcase) if color.is_a?(Symbol) bold = bold ? BOLD : "" "#{bold}#{color}#{text}#{CLEAR}" end + + private + + def event_stack + Thread.current[@queue_key] ||= [] + end end end diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 7b7fc81e6c..b65ea6208c 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -61,8 +61,12 @@ module ActiveSupport @logged = Hash.new { |h,k| h[k] = [] } end - def method_missing(level, message) - @logged[level] << message + def method_missing(level, message = nil) + if block_given? + @logged[level] << yield + else + @logged[level] << message + end end def logged(level) diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb index 5efe13c537..977fe95dbe 100644 --- a/activesupport/lib/active_support/multibyte.rb +++ b/activesupport/lib/active_support/multibyte.rb @@ -7,7 +7,6 @@ module ActiveSupport #:nodoc: # class so you can support other encodings. See the ActiveSupport::Multibyte::Chars implementation for # an example how to do this. # - # Example: # ActiveSupport::Multibyte.proxy_class = CharsForUTF32 def self.proxy_class=(klass) @proxy_class = klass @@ -18,4 +17,4 @@ module ActiveSupport #:nodoc: @proxy_class ||= ActiveSupport::Multibyte::Chars end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 9a748dfa60..47336d2143 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -62,8 +62,8 @@ module ActiveSupport #:nodoc: # Returns +true+ if _obj_ responds to the given method. Private methods are included in the search # only if the optional second parameter evaluates to +true+. - def respond_to?(method, include_private=false) - super || @wrapped_string.respond_to?(method, include_private) + def respond_to_missing?(method, include_private) + @wrapped_string.respond_to?(method, include_private) end # Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise. @@ -74,10 +74,9 @@ module ActiveSupport #:nodoc: # Works just like <tt>String#split</tt>, with the exception that the items in the resulting list are Chars # instances instead of String. This makes chaining methods easier. # - # Example: # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"] def split(*args) - @wrapped_string.split(*args).map { |i| i.mb_chars } + @wrapped_string.split(*args).map { |i| self.class.new(i) } end # Works like like <tt>String#slice!</tt>, but returns an instance of Chars, or nil if the string was not @@ -88,7 +87,6 @@ module ActiveSupport #:nodoc: # Reverses all characters in the string. # - # Example: # 'Café'.mb_chars.reverse.to_s # => 'éfaC' def reverse chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*')) @@ -97,7 +95,6 @@ module ActiveSupport #:nodoc: # Limits the byte size of the string to a number of bytes without breaking characters. Usable # when the storage for a string is limited for some reason. # - # Example: # 'こんにちは'.mb_chars.limit(7).to_s # => "こん" def limit(limit) slice(0...translate_offset(limit)) @@ -105,7 +102,6 @@ module ActiveSupport #:nodoc: # Converts characters in the string to uppercase. # - # Example: # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?" def upcase chars Unicode.upcase(@wrapped_string) @@ -113,7 +109,6 @@ module ActiveSupport #:nodoc: # Converts characters in the string to lowercase. # - # Example: # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum" def downcase chars Unicode.downcase(@wrapped_string) @@ -121,7 +116,6 @@ module ActiveSupport #:nodoc: # Converts characters in the string to the opposite case. # - # Example: # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN" def swapcase chars Unicode.swapcase(@wrapped_string) @@ -129,7 +123,6 @@ module ActiveSupport #:nodoc: # Converts the first character to uppercase and the remainder to lowercase. # - # Example: # 'über'.mb_chars.capitalize.to_s # => "Über" def capitalize (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase @@ -137,11 +130,10 @@ module ActiveSupport #:nodoc: # Capitalizes the first letter of every word, when possible. # - # Example: # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró" # "日本語".mb_chars.titleize # => "日本語" def titleize - chars(downcase.to_s.gsub(/\b('?[\S])/u) { Unicode.upcase($1)}) + chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)}) end alias_method :titlecase, :titleize @@ -157,7 +149,6 @@ module ActiveSupport #:nodoc: # Performs canonical decomposition on all the characters. # - # Example: # 'é'.length # => 2 # 'é'.mb_chars.decompose.to_s.length # => 3 def decompose @@ -166,7 +157,6 @@ module ActiveSupport #:nodoc: # Performs composition on all the characters. # - # Example: # 'é'.length # => 3 # 'é'.mb_chars.compose.to_s.length # => 2 def compose @@ -175,7 +165,6 @@ module ActiveSupport #:nodoc: # Returns the number of grapheme clusters in the string. # - # Example: # 'क्षि'.mb_chars.length # => 4 # 'क्षि'.mb_chars.grapheme_length # => 3 def grapheme_length diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index cb89d45c92..ef1711c60a 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -15,7 +15,6 @@ module ActiveSupport # The default normalization used for operations that require normalization. It can be set to any of the # normalizations in NORMALIZATION_FORMS. # - # Example: # ActiveSupport::Multibyte::Unicode.default_normalization_form = :c attr_accessor :default_normalization_form @default_normalization_form = :kc @@ -72,7 +71,6 @@ module ActiveSupport # Unpack the string at grapheme boundaries. Returns a list of character lists. # - # Example: # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]] # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]] def unpack_graphemes(string) @@ -107,7 +105,6 @@ module ActiveSupport # Reverse operation of unpack_graphemes. # - # Example: # Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि' def pack_graphemes(unpacked) unpacked.flatten.pack('U*') @@ -334,7 +331,7 @@ module ActiveSupport def load begin @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read } - rescue Exception => e + rescue => e raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable") end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 13f675c654..b4657a8ba9 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -1,7 +1,10 @@ +require 'active_support/notifications/instrumenter' +require 'active_support/notifications/fanout' + module ActiveSupport # = Notifications # - # +ActiveSupport::Notifications+ provides an instrumentation API for Ruby. + # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for Ruby. # # == Instrumenters # @@ -30,7 +33,7 @@ module ActiveSupport # end # # That code returns right away, you are just subscribing to "render" events. - # The block will be called asynchronously whenever someone instruments "render": + # The block is saved and will be called whenever someone instruments "render": # # ActiveSupport::Notifications.instrument("render", :extra => :information) do # render :text => "Foo" @@ -41,26 +44,53 @@ module ActiveSupport # event.duration # => 10 (in milliseconds) # event.payload # => { :extra => :information } # - # The block in the +subscribe+ call gets the name of the event, start + # The block in the <tt>subscribe</tt> 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 + # have a key <tt>:exception</tt> 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+ + # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt> # is able to take the arguments as they come and provide an object-oriented # interface to that data. # + # It is also possible to pass an object as the second parameter passed to the + # <tt>subscribe</tt> method instead of a block: + # + # module ActionController + # class PageRequest + # def call(name, started, finished, unique_id, payload) + # Rails.logger.debug ["notification:", name, started, finished, unique_id, payload].join(" ") + # end + # end + # end + # + # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new) + # + # resulting in the following output within the logs including a hash with the payload: + # + # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 { + # :controller=>"Devise::SessionsController", + # :action=>"new", + # :params=>{"action"=>"new", "controller"=>"devise/sessions"}, + # :format=>:html, + # :method=>"GET", + # :path=>"/login/sign_in", + # :status=>200, + # :view_runtime=>279.3080806732178, + # :db_runtime=>40.053 + # } + # # 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 + # and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing # to all events. # # == Temporary Subscriptions @@ -105,12 +135,6 @@ module ActiveSupport # to log subscribers in a thread. You can use any queue implementation you want. # module Notifications - autoload :Instrumenter, 'active_support/notifications/instrumenter' - autoload :Event, 'active_support/notifications/instrumenter' - autoload :Fanout, 'active_support/notifications/fanout' - - @instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) } - class << self attr_accessor :notifier @@ -119,7 +143,7 @@ module ActiveSupport end def instrument(name, payload = {}) - if @instrumenters[name] + if notifier.listening?(name) instrumenter.instrument(name, payload) { yield payload if block_given? } else yield payload if block_given? @@ -127,9 +151,7 @@ module ActiveSupport end def subscribe(*args, &block) - notifier.subscribe(*args, &block).tap do - @instrumenters.clear - end + notifier.subscribe(*args, &block) end def subscribed(callback, *args, &block) @@ -141,7 +163,6 @@ module ActiveSupport def unsubscribe(args) notifier.unsubscribe(args) - @instrumenters.clear end def instrumenter diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index a9aa5464e9..2e5bcf4639 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -1,24 +1,42 @@ +require 'mutex_m' + module ActiveSupport module Notifications # This is a default queue implementation that ships with Notifications. # It just pushes events to all registered log subscribers. + # + # This class is thread safe. All methods are reentrant. class Fanout + include Mutex_m + def initialize @subscribers = [] @listeners_for = {} + super end def subscribe(pattern = nil, block = Proc.new) - subscriber = Subscriber.new(pattern, block).tap do |s| - @subscribers << s + subscriber = Subscribers.new pattern, block + synchronize do + @subscribers << subscriber + @listeners_for.clear end - @listeners_for.clear subscriber end def unsubscribe(subscriber) - @subscribers.reject! {|s| s.matches?(subscriber)} - @listeners_for.clear + synchronize do + @subscribers.reject! { |s| s.matches?(subscriber) } + @listeners_for.clear + end + end + + def start(name, id, payload) + listeners_for(name).each { |s| s.start(name, id, payload) } + end + + def finish(name, id, payload) + listeners_for(name).each { |s| s.finish(name, id, payload) } end def publish(name, *args) @@ -26,7 +44,9 @@ module ActiveSupport end def listeners_for(name) - @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } + synchronize do + @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } + end end def listening?(name) @@ -37,23 +57,87 @@ module ActiveSupport def wait end - class Subscriber #:nodoc: - def initialize(pattern, delegate) - @pattern = pattern - @delegate = delegate + module Subscribers # :nodoc: + def self.new(pattern, listener) + if listener.respond_to?(:start) and listener.respond_to?(:finish) + subscriber = Evented.new pattern, listener + else + subscriber = Timed.new pattern, listener + end + + unless pattern + AllMessages.new(subscriber) + else + subscriber + end end - def publish(message, *args) - @delegate.call(message, *args) + class Evented #:nodoc: + def initialize(pattern, delegate) + @pattern = pattern + @delegate = delegate + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def subscribed_to?(name) + @pattern === name.to_s + end + + def matches?(subscriber_or_name) + self === subscriber_or_name || + @pattern && @pattern === subscriber_or_name + end end - def subscribed_to?(name) - !@pattern || @pattern === name.to_s + class Timed < Evented + def initialize(pattern, delegate) + @timestack = [] + super + end + + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + @timestack.push Time.now + end + + def finish(name, id, payload) + started = @timestack.pop + @delegate.call(name, started, Time.now, id, payload) + end end - def matches?(subscriber_or_name) - self === subscriber_or_name || - @pattern && @pattern === subscriber_or_name + class AllMessages # :nodoc: + def initialize(delegate) + @delegate = delegate + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def publish(name, *args) + @delegate.publish name, *args + end + + def subscribed_to?(name) + true + end + + alias :matches? :=== end end end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 3941c285a2..78d0397f1f 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -1,7 +1,8 @@ -require 'active_support/core_ext/module/delegation' +require 'securerandom' module ActiveSupport module Notifications + # Instrumentors are stored in a thread local. class Instrumenter attr_reader :id @@ -14,15 +15,14 @@ module ActiveSupport # and publish it. Notice that events get sent even if an error occurs # in the passed-in block def instrument(name, payload={}) - started = Time.now - + @notifier.start(name, @id, payload) begin yield rescue Exception => e payload[:exception] = [e.class.name, e.message] raise e ensure - @notifier.publish(name, started, Time.now, @id, payload) + @notifier.finish(name, @id, payload) end end @@ -33,7 +33,8 @@ module ActiveSupport end class Event - attr_reader :name, :time, :end, :transaction_id, :payload, :duration + attr_reader :name, :time, :transaction_id, :payload, :children + attr_accessor :end def initialize(name, start, ending, transaction_id, payload) @name = name @@ -41,12 +42,19 @@ module ActiveSupport @time = start @transaction_id = transaction_id @end = ending - @duration = 1000.0 * (@end - @time) + @children = [] + end + + def duration + 1000.0 * (self.end - time) + end + + def <<(event) + @children << event end def parent_of?(event) - start = (time - event.time) * 1000 - start <= 0 && (start + duration >= event.duration) + @children.include? event end end end diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb new file mode 100644 index 0000000000..3849f94a31 --- /dev/null +++ b/activesupport/lib/active_support/number_helper.rb @@ -0,0 +1,636 @@ +require 'active_support/core_ext/big_decimal/conversions' +require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/hash/keys' +require 'active_support/i18n' + +module ActiveSupport + module NumberHelper + extend self + + DEFAULTS = { + # Used in number_to_delimited + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: { + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: ".", + # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: ",", + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 3, + # If set to true, precision will mean the number of significant digits instead + # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) + significant: false, + # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2) + strip_insignificant_zeros: false + }, + + # Used in number_to_currency + currency: { + format: { + format: "%u%n", + negative_format: "-%u%n", + unit: "$", + # These five are to override number.format and are optional + separator: ".", + delimiter: ",", + precision: 2, + significant: false, + strip_insignificant_zeros: false + } + }, + + # Used in number_to_percentage + percentage: { + format: { + delimiter: "", + format: "%n%" + } + }, + + # Used in number_to_rounded + precision: { + format: { + delimiter: "" + } + }, + + # Used in number_to_human_size and number_to_human + human: { + format: { + # These five are to override number.format and are optional + delimiter: "", + precision: 3, + significant: true, + strip_insignificant_zeros: true + }, + # Used in number_to_human_size + storage_units: { + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u", + units: { + byte: "Bytes", + kb: "KB", + mb: "MB", + gb: "GB", + tb: "TB" + } + }, + # Used in number_to_human + decimal_units: { + format: "%n %u", + # Decimal units output formatting + # By default we will only quantify some of the exponents + # but the commented ones might be defined or overridden + # by the user. + units: { + # femto: Quadrillionth + # pico: Trillionth + # nano: Billionth + # micro: Millionth + # mili: Thousandth + # centi: Hundredth + # deci: Tenth + unit: "", + # ten: + # one: Ten + # other: Tens + # hundred: Hundred + thousand: "Thousand", + million: "Million", + billion: "Billion", + trillion: "Trillion", + quadrillion: "Quadrillion" + } + } + } + } + + DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, + -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto } + + STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb] + + # Formats a +number+ into a US phone number (e.g., (555) + # 123-9876). You can customize the format in the +options+ hash. + # + # ==== Options + # + # * <tt>:area_code</tt> - Adds parentheses around the area code. + # * <tt>:delimiter</tt> - Specifies the delimiter to use + # (defaults to "-"). + # * <tt>:extension</tt> - Specifies an extension to add to the + # end of the generated number. + # * <tt>:country_code</tt> - Sets the country code for the phone + # number. + # ==== Examples + # + # number_to_phone(5551234) # => 555-1234 + # number_to_phone("5551234") # => 555-1234 + # number_to_phone(1235551234) # => 123-555-1234 + # number_to_phone(1235551234, area_code: true) # => (123) 555-1234 + # number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234 + # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555 + # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234 + # number_to_phone("123a456") # => 123a456 + # + # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.') + # # => +1.123.555.1234 x 1343 + def number_to_phone(number, options = {}) + return unless number + options = options.symbolize_keys + + number = number.to_s.strip + area_code = options[:area_code] + delimiter = options[:delimiter] || "-" + extension = options[:extension] + country_code = options[:country_code] + + if area_code + number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3") + else + number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank? + end + + str = '' + str << "+#{country_code}#{delimiter}" unless country_code.blank? + str << number + str << " x #{extension}" unless extension.blank? + str + end + + # Formats a +number+ into a currency string (e.g., $13.65). You + # can customize the format in the +options+ hash. + # + # ==== Options + # + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the level of precision (defaults + # to 2). + # * <tt>:unit</tt> - Sets the denomination of the currency + # (defaults to "$"). + # * <tt>:separator</tt> - Sets the separator between the units + # (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ","). + # * <tt>:format</tt> - Sets the format for non-negative numbers + # (defaults to "%u%n"). Fields are <tt>%u</tt> for the + # currency, and <tt>%n</tt> for the number. + # * <tt>:negative_format</tt> - Sets the format for negative + # numbers (defaults to prepending an hyphen to the formatted + # number given by <tt>:format</tt>). Accepts the same fields + # than <tt>:format</tt>, except <tt>%n</tt> is here the + # absolute value of the number. + # + # ==== Examples + # + # number_to_currency(1234567890.50) # => $1,234,567,890.50 + # number_to_currency(1234567890.506) # => $1,234,567,890.51 + # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506 + # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 € + # number_to_currency('123a456') # => $123a456 + # + # number_to_currency(-1234567890.50, negative_format: '(%u%n)') + # # => ($1,234,567,890.50) + # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '') + # # => £1234567890,50 + # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u') + # # => 1234567890,50 £ + def number_to_currency(number, options = {}) + return unless number + options = options.symbolize_keys + + currency = i18n_format_options(options[:locale], :currency) + currency[:negative_format] ||= "-" + currency[:format] if currency[:format] + + defaults = default_format_options(:currency).merge!(currency) + defaults[:negative_format] = "-" + options[:format] if options[:format] + options = defaults.merge!(options) + + unit = options.delete(:unit) + format = options.delete(:format) + + if number.to_f.phase != 0 + format = options.delete(:negative_format) + number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '') + end + + format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit) + end + + # Formats a +number+ as a percentage string (e.g., 65%). You can + # customize the format in the +options+ hash. + # + # ==== Options + # + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the precision of the number + # (defaults to 3). + # * <tt>:significant</tt> - If +true+, precision will be the # + # of significant_digits. If +false+, the # of fractional + # digits (defaults to +false+). + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ""). + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # * <tt>:format</tt> - Specifies the format of the percentage + # string The number field is <tt>%n</tt> (defaults to "%n%"). + # + # ==== Examples + # + # number_to_percentage(100) # => 100.000% + # number_to_percentage('98') # => 98.000% + # number_to_percentage(100, precision: 0) # => 100% + # number_to_percentage(1000, delimiter: '.', separator: ,') # => 1.000,000% + # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% + # number_to_percentage(1000, :locale => :fr) # => 1 000,000% + # number_to_percentage('98a') # => 98a% + # number_to_percentage(100, format: '%n %') # => 100 % + def number_to_percentage(number, options = {}) + return unless number + options = options.symbolize_keys + + defaults = format_options(options[:locale], :percentage) + options = defaults.merge!(options) + + format = options[:format] || "%n%" + format.gsub('%n', self.number_to_rounded(number, options)) + end + + # Formats a +number+ with grouped thousands using +delimiter+ + # (e.g., 12,324). You can customize the format in the +options+ + # hash. + # + # ==== Options + # + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ","). + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # + # ==== Examples + # + # number_to_delimited(12345678) # => 12,345,678 + # number_to_delimited('123456') # => 123,456 + # number_to_delimited(12345678.05) # => 12,345,678.05 + # number_to_delimited(12345678, delimiter: '.') # => 12.345.678 + # number_to_delimited(12345678, delimiter: ',') # => 12,345,678 + # number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05 + # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05 + # number_to_delimited('112a') # => 112a + # number_to_delimited(98765432.98, delimiter: ' ', separator: ',') + # # => 98 765 432,98 + def number_to_delimited(number, options = {}) + options = options.symbolize_keys + + return number unless valid_float?(number) + + options = format_options(options[:locale]).merge!(options) + + parts = number.to_s.to_str.split('.') + parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") + parts.join(options[:separator]) + end + + # Formats a +number+ with the specified level of + # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if + # +:significant+ is +false+, and 5 if +:significant+ is +true+). + # You can customize the format in the +options+ hash. + # + # ==== Options + # + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the precision of the number + # (defaults to 3). + # * <tt>:significant</tt> - If +true+, precision will be the # + # of significant_digits. If +false+, the # of fractional + # digits (defaults to +false+). + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ""). + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # + # ==== Examples + # + # number_to_rounded(111.2345) # => 111.235 + # number_to_rounded(111.2345, precision: 2) # => 111.23 + # number_to_rounded(13, precision: 5) # => 13.00000 + # number_to_rounded(389.32314, precision: 0) # => 389 + # number_to_rounded(111.2345, significant: true) # => 111 + # number_to_rounded(111.2345, precision: 1, significant: true) # => 100 + # number_to_rounded(13, precision: 5, significant: true) # => 13.000 + # number_to_rounded(111.234, locale: :fr) # => 111,234 + # + # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => 13 + # + # number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3 + # number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.') + # # => 1.111,23 + def number_to_rounded(number, options = {}) + return number unless valid_float?(number) + number = Float(number) + options = options.symbolize_keys + + defaults = format_options(options[:locale], :precision) + options = defaults.merge!(options) + + precision = options.delete :precision + significant = options.delete :significant + strip_insignificant_zeros = options.delete :strip_insignificant_zeros + + if significant && precision > 0 + if number == 0 + digits, rounded_number = 1, 0 + else + digits = (Math.log10(number.abs) + 1).floor + rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision) + digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed + end + precision -= digits + precision = 0 if precision < 0 # don't let it be negative + else + rounded_number = BigDecimal.new(number.to_s).round(precision).to_f + rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros + end + formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options) + if strip_insignificant_zeros + escaped_separator = Regexp.escape(options[:separator]) + formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + else + formatted_number + end + end + + # Formats the bytes in +number+ into a more understandable + # representation (e.g., giving it 1500 yields 1.5 KB). This + # method is useful for reporting file sizes to users. You can + # customize the format in the +options+ hash. + # + # See <tt>number_to_human</tt> if you want to pretty-print a + # generic number. + # + # ==== Options + # + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the precision of the number + # (defaults to 3). + # * <tt>:significant</tt> - If +true+, precision will be the # + # of significant_digits. If +false+, the # of fractional + # digits (defaults to +true+) + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ""). + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # * <tt>:prefix</tt> - If +:si+ formats the number using the SI + # prefix (defaults to :binary) + # + # ==== Examples + # + # number_to_human_size(123) # => 123 Bytes + # number_to_human_size(1234) # => 1.21 KB + # number_to_human_size(12345) # => 12.1 KB + # number_to_human_size(1234567) # => 1.18 MB + # number_to_human_size(1234567890) # => 1.15 GB + # number_to_human_size(1234567890123) # => 1.12 TB + # number_to_human_size(1234567, precision: 2) # => 1.2 MB + # number_to_human_size(483989, precision: 2) # => 470 KB + # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB + # + # Non-significant zeros after the fractional separator are stripped out by + # default (set <tt>:strip_insignificant_zeros</tt> to +false+ to change that): + # + # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB" + # number_to_human_size(524288000, precision: 5) # => "500 MB" + def number_to_human_size(number, options = {}) + options = options.symbolize_keys + + return number unless valid_float?(number) + number = Float(number) + + defaults = format_options(options[:locale], :human) + options = defaults.merge!(options) + + #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) + + storage_units_format = translate_number_value_with_default('human.storage_units.format', :locale => options[:locale], :raise => true) + + base = options[:prefix] == :si ? 1000 : 1024 + + if number.to_i < base + unit = translate_number_value_with_default('human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true) + storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit) + else + max_exp = STORAGE_UNITS.size - 1 + exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base + exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit + number /= base ** exponent + + unit_key = STORAGE_UNITS[exponent] + unit = translate_number_value_with_default("human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true) + + formatted_number = self.number_to_rounded(number, options) + storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit) + end + end + + # Pretty prints (formats and approximates) a number in a way it + # is more readable by humans (eg.: 1200000000 becomes "1.2 + # Billion"). This is useful for numbers that can get very large + # (and too hard to read). + # + # See <tt>number_to_human_size</tt> if you want to print a file + # size. + # + # You can also define you own unit-quantifier names if you want + # to use other decimal units (eg.: 1500 becomes "1.5 + # kilometers", 0.150 becomes "150 milliliters", etc). You may + # define a wide range of unit quantifiers, even fractional ones + # (centi, deci, mili, etc). + # + # ==== Options + # + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the precision of the number + # (defaults to 3). + # * <tt>:significant</tt> - If +true+, precision will be the # + # of significant_digits. If +false+, the # of fractional + # digits (defaults to +true+) + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ""). + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # * <tt>:units</tt> - A Hash of unit quantifier names. Or a + # string containing an i18n scope where to find this hash. It + # might have the following keys: + # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, + # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, + # *<tt>:billion</tt>, <tt>:trillion</tt>, + # *<tt>:quadrillion</tt> + # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, + # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, + # *<tt>:pico</tt>, <tt>:femto</tt> + # * <tt>:format</tt> - Sets the format of the output string + # (defaults to "%n %u"). The field types are: + # * %u - The quantifier (ex.: 'thousand') + # * %n - The number + # + # ==== Examples + # + # number_to_human(123) # => "123" + # number_to_human(1234) # => "1.23 Thousand" + # number_to_human(12345) # => "12.3 Thousand" + # number_to_human(1234567) # => "1.23 Million" + # number_to_human(1234567890) # => "1.23 Billion" + # number_to_human(1234567890123) # => "1.23 Trillion" + # number_to_human(1234567890123456) # => "1.23 Quadrillion" + # number_to_human(1234567890123456789) # => "1230 Quadrillion" + # number_to_human(489939, precision: 2) # => "490 Thousand" + # number_to_human(489939, precision: 4) # => "489.9 Thousand" + # number_to_human(1234567, precision: 4, + # significant: false) # => "1.2346 Million" + # number_to_human(1234567, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + # + # Non-significant zeros after the decimal separator are stripped + # out by default (set <tt>:strip_insignificant_zeros</tt> to + # +false+ to change that): + # + # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion" + # number_to_human(500000000, precision: 5) # => "500 Million" + # + # ==== Custom Unit Quantifiers + # + # You can also use your own custom unit quantifiers: + # number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt" + # + # If in your I18n locale you have: + # + # distance: + # centi: + # one: "centimeter" + # other: "centimeters" + # unit: + # one: "meter" + # other: "meters" + # thousand: + # one: "kilometer" + # other: "kilometers" + # billion: "gazillion-distance" + # + # Then you could do: + # + # number_to_human(543934, :units => :distance) # => "544 kilometers" + # number_to_human(54393498, :units => :distance) # => "54400 kilometers" + # number_to_human(54393498000, :units => :distance) # => "54.4 gazillion-distance" + # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters" + # number_to_human(1, :units => :distance) # => "1 meter" + # number_to_human(0.34, :units => :distance) # => "34 centimeters" + def number_to_human(number, options = {}) + options = options.symbolize_keys + + return number unless valid_float?(number) + number = Float(number) + + defaults = format_options(options[:locale], :human) + options = defaults.merge!(options) + + #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) + + inverted_du = DECIMAL_UNITS.invert + + units = options.delete :units + unit_exponents = case units + when Hash + units + when String, Symbol + I18n.translate(:"#{units}", :locale => options[:locale], :raise => true) + when nil + translate_number_value_with_default("human.decimal_units.units", :locale => options[:locale], :raise => true) + else + raise ArgumentError, ":units must be a Hash or String translation scope." + end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e} + + number_exponent = number != 0 ? Math.log10(number.abs).floor : 0 + display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0 + number /= 10 ** display_exponent + + unit = case units + when Hash + units[DECIMAL_UNITS[display_exponent]] + when String, Symbol + I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i) + else + translate_number_value_with_default("human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i) + end + + decimal_format = options[:format] || translate_number_value_with_default('human.decimal_units.format', :locale => options[:locale]) + formatted_number = self.number_to_rounded(number, options) + decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip + end + + def self.private_module_and_instance_method(method_name) #:nodoc: + private method_name + private_class_method method_name + end + private_class_method :private_module_and_instance_method + + def format_options(locale, namespace = nil) #:nodoc: + default_format_options(namespace).merge!(i18n_format_options(locale, namespace)) + end + private_module_and_instance_method :format_options + + def default_format_options(namespace = nil) #:nodoc: + options = DEFAULTS[:format].dup + options.merge!(DEFAULTS[namespace][:format]) if namespace + options + end + private_module_and_instance_method :default_format_options + + def i18n_format_options(locale, namespace = nil) #:nodoc: + options = I18n.translate(:'number.format', locale: locale, default: {}).dup + if namespace + options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {})) + end + options + end + private_module_and_instance_method :i18n_format_options + + def translate_number_value_with_default(key, i18n_options = {}) #:nodoc: + default = key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } + + I18n.translate(key, { default: default, scope: :number }.merge!(i18n_options)) + end + private_module_and_instance_method :translate_number_value_with_default + + def valid_float?(number) #:nodoc: + Float(number) + rescue ArgumentError, TypeError + false + end + private_module_and_instance_method :valid_float? + end +end diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 8edd3960c7..1a3693f766 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -5,16 +5,20 @@ YAML.add_builtin_type("omap") do |type, val| end module ActiveSupport - # The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the - # order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt> - # implements a hash that preserves insertion order, as in Ruby 1.9: + # <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves + # insertion order. # # oh = ActiveSupport::OrderedHash.new # oh[:a] = 1 # oh[:b] = 2 # oh.keys # => [:a, :b], this order is guaranteed # - # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations. + # Also, maps the +omap+ feature for YAML files + # (See http://yaml.org/type/omap.html) to support ordered items + # when loading from yaml. + # + # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts + # with other implementations. class OrderedHash < ::Hash def to_yaml_type "!tag:yaml.org,2002:omap" diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 9e5a5d0246..60e6cd55ad 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -1,5 +1,3 @@ -require 'active_support/ordered_hash' - # Usually key value pairs are handled something like this: # # h = {} @@ -17,7 +15,7 @@ require 'active_support/ordered_hash' # h.girl # => 'Mary' # module ActiveSupport #:nodoc: - class OrderedOptions < OrderedHash + class OrderedOptions < Hash alias_method :_get, :[] # preserve the original #[] method protected :_get # make it protected @@ -38,7 +36,7 @@ module ActiveSupport #:nodoc: end end - def respond_to?(name) + def respond_to_missing?(name, include_private) true end end diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb new file mode 100644 index 0000000000..b05c3ff126 --- /dev/null +++ b/activesupport/lib/active_support/rails.rb @@ -0,0 +1,27 @@ +# This is private interface. +# +# Rails components cherry pick from Active Support as needed, but there are a +# few features that are used for sure some way or another and it is not worth +# to put individual requires absolutely everywhere. Think blank? for example. +# +# This file is loaded by every Rails component except Active Support itself, +# but it does not belong to the Rails public interface. It is internal to +# Rails and can change anytime. + +# Defines Object#blank? and Object#present?. +require 'active_support/core_ext/object/blank' + +# Rails own autoload, eager_load, etc. +require 'active_support/dependencies/autoload' + +# Support for ClassMethods and the included macro. +require 'active_support/concern' + +# Defines Class#class_attribute. +require 'active_support/core_ext/class/attribute' + +# Defines Module#delegate. +require 'active_support/core_ext/module/delegation' + +# Defines ActiveSupport::Deprecation. +require 'active_support/deprecation' diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index f696716cc8..aa8d408da9 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -5,33 +5,11 @@ module ActiveSupport class Railtie < Rails::Railtie config.active_support = ActiveSupport::OrderedOptions.new + config.eager_load_namespaces << ActiveSupport + initializer "active_support.deprecation_behavior" do |app| if deprecation = app.config.active_support.deprecation ActiveSupport::Deprecation.behavior = deprecation - else - defaults = {"development" => :log, - "production" => :notify, - "test" => :stderr} - - env = Rails.env - - if defaults.key?(env) - msg = "You did not specify how you would like Rails to report " \ - "deprecation notices for your #{env} environment, please " \ - "set config.active_support.deprecation to :#{defaults[env]} " \ - "at config/environments/#{env}.rb" - - warn msg - ActiveSupport::Deprecation.behavior = defaults[env] - else - msg = "You did not specify how you would like Rails to report " \ - "deprecation notices for your #{env} environment, please " \ - "set config.active_support.deprecation to :log, :notify or " \ - ":stderr at config/environments/#{env}.rb" - - warn msg - ActiveSupport::Deprecation.behavior = :stderr - end end end @@ -42,12 +20,18 @@ module ActiveSupport zone_default = Time.find_zone!(app.config.time_zone) unless zone_default - raise \ - 'Value assigned to config.time_zone not recognized.' + + raise 'Value assigned to config.time_zone not recognized. ' \ 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.' end Time.zone_default = zone_default end + + initializer "active_support.set_configs" do |app| + app.config.active_support.each do |k, v| + k = "#{k}=" + ActiveSupport.send(k, v) if ActiveSupport.respond_to? k + end + end end end diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index 0f4a06468a..7aecdd11d3 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -48,6 +48,7 @@ module ActiveSupport # end # end # + # Exceptions raised inside exception handlers are not propagated up. def rescue_from(*klasses, &block) options = klasses.extract_options! @@ -108,7 +109,11 @@ module ActiveSupport when Symbol method(rescuer) when Proc - rescuer.bind(self) + if rescuer.arity == 0 + Proc.new { instance_exec(&rescuer) } + else + Proc.new { |_exception| instance_exec(_exception, &rescuer) } + end end end end diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb deleted file mode 100644 index 41fd866481..0000000000 --- a/activesupport/lib/active_support/ruby/shim.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Backported Ruby builtins so you can code with the latest & greatest -# but still run on any Ruby 1.8.x. -# -# Date next_year, next_month -# DateTime to_date, to_datetime, xmlschema -# 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/string/conversions' -require 'active_support/core_ext/string/interpolation' -require 'active_support/core_ext/string/encoding' -require 'active_support/core_ext/time/conversions' diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb index f3f3909a90..5f20bfa7bc 100644 --- a/activesupport/lib/active_support/string_inquirer.rb +++ b/activesupport/lib/active_support/string_inquirer.rb @@ -10,12 +10,18 @@ module ActiveSupport # Rails.env.production? # class StringInquirer < String - def method_missing(method_name, *arguments) - if method_name[-1, 1] == "?" - self == method_name[0..-2] - else - super + private + + def respond_to_missing?(method_name, include_private = false) + method_name[-1] == '?' + end + + def method_missing(method_name, *arguments) + if method_name[-1] == '?' + self == method_name[0..-2] + else + super + end end - end end end diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index f6ad861353..5e080df518 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -3,7 +3,7 @@ require 'logger' require 'active_support/logger' module ActiveSupport - # Wraps any standard Logger object to provide tagging capabilities. Examples: + # Wraps any standard Logger object to provide tagging capabilities. # # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) # logger.tagged("BCX") { logger.info "Stuff" } # Logs "[BCX] Stuff" @@ -13,7 +13,7 @@ module ActiveSupport # 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. module TaggedLogging - class Formatter < ActiveSupport::Logger::SimpleFormatter # :nodoc: + module Formatter # :nodoc: # This method is invoked when a log event occurs def call(severity, timestamp, progname, msg) super(severity, timestamp, progname, "#{tags_text}#{msg}") @@ -37,7 +37,7 @@ module ActiveSupport end def self.new(logger) - logger.formatter = Formatter.new + logger.formatter.extend Formatter logger.extend(self) end @@ -45,7 +45,7 @@ module ActiveSupport tags = formatter.current_tags new_tags = new_tags.flatten.reject(&:blank?) tags.concat new_tags - yield + yield self ensure tags.pop(new_tags.size) end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 9a52c916ec..d2b8e602f9 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -1,29 +1,25 @@ +gem 'minitest' # make sure we get the gem, not stdlib require 'minitest/spec' require 'active_support/testing/setup_and_teardown' require 'active_support/testing/assertions' require 'active_support/testing/deprecation' -require 'active_support/testing/declarative' require 'active_support/testing/isolation' -require 'active_support/testing/mochaing' +require 'active_support/testing/mocha_module' require 'active_support/core_ext/kernel/reporting' +require 'active_support/deprecation' module ActiveSupport class TestCase < ::MiniTest::Spec - if MiniTest::Unit::VERSION < '2.6.1' - class << self - alias :name :to_s - end - end + include ActiveSupport::Testing::MochaModule # Use AS::TestCase for the base class when describing a model register_spec_type(self) do |desc| - desc < ActiveRecord::Model + Class === desc && desc < ActiveRecord::Model end Assertion = MiniTest::Assertion - alias_method :method_name, :name if method_defined? :name - alias_method :method_name, :__name__ if method_defined? :__name__ + alias_method :method_name, :__name__ $tags = {} def self.for_tag(tag) @@ -39,7 +35,24 @@ module ActiveSupport include ActiveSupport::Testing::SetupAndTeardown include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation - extend ActiveSupport::Testing::Declarative + + def self.describe(text) + if block_given? + super + else + ActiveSupport::Deprecation.warn("`describe` without a block is deprecated, please switch to: `def self.name; #{text.inspect}; end`\n") + + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def self.name + "#{text}" + end + RUBY_EVAL + end + end + + class << self + alias :test :it + end # test/unit backwards compatibility methods alias :assert_raise :assert_raises @@ -48,6 +61,11 @@ module ActiveSupport alias :assert_no_match :refute_match alias :assert_not_same :refute_same + # Fails if the block raises an exception. + # + # assert_nothing_raised do + # ... + # end def assert_nothing_raised(*args) yield end diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index d84595fa8f..ee1a647ed8 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -3,45 +3,46 @@ require 'active_support/core_ext/object/blank' module ActiveSupport module Testing module Assertions - # Test numeric difference between the return value of an expression as a result of what is evaluated - # in the yielded block. + # Test numeric difference between the return value of an expression as a + # result of what is evaluated in the yielded block. # # assert_difference 'Article.count' do - # post :create, :article => {...} + # post :create, article: {...} # end # # An arbitrary expression is passed in and evaluated. # # assert_difference 'assigns(:article).comments(:reload).size' do - # post :create, :comment => {...} + # post :create, comment: {...} # end # - # An arbitrary positive or negative difference can be specified. The default is +1. + # An arbitrary positive or negative difference can be specified. + # The default is <tt>1</tt>. # # assert_difference 'Article.count', -1 do - # post :delete, :id => ... + # post :delete, id: ... # end # # An array of expressions can also be passed in and evaluated. # - # assert_difference [ 'Article.count', 'Post.count' ], +2 do - # post :create, :article => {...} + # assert_difference [ 'Article.count', 'Post.count' ], 2 do + # post :create, article: {...} # end # # A lambda or a list of lambdas can be passed in and evaluated: # # assert_difference lambda { Article.count }, 2 do - # post :create, :article => {...} + # post :create, article: {...} # end # # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do - # post :create, :article => {...} + # post :create, article: {...} # end # - # A error message can be specified. + # An error message can be specified. # - # assert_difference 'Article.count', -1, "An Article should be destroyed" do - # post :delete, :id => ... + # assert_difference 'Article.count', -1, 'An Article should be destroyed' do + # post :delete, id: ... # end def assert_difference(expression, difference = 1, message = nil, &block) expressions = Array(expression) @@ -60,33 +61,43 @@ module ActiveSupport end end - # Assertion that the numeric result of evaluating an expression is not changed before and after - # invoking the passed in block. + # Assertion that the numeric result of evaluating an expression is not + # changed before and after invoking the passed in block. # # assert_no_difference 'Article.count' do - # post :create, :article => invalid_attributes + # post :create, article: invalid_attributes # end # - # A error message can be specified. + # An error message can be specified. # - # assert_no_difference 'Article.count', "An Article should not be created" do - # post :create, :article => invalid_attributes + # 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) assert_difference expression, 0, message, &block end - # Test if an expression is blank. Passes if object.blank? is true. + # Test if an expression is blank. Passes if <tt>object.blank?</tt> is +true+. # - # assert_blank [] # => true + # assert_blank [] # => true + # assert_blank [[]] # => [[]] is not blank + # + # An error message can be specified. + # + # assert_blank [], 'this should be blank' def assert_blank(object, message=nil) message ||= "#{object.inspect} is not blank" assert object.blank?, message end - # Test if an expression is not blank. Passes if object.present? is true. + # Test if an expression is not blank. Passes if <tt>object.present?</tt> is +true+. + # + # assert_present({ data: 'x' }) # => true + # assert_present({}) # => {} is blank + # + # An error message can be specified. # - # assert_present({:data => 'x' }) # => true + # assert_present({ data: 'x' }, 'this should not be blank') def assert_present(object, message=nil) message ||= "#{object.inspect} is blank" assert object.present?, message diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb deleted file mode 100644 index 1c05d45888..0000000000 --- a/activesupport/lib/active_support/testing/declarative.rb +++ /dev/null @@ -1,40 +0,0 @@ -module ActiveSupport - module Testing - module Declarative - - def self.extended(klass) - klass.class_eval do - - unless method_defined?(:describe) - def self.describe(text) - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def self.name - "#{text}" - end - RUBY_EVAL - end - end - - end - end - - unless defined?(Spec) - # test "verify something" do - # ... - # end - def test(name, &block) - test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym - defined = instance_method(test_name) rescue false - raise "#{test_name} is already defined in #{self}" if defined - if block_given? - define_method(test_name, &block) - else - define_method(test_name) do - flunk "No implementation provided for #{name}" - end - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 1a0681e850..27d444fd91 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -33,6 +33,45 @@ module ActiveSupport end module Isolation + require 'thread' + + class ParallelEach + include Enumerable + + # default to 2 cores + CORES = (ENV['TEST_CORES'] || 2).to_i + + def initialize list + @list = list + @queue = SizedQueue.new CORES + end + + def grep pattern + self.class.new super + end + + def each + threads = CORES.times.map { + Thread.new { + while job = @queue.pop + yield job + end + } + } + @list.each { |i| @queue << i } + CORES.times { @queue << nil } + threads.each(&:join) + end + end + + def self.included(klass) #:nodoc: + klass.extend(Module.new { + def test_methods + ParallelEach.new super + end + }) + end + def self.forking_env? !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/)) end diff --git a/activesupport/lib/active_support/testing/mocha_module.rb b/activesupport/lib/active_support/testing/mocha_module.rb new file mode 100644 index 0000000000..ed2942d23a --- /dev/null +++ b/activesupport/lib/active_support/testing/mocha_module.rb @@ -0,0 +1,22 @@ +module ActiveSupport + module Testing + module MochaModule + begin + require 'mocha_standalone' + include Mocha::API + + def before_setup + mocha_setup + super + end + + def after_teardown + super + mocha_verify + mocha_teardown + end + rescue LoadError + end + end + end +end diff --git a/activesupport/lib/active_support/testing/mochaing.rb b/activesupport/lib/active_support/testing/mochaing.rb deleted file mode 100644 index 4ad75a6681..0000000000 --- a/activesupport/lib/active_support/testing/mochaing.rb +++ /dev/null @@ -1,7 +0,0 @@ -begin - silence_warnings { require 'mocha' } -rescue LoadError - # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh. - Object.const_set :Mocha, Module.new - Mocha.const_set :ExpectationError, Class.new(StandardError) -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 244ee1a224..a6c57cd0ff 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -1,9 +1,9 @@ require 'fileutils' -require 'rails/version' require 'active_support/concern' require 'active_support/core_ext/class/delegating_attributes' require 'active_support/core_ext/string/inflections' -require 'action_view/helpers/number_helper' +require 'active_support/core_ext/module/delegation' +require 'active_support/number_helper' module ActiveSupport module Testing @@ -61,7 +61,7 @@ module ActiveSupport ensure begin teardown - run_callbacks :teardown, :enumerator => :reverse_each + run_callbacks :teardown rescue Exception => e result = @runner.puke(self.class, method_name, e) end @@ -126,7 +126,7 @@ module ActiveSupport def record; end end - class Benchmarker < Performer + class Benchmarker < Performer def initialize(*args) super @supported = @metric.respond_to?('measure') @@ -148,26 +148,20 @@ module ActiveSupport end def environment - unless defined? @env - app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - - rails = Rails::VERSION::STRING - if File.directory?('vendor/rails/.git') - Dir.chdir('vendor/rails') do - rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - end - end - - ruby = "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" - - @env = [app, rails, ruby, RUBY_PLATFORM] * ',' - end - - @env + @env ||= [].tap do |env| + env << "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/ + env << rails_version if defined?(Rails::VERSION::STRING) + env << "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" + env << RUBY_PLATFORM + end.join(',') end protected - HEADER = 'measurement,created_at,app,rails,ruby,platform' + if defined?(Rails::VERSION::STRING) + HEADER = 'measurement,created_at,app,rails,ruby,platform' + else + HEADER = 'measurement,created_at,app,ruby,platform' + end def with_output_file fname = output_filename @@ -185,6 +179,18 @@ module ActiveSupport def output_filename "#{super}.csv" end + + def rails_version + "rails-#{Rails::VERSION::STRING}#{rails_branch}" + end + + def rails_branch + if File.directory?('vendor/rails/.git') + Dir.chdir('vendor/rails') do + ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ + end + end + end end module Metrics @@ -195,7 +201,7 @@ module ActiveSupport end class Base - include ActionView::Helpers::NumberHelper + include ActiveSupport::NumberHelper attr_reader :total @@ -207,7 +213,7 @@ module ActiveSupport @name ||= self.class.name.demodulize.underscore end - def benchmark + def benchmark with_gc_stats do before = measure yield @@ -239,7 +245,7 @@ module ActiveSupport class Amount < Base def format(measurement) - number_with_delimiter(measurement.floor) + number_to_delimited(measurement.floor) end end diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb index b347539f13..34e3f9f45f 100644 --- a/activesupport/lib/active_support/testing/performance/jruby.rb +++ b/activesupport/lib/active_support/testing/performance/jruby.rb @@ -42,7 +42,7 @@ module ActiveSupport klasses.each do |klass| fname = output_filename(klass) FileUtils.mkdir_p(File.dirname(fname)) - file = File.open(fname, 'wb') do |file| + File.open(fname, 'wb') do |file| klass.new(@data).printProfile(file) end end diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb index b7a34ea279..12aef0d7fe 100644 --- a/activesupport/lib/active_support/testing/performance/ruby.rb +++ b/activesupport/lib/active_support/testing/performance/ruby.rb @@ -18,6 +18,7 @@ module ActiveSupport end).freeze protected + remove_method :run_gc def run_gc GC.start end @@ -28,6 +29,7 @@ module ActiveSupport @supported = @metric.measure_mode rescue false end + remove_method :run def run return unless @supported @@ -36,9 +38,10 @@ module ActiveSupport RubyProf.pause full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } @data = RubyProf.stop - @total = @data.threads.values.sum(0) { |method_infos| method_infos.max.total_time } + @total = @data.threads.sum(0) { |thread| thread.methods.max.total_time } end + remove_method :record def record return unless @supported @@ -78,6 +81,7 @@ module ActiveSupport self.class::Mode end + remove_method :profile def profile RubyProf.resume yield @@ -86,6 +90,7 @@ module ActiveSupport end protected + remove_method :with_gc_stats def with_gc_stats GC::Profiler.enable GC.start diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index 772c7b4209..a65148cf1f 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -9,7 +9,6 @@ module ActiveSupport included do include ActiveSupport::Callbacks define_callbacks :setup, :teardown - end module ClassMethods @@ -22,24 +21,15 @@ module ActiveSupport end end - def run(runner) - result = '.' - begin - run_callbacks :setup do - result = super - end - rescue Exception => e - result = runner.puke(self.class, method_name, e) - ensure - begin - run_callbacks :teardown - rescue Exception => e - result = runner.puke(self.class, method_name, e) - end - end - result + def before_setup + super + run_callbacks :setup end + def after_teardown + run_callbacks :teardown + super + end end end end diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb index 9634b52ecf..bcd5d78b54 100644 --- a/activesupport/lib/active_support/time.rb +++ b/activesupport/lib/active_support/time.rb @@ -4,10 +4,6 @@ module ActiveSupport autoload :Duration, 'active_support/duration' autoload :TimeWithZone, 'active_support/time_with_zone' autoload :TimeZone, 'active_support/values/time_zone' - - on_load_all do - [Duration, TimeWithZone, TimeZone] - end end require 'date' diff --git a/activesupport/lib/active_support/time/autoload.rb b/activesupport/lib/active_support/time/autoload.rb deleted file mode 100644 index c9a7731b39..0000000000 --- a/activesupport/lib/active_support/time/autoload.rb +++ /dev/null @@ -1,5 +0,0 @@ -module ActiveSupport - autoload :Duration, 'active_support/duration' - autoload :TimeWithZone, 'active_support/time_with_zone' - autoload :TimeZone, 'active_support/values/time_zone' -end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 1cb71012ef..93c2d614f5 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -1,6 +1,5 @@ require 'active_support/values/time_zone' require 'active_support/core_ext/object/acts_like' -require 'active_support/core_ext/object/inclusion' module ActiveSupport # A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are @@ -8,7 +7,6 @@ module ActiveSupport # # You shouldn't ever need to create a TimeWithZone instance directly via <tt>new</tt> . Instead use methods # +local+, +parse+, +at+ and +now+ on TimeZone instances, and +in_time_zone+ on Time and DateTime instances. - # Examples: # # 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 @@ -20,7 +18,6 @@ module ActiveSupport # See Time and TimeZone for further documentation of these methods. # # TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable. - # Examples: # # t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00 # t.hour # => 13 @@ -35,8 +32,10 @@ module ActiveSupport # t.is_a?(ActiveSupport::TimeWithZone) # => true # class TimeWithZone + + # Report class name as 'Time' to thwart type checking def self.name - 'Time' # Report class name as 'Time' to thwart type checking + 'Time' end include Comparable @@ -120,8 +119,6 @@ module ActiveSupport # %Y/%m/%d %H:%M:%S +offset style by setting <tt>ActiveSupport::JSON::Encoding.use_standard_json_time_format</tt> # to false. # - # ==== Examples - # # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json # # => "2005-02-01T15:15:10Z" @@ -311,16 +308,15 @@ module ActiveSupport end # Ensure proxy class responds to all methods that underlying time instance responds to. - def respond_to?(sym, include_priv = false) + def respond_to_missing?(sym, include_priv) # consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime - return false if sym.to_s == 'acts_like_date?' - super || time.respond_to?(sym, include_priv) + return false if sym.to_sym == :acts_like_date? + time.respond_to?(sym, include_priv) end # Send the missing method to +time+ instance, and wrap result in a new TimeWithZone with the existing +time_zone+. def method_missing(sym, *args, &block) - result = time.__send__(sym, *args, &block) - result.acts_like?(:time) ? self.class.new(nil, time_zone, result) : result + wrap_with_time_zone time.__send__(sym, *args, &block) end private @@ -338,11 +334,21 @@ module ActiveSupport end def transfer_time_values_to_utc_constructor(time) - ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:usec) ? time.usec : 0) + ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:nsec) ? Rational(time.nsec, 1000) : 0) end def duration_of_variable_length?(obj) - ActiveSupport::Duration === obj && obj.parts.any? {|p| p[0].in?([:years, :months, :days]) } + ActiveSupport::Duration === obj && obj.parts.any? {|p| [:years, :months, :days].include?(p[0]) } + end + + def wrap_with_time_zone(time) + if time.acts_like?(:time) + self.class.new(nil, time_zone, time) + elsif time.is_a?(Range) + wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end) + else + time + end end end end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 35f400f9df..cc3e6a4bf3 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -1,34 +1,34 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/try' -# The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following: -# -# * Limit the set of zones provided by TZInfo to a meaningful subset of 142 zones. -# * Retrieve and display zones with a friendlier name (e.g., "Eastern Time (US & Canada)" instead of "America/New_York"). -# * Lazily load TZInfo::Timezone instances only when they're needed. -# * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods. -# -# If you set <tt>config.time_zone</tt> in the Rails Application, you can access this TimeZone object via <tt>Time.zone</tt>: -# -# # application.rb: -# class Application < Rails::Application -# config.time_zone = "Eastern Time (US & Canada)" -# end -# -# Time.zone # => #<TimeZone:0x514834...> -# Time.zone.name # => "Eastern Time (US & Canada)" -# Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00 -# -# The version of TZInfo bundled with Active Support only includes the definitions necessary to support the zones -# defined by the TimeZone class. If you need to use zones that aren't defined by TimeZone, you'll need to install the TZInfo gem -# (if a recent version of the gem is installed locally, this will be used instead of the bundled version.) module ActiveSupport + # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following: + # + # * Limit the set of zones provided by TZInfo to a meaningful subset of 142 zones. + # * Retrieve and display zones with a friendlier name (e.g., "Eastern Time (US & Canada)" instead of "America/New_York"). + # * Lazily load TZInfo::Timezone instances only when they're needed. + # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods. + # + # If you set <tt>config.time_zone</tt> in the Rails Application, you can access this TimeZone object via <tt>Time.zone</tt>: + # + # # application.rb: + # class Application < Rails::Application + # config.time_zone = "Eastern Time (US & Canada)" + # end + # + # Time.zone # => #<TimeZone:0x514834...> + # Time.zone.name # => "Eastern Time (US & Canada)" + # Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00 + # + # The version of TZInfo bundled with Active Support only includes the definitions necessary to support the zones + # defined by the TimeZone class. If you need to use zones that aren't defined by TimeZone, you'll need to install the TZInfo gem + # (if a recent version of the gem is installed locally, this will be used instead of the bundled version.) class TimeZone # Keys are Rails TimeZone names, values are TZInfo identifiers MAPPING = { "International Date Line West" => "Pacific/Midway", "Midway Island" => "Pacific/Midway", - "Samoa" => "Pacific/Pago_Pago", + "American Samoa" => "Pacific/Pago_Pago", "Hawaii" => "Pacific/Honolulu", "Alaska" => "America/Juneau", "Pacific Time (US & Canada)" => "America/Los_Angeles", @@ -167,15 +167,16 @@ module ActiveSupport "Marshall Is." => "Pacific/Majuro", "Auckland" => "Pacific/Auckland", "Wellington" => "Pacific/Auckland", - "Nuku'alofa" => "Pacific/Tongatapu" - }.each { |name, zone| name.freeze; zone.freeze } - MAPPING.freeze + "Nuku'alofa" => "Pacific/Tongatapu", + "Tokelau Is." => "Pacific/Fakaofo", + "Samoa" => "Pacific/Apia" + } UTC_OFFSET_WITH_COLON = '%s%02d:%02d' UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '') # Assumes self represents an offset from UTC in seconds (as returned from Time#utc_offset) - # and turns this into an +HH:MM formatted string. Example: + # and turns this into an +HH:MM formatted string. # # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" def self.seconds_to_utc_offset(seconds, colon = true) @@ -203,6 +204,7 @@ module ActiveSupport @current_period = nil end + # Returns the offset of this time zone from UTC in seconds. def utc_offset if @utc_offset @utc_offset @@ -237,7 +239,7 @@ module ActiveSupport "(GMT#{formatted_offset}) #{name}" end - # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values. Example: + # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values. # # Time.zone = "Hawaii" # => "Hawaii" # Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00 @@ -246,17 +248,16 @@ module ActiveSupport ActiveSupport::TimeWithZone.new(nil, self, time) end - # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch. Example: + # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch. # # Time.zone = "Hawaii" # => "Hawaii" # Time.utc(2000).to_f # => 946684800.0 # Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00 def at(secs) - utc = Time.at(secs).utc rescue DateTime.civil(1970).since(secs) - utc.in_time_zone(self) + Time.at(secs).utc.in_time_zone(self) end - # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. Example: + # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. # # Time.zone = "Hawaii" # => "Hawaii" # Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 @@ -267,9 +268,14 @@ module ActiveSupport # Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 def parse(str, now=now) date_parts = Date._parse(str) - return if date_parts.blank? + return if date_parts.empty? time = Time.parse(str, now) rescue DateTime.parse(str) + if date_parts[:offset].nil? + if date_parts[:hour] && time.hour != date_parts[:hour] + time = DateTime.parse(str) + end + ActiveSupport::TimeWithZone.new(nil, self, time) else time.in_time_zone(self) @@ -277,12 +283,12 @@ module ActiveSupport end # Returns an ActiveSupport::TimeWithZone instance representing the current time - # in the time zone represented by +self+. Example: + # in the time zone represented by +self+. # # Time.zone = 'Hawaii' # => "Hawaii" # Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00 def now - Time.now.utc.in_time_zone(self) + time_now.utc.in_time_zone(self) end # Return the current date in this time zone. @@ -391,5 +397,11 @@ module ActiveSupport end end end + + private + + def time_now + Time.now + end end end diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 677e9910bb..88f9acb588 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -39,8 +39,8 @@ module ActiveSupport "TrueClass" => "boolean", "FalseClass" => "boolean", "Date" => "date", - "DateTime" => "datetime", - "Time" => "datetime", + "DateTime" => "dateTime", + "Time" => "dateTime", "Array" => "array", "Hash" => "hash" } unless defined?(TYPE_NAMES) @@ -48,7 +48,7 @@ module ActiveSupport FORMATTING = { "symbol" => Proc.new { |symbol| symbol.to_s }, "date" => Proc.new { |date| date.to_s(:db) }, - "datetime" => Proc.new { |time| time.xmlschema }, + "dateTime" => Proc.new { |time| time.xmlschema }, "binary" => Proc.new { |binary| ::Base64.encode64(binary) }, "yaml" => Proc.new { |yaml| yaml.to_yaml } } unless defined?(FORMATTING) @@ -83,7 +83,7 @@ module ActiveSupport if name.is_a?(Module) @backend = name else - require "active_support/xml_mini/#{name.to_s.downcase}" + require "active_support/xml_mini/#{name.downcase}" @backend = ActiveSupport.const_get("XmlMini_#{name}") end end @@ -111,6 +111,7 @@ module ActiveSupport type_name ||= TYPE_NAMES[value.class.name] type_name ||= value.class.name if value && !value.respond_to?(:to_str) type_name = type_name.to_s if type_name + type_name = "dateTime" if type_name == "datetime" key = rename_key(key.to_s, options) @@ -145,7 +146,7 @@ module ActiveSupport "#{left}#{middle.tr('_ ', '--')}#{right}" end - # TODO: Add support for other encodings + # TODO: Add support for other encodings def _parse_binary(bin, entity) #:nodoc: case entity['encoding'] when 'base64' diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index dbb6c71907..4551dd2f2d 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -12,7 +12,6 @@ java_import org.xml.sax.InputSource unless defined? InputSource java_import org.xml.sax.Attributes unless defined? Attributes java_import org.w3c.dom.Node unless defined? Node -# = XmlMini JRuby JDOM implementation module ActiveSupport module XmlMini_JDOM #:nodoc: extend self diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index 16570c6aea..26556598fd 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -2,7 +2,6 @@ require 'libxml' require 'active_support/core_ext/object/blank' require 'stringio' -# = XmlMini LibXML implementation module ActiveSupport module XmlMini_LibXML #:nodoc: extend self diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb index 2536b1f33e..acc018fd2d 100644 --- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb +++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb @@ -2,9 +2,8 @@ require 'libxml' require 'active_support/core_ext/object/blank' require 'stringio' -# = XmlMini LibXML implementation using a SAX-based parser module ActiveSupport - module XmlMini_LibXMLSAX + module XmlMini_LibXMLSAX #:nodoc: extend self # Class that will build the hash while the XML document diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb index 04ec9e8ab8..bb0a52bdcf 100644 --- a/activesupport/lib/active_support/xml_mini/nokogiri.rb +++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb @@ -7,7 +7,6 @@ end require 'active_support/core_ext/object/blank' require 'stringio' -# = XmlMini Nokogiri implementation module ActiveSupport module XmlMini_Nokogiri #:nodoc: extend self diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb index 93fd3dfe57..30b94aac47 100644 --- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb +++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb @@ -7,9 +7,8 @@ end require 'active_support/core_ext/object/blank' require 'stringio' -# = XmlMini Nokogiri implementation using a SAX-based parser module ActiveSupport - module XmlMini_NokogiriSAX + module XmlMini_NokogiriSAX #:nodoc: extend self # Class that will build the hash while the XML document diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb index a13ad10118..a2a87337a6 100644 --- a/activesupport/lib/active_support/xml_mini/rexml.rb +++ b/activesupport/lib/active_support/xml_mini/rexml.rb @@ -2,7 +2,6 @@ require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/object/blank' require 'stringio' -# = XmlMini ReXML implementation module ActiveSupport module XmlMini_REXML #:nodoc: extend self |