diff options
Diffstat (limited to 'activesupport')
153 files changed, 2204 insertions, 1101 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 29109ea64d..c1fef1b9d9 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,32 +1,82 @@ ## Rails 4.0.0 (unreleased) ## -* Adds Integer#ordinal to get the ordinal suffix string of an integer. *Tim Gildea* +* Add `:instance_accessor` option for `class_attribute`. *Alexey Vakhov* -* AS::Callbacks: `:per_key` option is no longer supported +* `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White* -* `AS::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. +* `Object#try` can't call private methods. *Vasiliy Ermolovich* -* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva* +* `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez* -* Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva* +* `deep_dup` works more expectedly now and duplicates also values in +Hash+ instances and elements in +Array+ instances. *Alexey Gaziev* -* Deprecates the compatibility method Module#local_constant_names, - use Module#local_constants instead (which returns symbols). *fxn* +* Inflector no longer applies ice -> ouse to words like slice, police, ets *Wes Morgan* -* Deletes the compatibility method Module#method_names, - use Module#methods from now on (which returns symbols). *fxn* +* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations *twinturbo* -* Deletes the compatibility method Module#instance_method_names, - use Module#instance_methods from now on (which returns symbols). *fxn* +* Make Module#delegate stop using `send` - can no longer delegate to private methods. *dasch* -* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger - from Ruby stdlib. +* AS::Callbacks: deprecate `:rescuable` option. *Bogdan Gusiev* + +* Adds Integer#ordinal to get the ordinal suffix string of an integer. *Tim Gildea* + +* AS::Callbacks: `:per_key` option is no longer supported + +* `AS::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. + +* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva* + +* Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva* + +* Deprecates the compatibility method Module#local_constant_names, + use Module#local_constants instead (which returns symbols). *fxn* + +* Deletes the compatibility method Module#method_names, + use Module#methods from now on (which returns symbols). *fxn* + +* Deletes the compatibility method Module#instance_method_names, + use Module#instance_methods from now on (which returns symbols). *fxn* + +* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger + from Ruby stdlib. + +* Unicode database updated to 6.1.0. + +* Adds `encode_big_decimal_as_string` option to force JSON serialization of BigDecimals as numeric instead + of wrapping them in strings for safety. + + +## Rails 3.2.4 (unreleased) ## + +* Added #beginning_of_hour and #end_of_hour to Time and DateTime core + extensions. *Mark J. Titorenko* + + +## Rails 3.2.3 (March 30, 2012) ## + +* No changes. + + +## Rails 3.2.2 (March 1, 2012) ## + +* No changes. + + +## Rails 3.2.1 (January 26, 2012) ## + +* Documentation fixes and improvements. + +* Update time zone offset information. *Ravil Bayramgalin* + +* The deprecated `ActiveSupport::Base64.decode64` calls `::Base64.decode64` + now. *Jonathan Viney* + +* Fixes uninitialized constant `ActiveSupport::TaggedLogging::ERROR`. *kennyj* -* Unicode database updated to 6.1.0. ## Rails 3.2.0 (January 20, 2012) ## -* Add ActiveSupport::Cache::NullStore for use in development and testing. *Brian Durand* +* ActiveSupport::Base64 is deprecated in favor of ::Base64. *Sergey Nartimov* * Module#synchronize is deprecated with no replacement. Please use `monitor` from ruby's standard library. @@ -95,6 +145,37 @@ * ActiveSupport::BufferedLogger#flush is deprecated. Set sync on your filehandle, or tune your filesystem. + +## Rails 3.1.4 (March 1, 2012) ## + +* No changes + + +## Rails 3.1.3 (November 20, 2011) ## + +* No changes + + +## Rails 3.1.2 (November 18, 2011) ## + +* No changes + + +## Rails 3.1.1 (October 7, 2011) ## + +* ruby193: String#prepend is also unsafe *Akira Matsuda* + +* Fix obviously breakage of Time.=== for Time subclasses *jeremyevans* + +* Added fix so that file store does not raise an exception when cache dir does + not exist yet. This can happen if a delete_matched is called before anything + is saved in the cache. *Philippe Huibonhoa* + +* Fixed performance issue where TimeZone lookups would require tzinfo each time *Tim Lucas* + +* ActiveSupport::OrderedHash is now marked as extractable when using Array#extract_options! *Prem Sichanugrist* + + ## Rails 3.1.0 (August 30, 2011) ## * ActiveSupport::Dependencies#load and ActiveSupport::Dependencies#require now @@ -137,12 +218,38 @@ * JSON decoding now uses the multi_json gem which also vendors a json engine called OkJson. The yaml backend has been removed in favor of OkJson as a default engine for 1.8.x, while the built in 1.9.x json implementation will be used by default. *Josh Kalderimis* +## Rails 3.0.12 (March 1, 2012) ## + +* No changes. + + +## Rails 3.0.11 (November 18, 2011) ## + +* No changes. + + +## Rails 3.0.10 (August 16, 2011) ## + +* Delayed backtrace scrubbing in `load_missing_constant` until we actually + raise the exception + + +## Rails 3.0.9 (June 16, 2011) ## + +* No changes. + + +## Rails 3.0.8 (June 7, 2011) ## + +* No changes. + + ## Rails 3.0.7 (April 18, 2011) ## * Hash.from_xml no longer loses attributes on tags containing only whitespace *André Arko* -* Rails 3.0.6 (April 5, 2011) +## Rails 3.0.6 (April 5, 2011) ## * No changes. diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 822c9d98ae..822c9d98ae 100755..100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index d26d71b615..2c874e932e 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -19,6 +19,6 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] s.add_dependency('i18n', '~> 0.6') - s.add_dependency('multi_json', '~> 1.0') + s.add_dependency('multi_json', '~> 1.3') s.add_dependency('tzinfo', '~> 0.3.31') end diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index dbf0c25c5c..8f018dcbc6 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -22,20 +22,6 @@ #++ require 'securerandom' - -module ActiveSupport - class << self - attr_accessor :load_all_hooks - def on_load_all(&hook) load_all_hooks << hook end - def load_all!; load_all_hooks.each { |hook| hook.call } end - end - self.load_all_hooks = [] - - on_load_all do - [Dependencies, Deprecation, Gzip, MessageVerifier, Multibyte] - end -end - require "active_support/dependencies/autoload" require "active_support/version" require "active_support/logger" @@ -44,6 +30,7 @@ module ActiveSupport extend ActiveSupport::Autoload autoload :Concern + autoload :Dependencies autoload :DescendantsTracker autoload :FileUpdateChecker autoload :LogSubscriber 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 d7408eff9f..55791bfa56 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) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 70116c0b36..5be63af342 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -1,7 +1,7 @@ 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 +13,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 +23,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| f.in?(EXCLUDED_DIRS + [".gitkeep"])} FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) end @@ -127,7 +127,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) @@ -145,7 +145,7 @@ module ActiveSupport # Translate a file path into a key. def file_path_key(path) fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last - Rack::Utils.unescape(fname) + URI.decode_www_form_component(fname, Encoding::UTF_8) end # Delete empty directories in the cache. 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 6de49409e5..a9253c186d 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -23,8 +23,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 +52,6 @@ module ActiveSupport # saving... # - save # saved - # module Callbacks extend Concern @@ -73,10 +70,9 @@ 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 @@ -187,7 +183,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 +194,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 +247,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 @@ -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,29 +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: - name = __callback_runner_name(kind) + def __define_callbacks(kind, object) #:nodoc: + chain = object.send("_#{kind}_callbacks") + name = "_run_callbacks_#{chain.object_id.abs}" unless object.respond_to?(name, true) - str = object.send("_#{kind}_callbacks").compile class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{name}() #{str} end + def #{name}() #{chain.compile} end protected :#{name} RUBY_EVAL end - object.send(name, &blk) - end - - def __reset_runner(symbol) - name = __callback_runner_name(symbol) - undef_method(name) if method_defined?(name) - end - - def __callback_runner_name(kind) - "_run__#{self.name.hash.abs}__#{kind}__callbacks" + name end # This is used internally to append, prepend and skip callbacks to the @@ -386,7 +350,6 @@ module ActiveSupport ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target| chain = target.send("_#{name}_callbacks") yield target, chain.dup, type, filters, options - target.__reset_runner(name) end end @@ -425,7 +388,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 +412,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 +430,6 @@ module ActiveSupport end # Remove all set callbacks for the given event. - # def reset_callbacks(symbol) callbacks = send("_#{symbol}_callbacks") @@ -477,12 +437,9 @@ module ActiveSupport chain = target.send("_#{symbol}_callbacks").dup callbacks.each { |c| chain.delete(c) } target.send("_#{symbol}_callbacks=", chain) - target.__reset_runner(symbol) end self.send("_#{symbol}_callbacks=", callbacks.dup.clear) - - __reset_runner(symbol) end # Define sets of events in the object lifecycle that support callbacks. @@ -508,11 +465,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 +507,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/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 6162f7af27..44d90ef732 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -16,7 +16,7 @@ class Array # %w( a b c d ).to(10) # => %w( a b c d ) # %w().to(0) # => %w() def to(position) - self.first position + 1 + first position + 1 end # Equal to <tt>self[1]</tt>. diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index f3d06ecb2f..24aa28b895 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -9,28 +9,32 @@ class Array # * <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 ") 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 " + namespace = 'support.array.' + default_connectors.each_key do |name| + i18n_key = (namespace + name.to_s).to_sym + default_connectors[name] = I18n.translate i18n_key, :locale => options[:locale] + end 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.reverse_merge! default_connectors 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 @@ -45,14 +49,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 +90,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 +143,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..ac1ae53db0 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -82,11 +82,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/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/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index af78226c21..3e36c54eba 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -5,7 +5,15 @@ require 'active_support/core_ext/date/zones' require 'active_support/core_ext/time/zones' class Date - DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 } + DAYS_INTO_WEEK = { + :monday => 0, + :tuesday => 1, + :wednesday => 2, + :thursday => 3, + :friday => 4, + :saturday => 5, + :sunday => 6 + } class << self # Returns a new Date representing the date 1 day ago (i.e. yesterday's date). @@ -31,7 +39,7 @@ class Date # 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 + 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. @@ -99,15 +107,13 @@ class Date # Returns a new Date where one or more of the elements have been changed according to the +options+ parameter. # - # Examples: - # # 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 @@ -166,7 +172,7 @@ class Date 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 + acts_like?(:time) ? result.end_of_day : result end alias :at_end_of_week :end_of_week @@ -180,49 +186,70 @@ class Date # 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 + acts_like?(:time) ? result.change(:hour => 0) : result end + alias :last_week :prev_week + + # Alias of prev_month + alias :last_month :prev_month + + # Alias of prev_year + alias :last_year :prev_year # 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 + 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) + 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) + last_day = ::Time.days_in_month(month, year) + if acts_like?(:time) + change(:day => last_day, :hour => 23, :min => 59, :sec => 59) + else + change(:day => last_day) + end 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 }) + 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/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 + 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 # 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) + if acts_like?(:time) + change(:month => 1, :day => 1, :hour => 0) + else + change(:month => 1, :day => 1) + end 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) + if acts_like?(:time) + change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) + else + change(:month => 12, :day => 31) + end end alias :at_end_of_year :end_of_year 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_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 6f730e4b6f..fd78044b5d 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 @@ -35,14 +35,14 @@ class DateTime # minute is passed, then sec is set to 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 +53,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 +91,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 +125,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..19925198c0 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -30,7 +30,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] @@ -42,7 +42,6 @@ class DateTime alias_method :to_default_s, :to_s unless (instance_methods(false) & [:to_s, 'to_s']).empty? 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" @@ -61,7 +60,11 @@ class DateTime # Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class. # If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time. def to_time - self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) : self + if offset == 0 + ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) + else + self + end end # Returns DateTime with local offset for given year if format is local else offset is zero 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..02d5a7080f 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,11 @@ 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.instance_of?(Integer) && last.instance_of?(Integer)) + super + else + actual_last = exclude_end? ? (last - 1) : last + (actual_last - first + 1) * (actual_last + first) / 2 + 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..9e504851e7 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -2,21 +2,22 @@ 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 @@ -25,8 +26,14 @@ class File 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") { } + temp_file_name = [ + '.permissions_check', + Thread.current.object_id, + Process.pid, + rand(1000000) + ].join('.') + check_name = join(dirname(file_name), temp_file_name) + open(check_name, 'w') { } old_stat = stat(check_name) unlink(check_name) 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..469dc41f2d 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>" # @@ -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 @@ -131,7 +131,7 @@ class Hash # 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..5a61906222 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -9,7 +9,7 @@ class Hash # for instance: # # {:a => 1}.with_indifferent_access.except(:a) # => {} - # {:a => 1}.with_indifferent_access.except("a") # => {} + # {:a => 1}.with_indifferent_access.except('a') # => {} # def except(*keys) dup.except!(*keys) diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index d8748b1138..be4d611ce7 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -1,10 +1,18 @@ class Hash # Return a new hash with all keys converted to strings. + # + # { :name => 'Rob', :years => '28' }.stringify_keys + # #=> { "name" => "Rob", "years" => "28" } def stringify_keys - dup.stringify_keys! + result = {} + keys.each do |key| + result[key.to_s] = self[key] + end + result end - # Destructively convert all keys to strings. + # Destructively convert all keys to strings. Same as + # +stringify_keys+, but modifies +self+. def stringify_keys! keys.each do |key| self[key.to_s] = delete(key) @@ -14,34 +22,39 @@ class Hash # Return a new hash with all keys converted to symbols, as long as # they respond to +to_sym+. + # + # { 'name' => 'Rob', 'years' => '28' }.symbolize_keys + # #=> { :name => "Rob", :years => "28" } def symbolize_keys - dup.symbolize_keys! + result = {} + keys.each do |key| + result[(key.to_sym rescue key)] = self[key] + end + result 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) + self[(key.to_sym rescue key)] = delete(key) end self 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 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/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..f914425827 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 diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index ac2a63d3a1..fbef27c76a 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,17 @@ 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." + to = to.to_s + 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 @@ -125,11 +127,15 @@ class Module 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 +143,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/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 d67711f3b8..e238fef5a2 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -2,7 +2,7 @@ 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: # @@ -90,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..883f5f556c --- /dev/null +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -0,0 +1,44 @@ +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/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index e77a9da0ec..30c835f5cd 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -1,12 +1,16 @@ 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. # # If try is called without a method to call, it will yield any given block with the object. # + # 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. + # # ==== Examples # # Without +try+ @@ -24,12 +28,12 @@ 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) end 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/string.rb b/activesupport/lib/active_support/core_ext/string.rb index ab49af55bf..fb36262528 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -6,7 +6,6 @@ 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/strip' diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 9b5266c58c..5c32a2453d 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -1,18 +1,79 @@ -require "active_support/multibyte" +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 +84,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..2478f42290 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -5,7 +5,6 @@ class String # 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 +21,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 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 => /\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>: # - # "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/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 2194dafe4d..070bfd7af6 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,15 +13,14 @@ 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" + # '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 count == 1 self @@ -32,12 +31,12 @@ class String # 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" + # '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) end @@ -46,10 +45,9 @@ class String # 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 +56,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 +68,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 +88,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 +99,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' # => "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 +124,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 +137,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 +155,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 +166,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 +179,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 +189,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..5226ff0cbe 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -14,8 +14,7 @@ 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 @@ -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 @@ -92,40 +90,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 +150,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 +186,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/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index dd2a21fe2a..92b8417150 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,10 +1,17 @@ require 'active_support/duration' -require 'active_support/core_ext/time/zones' require 'active_support/core_ext/time/conversions' class Time COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 } + 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 +22,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 +34,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 @@ -72,13 +87,13 @@ class Time 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)) + 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(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) ) end @@ -89,18 +104,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 @@ -145,6 +168,7 @@ class Time def prev_year years_ago(1) end + alias_method :last_year, :prev_year # Short-hand for years_since(1) def next_year @@ -155,6 +179,7 @@ class Time def prev_month months_ago(1) end + alias_method :last_month, :prev_month # Short-hand for months_since(1) def next_month @@ -166,6 +191,7 @@ class Time 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 @@ -197,12 +223,19 @@ class Time # 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) + ago(1.week). + beginning_of_week. + since(DAYS_INTO_WEEK[day].day). + change(:hour => 0) end + alias_method :last_week, :prev_week # 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) + 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) @@ -216,7 +249,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) + change( + :hour => 23, + :min => 59, + :sec => 59, + :usec => Rational(999999999, 1000) + ) + end + + # 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 + + # 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 new Time representing the start of the month (1st of the month, 0:00) @@ -230,19 +283,27 @@ class Time 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) + change( + :day => last_day, + :hour => 23, + :min => 59, + :sec => 59, + :usec => Rational(999999999, 1000) + ) 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 }) + 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 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 + 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 @@ -254,7 +315,14 @@ class Time # 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( + :month => 12, + :day => 31, + :hour => 23, + :min => 59, + :sec => 59, + :usec => Rational(999999999, 1000) + ) end alias :at_end_of_year :end_of_year @@ -327,7 +395,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 +413,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..e48866abe3 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/time/calculations' require 'active_support/time_with_zone' class Time @@ -50,13 +51,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 +88,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..66f3af7002 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 @@ -365,7 +363,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,10 +371,6 @@ 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) @@ -434,7 +428,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 @@ -458,7 +452,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,7 +471,7 @@ 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 @@ -486,12 +480,12 @@ module ActiveSupport #:nodoc: 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) + raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless from_mod.const_defined?(const_name, false) return from_mod.const_get(const_name) 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 @@ -505,7 +499,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 +537,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 +591,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 +622,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 +654,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..a1626ebeba 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -9,13 +9,16 @@ module ActiveSupport @@eager_autoload = false def autoload(const_name, path = @@at_path) - full = [self.name, @@under_path, const_name.to_s, path].compact.join("::") - location = path || Inflector.underscore(full) + 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 + @@autoloads[const_name] = path end - super const_name, location + + super const_name, path end def autoload_under(path) diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 45b9dda5ca..176edefa42 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' 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..c5de5e6a95 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -1,11 +1,10 @@ -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) + def self.deprecate_methods(target_module, *method_names) options = method_names.extract_options! method_names += options.keys diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 00c67a470d..2cdc991120 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 diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 2ede084e95..8860636168 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; # @@ -41,13 +39,13 @@ module ActiveSupport # # == 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. + # This particular implementation checks for added, updated, and 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 supported. # - # Notice that other objects that implements FileUpdateChecker API may + # Notice that other objects that implement the 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. @@ -55,27 +53,41 @@ module ActiveSupport @files = files @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 +103,33 @@ 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 || paths.map { |path| File.mtime(path) }.max || Time.at(0) + 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/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 674e4acfd6..91459f3e5b 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -1,12 +1,11 @@ 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 + # 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. class HashWithIndifferentAccess < Hash - + # Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class. def extractable_options? true @@ -43,6 +42,10 @@ 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) 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/inflections.rb b/activesupport/lib/active_support/inflections.rb index b3eb1333ca..c04c2ed15b 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -16,8 +16,8 @@ module ActiveSupport inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') inflect.plural(/(x|ch|ss|sh)$/i, '\1es') inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices') - inflect.plural(/(m|l)ouse$/i, '\1ice') - inflect.plural(/(m|l)ice$/i, '\1ice') + inflect.plural(/^(m|l)ouse$/i, '\1ice') + inflect.plural(/^(m|l)ice$/i, '\1ice') inflect.plural(/^(ox)$/i, '\1en') inflect.plural(/^(oxen)$/i, '\1') inflect.plural(/(quiz)$/i, '\1zes') @@ -26,7 +26,7 @@ module ActiveSupport 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)(sis|ses)$/i, '\1\2sis') + 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') @@ -36,7 +36,7 @@ module ActiveSupport inflect.singular(/(s)eries$/i, '\1eries') inflect.singular(/(m)ovies$/i, '\1ovie') inflect.singular(/(x|ch|ss|sh)es$/i, '\1') - inflect.singular(/(m|l)ice$/i, '\1ouse') + inflect.singular(/^(m|l)ice$/i, '\1ouse') inflect.singular(/(bus)(es)?$/i, '\1') inflect.singular(/(o)es$/i, '\1') inflect.singular(/(shoe)s$/i, '\1') @@ -58,6 +58,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 8cd96fe2d1..600e353812 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/array/prepend_and_append' module ActiveSupport module Inflector # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional - # inflection rules. Examples: + # inflection rules. # # ActiveSupport::Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1\2en' @@ -28,12 +28,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' @@ -63,7 +69,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' @@ -98,7 +103,6 @@ module ActiveSupport # 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) @@ -120,7 +124,6 @@ module ActiveSupport # Add uncountable words that shouldn't be attempted inflected. # - # Examples: # uncountable "money" # uncountable "money", "information" # uncountable %w( money information rice ) @@ -132,7 +135,6 @@ 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) @@ -143,7 +145,6 @@ module ActiveSupport # 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) @@ -159,7 +160,6 @@ module ActiveSupport # Yields a singleton instance of Inflector::Inflections so you can specify additional # inflector rules. # - # Example: # ActiveSupport::Inflector.inflections do |inflect| # inflect.uncountable "rails" # end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index c630bd21b1..2acc6ddee5 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + require 'active_support/inflector/inflections' module ActiveSupport @@ -14,7 +16,6 @@ module ActiveSupport # Returns the plural form of the word in the string. # - # Examples: # "post".pluralize # => "posts" # "octopus".pluralize # => "octopi" # "sheep".pluralize # => "sheep" @@ -26,7 +27,6 @@ module ActiveSupport # The reverse of +pluralize+, returns the singular form of a word in a string. # - # Examples: # "posts".singularize # => "post" # "octopi".singularize # => "octopus" # "sheep".singularize # => "sheep" @@ -41,7 +41,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 +64,6 @@ module ActiveSupport # # Changes '::' to '/' to convert namespaces to paths. # - # Examples: # "ActiveModel".underscore # => "active_model" # "ActiveModel::Errors".underscore # => "active_model/errors" # @@ -75,7 +73,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,7 +85,6 @@ 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) @@ -104,21 +101,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 +125,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,8 +137,7 @@ 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.tr('_', '-') end @@ -181,7 +174,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 +198,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 +260,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 +282,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,7 +307,6 @@ 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) 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..986a764479 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -9,7 +9,7 @@ module ActiveSupport module JSON class << self 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 +18,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= diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index b2adfea273..a6e4e7ced2 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 @@ -104,6 +105,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 +136,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 @@ -182,6 +187,12 @@ class Numeric def encode_json(encoder) to_s end #:nodoc: end +class Float + # Encoding Infinity or NaN to JSON should return "null". The default returns + # "Infinity" or "NaN" what breaks parsing the JSON. E.g. JSON.parse('[NaN]'). + def as_json(options = nil) finite? ? self : NilClass::AS_JSON end #:nodoc: +end + class BigDecimal # A BigDecimal would be naturally represented as a JSON number. Most libraries, # however, parse non-integer JSON numbers directly as floats. Clients using @@ -191,7 +202,15 @@ 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 + NilClass::AS_JSON + end + end end class Regexp 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/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 58938cdc3d..d2a6e1bd82 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: @@ -75,7 +75,8 @@ module ActiveSupport @@flushable_loggers ||= begin loggers = log_subscribers.map(&:logger) loggers.uniq! - loggers.select { |l| l.respond_to?(:flush) } + loggers.select! { |l| l.respond_to?(:flush) } + loggers end end @@ -101,8 +102,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 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..4fe925f7f4 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,7 +74,6 @@ 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 } @@ -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,7 +130,6 @@ 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 @@ -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..678f551193 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*') diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 13f675c654..6735c561d3 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 # @@ -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,10 +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 diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index a9aa5464e9..17c99089c1 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -9,18 +9,25 @@ module ActiveSupport end def subscribe(pattern = nil, block = Proc.new) - subscriber = Subscriber.new(pattern, block).tap do |s| - @subscribers << s - end + subscriber = Subscribers.new pattern, block + @subscribers << subscriber @listeners_for.clear subscriber end def unsubscribe(subscriber) - @subscribers.reject! {|s| s.matches?(subscriber)} + @subscribers.reject! { |s| s.matches?(subscriber) } @listeners_for.clear 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) listeners_for(name).each { |s| s.publish(name, *args) } end @@ -37,23 +44,89 @@ 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?(:call) + subscriber = Timed.new pattern, listener + else + subscriber = Evented.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 = Hash.new { |h,id| + h[id] = Hash.new { |ids,name| ids[name] = [] } + } + super + end + + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + @timestack[id][name].push Time.now + end + + def finish(name, id, payload) + started = @timestack[id][name].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..58e292c658 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -1,7 +1,6 @@ -require 'active_support/core_ext/module/delegation' - module ActiveSupport module Notifications + # Instrumentors are stored in a thread local. class Instrumenter attr_reader :id @@ -14,15 +13,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 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 538e41e0eb..60e6cd55ad 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -36,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/railtie.rb b/activesupport/lib/active_support/railtie.rb index f696716cc8..30ac881090 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -8,30 +8,6 @@ module 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 +18,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 13e96b3596..0000000000 --- a/activesupport/lib/active_support/ruby/shim.rb +++ /dev/null @@ -1,15 +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/time/conversions' 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/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 244ee1a224..2bea0f991a 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -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') @@ -196,6 +196,7 @@ module ActiveSupport class Base include ActionView::Helpers::NumberHelper + include ActionView::Helpers::OutputSafetyHelper attr_reader :total @@ -207,7 +208,7 @@ module ActiveSupport @name ||= self.class.name.demodulize.underscore end - def benchmark + def benchmark with_gc_stats do before = measure yield 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..1104fc0a03 100644 --- a/activesupport/lib/active_support/testing/performance/ruby.rb +++ b/activesupport/lib/active_support/testing/performance/ruby.rb @@ -36,7 +36,7 @@ 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 def record 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..451520ac5c 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -8,7 +8,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 +19,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 +33,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 +120,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 +309,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 +335,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]) } 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..28bc06f103 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,7 +248,7 @@ 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 @@ -256,7 +258,7 @@ module ActiveSupport 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 +269,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 +284,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 +398,11 @@ module ActiveSupport end end end + + private + + def time_now + Time.now + end end end 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 diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 40e25ce0cd..57ed4a6b60 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -7,9 +7,6 @@ ensure $VERBOSE = old end -lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") -$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) - require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/string/encoding' diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index d6aefa6629..a75db47be8 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -69,6 +69,10 @@ class CacheKeyTest < ActiveSupport::TestCase def test_expand_cache_key_of_true assert_equal 'true', ActiveSupport::Cache.expand_cache_key(true) end + + def test_expand_cache_key_of_array_like_object + assert_equal 'foo/bar/baz', ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum) + end end class CacheStoreSettingTest < ActiveSupport::TestCase @@ -567,6 +571,13 @@ class FileStoreTest < ActiveSupport::TestCase include CacheDeleteMatchedBehavior include CacheIncrementDecrementBehavior + def test_clear + filepath = File.join(cache_dir, ".gitkeep") + FileUtils.touch(filepath) + @cache.clear + assert File.exist?(filepath) + end + def test_key_transformation key = @cache.send(:key_file_path, "views/index?id=1") assert_equal "views/index?id=1", @cache.send(:file_path_key, key) @@ -578,6 +589,16 @@ class FileStoreTest < ActiveSupport::TestCase assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key) end + # Test that generated cache keys are short enough to have Tempfile stuff added to them and + # remain valid + def test_filename_max_size + key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}" + path = @cache.send(:key_file_path, key) + Dir::Tmpname.create(path) do |tmpname, n, opts| + assert File.basename(tmpname+'.lock').length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}" + end + end + # Because file systems have a maximum filename size, filenames > max size should be split in to directories # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B def test_key_transformation_max_filename_size @@ -672,6 +693,13 @@ class MemoryStoreTest < ActiveSupport::TestCase assert @cache.exist?(2) assert !@cache.exist?(1) end + + def test_write_with_unless_exist + assert_equal true, @cache.write(1, "aaaaaaaaaa") + assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true) + @cache.write(1, nil) + assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true) + end end uses_memcached 'memcached backed store' do diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb index e5ac9511df..6be8ea8b84 100644 --- a/activesupport/test/callback_inheritance_test.rb +++ b/activesupport/test/callback_inheritance_test.rb @@ -29,7 +29,7 @@ class GrandParent end def dispatch - run_callbacks(:dispatch, action_name) do + run_callbacks :dispatch do @log << action_name end self diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 3c995e0793..b7c3b130c3 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module CallbacksTest class Phone include ActiveSupport::Callbacks - define_callbacks :save, :rescuable => true + define_callbacks :save set_callback :save, :before, :before_save1 set_callback :save, :after, :after_save1 @@ -112,7 +112,7 @@ module CallbacksTest end def dispatch - run_callbacks :dispatch, action_name do + run_callbacks :dispatch do @logger << "Done" end self @@ -153,7 +153,7 @@ module CallbacksTest end def save - run_callbacks :save, :action + run_callbacks :save end end @@ -338,7 +338,7 @@ module CallbacksTest end def save - run_callbacks :save, "hyphen-ated" do + run_callbacks :save do @stuff end end @@ -439,13 +439,6 @@ module CallbacksTest end class CallbacksTest < ActiveSupport::TestCase - def test_save_phone - phone = Phone.new - assert_raise RuntimeError do - phone.save - end - assert_equal [:before, :after], phone.history - end def test_save_person person = Person.new diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index 135f894056..ec05213409 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -1,16 +1,41 @@ module Ace module Base class Case + class Dice + end + end + class Fase < Case + end + end + class Gas + include Base + end +end + +class Object + module AddtlGlobalConstants + class Case + class Dice + end end end + include AddtlGlobalConstants end module ConstantizeTestCases def run_constantize_tests_on assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") } assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") } + assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") } + assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") } + assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") } + assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") } + assert_nothing_raised { assert_equal Object, yield("") } + assert_nothing_raised { assert_equal Object, yield("::") } assert_raise(NameError) { yield("UnknownClass") } assert_raise(NameError) { yield("UnknownClass::Ace") } assert_raise(NameError) { yield("UnknownClass::Ace::Base") } @@ -18,13 +43,23 @@ module ConstantizeTestCases assert_raise(NameError) { yield("InvalidClass\n") } assert_raise(NameError) { yield("Ace::ConstantizeTestCases") } assert_raise(NameError) { yield("Ace::Base::ConstantizeTestCases") } + assert_raise(NameError) { yield("Ace::Gas::Base") } + assert_raise(NameError) { yield("Ace::Gas::ConstantizeTestCases") } end def run_safe_constantize_tests_on assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") } assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") } + assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") } + assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") } + assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") } + assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") } + assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") } assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") } + assert_nothing_raised { assert_equal Object, yield("") } + assert_nothing_raised { assert_equal Object, yield("::") } assert_nothing_raised { assert_equal nil, yield("UnknownClass") } assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace") } assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace::Base") } @@ -33,6 +68,8 @@ module ConstantizeTestCases assert_nothing_raised { assert_equal nil, yield("blargle") } assert_nothing_raised { assert_equal nil, yield("Ace::ConstantizeTestCases") } assert_nothing_raised { assert_equal nil, yield("Ace::Base::ConstantizeTestCases") } + assert_nothing_raised { assert_equal nil, yield("Ace::Gas::Base") } + assert_nothing_raised { assert_equal nil, yield("Ace::Gas::ConstantizeTestCases") } assert_nothing_raised { assert_equal nil, yield("#<Class:0x7b8b718b>::Nested_1") } end end diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb index 3822e7af66..8d827f054e 100644 --- a/activesupport/test/core_ext/class/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb @@ -42,4 +42,18 @@ class ClassAttributeAccessorTest < ActiveSupport::TestCase assert !@object.respond_to?(:camp) assert !@object.respond_to?(:camp=) end + + def test_should_raise_name_error_if_attribute_name_is_invalid + assert_raises NameError do + Class.new do + cattr_reader "invalid attribute name" + end + end + + assert_raises NameError do + Class.new do + cattr_writer "invalid attribute name" + end + end + end end diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index e290a6e012..1c3ba8a7a0 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -66,6 +66,13 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_raise(NoMethodError) { object.setting? } end + test 'disabling both instance writer and reader' do + object = Class.new { class_attribute :setting, :instance_accessor => false }.new + assert_raise(NoMethodError) { object.setting } + assert_raise(NoMethodError) { object.setting? } + assert_raise(NoMethodError) { object.setting = 'boom' } + end + test 'works well with singleton classes' do object = @klass.new object.singleton_class.setting = 'foo' diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 6e91fdedce..e14a137f84 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -175,6 +175,18 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(1582,10,4), Date.new(1583,10,14).prev_year end + def test_last_year + assert_equal Date.new(2004,6,5), Date.new(2005,6,5).last_year + end + + def test_last_year_in_leap_years + assert_equal Date.new(1999,2,28), Date.new(2000,2,29).last_year + end + + def test_last_year_in_calendar_reform + assert_equal Date.new(1582,10,4), Date.new(1583,10,14).last_year + end + def test_next_year assert_equal Date.new(2006,6,5), Date.new(2005,6,5).next_year end @@ -245,6 +257,14 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(2010,2,27), Date.new(2010,3,4).prev_week(:saturday) end + def test_last_week + assert_equal Date.new(2005,5,9), Date.new(2005,5,17).last_week + assert_equal Date.new(2006,12,25), Date.new(2007,1,7).last_week + assert_equal Date.new(2010,2,12), Date.new(2010,2,19).last_week(:friday) + assert_equal Date.new(2010,2,13), Date.new(2010,2,19).last_week(:saturday) + assert_equal Date.new(2010,2,27), Date.new(2010,3,4).last_week(:saturday) + end + def test_next_week assert_equal Date.new(2005,2,28), Date.new(2005,2,22).next_week assert_equal Date.new(2005,3,4), Date.new(2005,2,22).next_week(:friday) @@ -265,6 +285,10 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).prev_month end + def test_last_month_on_31st + assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month + end + def test_yesterday_constructor assert_equal Date.current - 1, Date.yesterday end @@ -350,14 +374,14 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_day - assert_equal Time.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day + assert_equal Time.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day end def test_end_of_day_when_zone_is_set zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'UTC' do with_tz_default zone do - assert_equal zone.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day + assert_equal zone.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day assert_equal zone, Date.new(2005,2,21).end_of_day.time_zone end end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 57b5b95a66..3da0825489 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -91,6 +91,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2005,2,4,23,59,59), DateTime.civil(2005,2,4,10,10,10).end_of_day end + def test_beginning_of_hour + assert_equal DateTime.civil(2005,2,4,19,0,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_hour + end + + def test_end_of_hour + assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour + end + def test_beginning_of_month assert_equal DateTime.civil(2005,2,1,0,0,0), DateTime.civil(2005,2,22,10,10,10).beginning_of_month end @@ -159,6 +167,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).prev_year end + def test_last_year + assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).last_year + end + def test_next_year assert_equal DateTime.civil(2006,6,5,10), DateTime.civil(2005,6,5,10,0,0).next_year end @@ -232,6 +244,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).prev_week(:wednesday) end + def test_last_week + assert_equal DateTime.civil(2005,2,21), DateTime.civil(2005,3,1,15,15,10).last_week + assert_equal DateTime.civil(2005,2,22), DateTime.civil(2005,3,1,15,15,10).last_week(:tuesday) + assert_equal DateTime.civil(2005,2,25), DateTime.civil(2005,3,1,15,15,10).last_week(:friday) + assert_equal DateTime.civil(2006,10,30), DateTime.civil(2006,11,6,0,0,0).last_week + assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).last_week(:wednesday) + end + def test_next_week assert_equal DateTime.civil(2005,2,28), DateTime.civil(2005,2,22,15,15,10).next_week assert_equal DateTime.civil(2005,3,4), DateTime.civil(2005,2,22,15,15,10).next_week(:friday) @@ -247,6 +267,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).prev_month end + def test_last_month_on_31st + assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month + end + def test_xmlschema assert_match(/^1880-02-28T15:15:10\+00:?00$/, DateTime.civil(1880, 2, 28, 15, 15, 10).xmlschema) assert_match(/^1980-02-28T15:15:10\+00:?00$/, DateTime.civil(1980, 2, 28, 15, 15, 10).xmlschema) diff --git a/activesupport/test/core_ext/deep_dup_test.rb b/activesupport/test/core_ext/deep_dup_test.rb new file mode 100644 index 0000000000..91d558dbb5 --- /dev/null +++ b/activesupport/test/core_ext/deep_dup_test.rb @@ -0,0 +1,53 @@ +require 'abstract_unit' +require 'active_support/core_ext/object' + +class DeepDupTest < ActiveSupport::TestCase + + def test_array_deep_dup + array = [1, [2, 3]] + dup = array.deep_dup + dup[1][2] = 4 + assert_equal nil, array[1][2] + assert_equal 4, dup[1][2] + end + + def test_hash_deep_dup + hash = { :a => { :b => 'b' } } + dup = hash.deep_dup + dup[:a][:c] = 'c' + assert_equal nil, hash[:a][:c] + assert_equal 'c', dup[:a][:c] + end + + def test_array_deep_dup_with_hash_inside + array = [1, { :a => 2, :b => 3 } ] + dup = array.deep_dup + dup[1][:c] = 4 + assert_equal nil, array[1][:c] + assert_equal 4, dup[1][:c] + end + + def test_hash_deep_dup_with_array_inside + hash = { :a => [1, 2] } + dup = hash.deep_dup + dup[:a][2] = 'c' + assert_equal nil, hash[:a][2] + assert_equal 'c', dup[:a][2] + end + + def test_deep_dup_initialize + zero_hash = Hash.new 0 + hash = { :a => zero_hash } + dup = hash.deep_dup + assert_equal 0, dup[:a][44] + end + + def test_object_deep_dup + object = Object.new + dup = object.deep_dup + dup.instance_variable_set(:@a, 1) + assert !object.instance_variable_defined?(:@a) + assert dup.instance_variable_defined?(:@a) + end + +end diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/duplicable_test.rb index 3e54266051..e0566e012c 100644 --- a/activesupport/test/core_ext/duplicable_test.rb +++ b/activesupport/test/core_ext/duplicable_test.rb @@ -4,9 +4,17 @@ require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/numeric/time' class DuplicableTest < ActiveSupport::TestCase - RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, BigDecimal.new('4.56'), 5.seconds] - YES = ['1', Object.new, /foo/, [], {}, Time.now] - NO = [Class.new, Module.new] + RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds] + YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] + NO = [] + + begin + bd = BigDecimal.new('4.56') + YES << bd.dup + rescue TypeError + RAISE_DUP << bd + end + def test_duplicable (RAISE_DUP + NO).each do |v| @@ -14,7 +22,7 @@ class DuplicableTest < ActiveSupport::TestCase end YES.each do |v| - assert v.duplicable? + assert v.duplicable?, "#{v.class} should be duplicable" end (YES + NO).each do |v| diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index a0f261ebdb..8239054117 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -363,21 +363,6 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, hash_1 end - def test_deep_dup - hash = { :a => { :b => 'b' } } - dup = hash.deep_dup - dup[:a][:c] = 'c' - assert_equal nil, hash[:a][:c] - assert_equal 'c', dup[:a][:c] - end - - def test_deep_dup_initialize - zero_hash = Hash.new 0 - hash = { :a => zero_hash } - dup = hash.deep_dup - assert_equal 0, dup[:a][44] - end - def test_store_on_indifferent_access hash = HashWithIndifferentAccess.new hash.store(:test1, 1) @@ -388,6 +373,15 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, hash end + def test_constructor_on_indifferent_access + hash = HashWithIndifferentAccess[:foo, 1] + assert_equal 1, hash[:foo] + assert_equal 1, hash['foo'] + hash[:foo] = 3 + assert_equal 3, hash[:foo] + assert_equal 3, hash['foo'] + end + def test_reverse_merge defaults = { :a => "x", :b => "y", :c => 10 }.freeze options = { :a => 1, :b => 2 } @@ -486,19 +480,32 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 'bender', slice['login'] end + def test_extract + original = {:a => 1, :b => 2, :c => 3, :d => 4} + expected = {:a => 1, :b => 2} + + assert_equal expected, original.extract!(:a, :b) + end + def test_except original = { :a => 'x', :b => 'y', :c => 10 } expected = { :a => 'x', :b => 'y' } - # Should return a new hash with only the given keys. + # Should return a new hash without the given keys. assert_equal expected, original.except(:c) assert_not_equal expected, original - # Should replace the hash with only the given keys. + # Should replace the hash without the given keys. assert_equal expected, original.except!(:c) assert_equal expected, original end + def test_except_with_more_than_one_argument + original = { :a => 'x', :b => 'y', :c => 10 } + expected = { :a => 'x' } + assert_equal expected, original.except(:b, :c) + end + def test_except_with_original_frozen original = { :a => 'x', :b => 'y' } original.freeze @@ -550,7 +557,7 @@ class HashExtToParamTests < ActiveSupport::TestCase end def test_to_param_orders_by_key_in_ascending_order - assert_equal 'a=2&b=1&c=0', ActiveSupport::OrderedHash[*%w(b 1 c 0 a 2)].to_param + assert_equal 'a=2&b=1&c=0', Hash[*%w(b 1 c 0 a 2)].to_param end end diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb index 6a2ad2f241..a577f90bdd 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb @@ -44,4 +44,18 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase assert !@object.respond_to?(:camp) assert !@object.respond_to?(:camp=) end + + def test_should_raise_name_error_if_attribute_name_is_invalid + assert_raises NameError do + Class.new do + mattr_reader "invalid attribute name" + end + end + + assert_raises NameError do + Class.new do + mattr_writer "invalid attribute name" + end + end + end end diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 09ca4e7296..6e1b3ca010 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -60,6 +60,14 @@ Tester = Struct.new(:client) do delegate :name, :to => :client, :prefix => false end +class ParameterSet + delegate :[], :[]=, :to => :@params + + def initialize + @params = {:foo => "bar"} + end +end + class Name delegate :upcase, :to => :@full_name @@ -83,6 +91,17 @@ class ModuleTest < ActiveSupport::TestCase assert_equal "Fred", @david.place.name end + def test_delegation_to_index_get_method + @params = ParameterSet.new + assert_equal "bar", @params[:foo] + end + + def test_delegation_to_index_set_method + @params = ParameterSet.new + @params[:foo] = "baz" + assert_equal "baz", @params[:foo] + end + def test_delegation_down_hierarchy assert_equal "CHICAGO", @david.upcase end diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index 6a26e1fa4f..c34647c1df 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -28,12 +28,12 @@ class ToQueryTest < ActiveSupport::TestCase def test_nested_conversion assert_query_equal 'person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas', - :person => ActiveSupport::OrderedHash[:login, 'seckar', :name, 'Nicholas'] + :person => Hash[:login, 'seckar', :name, 'Nicholas'] end def test_multiple_nested assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10', - ActiveSupport::OrderedHash[:account, {:person => {:id => 20}}, :person, {:id => 10}] + Hash[:account, {:person => {:id => 20}}, :person, {:id => 10}] end def test_array_values diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index b027fccab3..98ab82609e 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -101,7 +101,7 @@ class ObjectTryTest < ActiveSupport::TestCase assert !@string.respond_to?(method) assert_raise(NoMethodError) { @string.try(method) } end - + def test_nonexisting_method_with_arguments method = :undefined_method assert !@string.respond_to?(method) @@ -138,4 +138,16 @@ class ObjectTryTest < ActiveSupport::TestCase nil.try { ran = true } assert_equal false, ran end + + def test_try_with_private_method + klass = Class.new do + private + + def private_method + 'private method' + end + end + + assert_raise(NoMethodError) { klass.new.try(:private_method) } + end end diff --git a/activesupport/test/core_ext/proc_test.rb b/activesupport/test/core_ext/proc_test.rb index 690bfd3bf8..c4d5592196 100644 --- a/activesupport/test/core_ext/proc_test.rb +++ b/activesupport/test/core_ext/proc_test.rb @@ -3,10 +3,12 @@ require 'active_support/core_ext/proc' class ProcTests < ActiveSupport::TestCase def test_bind_returns_method_with_changed_self - block = Proc.new { self } - assert_equal self, block.call - bound_block = block.bind("hello") - assert_not_equal block, bound_block - assert_equal "hello", bound_block.call + assert_deprecated do + block = Proc.new { self } + assert_equal self, block.call + bound_block = block.bind("hello") + assert_not_equal block, bound_block + assert_equal "hello", bound_block.call + end end end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index cf1ec448c2..f0cdc0bfd4 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -41,6 +41,18 @@ class RangeTest < ActiveSupport::TestCase assert((1..10).include?(1...10)) end + def test_should_compare_identical_inclusive + assert((1..10) === (1..10)) + end + + def test_should_compare_identical_exclusive + assert((1...10) === (1...10)) + end + + def test_should_compare_other_with_exlusive_end + assert((1..10) === (1...10)) + end + def test_exclusive_end_should_not_include_identical_with_inclusive_end assert !(1...10).include?(1..10) end @@ -57,16 +69,6 @@ class RangeTest < ActiveSupport::TestCase assert((1.0...10.0).include?(1.0...10.0)) end - def test_blockless_step - assert_equal [1,3,5,7,9], (1..10).step(2) - end - - def test_original_step - array = [] - (1..10).step(2) {|i| array << i } - assert_equal [1,3,5,7,9], array - end - def test_cover_is_not_override range = (1..3) assert range.method(:include?) != range.method(:cover?) diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 6c2828b74e..8437ef1347 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -279,6 +279,12 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => ' ') end + def test_truncate_with_omission_and_regexp_seperator + assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => /\s/) + assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => /\s/) + assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => /\s/) + end + def test_truncate_multibyte assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'), "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10) @@ -433,6 +439,37 @@ class OutputSafetyTest < ActiveSupport::TestCase assert @other_string.html_safe? end + test "Concatting safe onto unsafe with % yields unsafe" do + @other_string = "other%s" + string = @string.html_safe + + @other_string = @other_string % string + assert !@other_string.html_safe? + end + + test "Concatting unsafe onto safe with % yields escaped safe" do + @other_string = "other%s".html_safe + string = @other_string % "<foo>" + + assert_equal "other<foo>", string + assert string.html_safe? + end + + test "Concatting safe onto safe with % yields safe" do + @other_string = "other%s".html_safe + string = @string.html_safe + + @other_string = @other_string % string + assert @other_string.html_safe? + end + + test "Concatting with % doesn't modify a string" do + @other_string = ["<p>", "<b>", "<h1>"] + _ = "%s %s %s".html_safe % @other_string + + assert_equal ["<p>", "<b>", "<h1>"], @other_string + end + test "Concatting a fixnum to safe always yields safe" do string = @string.html_safe string = string.concat(13) diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index eda8066579..15c04bedf7 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -93,6 +93,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end end + def test_beginning_of_hour + assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour + end + def test_beginning_of_month assert_equal Time.local(2005,2,1,0,0,0), Time.local(2005,2,22,10,10,10).beginning_of_month end @@ -105,45 +109,49 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_day - assert_equal Time.local(2007,8,12,23,59,59,999999.999), Time.local(2007,8,12,10,10,10).end_of_day + assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day with_env_tz 'US/Eastern' do - assert_equal Time.local(2007,4,2,23,59,59,999999.999), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' - assert_equal Time.local(2007,10,29,23,59,59,999999.999), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' + assert_equal Time.local(2007,4,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' + assert_equal Time.local(2007,10,29,23,59,59,Rational(999999999, 1000)), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' end with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,23,59,59,999999.999), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,23,59,59,999999.999), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' + assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' + assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' end end def test_end_of_week - assert_equal Time.local(2008,1,6,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_week - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,27,0,0,0).end_of_week #monday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,28,0,0,0).end_of_week #tuesday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,29,0,0,0).end_of_week #wednesday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,30,0,0,0).end_of_week #thursday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,31,0,0,0).end_of_week #friday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,01,0,0,0).end_of_week #saturday - assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday + assert_equal Time.local(2008,1,6,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_week + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,27,0,0,0).end_of_week #monday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,28,0,0,0).end_of_week #tuesday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,29,0,0,0).end_of_week #wednesday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,30,0,0,0).end_of_week #thursday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,31,0,0,0).end_of_week #friday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,01,0,0,0).end_of_week #saturday + assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,02,0,0,0).end_of_week #sunday + end + + def test_end_of_hour + assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour end def test_end_of_month - assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month - assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month - assert_equal Time.local(2005,4,30,23,59,59,999999.999), Time.local(2005,4,20,10,10,10).end_of_month + assert_equal Time.local(2005,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2005,3,20,10,10,10).end_of_month + assert_equal Time.local(2005,2,28,23,59,59,Rational(999999999, 1000)), Time.local(2005,2,20,10,10,10).end_of_month + assert_equal Time.local(2005,4,30,23,59,59,Rational(999999999, 1000)), Time.local(2005,4,20,10,10,10).end_of_month end def test_end_of_quarter - assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,2,15,10,10,10).end_of_quarter - assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,3,31,0,0,0).end_of_quarter - assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,21,10,10,10).end_of_quarter - assert_equal Time.local(2007,6,30,23,59,59,999999.999), Time.local(2007,4,1,0,0,0).end_of_quarter - assert_equal Time.local(2008,6,30,23,59,59,999999.999), Time.local(2008,5,31,0,0,0).end_of_quarter + assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,15,10,10,10).end_of_quarter + assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,3,31,0,0,0).end_of_quarter + assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,21,10,10,10).end_of_quarter + assert_equal Time.local(2007,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,1,0,0,0).end_of_quarter + assert_equal Time.local(2008,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2008,5,31,0,0,0).end_of_quarter end def test_end_of_year - assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,2,22,10,10,10).end_of_year - assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_year + assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,22,10,10,10).end_of_year + assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_year end def test_beginning_of_year @@ -198,6 +206,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).prev_year end + def test_last_year + assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year + end + def test_next_year assert_equal Time.local(2006,6,5,10), Time.local(2005,6,5,10,0,0).next_year end @@ -506,6 +518,16 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end end + def test_last_week + with_env_tz 'US/Eastern' do + assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week + assert_equal Time.local(2005,2,22), Time.local(2005,3,1,15,15,10).last_week(:tuesday) + assert_equal Time.local(2005,2,25), Time.local(2005,3,1,15,15,10).last_week(:friday) + assert_equal Time.local(2006,10,30), Time.local(2006,11,6,0,0,0).last_week + assert_equal Time.local(2006,11,15), Time.local(2006,11,23,0,0,0).last_week(:wednesday) + end + end + def test_next_week with_env_tz 'US/Eastern' do assert_equal Time.local(2005,2,28), Time.local(2005,2,22,15,15,10).next_week @@ -535,12 +557,14 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_to_s - time = Time.utc(2005, 2, 21, 17, 44, 30) + time = Time.utc(2005, 2, 21, 17, 44, 30.12345678901) assert_equal time.to_default_s, time.to_s assert_equal time.to_default_s, time.to_s(:doesnt_exist) assert_equal "2005-02-21 17:44:30", time.to_s(:db) assert_equal "21 Feb 17:44", time.to_s(:short) assert_equal "17:44", time.to_s(:time) + assert_equal "20050221174430", time.to_s(:number) + assert_equal "20050221174430123456789", time.to_s(:nsec) assert_equal "February 21, 2005 17:44", time.to_s(:long) assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal) with_env_tz "UTC" do @@ -662,6 +686,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).prev_month end + def test_last_month_on_31st + assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month + end + def test_xmlschema_is_available assert_nothing_raised { Time.now.xmlschema } end @@ -802,24 +830,32 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_all_day - assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_day + assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_day + end + + def test_all_day_with_timezone + beginning_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,0,0,0)) + end_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))) + + assert_equal beginning_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.begin + assert_equal end_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.end end def test_all_week - assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week - assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week(:sunday) + assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week + assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week(:sunday) end def test_all_month - assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_month + assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_month end def test_all_quarter - assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_quarter + assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_quarter end def test_all_year - assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_year + assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_year end protected diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 7cf3842a16..1293f104e5 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -80,6 +80,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase ActiveSupport.use_standard_json_time_format = old end + def test_nsec + local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)) + with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local) + + assert_equal local.nsec, with_zone.nsec + assert_equal with_zone.nsec, 999999999 + end + def test_strftime assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z') end @@ -191,7 +199,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase end end - def future_with_time_current_as_time_with_zone + def test_future_with_time_current_as_time_with_zone twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) ) Time.stubs(:current).returns(twz) assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future? @@ -450,6 +458,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_ruby_19_weekday_name_query_methods %w(sunday? monday? tuesday? wednesday? thursday? friday? saturday?).each do |name| assert_respond_to @twz, name + assert_equal @twz.send(name), @twz.method(name).call end end @@ -482,36 +491,50 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.advance(:seconds => 30).inspect end - def beginning_of_year + def test_beginning_of_year assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 01 Jan 1999 00:00:00 EST -05:00", @twz.beginning_of_year.inspect end - def end_of_year + def test_end_of_year assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_year.inspect end - def beginning_of_month + def test_beginning_of_month assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect - assert_equal "Fri, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect + assert_equal "Wed, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect end - def end_of_month + def test_end_of_month assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_month.inspect end - def beginning_of_day + def test_beginning_of_day assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 31 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_day.inspect end - def end_of_day + def test_end_of_day assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_day.inspect end + def test_beginning_of_hour + utc = Time.utc(2000, 1, 1, 0, 30) + twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) + assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect + end + + def test_end_of_hour + utc = Time.utc(2000, 1, 1, 0, 30) + twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) + assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect + assert_equal "Fri, 31 Dec 1999 19:59:59 EST -05:00", twz.end_of_hour.inspect + end + def test_since assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect end diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index e821a285d7..e21f3efe36 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -93,6 +93,26 @@ class DeprecationTest < ActiveSupport::TestCase assert_match(/foo=nil/, @b) end + def test_default_stderr_behavior + ActiveSupport::Deprecation.behavior = :stderr + behavior = ActiveSupport::Deprecation.behavior.first + + content = capture(:stderr) { + assert_nil behavior.call('Some error!', ['call stack!']) + } + assert_match(/Some error!/, content) + assert_match(/call stack!/, content) + end + + def test_default_silence_behavior + ActiveSupport::Deprecation.behavior = :silence + behavior = ActiveSupport::Deprecation.behavior.first + + assert_blank capture(:stderr) { + assert_nil behavior.call('Some error!', ['call stack!']) + } + end + def test_deprecated_instance_variable_proxy assert_not_deprecated { @dtc.request.size } diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb index dd2483287b..8adff5de8d 100644 --- a/activesupport/test/file_update_checker_test.rb +++ b/activesupport/test/file_update_checker_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'fileutils' +require 'thread' MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__) @@ -43,8 +44,8 @@ class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase i = 0 checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } FileUtils.rm(FILES) - assert !checker.execute_if_updated - assert_equal 0, i + assert checker.execute_if_updated + assert_equal 1, i end def test_should_cache_updated_result_until_execute @@ -79,4 +80,18 @@ class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase assert !checker.execute_if_updated assert_equal 0, i end + + def test_should_not_block_if_a_strange_filename_used + FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,") + FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" }) + + test = Thread.new do + ActiveSupport::FileUpdateChecker.new([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 } + Thread.exit + end + test.priority = -1 + test.join(5) + + assert !test.alive? + end end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 7b012f7caa..91ae6bc189 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -26,23 +26,20 @@ class InflectorTest < ActiveSupport::TestCase end def test_uncountable_word_is_not_greedy - uncountable_word = "ors" - countable_word = "sponsor" + with_dup do + uncountable_word = "ors" + countable_word = "sponsor" - cached_uncountables = ActiveSupport::Inflector.inflections.uncountables + ActiveSupport::Inflector.inflections.uncountable << uncountable_word - ActiveSupport::Inflector.inflections.uncountable << uncountable_word + assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word) + assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word) + assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word) - assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word) - assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word) - assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word) - - assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word) - assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word) - assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word)) - - ensure - ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_uncountables + assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word) + assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word) + assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word)) + end end SingularToPlural.each do |singular, plural| @@ -65,14 +62,14 @@ class InflectorTest < ActiveSupport::TestCase assert_equal(plural.capitalize, ActiveSupport::Inflector.pluralize(plural.capitalize)) end end - + SingularToPlural.each do |singular, plural| define_method "test_singularize_singular_#{singular}" do assert_equal(singular, ActiveSupport::Inflector.singularize(singular)) assert_equal(singular.capitalize, ActiveSupport::Inflector.singularize(singular.capitalize)) end end - + def test_overwrite_previous_inflectors assert_equal("series", ActiveSupport::Inflector.singularize("series")) @@ -303,7 +300,7 @@ class InflectorTest < ActiveSupport::TestCase ActiveSupport::Inflector.constantize(string) end end - + def test_safe_constantize run_safe_constantize_tests_on do |string| ActiveSupport::Inflector.safe_constantize(string) @@ -349,56 +346,50 @@ class InflectorTest < ActiveSupport::TestCase %w{plurals singulars uncountables humans}.each do |inflection_type| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def test_clear_#{inflection_type} - cached_values = ActiveSupport::Inflector.inflections.#{inflection_type} - ActiveSupport::Inflector.inflections.clear :#{inflection_type} - assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\" - ActiveSupport::Inflector.inflections.instance_variable_set :@#{inflection_type}, cached_values + with_dup do + ActiveSupport::Inflector.inflections.clear :#{inflection_type} + assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\" + end end RUBY end def test_clear_all - cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup - ActiveSupport::Inflector.inflections do |inflect| - # ensure any data is present - inflect.plural(/(quiz)$/i, '\1zes') - inflect.singular(/(database)s$/i, '\1') - inflect.uncountable('series') - inflect.human("col_rpted_bugs", "Reported bugs") - - inflect.clear :all - - assert inflect.plurals.empty? - assert inflect.singulars.empty? - assert inflect.uncountables.empty? - assert inflect.humans.empty? + with_dup do + ActiveSupport::Inflector.inflections do |inflect| + # ensure any data is present + inflect.plural(/(quiz)$/i, '\1zes') + inflect.singular(/(database)s$/i, '\1') + inflect.uncountable('series') + inflect.human("col_rpted_bugs", "Reported bugs") + + inflect.clear :all + + assert inflect.plurals.empty? + assert inflect.singulars.empty? + assert inflect.uncountables.empty? + assert inflect.humans.empty? + end end - ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0] - ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1] - ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2] - ActiveSupport::Inflector.inflections.instance_variable_set :@humans, cached_values[3] end def test_clear_with_default - cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup - ActiveSupport::Inflector.inflections do |inflect| - # ensure any data is present - inflect.plural(/(quiz)$/i, '\1zes') - inflect.singular(/(database)s$/i, '\1') - inflect.uncountable('series') - inflect.human("col_rpted_bugs", "Reported bugs") - - inflect.clear - - assert inflect.plurals.empty? - assert inflect.singulars.empty? - assert inflect.uncountables.empty? - assert inflect.humans.empty? + with_dup do + ActiveSupport::Inflector.inflections do |inflect| + # ensure any data is present + inflect.plural(/(quiz)$/i, '\1zes') + inflect.singular(/(database)s$/i, '\1') + inflect.uncountable('series') + inflect.human("col_rpted_bugs", "Reported bugs") + + inflect.clear + + assert inflect.plurals.empty? + assert inflect.singulars.empty? + assert inflect.uncountables.empty? + assert inflect.humans.empty? + end end - ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0] - ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1] - ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2] - ActiveSupport::Inflector.inflections.instance_variable_set :@humans, cached_values[3] end Irregularities.each do |irregularity| @@ -447,26 +438,28 @@ class InflectorTest < ActiveSupport::TestCase end end - { :singulars => :singular, :plurals => :plural, :uncountables => :uncountable, :humans => :human }.each do |scope, method| + %w(plurals singulars uncountables humans acronyms).each do |scope| ActiveSupport::Inflector.inflections do |inflect| define_method("test_clear_inflections_with_#{scope}") do - # save the inflections - values = inflect.send(scope) - - # clear the inflections - inflect.clear(scope) - - assert_equal [], inflect.send(scope) - - # restore the inflections - if scope == :uncountables - inflect.send(method, values) - else - values.reverse.each { |value| inflect.send(method, *value) } + with_dup do + # clear the inflections + inflect.clear(scope) + assert_equal [], inflect.send(scope) end - - assert_equal values, inflect.send(scope) end end end + + # Dups the singleton and yields, restoring the original inflections later. + # Use this in tests what modify the state of the singleton. + # + # This helper is implemented by setting @__instance__ because in some tests + # there are module functions that access ActiveSupport::Inflector.inflections, + # so we need to replace the singleton itself. + def with_dup + original = ActiveSupport::Inflector.inflections + ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original.dup) + ensure + ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original) + end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 809b8b46c9..9fa1f417e4 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -47,6 +47,7 @@ module InflectorTestCases "medium" => "media", "stadium" => "stadia", "analysis" => "analyses", + "my_analysis" => "my_analyses", "node_child" => "node_children", "child" => "children", @@ -109,7 +110,9 @@ module InflectorTestCases # regression tests against improper inflection regexes "|ice" => "|ices", - "|ouse" => "|ouses" + "|ouse" => "|ouses", + "slice" => "slices", + "police" => "police" } CamelToUnderscore = { @@ -211,16 +214,22 @@ module InflectorTestCases } MixtureToTitleCase = { - 'active_record' => 'Active Record', - 'ActiveRecord' => 'Active Record', - 'action web service' => 'Action Web Service', - 'Action Web Service' => 'Action Web Service', - 'Action web service' => 'Action Web Service', - 'actionwebservice' => 'Actionwebservice', - 'Actionwebservice' => 'Actionwebservice', - "david's code" => "David's Code", - "David's code" => "David's Code", - "david's Code" => "David's Code" + 'active_record' => 'Active Record', + 'ActiveRecord' => 'Active Record', + 'action web service' => 'Action Web Service', + 'Action Web Service' => 'Action Web Service', + 'Action web service' => 'Action Web Service', + 'actionwebservice' => 'Actionwebservice', + 'Actionwebservice' => 'Actionwebservice', + "david's code" => "David's Code", + "David's code" => "David's Code", + "david's Code" => "David's Code", + "sgt. pepper's" => "Sgt. Pepper's", + "i've just seen a face" => "I've Just Seen A Face", + "maybe you'll be there" => "Maybe You'll Be There", + "¿por qué?" => '¿Por Qué?', + "Fred’s" => "Fred’s", + "Fred`s" => "Fred`s" } OrdinalNumbers = { diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index a2e61d88d5..0566ebf291 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -27,6 +27,10 @@ class TestJSONEncoding < ActiveSupport::TestCase NilTests = [[ nil, %(null) ]] NumericTests = [[ 1, %(1) ], [ 2.5, %(2.5) ], + [ 0.0/0.0, %(null) ], + [ 1.0/0.0, %(null) ], + [ -1.0/0.0, %(null) ], + [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ], [ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]] StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")], @@ -270,6 +274,17 @@ class TestJSONEncoding < ActiveSupport::TestCase JSON.parse(json_string_and_date)) end + def test_opt_out_big_decimal_string_serialization + big_decimal = BigDecimal('2.5') + + begin + ActiveSupport.encode_big_decimal_as_string = false + assert_equal big_decimal.to_s, big_decimal.to_json + ensure + ActiveSupport.encode_big_decimal_as_string = true + end + end + protected def object_keys(json_object) diff --git a/activesupport/test/lazy_load_hooks_test.rb b/activesupport/test/lazy_load_hooks_test.rb index 58ccc14324..7851634dbf 100644 --- a/activesupport/test/lazy_load_hooks_test.rb +++ b/activesupport/test/lazy_load_hooks_test.rb @@ -8,6 +8,16 @@ class LazyLoadHooksTest < ActiveSupport::TestCase assert_equal 1, i end + def test_basic_hook_with_two_registrations + i = 0 + ActiveSupport.on_load(:basic_hook_with_two) { i += incr } + assert_equal 0, i + ActiveSupport.run_load_hooks(:basic_hook_with_two, FakeContext.new(2)) + assert_equal 2, i + ActiveSupport.run_load_hooks(:basic_hook_with_two, FakeContext.new(5)) + assert_equal 7, i + end + def test_hook_registered_after_run i = 0 ActiveSupport.run_load_hooks(:registered_after) @@ -16,6 +26,25 @@ class LazyLoadHooksTest < ActiveSupport::TestCase assert_equal 1, i end + def test_hook_registered_after_run_with_two_registrations + i = 0 + ActiveSupport.run_load_hooks(:registered_after_with_two, FakeContext.new(2)) + ActiveSupport.run_load_hooks(:registered_after_with_two, FakeContext.new(5)) + assert_equal 0, i + ActiveSupport.on_load(:registered_after_with_two) { i += incr } + assert_equal 7, i + end + + def test_hook_registered_interleaved_run_with_two_registrations + i = 0 + ActiveSupport.run_load_hooks(:registered_interleaved_with_two, FakeContext.new(2)) + assert_equal 0, i + ActiveSupport.on_load(:registered_interleaved_with_two) { i += incr } + assert_equal 2, i + ActiveSupport.run_load_hooks(:registered_interleaved_with_two, FakeContext.new(5)) + assert_equal 7, i + end + def test_hook_receives_a_context i = 0 ActiveSupport.on_load(:contextual) { i += incr } diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb index 8e160714b1..2a0e8d20ed 100644 --- a/activesupport/test/log_subscriber_test.rb +++ b/activesupport/test/log_subscriber_test.rb @@ -11,7 +11,7 @@ class MyLogSubscriber < ActiveSupport::LogSubscriber def foo(event) debug "debug" - info "info" + info { "info" } warn "warn" end diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/logger_test.rb index 615635607c..eedeca30a8 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/logger_test.rb @@ -3,11 +3,9 @@ require 'multibyte_test_helpers' require 'stringio' require 'fileutils' require 'tempfile' -require 'active_support/testing/deprecation' -class BufferedLoggerTest < ActiveSupport::TestCase +class LoggerTest < ActiveSupport::TestCase include MultibyteTestHelpers - include ActiveSupport::Testing::Deprecation Logger = ActiveSupport::Logger diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 90aa13b3e6..a8d69d0ec3 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -458,6 +458,15 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert !''.mb_chars.respond_to?(:undefined_method) # Not defined end + def test_method_works_for_proxyed_methods + assert_equal 'll', 'hello'.mb_chars.method(:slice).call(2..3) # Defined on Chars + chars = 'hello'.mb_chars + assert_equal 'Hello', chars.method(:capitalize!).call # Defined on Chars + assert_equal 'Hello', chars + assert_equal 'jello', 'hello'.mb_chars.method(:gsub).call(/h/, 'j') # Defined on String + assert_raise(NameError){ ''.mb_chars.method(:undefined_method) } # Not defined + end + def test_acts_like_string assert 'Bambi'.mb_chars.acts_like_string? end diff --git a/activesupport/test/notifications/evented_notification_test.rb b/activesupport/test/notifications/evented_notification_test.rb new file mode 100644 index 0000000000..f77a0eb3fa --- /dev/null +++ b/activesupport/test/notifications/evented_notification_test.rb @@ -0,0 +1,67 @@ +require 'abstract_unit' + +module ActiveSupport + module Notifications + class EventedTest < ActiveSupport::TestCase + class Listener + attr_reader :events + + def initialize + @events = [] + end + + def start(name, id, payload) + @events << [:start, name, id, payload] + end + + def finish(name, id, payload) + @events << [:finish, name, id, payload] + end + end + + def test_evented_listener + notifier = Fanout.new + listener = Listener.new + notifier.subscribe 'hi', listener + notifier.start 'hi', 1, {} + notifier.start 'hi', 2, {} + notifier.finish 'hi', 2, {} + notifier.finish 'hi', 1, {} + + assert_equal 4, listener.events.length + assert_equal [ + [:start, 'hi', 1, {}], + [:start, 'hi', 2, {}], + [:finish, 'hi', 2, {}], + [:finish, 'hi', 1, {}], + ], listener.events + end + + def test_evented_listener_no_events + notifier = Fanout.new + listener = Listener.new + notifier.subscribe 'hi', listener + notifier.start 'world', 1, {} + assert_equal 0, listener.events.length + end + + def test_listen_to_everything + notifier = Fanout.new + listener = Listener.new + notifier.subscribe nil, listener + notifier.start 'hello', 1, {} + notifier.start 'world', 1, {} + notifier.finish 'world', 1, {} + notifier.finish 'hello', 1, {} + + assert_equal 4, listener.events.length + assert_equal [ + [:start, 'hello', 1, {}], + [:start, 'world', 1, {}], + [:finish, 'world', 1, {}], + [:finish, 'hello', 1, {}], + ], listener.events + end + end + end +end diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb index 0eee991e20..f60f9a58e3 100644 --- a/activesupport/test/ordered_options_test.rb +++ b/activesupport/test/ordered_options_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'active_support/ordered_options' class OrderedOptionsTest < ActiveSupport::TestCase def test_usage @@ -76,4 +77,12 @@ class OrderedOptionsTest < ActiveSupport::TestCase assert copy.kind_of?(original.class) assert_not_equal copy.object_id, original.object_id end + + def test_introspection + a = ActiveSupport::OrderedOptions.new + assert a.respond_to?(:blah) + assert a.respond_to?(:blah=) + assert_equal 42, a.method(:blah=).call(42) + assert_equal 42, a.method(:blah).call + end end diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index bdde5141a9..047b89be2a 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -84,13 +84,13 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal "hello<>", clean + @buffer end - test "Should concat as a normal string when dirty" do + test "Should concat as a normal string when safe" do clean = "hello".html_safe @buffer.gsub!('', '<>') assert_equal "<>hello", @buffer + clean end - test "Should preserve dirty? status on copy" do + test "Should preserve html_safe? status on copy" do @buffer.gsub!('', '<>') assert !@buffer.dup.html_safe? end @@ -102,20 +102,42 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal "<script>", result_buffer end - test "Should raise an error when safe_concat is called on dirty buffers" do + test "Should raise an error when safe_concat is called on unsafe buffers" do @buffer.gsub!('', '<>') assert_raise ActiveSupport::SafeBuffer::SafeConcatError do @buffer.safe_concat "BUSTED" end end - test "should not fail if the returned object is not a string" do + test "Should not fail if the returned object is not a string" do assert_kind_of NilClass, @buffer.slice("chipchop") end - test "Should initialize @dirty to false for new instance when sliced" do - dirty = @buffer[0,0].send(:dirty?) - assert_not_nil dirty - assert !dirty + test "clone_empty returns an empty buffer" do + assert_equal '', ActiveSupport::SafeBuffer.new('foo').clone_empty + end + + test "clone_empty keeps the original dirtyness" do + assert @buffer.clone_empty.html_safe? + assert !@buffer.gsub!('', '').clone_empty.html_safe? + end + + test "Should be safe when sliced if original value was safe" do + new_buffer = @buffer[0,0] + assert_not_nil new_buffer + assert new_buffer.html_safe?, "should be safe" + end + + test "Should continue unsafe on slice" do + x = 'foo'.html_safe.gsub!('f', '<script>alert("lolpwnd");</script>') + + # calling gsub! makes the dirty flag true + assert !x.html_safe?, "should not be safe" + + # getting a slice of it + y = x[0..-1] + + # should still be unsafe + assert !y.html_safe?, "should not be safe" end end diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb index dd4ae319e5..0751c2469e 100644 --- a/activesupport/test/tagged_logging_test.rb +++ b/activesupport/test/tagged_logging_test.rb @@ -18,7 +18,7 @@ class TaggedLoggingTest < ActiveSupport::TestCase @logger.tagged("BCX") { @logger.info "Funky time" } assert_equal "[BCX] Funky time\n", @output.string end - + test "tagged twice" do @logger.tagged("BCX") { @logger.tagged("Jason") { @logger.info "Funky time" } } assert_equal "[BCX] [Jason] Funky time\n", @output.string @@ -29,6 +29,11 @@ class TaggedLoggingTest < ActiveSupport::TestCase assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string end + test "provides access to the logger instance" do + @logger.tagged("BCX") { |logger| logger.info "Funky time" } + assert_equal "[BCX] Funky time\n", @output.string + end + test "tagged once with blank and nil" do @logger.tagged(nil, "", "New") { @logger.info "Funky time" } assert_equal "[New] Funky time\n", @output.string diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index e26256f9c6..b9434489bb 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -48,8 +48,8 @@ class TimeZoneTest < ActiveSupport::TestCase def test_now with_env_tz 'US/Eastern' do - Time.stubs(:now).returns(Time.local(2000)) - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup + def zone.time_now; Time.local(2000); end assert_instance_of ActiveSupport::TimeWithZone, zone.now assert_equal Time.utc(2000,1,1,5), zone.now.utc assert_equal Time.utc(2000), zone.now.time @@ -59,8 +59,11 @@ class TimeZoneTest < ActiveSupport::TestCase def test_now_enforces_spring_dst_rules with_env_tz 'US/Eastern' do - Time.stubs(:now).returns(Time.local(2006,4,2,2)) # 2AM springs forward to 3AM - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup + def zone.time_now + Time.local(2006,4,2,2) # 2AM springs forward to 3AM + end + assert_equal Time.utc(2006,4,2,3), zone.now.time assert_equal true, zone.now.dst? end @@ -68,8 +71,10 @@ class TimeZoneTest < ActiveSupport::TestCase def test_now_enforces_fall_dst_rules with_env_tz 'US/Eastern' do - Time.stubs(:now).returns(Time.at(1162098000)) # equivalent to 1AM DST - zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup + def zone.time_now + Time.at(1162098000) # equivalent to 1AM DST + end assert_equal Time.utc(2006,10,29,1), zone.now.time assert_equal true, zone.now.dst? end @@ -198,6 +203,24 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Time.utc(1999,12,31,19), twz.time end + def test_parse_should_not_black_out_system_timezone_dst_jump + zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)'] + zone.stubs(:now).returns(zone.now) + Time.stubs(:parse).with('2012-03-25 03:29', zone.now). + returns(Time.local(0,29,4,25,3,2012,nil,nil,true,"+03:00")) + twz = zone.parse('2012-03-25 03:29') + assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0,6] + end + + def test_parse_should_black_out_app_timezone_dst_jump + zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)'] + zone.stubs(:now).returns(zone.now) + Time.stubs(:parse).with('2012-03-11 02:29', zone.now). + returns(Time.local(0,29,2,11,3,2012,nil,nil,false,"+02:00")) + twz = zone.parse('2012-03-11 02:29') + assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0,6] + end + def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize tzinfo = TZInfo::Timezone.get('America/New_York') zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo) diff --git a/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb index 1d96c20bb6..2c217157d3 100644 --- a/activesupport/test/ts_isolated.rb +++ b/activesupport/test/ts_isolated.rb @@ -1,5 +1,3 @@ -$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib') - require 'minitest/autorun' require 'active_support/test_case' require 'rbconfig' @@ -12,7 +10,7 @@ class TestIsolated < ActiveSupport::TestCase define_method("test #{file}") do command = "#{ruby} -Ilib:test #{file}" result = silence_stderr { `#{command}` } - assert_block("#{command}\n#{result}") { $?.to_i.zero? } + assert $?.to_i.zero?, "#{command}\n#{result}" end end end |