diff options
Diffstat (limited to 'activesupport')
31 files changed, 1030 insertions, 126 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 47280c3dc8..05a573076e 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,79 @@ ## Rails 4.0.0 (unreleased) ## +* Allow delegation to the class using the `:class` keyword, replacing + `self.class` usage: + + class User + def self.hello + "world" + end + + delegate :hello, to: :class + end + + *Marc-Andre Lafortune* + +* `Date.beginning_of_week` thread local and `beginning_of_week` application + config option added (default is Monday). + + *Innokenty Mikhailov* + +* An optional block can be passed to `config_accessor` to set its default value + + class User + include ActiveSupport::Configurable + config_accessor :hair_colors do + [:brown, :black, :blonde, :red] + end + end + + User.hair_colors # => [:brown, :black, :blonde, :red] + + *Larry Lv* + +* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of + thread safety. It will be removed without replacement in Rails 4.1. + + *Steve Klabnik* + +* An optional block can be passed to `Hash#deep_merge`. The block will be invoked + for each duplicated key and used to resolve the conflict. + + *Pranas Kiziela* + +* ActiveSupport::Deprecation is now a class. It is possible to create an instance + of deprecator. Backwards compatibility has been preserved. + + You can choose which instance of the deprecator will be used. + + deprecate :method_name, :deprecator => deprecator_instance + + You can use ActiveSupport::Deprecation in your gem. + + require 'active_support/deprecation' + require 'active_support/core_ext/module/deprecation' + + class MyGem + def self.deprecator + ActiveSupport::Deprecation.new('2.0', 'MyGem') + end + + def old_method + end + + def new_method + end + + deprecate :old_method => :new_method, :deprecator => deprecator + end + + MyGem.new.old_method + # => DEPRECATION WARNING: old_method is deprecated and will be removed from MyGem 2.0 (use new_method instead). (called from <main> at file.rb:18) + + *Piotr Niełacny & Robert Pankowecki* + +* `ERB::Util.html_escape` encodes single quote as `#39`. Decimal form has better support in old browsers. *Kalys Osmonov* + * `ActiveSupport::Callbacks`: deprecate monkey patch of object callbacks. Using the #filter method like this: diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index b3c49ec169..3d8bb13c49 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -48,6 +48,7 @@ module ActiveSupport # Silence the logger during the execution of the block. def silence + ActiveSupport::Deprecation.warn "ActiveSupport::Benchmarkable#silence is deprecated. It will be removed from Rails 4.1." old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger yield ensure diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 76a1de4077..16d2a6a290 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -92,6 +92,17 @@ module ActiveSupport # # User.new.allowed_access = true # => NoMethodError # User.new.allowed_access # => NoMethodError + # + # Also you can pass a block to set up the attribute with a default value. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # User.hair_colors # => [:brown, :black, :blonde, :red] def config_accessor(*names) options = names.extract_options! @@ -108,6 +119,7 @@ module ActiveSupport class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false end + send("#{name}=", yield) if block_given? end 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 c6eec9c486..a7551d9c64 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -9,6 +9,29 @@ class Date include DateAndTime::Calculations class << self + attr_accessor :beginning_of_week_default + + # Returns the week start (e.g. :monday) for the current request, if this has been set (via Date.beginning_of_week=). + # If <tt>Date.beginning_of_week</tt> has not been set for the current request, returns the week start specified in <tt>config.beginning_of_week</tt>. + # If no config.beginning_of_week was specified, returns :monday. + def beginning_of_week + Thread.current[:beginning_of_week] || beginning_of_week_default || :monday + end + + # Sets <tt>Date.beginning_of_week</tt> to a week start (e.g. :monday) for current request/thread. + # + # This method accepts any of the following day symbols: + # :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday + def beginning_of_week=(week_start) + Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start) + end + + # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol. + def find_beginning_of_week!(week_start) + raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.keys.include?(week_start) + week_start + end + # Returns a new Date representing the date 1 day ago (i.e. yesterday's date). def yesterday ::Date.current.yesterday diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb index e703fca7a7..1f78b9eb5a 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb @@ -109,10 +109,11 @@ module DateAndTime alias :at_beginning_of_year :beginning_of_year # Returns a new date/time representing the given day in the next week. - # Default is :monday. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. # DateTime objects have their time set to 0:00. - def next_week(day = :monday) - first_hour{ weeks_since(1).beginning_of_week.days_since(DAYS_INTO_WEEK[day]) } + def next_week(start_day = Date.beginning_of_week) + first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(start_day)) } end # Short-hand for months_since(1). @@ -131,10 +132,11 @@ module DateAndTime end # Returns a new date/time representing the given day in the previous week. - # Default is :monday. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. # DateTime objects have their time set to 0:00. - def prev_week(day = :monday) - first_hour{ weeks_ago(1).beginning_of_week.days_since(DAYS_INTO_WEEK[day]) } + def prev_week(start_day = Date.beginning_of_week) + first_hour{ weeks_ago(1).beginning_of_week.days_since(days_span(start_day)) } end alias_method :last_week, :prev_week @@ -157,31 +159,44 @@ module DateAndTime alias_method :last_year, :prev_year # Returns the number of days to the start of the week on the given day. - # Default is :monday. - def days_to_week_start(start_day = :monday) + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + def days_to_week_start(start_day = Date.beginning_of_week) start_day_number = DAYS_INTO_WEEK[start_day] current_day_number = wday != 0 ? wday - 1 : 6 (current_day_number - start_day_number) % 7 end # Returns a new date/time representing the start of this week on the given day. - # Default is :monday. - # DateTime objects have their time set to 0:00. - def beginning_of_week(start_day = :monday) + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # +DateTime+ objects have their time set to 0:00. + def beginning_of_week(start_day = Date.beginning_of_week) result = days_ago(days_to_week_start(start_day)) acts_like?(:time) ? result.midnight : result end alias :at_beginning_of_week :beginning_of_week - alias :monday :beginning_of_week + + # Returns Monday of this week assuming that week starts on Monday. + # +DateTime+ objects have their time set to 0:00. + def monday + beginning_of_week(:monday) + end # Returns a new date/time representing the end of this week on the given day. - # Default is :monday (i.e end of Sunday). + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. # DateTime objects have their time set to 23:59:59. - def end_of_week(start_day = :monday) + def end_of_week(start_day = Date.beginning_of_week) last_hour{ days_since(6 - days_to_week_start(start_day)) } end alias :at_end_of_week :end_of_week - alias :sunday :end_of_week + + # Returns Sunday of this week assuming that week starts on Monday. + # +DateTime+ objects have their time set to 23:59:59. + def sunday + end_of_week(:monday) + end # Returns a new date/time representing the end of the month. # DateTime objects will have a time set to 23:59:59. @@ -209,5 +224,9 @@ module DateAndTime result = yield acts_like?(:time) ? result.end_of_day : result end + + def days_span(day) + (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 + end 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 485f88cc06..83f0c87b04 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb @@ -6,15 +6,21 @@ class Hash # # 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) + # h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) } + # #=> {:x => {:y => [4, 5, 6, 7, 8, 9]}, :z => [7, 8, 9, "xyz"]} + def deep_merge(other_hash, &block) + dup.deep_merge!(other_hash, &block) end # Same as +deep_merge+, but modifies +self+. - def deep_merge!(other_hash) + def deep_merge!(other_hash, &block) other_hash.each_pair do |k,v| tv = self[k] - self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v + if tv.is_a?(Hash) && v.is_a?(Hash) + self[k] = tv.deep_merge(v, &block) + else + self[k] = block && tv ? block.call(k, tv, v) : v + end end self end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 55966ffd15..e608eeaf42 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -52,6 +52,18 @@ class Module # Foo.new.min # => 4 # Foo.new.max # => 11 # + # It's also possible to delegate a method to the class by using +:class+: + # + # class Foo + # def self.hello + # "world" + # end + # + # delegate :hello, to: :class + # end + # + # Foo.new.hello # => "world" + # # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being # delegated to. @@ -103,7 +115,7 @@ 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, allow_nil = options.values_at(:prefix, :allow_nil) @@ -122,6 +134,9 @@ class Module file, line = caller.first.split(':', 2) line = line.to_i + to = to.to_s + to = 'self.class' if to == 'class' + methods.each do |method| # Attribute writer methods only accept one argument. Makes sure []= # methods still accept two arguments. diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb index 4f629240fe..34ec6a3d8f 100644 --- a/activesupport/lib/active_support/core_ext/module/deprecation.rb +++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb @@ -1,10 +1,24 @@ require 'active_support/deprecation/method_wrappers' class Module - # Declare that a method has been deprecated. # deprecate :foo - # deprecate bar: 'message' - # deprecate :foo, :bar, baz: 'warning!', qux: 'gone!' + # deprecate :bar => 'message' + # deprecate :foo, :bar, :baz => 'warning!', :qux => 'gone!' + # + # You can also use custom deprecator instance: + # + # deprecate :foo, :deprecator => MyLib::Deprecator.new + # deprecate :foo, :bar => "warning!", :deprecator => MyLib::Deprecator.new + # + # \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt> + # method where you can implement your custom warning behavior. + # + # class MyLib::Deprecator + # def deprecation_warning(deprecated_method_name, message, caller_backtrace) + # message = "#{method_name} is deprecated and will be removed from MyLibrary | #{message}" + # Kernel.warn message + # end + # end def deprecate(*method_names) ActiveSupport::Deprecation.deprecate_methods(self, *method_names) end diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb index e7dc60a612..83cc8066e7 100644 --- a/activesupport/lib/active_support/core_ext/object/to_json.rb +++ b/activesupport/lib/active_support/core_ext/object/to_json.rb @@ -17,3 +17,11 @@ end end end end + +module Process + class Status + def as_json(options = nil) + { :exitstatus => exitstatus, :pid => pid } + end + end +end diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 7d9e5bbe7d..5f85cedcf5 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/kernel/singleton_class' class ERB module Util - HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } + HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ JSON_ESCAPE_REGEXP = /[&"><]/ diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 359f6fa716..e3665cd896 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -172,8 +172,9 @@ class Time beginning_of_day..end_of_day end - # Returns a Range representing the whole week of the current time. Week starts on start_day (default is :monday, i.e. end of Sunday). - def all_week(start_day = :monday) + # Returns a Range representing the whole week of the current time. + # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set. + def all_week(start_day = Date.beginning_of_week) beginning_of_week(start_day)..end_of_week(start_day) end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index e3b4a7240e..b3f5fde335 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -1,19 +1,34 @@ require 'active_support/core_ext/module/deprecation' +require 'active_support/deprecation/instance_delegator' require 'active_support/deprecation/behaviors' require 'active_support/deprecation/reporting' require 'active_support/deprecation/method_wrappers' require 'active_support/deprecation/proxy_wrappers' +require 'singleton' module ActiveSupport - module Deprecation - class << self - # The version the deprecated behavior will be removed, by default. - attr_accessor :deprecation_horizon - end - self.deprecation_horizon = '4.1' + # \Deprecation specifies the API used by Rails to deprecate methods, instance + # variables, objects and constants. + class Deprecation + include Singleton + include InstanceDelegator + include Behavior + include Reporting + include MethodWrapper + + # The version the deprecated behavior will be removed, by default. + attr_accessor :deprecation_horizon - # By default, warnings are not silenced and debugging is off. - self.silenced = false - self.debug = false + # It accepts two parameters on initialization. The first is an version of library + # and the second is an library name + # + # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') + def initialize(deprecation_horizon = '4.1', gem_name = 'Rails') + self.gem_name = gem_name + self.deprecation_horizon = deprecation_horizon + # By default, warnings are not silenced and debugging is off. + self.silenced = false + self.debug = false + end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 6bc3c7e212..90db180124 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -1,8 +1,32 @@ require "active_support/notifications" module ActiveSupport - module Deprecation - class << self + class Deprecation + # Default warning behaviors per Rails.env. + DEFAULT_BEHAVIORS = { + :stderr => Proc.new { |message, callstack| + $stderr.puts(message) + $stderr.puts callstack.join("\n ") if debug + }, + :log => Proc.new { |message, callstack| + logger = + if defined?(Rails) && Rails.logger + Rails.logger + else + require 'active_support/logger' + ActiveSupport::Logger.new($stderr) + end + logger.warn message + logger.debug callstack.join("\n ") if debug + }, + :notify => Proc.new { |message, callstack| + ActiveSupport::Notifications.instrument("deprecation.rails", + :message => message, :callstack => callstack) + }, + :silence => Proc.new { |message, callstack| } + } + + module Behavior # Whether to print a backtrace along with the warning. attr_accessor :debug @@ -16,9 +40,9 @@ module ActiveSupport # # Available behaviors: # - # [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>. + # [+stderr+] Log all deprecation warnings to +$stderr+. # [+log+] Log all deprecation warnings to +Rails.logger+. - # [+notify+] Use <tt>ActiveSupport::Notifications</tt> to notify +deprecation.rails+. + # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. # [+silence+] Do nothing. # # Setting behaviors only affects deprecations that happen after boot time. @@ -35,29 +59,5 @@ module ActiveSupport @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b } end end - - # Default warning behaviors per Rails.env. - DEFAULT_BEHAVIORS = { - :stderr => Proc.new { |message, callstack| - $stderr.puts(message) - $stderr.puts callstack.join("\n ") if debug - }, - :log => Proc.new { |message, callstack| - logger = - if defined?(Rails) && Rails.logger - Rails.logger - else - require 'active_support/logger' - ActiveSupport::Logger.new($stderr) - end - logger.warn message - logger.debug callstack.join("\n ") if debug - }, - :notify => Proc.new { |message, callstack| - ActiveSupport::Notifications.instrument("deprecation.rails", - :message => message, :callstack => callstack) - }, - :silence => Proc.new { |message, callstack| } - } end end diff --git a/activesupport/lib/active_support/deprecation/instance_delegator.rb b/activesupport/lib/active_support/deprecation/instance_delegator.rb new file mode 100644 index 0000000000..ff240cb887 --- /dev/null +++ b/activesupport/lib/active_support/deprecation/instance_delegator.rb @@ -0,0 +1,24 @@ +require 'active_support/core_ext/kernel/singleton_class' +require 'active_support/core_ext/module/delegation' + +module ActiveSupport + class Deprecation + module InstanceDelegator + def self.included(base) + base.extend(ClassMethods) + base.public_class_method :new + end + + module ClassMethods + def include(included_module) + included_module.instance_methods.each { |m| method_added(m) } + super + end + + def method_added(method_name) + singleton_class.delegate(method_name, to: :instance) + end + end + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index 257b70e34a..d3907b03e5 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -2,45 +2,41 @@ require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/array/extract_options' module ActiveSupport - module Deprecation - # Declare that a method has been deprecated. - # - # module Fred - # extend self - # - # def foo; end - # def bar; end - # def baz; end - # end - # - # ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead') - # # => [:foo, :bar, :baz] - # - # Fred.foo - # # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1." - # - # Fred.bar - # # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)." - # - # Fred.baz - # # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)." - def self.deprecate_methods(target_module, *method_names) - options = method_names.extract_options! - method_names += options.keys + class Deprecation + module MethodWrapper + # Declare that a method has been deprecated. + # + # module Fred + # extend self + # + # def foo; end + # def bar; end + # def baz; end + # end + # + # ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead') + # # => [:foo, :bar, :baz] + # + # Fred.foo + # # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1." + # + # Fred.bar + # # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)." + # + # Fred.baz + # # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)." + def deprecate_methods(target_module, *method_names) + options = method_names.extract_options! + deprecator = options.delete(:deprecator) || ActiveSupport::Deprecation.instance + method_names += options.keys - method_names.each do |method_name| - target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation| - target_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1) - def #{target}_with_deprecation#{punctuation}(*args, &block) - ::ActiveSupport::Deprecation.warn( - ::ActiveSupport::Deprecation.deprecated_method_warning( - :#{method_name}, - #{options[method_name].inspect}), - caller - ) - send(:#{target}_without_deprecation#{punctuation}, *args, &block) + method_names.each do |method_name| + target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation| + target_module.send(:define_method, "#{target}_with_deprecation#{punctuation}") do |*args, &block| + deprecator.deprecation_warning(method_name, options[method_name], caller) + send(:"#{target}_without_deprecation#{punctuation}", *args, &block) end - end_eval + end end end end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index a65fcafb44..17e69c34a5 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -1,7 +1,7 @@ require 'active_support/inflector/methods' module ActiveSupport - module Deprecation + class Deprecation class DeprecationProxy #:nodoc: def self.new(*args, &block) object = args.first @@ -25,10 +25,20 @@ module ActiveSupport end end - class DeprecatedObjectProxy < DeprecationProxy #:nodoc: - def initialize(object, message) + # This DeprecatedObjectProxy transforms object to depracated object. + # + # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!") + # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance) + # + # When someone execute any method expect +inspect+ on proxy object this will + # trigger +warn+ method on +deprecator_instance+. + # + # Default deprecator is <tt>ActiveSupport::Deprecation</tt> + class DeprecatedObjectProxy < DeprecationProxy + def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance) @object = object @message = message + @deprecator = deprecator end private @@ -37,15 +47,40 @@ module ActiveSupport end def warn(callstack, called, args) - ActiveSupport::Deprecation.warn(@message, callstack) + @deprecator.warn(@message, callstack) end end - # Stand-in for <tt>@request</tt>, <tt>@attributes</tt>, <tt>@params</tt>, etc. - # which emits deprecation warnings on any method call (except +inspect+). - class DeprecatedInstanceVariableProxy < DeprecationProxy #:nodoc: - def initialize(instance, method, var = "@#{method}") - @instance, @method, @var = instance, method, var + # This DeprecatedInstanceVariableProxy transforms instance variable to + # depracated instance variable. + # + # class Example + # def initialize(deprecator) + # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator) + # @_request = :a_request + # end + # + # def request + # @_request + # end + # + # def old_request + # @request + # end + # end + # + # When someone execute any method on @request variable this will trigger + # +warn+ method on +deprecator_instance+ and will fetch <tt>@_request</tt> + # variable via +request+ method and execute the same method on non-proxy + # instance variable. + # + # Default deprecator is <tt>ActiveSupport::Deprecation</tt>. + class DeprecatedInstanceVariableProxy < DeprecationProxy + def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance) + @instance = instance + @method = method + @var = var + @deprecator = deprecator end private @@ -54,14 +89,24 @@ module ActiveSupport end def warn(callstack, called, args) - ActiveSupport::Deprecation.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack) + @deprecator.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack) end end - class DeprecatedConstantProxy < DeprecationProxy #:nodoc:all - def initialize(old_const, new_const) + # This DeprecatedConstantProxy transforms constant to depracated constant. + # + # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST') + # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance) + # + # When someone use old constant this will trigger +warn+ method on + # +deprecator_instance+. + # + # Default deprecator is <tt>ActiveSupport::Deprecation</tt>. + class DeprecatedConstantProxy < DeprecationProxy + def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance) @old_const = old_const @new_const = new_const + @deprecator = deprecator end def class @@ -74,7 +119,7 @@ module ActiveSupport end def warn(callstack, called, args) - ActiveSupport::Deprecation.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack) + @deprecator.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack) end end end diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index 874270c1c2..1ce54d9381 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -1,7 +1,10 @@ module ActiveSupport - module Deprecation - class << self + class Deprecation + module Reporting + # Whether to print a message (silent mode) attr_accessor :silenced + # Name of gem where method is deprecated + attr_accessor :gem_name # Outputs a deprecation warning to the output configured by # <tt>ActiveSupport::Deprecation.behavior</tt>. @@ -31,16 +34,30 @@ module ActiveSupport @silenced = old_silenced end - def deprecated_method_warning(method_name, message = nil) - warning = "#{method_name} is deprecated and will be removed from Rails #{deprecation_horizon}" - case message - when Symbol then "#{warning} (use #{message} instead)" - when String then "#{warning} (#{message})" - else warning + def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = caller) + deprecated_method_warning(deprecated_method_name, message).tap do |msg| + warn(msg, caller_backtrace) end end private + # Outputs a deprecation warning message + # + # ActiveSupport::Deprecation.deprecated_method_warning(:method_name) + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}" + # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method) + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)" + # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message") + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)" + def deprecated_method_warning(method_name, message = nil) + warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}" + case message + when Symbol then "#{warning} (use #{message} instead)" + when String then "#{warning} (#{message})" + else warning + end + end + def deprecation_message(callstack, message = nil) message ||= "You are using deprecated behavior which will be removed from the next major or minor release." message += '.' unless message =~ /\.$/ diff --git a/activesupport/lib/active_support/queueing.rb b/activesupport/lib/active_support/queueing.rb new file mode 100644 index 0000000000..0a4ab05b78 --- /dev/null +++ b/activesupport/lib/active_support/queueing.rb @@ -0,0 +1,133 @@ +require 'delegate' +require 'thread' + +module ActiveSupport + # A Queue that simply inherits from STDLIB's Queue. When this + # queue is used, Rails automatically starts a job runner in a + # background thread. + class Queue < ::Queue + attr_writer :consumer + + def initialize(consumer_options = {}) + super() + @consumer_options = consumer_options + end + + def consumer + @consumer ||= ThreadedQueueConsumer.new(self, @consumer_options) + end + + # Drain the queue, running all jobs in a different thread. This method + # may not be available on production queues. + def drain + # run the jobs in a separate thread so assumptions of synchronous + # jobs are caught in test mode. + consumer.drain + end + end + + class SynchronousQueue < Queue + def push(job) + super.tap { drain } + end + alias << push + alias enq push + end + + # In test mode, the Rails queue is backed by an Array so that assertions + # can be made about its contents. The test queue provides a +jobs+ + # method to make assertions about the queue's contents and a +drain+ + # method to drain the queue and run the jobs. + # + # Jobs are run in a separate thread to catch mistakes where code + # assumes that the job is run in the same thread. + class TestQueue < Queue + # Get a list of the jobs off this queue. This method may not be + # available on production queues. + def jobs + @que.dup + end + + # Marshal and unmarshal job before pushing it onto the queue. This will + # raise an exception on any attempts in tests to push jobs that can't (or + # shouldn't) be marshalled. + def push(job) + super Marshal.load(Marshal.dump(job)) + end + end + + # A container for multiple queues. This class delegates to a default Queue + # so that <tt>Rails.queue.push</tt> and friends will Just Work. To use this class + # with multiple queues: + # + # # In your configuration: + # Rails.queue[:image_queue] = SomeQueue.new + # Rails.queue[:mail_queue] = SomeQueue.new + # + # # In your app code: + # Rails.queue[:mail_queue].push SomeJob.new + # + class QueueContainer < DelegateClass(::Queue) + def initialize(default_queue) + @queues = { :default => default_queue } + super(default_queue) + end + + def [](queue_name) + @queues[queue_name] + end + + def []=(queue_name, queue) + @queues[queue_name] = queue + end + end + + # The threaded consumer will run jobs in a background thread in + # development mode or in a VM where running jobs on a thread in + # production mode makes sense. + # + # When the process exits, the consumer pushes a nil onto the + # queue and joins the thread, which will ensure that all jobs + # are executed before the process finally dies. + class ThreadedQueueConsumer + def self.start(*args) + new(*args).start + end + + def initialize(queue, options = {}) + @queue = queue + @logger = options[:logger] + end + + def start + @thread = Thread.new { consume } + self + end + + def shutdown + @queue.push nil + @thread.join + end + + def drain + run(@queue.pop) until @queue.empty? + end + + def consume + while job = @queue.pop + run job + end + end + + def run(job) + job.run + rescue Exception => exception + handle_exception job, exception + end + + def handle_exception(job, exception) + raise unless @logger + @logger.error "Job Error: #{exception.message}\n#{exception.backtrace.join("\n")}" + end + end +end diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index bf9e889204..133aa6a054 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -27,6 +27,15 @@ module ActiveSupport Time.zone_default = zone_default end + # Sets the default week start + # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised. + initializer "active_support.initialize_beginning_of_week" do |app| + require 'active_support/core_ext/date/calculations' + beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week) + + Date.beginning_of_week_default = beginning_of_week_default + end + initializer "active_support.set_configs" do |app| app.config.active_support.each do |k, v| k = "#{k}=" diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index eaf484fad4..b931de3fac 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -173,7 +173,10 @@ module ActiveSupport # +formatted_offset+, respectively, before passing to Time#strftime, so # that zone information is correct def strftime(format) - format = format.gsub('%Z', zone).gsub('%z', formatted_offset(false)) + format = format.gsub('%Z', zone) + .gsub('%z', formatted_offset(false)) + .gsub('%:z', formatted_offset(true)) + .gsub('%::z', formatted_offset(true) + ":00") time.strftime(format) end diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index da7729d066..215a6e06b0 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -48,6 +48,18 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase assert !instance.respond_to?(:baz=) end + test "configuration accessors can take a default value" do + parent = Class.new do + include ActiveSupport::Configurable + config_accessor :hair_colors, :tshirt_colors do + [:black, :blue, :white] + end + end + + assert_equal [:black, :blue, :white], parent.hair_colors + assert_equal [:black, :blue, :white], parent.tshirt_colors + end + test "configuration hash is available on instance" do instance = Parent.new assert_equal :bar, instance.config.foo @@ -78,7 +90,7 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase test "should raise name error if attribute name is invalid" do assert_raises NameError do - Class.new do + Class.new do include ActiveSupport::Configurable config_accessor "invalid attribute name" end @@ -94,4 +106,4 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase methods = object.public_methods.map(&:to_s) assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}" end -end
\ No newline at end of file +end diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb index 014935b0c0..9927856aa2 100644 --- a/activesupport/test/core_ext/date_and_time_behavior.rb +++ b/activesupport/test/core_ext/date_and_time_behavior.rb @@ -101,6 +101,15 @@ module DateAndTimeBehavior assert_equal date_time_init(2006,11,1,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:wednesday) end + def test_next_week_with_default_beginning_of_week_set + with_bw_default(:tuesday) do + assert_equal Time.local(2012, 3, 28), Time.local(2012, 3, 21).next_week(:wednesday) + assert_equal Time.local(2012, 3, 31), Time.local(2012, 3, 21).next_week(:saturday) + assert_equal Time.local(2012, 3, 27), Time.local(2012, 3, 21).next_week(:tuesday) + assert_equal Time.local(2012, 4, 02), Time.local(2012, 3, 21).next_week(:monday) + end + end + def test_next_month_on_31st assert_equal date_time_init(2005,9,30,15,15,10), date_time_init(2005,8,31,15,15,10).next_month end @@ -121,6 +130,15 @@ module DateAndTimeBehavior assert_equal date_time_init(2006,11,15,0,0,0), date_time_init(2006,11,23,0,0,0).prev_week(:wednesday) end + def test_prev_week_with_default_beginning_of_week + with_bw_default(:tuesday) do + assert_equal Time.local(2012, 3, 14), Time.local(2012, 3, 21).prev_week(:wednesday) + assert_equal Time.local(2012, 3, 17), Time.local(2012, 3, 21).prev_week(:saturday) + assert_equal Time.local(2012, 3, 13), Time.local(2012, 3, 21).prev_week(:tuesday) + assert_equal Time.local(2012, 3, 19), Time.local(2012, 3, 21).prev_week(:monday) + end + end + def test_prev_month_on_31st assert_equal date_time_init(2004,2,29,10,10,10), date_time_init(2004,3,31,10,10,10).prev_month end @@ -151,6 +169,18 @@ module DateAndTimeBehavior assert_equal 3, date_time_init(2011,11,9,0,0,0).days_to_week_start(:sunday) end + def test_days_to_week_start_with_default_set + with_bw_default(:friday) do + assert_equal 6, Time.local(2012,03,8,0,0,0).days_to_week_start + assert_equal 5, Time.local(2012,03,7,0,0,0).days_to_week_start + assert_equal 4, Time.local(2012,03,6,0,0,0).days_to_week_start + assert_equal 3, Time.local(2012,03,5,0,0,0).days_to_week_start + assert_equal 2, Time.local(2012,03,4,0,0,0).days_to_week_start + assert_equal 1, Time.local(2012,03,3,0,0,0).days_to_week_start + assert_equal 0, Time.local(2012,03,2,0,0,0).days_to_week_start + end + end + def test_beginning_of_week assert_equal date_time_init(2005,1,31,0,0,0), date_time_init(2005,2,4,10,10,10).beginning_of_week assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,11,28,0,0,0).beginning_of_week #monday @@ -183,4 +213,24 @@ module DateAndTimeBehavior assert_equal date_time_init(2007,12,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,2,22,10,10,10).end_of_year assert_equal date_time_init(2007,12,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,12,31,10,10,10).end_of_year end + + def test_monday_with_default_beginning_of_week_set + with_bw_default(:saturday) do + assert_equal date_time_init(2012,9,17,0,0,0), date_time_init(2012,9,18,0,0,0).monday + end + end + + def test_sunday_with_default_beginning_of_week_set + with_bw_default(:wednesday) do + assert_equal date_time_init(2012,9,23,23,59,59, Rational(999999999, 1000)), date_time_init(2012,9,19,0,0,0).sunday + end + end + + def with_bw_default(bw = :monday) + old_bw = Date.beginning_of_week + Date.beginning_of_week = bw + yield + ensure + Date.beginning_of_week = old_bw + end end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 94463cc311..01934dd2c3 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -582,6 +582,16 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, hash_1 end + def test_deep_merge_with_block + hash_1 = { :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } } + hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } } + expected = { :a => [:a, "a", 1], :b => "b", :c => { :c1 => [:c1, "c1", 2], :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } } + assert_equal(expected, hash_1.deep_merge(hash_2) { |k,o,n| [k, o, n] }) + + hash_1.deep_merge!(hash_2) { |k,o,n| [k, o, n] } + assert_equal expected, hash_1 + end + def test_deep_merge_on_indifferent_access hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }) hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }) diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index bd41311739..82249ddd1b 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -34,6 +34,12 @@ class Someone < Struct.new(:name, :place) delegate :street, :city, :to_f, :to => :place delegate :name=, :to => :place, :prefix => true delegate :upcase, :to => "place.city" + delegate :table_name, :to => :class + delegate :table_name, :to => :class, :prefix => true + + def self.table_name + 'some_table' + end FAILED_DELEGATE_LINE = __LINE__ + 1 delegate :foo, :to => :place @@ -111,6 +117,11 @@ class ModuleTest < ActiveSupport::TestCase assert_equal "DAVID HANSSON", david.upcase end + def test_delegation_to_class_method + assert_equal 'some_table', @david.table_name + assert_equal 'some_table', @david.class_table_name + end + def test_missing_delegation_target assert_raise(ArgumentError) do Name.send :delegate, :nowhere diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index dc5ae0eafc..6720ed42f0 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -500,7 +500,7 @@ class OutputSafetyTest < ActiveSupport::TestCase test "ERB::Util.html_escape should escape unsafe characters" do string = '<>&"\'' - expected = '<>&"'' + expected = '<>&"'' assert_equal expected, ERB::Util.html_escape(string) end diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index e21f3efe36..c081103cc7 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -104,6 +104,17 @@ class DeprecationTest < ActiveSupport::TestCase assert_match(/call stack!/, content) end + def test_default_stderr_behavior_with_warn_method + ActiveSupport::Deprecation.behavior = :stderr + + content = capture(:stderr) { + ActiveSupport::Deprecation.warn('Instance error!', ['instance call stack!']) + } + + assert_match(/Instance error!/, content) + assert_match(/instance call stack!/, content) + end + def test_default_silence_behavior ActiveSupport::Deprecation.behavior = :silence behavior = ActiveSupport::Deprecation.behavior.first @@ -186,4 +197,142 @@ class DeprecationTest < ActiveSupport::TestCase def test_deprecation_with_explicit_message assert_deprecated(/you now need to do something extra for this one/) { @dtc.d } end + + def test_deprecation_in_other_object + messages = [] + + klass = Class.new do + delegate :warn, :behavior=, to: ActiveSupport::Deprecation + end + + o = klass.new + o.behavior = Proc.new { |message, callstack| messages << message } + assert_difference("messages.size") do + o.warn("warning") + end + end + + def test_deprecated_method_with_custom_method_warning + deprecator = deprecator_with_messages + + class << deprecator + private + def deprecated_method_warning(method, message) + "deprecator.deprecated_method_warning.#{method}" + end + end + + deprecatee = Class.new do + def method + end + deprecate :method, deprecator: deprecator + end + + deprecatee.new.method + assert deprecator.messages.first.match("DEPRECATION WARNING: deprecator.deprecated_method_warning.method") + end + + def test_deprecate_with_custom_deprecator + custom_deprecator = mock('Deprecator') do + expects(:deprecation_warning) + end + + klass = Class.new do + def method + end + deprecate :method, deprecator: custom_deprecator + end + + klass.new.method + end + + def test_deprecated_constant_with_deprecator_given + deprecator = deprecator_with_messages + klass = Class.new + klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new('klass::OLD', 'Object', deprecator) ) + assert_difference("deprecator.messages.size") do + klass::OLD.to_s + end + end + + def test_deprecated_instance_variable_with_instance_deprecator + deprecator = deprecator_with_messages + + klass = Class.new() do + def initialize(deprecator) + @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator) + @_request = :a_request + end + def request; @_request end + def old_request; @request end + end + + assert_difference("deprecator.messages.size") { klass.new(deprecator).old_request.to_s } + end + + def test_deprecated_instance_variable_with_given_deprecator + deprecator = deprecator_with_messages + + klass = Class.new do + define_method(:initialize) do + @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator) + @_request = :a_request + end + def request; @_request end + def old_request; @request end + end + + assert_difference("deprecator.messages.size") { klass.new.old_request.to_s } + end + + def test_delegate_deprecator_instance + klass = Class.new do + attr_reader :last_message + delegate :warn, :behavior=, to: ActiveSupport::Deprecation + + def initialize + self.behavior = [Proc.new { |message| @last_message = message }] + end + + def deprecated_method + warn(deprecated_method_warning(:deprecated_method, "You are calling deprecated method")) + end + + private + def deprecated_method_warning(method_name, message = nil) + message || "#{method_name} is deprecated and will be removed from This Library" + end + end + + object = klass.new + object.deprecated_method + assert_match(/You are calling deprecated method/, object.last_message) + end + + def test_default_gem_name + deprecator = ActiveSupport::Deprecation.new + + deprecator.send(:deprecated_method_warning, :deprecated_method, "You are calling deprecated method").tap do |message| + assert_match(/is deprecated and will be removed from Rails/, message) + end + end + + def test_custom_gem_name + deprecator = ActiveSupport::Deprecation.new('2.0', 'Custom') + + deprecator.send(:deprecated_method_warning, :deprecated_method, "You are calling deprecated method").tap do |message| + assert_match(/is deprecated and will be removed from Custom/, message) + end + end + + private + def deprecator_with_messages + klass = Class.new(ActiveSupport::Deprecation) + deprecator = klass.new + deprecator.behavior = Proc.new{|message, callstack| deprecator.messages << message} + def deprecator.messages + @messages ||= [] + end + deprecator + end end diff --git a/activesupport/test/queueing/container_test.rb b/activesupport/test/queueing/container_test.rb new file mode 100644 index 0000000000..7afc11e7a9 --- /dev/null +++ b/activesupport/test/queueing/container_test.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' +require 'active_support/queueing' + +module ActiveSupport + class ContainerTest < ActiveSupport::TestCase + def test_delegates_to_default + q = Queue.new + container = QueueContainer.new q + job = Object.new + + container.push job + assert_equal job, q.pop + end + + def test_access_default + q = Queue.new + container = QueueContainer.new q + assert_equal q, container[:default] + end + + def test_assign_queue + container = QueueContainer.new Object.new + q = Object.new + container[:foo] = q + assert_equal q, container[:foo] + end + end +end diff --git a/activesupport/test/queueing/synchronous_queue_test.rb b/activesupport/test/queueing/synchronous_queue_test.rb new file mode 100644 index 0000000000..86c39d0f6c --- /dev/null +++ b/activesupport/test/queueing/synchronous_queue_test.rb @@ -0,0 +1,27 @@ +require 'abstract_unit' +require 'active_support/queueing' + +class SynchronousQueueTest < ActiveSupport::TestCase + class Job + attr_reader :ran + def run; @ran = true end + end + + class ExceptionRaisingJob + def run; raise end + end + + def setup + @queue = ActiveSupport::SynchronousQueue.new + end + + def test_runs_jobs_immediately + job = Job.new + @queue.push job + assert job.ran + + assert_raises RuntimeError do + @queue.push ExceptionRaisingJob.new + end + end +end diff --git a/activesupport/test/queueing/test_queue_test.rb b/activesupport/test/queueing/test_queue_test.rb new file mode 100644 index 0000000000..e398a48bea --- /dev/null +++ b/activesupport/test/queueing/test_queue_test.rb @@ -0,0 +1,102 @@ +require 'abstract_unit' +require 'active_support/queueing' + +class TestQueueTest < ActiveSupport::TestCase + def setup + @queue = ActiveSupport::TestQueue.new + end + + class ExceptionRaisingJob + def run + raise + end + end + + def test_drain_raises_exceptions_from_running_jobs + @queue.push ExceptionRaisingJob.new + assert_raises(RuntimeError) { @queue.drain } + end + + def test_jobs + @queue.push 1 + @queue.push 2 + assert_equal [1,2], @queue.jobs + end + + class EquivalentJob + def initialize + @initial_id = self.object_id + end + + def run + end + + def ==(other) + other.same_initial_id?(@initial_id) + end + + def same_initial_id?(other_id) + other_id == @initial_id + end + end + + def test_contents + job = EquivalentJob.new + assert @queue.empty? + @queue.push job + refute @queue.empty? + assert_equal job, @queue.pop + end + + class ProcessingJob + def self.clear_processed + @processed = [] + end + + def self.processed + @processed + end + + def initialize(object) + @object = object + end + + def run + self.class.processed << @object + end + end + + def test_order + ProcessingJob.clear_processed + job1 = ProcessingJob.new(1) + job2 = ProcessingJob.new(2) + + @queue.push job1 + @queue.push job2 + @queue.drain + + assert_equal [1,2], ProcessingJob.processed + end + + class ThreadTrackingJob + attr_reader :thread_id + + def run + @thread_id = Thread.current.object_id + end + + def ran? + @thread_id + end + end + + def test_drain + @queue.push ThreadTrackingJob.new + job = @queue.jobs.last + @queue.drain + + assert @queue.empty? + assert job.ran?, "The job runs synchronously when the queue is drained" + assert_equal job.thread_id, Thread.current.object_id + end +end diff --git a/activesupport/test/queueing/threaded_consumer_test.rb b/activesupport/test/queueing/threaded_consumer_test.rb new file mode 100644 index 0000000000..fc43cb555a --- /dev/null +++ b/activesupport/test/queueing/threaded_consumer_test.rb @@ -0,0 +1,92 @@ +require 'abstract_unit' +require 'active_support/queueing' +require "active_support/log_subscriber/test_helper" + +class TestThreadConsumer < ActiveSupport::TestCase + class Job + attr_reader :id + def initialize(id = 1, &block) + @id = id + @block = block + end + + def run + @block.call if @block + end + end + + def setup + @logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + @queue = ActiveSupport::Queue.new(logger: @logger) + end + + def teardown + @queue.drain + end + + test "the jobs are executed" do + ran = false + job = Job.new { ran = true } + + @queue.push job + @queue.drain + + assert_equal true, ran + end + + test "the jobs are not executed synchronously" do + run, ran = Queue.new, Queue.new + job = Job.new { ran.push run.pop } + + @queue.consumer.start + @queue.push job + assert ran.empty? + + run.push true + assert_equal true, ran.pop + end + + test "shutting down the queue synchronously drains the jobs" do + ran = false + job = Job.new do + sleep 0.1 + ran = true + end + + @queue.consumer.start + @queue.push job + assert_equal false, ran + + @queue.consumer.shutdown + assert_equal true, ran + end + + test "log job that raises an exception" do + job = Job.new { raise "RuntimeError: Error!" } + + @queue.push job + @queue.drain + + assert_equal 1, @logger.logged(:error).size + assert_match 'Job Error: RuntimeError: Error!', @logger.logged(:error).last + end + + test "test overriding exception handling" do + @queue.consumer.instance_eval do + def handle_exception(job, exception) + @last_error = exception.message + end + + def last_error + @last_error + end + end + + job = Job.new { raise "RuntimeError: Error!" } + + @queue.push job + @queue.drain + + assert_equal "RuntimeError: Error!", @queue.consumer.last_error + end +end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index b9434489bb..bfd6863e40 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -258,6 +258,14 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal "-0500", zone.formatted_offset(false) end + def test_z_format_strings + zone = ActiveSupport::TimeZone['Tokyo'] + twz = zone.now + assert_equal '+0900', twz.strftime('%z') + assert_equal '+09:00', twz.strftime('%:z') + assert_equal '+09:00:00', twz.strftime('%::z') + end + def test_formatted_offset_zero zone = ActiveSupport::TimeZone['London'] assert_equal "+00:00", zone.formatted_offset |