aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md25
-rw-r--r--activesupport/Rakefile9
-rw-r--r--activesupport/activesupport.gemspec4
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb10
-rw-r--r--activesupport/lib/active_support/callbacks.rb650
-rw-r--r--activesupport/lib/active_support/concern.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range/include_range.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb22
-rw-r--r--activesupport/lib/active_support/json/decoding.rb25
-rw-r--r--activesupport/lib/active_support/json/encoding.rb14
-rw-r--r--activesupport/lib/active_support/json/variable.rb18
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb6
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb7
-rw-r--r--activesupport/lib/active_support/ordered_options.rb8
-rw-r--r--activesupport/lib/active_support/test_case.rb28
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb4
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb17
-rw-r--r--activesupport/lib/active_support/testing/pending.rb14
-rw-r--r--activesupport/lib/active_support/testing/tagged_logging.rb2
-rw-r--r--activesupport/test/callbacks_test.rb103
-rw-r--r--activesupport/test/concern_test.rb14
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb16
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb2
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb2
-rw-r--r--activesupport/test/json/decoding_test.rb36
-rw-r--r--activesupport/test/json/encoding_test.rb13
-rw-r--r--activesupport/test/notifications_test.rb22
-rw-r--r--activesupport/test/rescuable_test.rb2
-rw-r--r--activesupport/test/test_case_test.rb121
-rw-r--r--activesupport/test/test_test.rb2
-rw-r--r--activesupport/test/ts_isolated.rb16
38 files changed, 685 insertions, 563 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 2e04b17e78..ae3fd811b3 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,28 @@
+* Prevent side effects to hashes inside arrays when
+ `Hash#with_indifferent_access` is called.
+ Fixes #10526
+
+ *Yves Senn*
+
+* Raise an error when multiple `included` blocks are defined for a Concern.
+ The old behavior would silently discard previously defined blocks, running
+ only the last one.
+
+ *Mike Dillon*
+
+* Replace `multi_json` with `json`.
+
+ Since Rails requires Ruby 1.9 and since Ruby 1.9 includes `json` in the standard library,
+ `multi_json` is no longer necessary.
+
+ *Erik Michaels-Ober*
+
+* Added escaping of U+2028 and U+2029 inside the json encoder.
+ These characters are legal in JSON but break the Javascript interpreter.
+ After escaping them, the JSON is still legal and can be parsed by Javascript.
+
+ *Mario Caropreso + Viktor Kelemen + zackham*
+
* Fix skipping object callbacks using metadata fetched via callback chain
inspection methods (`_*_callbacks`)
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index e3788ed54f..f50225a9f0 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -9,13 +9,16 @@ Rake::TestTask.new do |t|
t.verbose = true
end
+
namespace :test do
- Rake::TestTask.new(:isolated) do |t|
- t.pattern = 'test/ts_isolated.rb'
+ task :isolated do
+ ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
+ Dir.glob("test/**/*_test.rb").all? do |file|
+ sh(ruby, '-Ilib:test', file)
+ end or raise "Failures"
end
end
-
spec = eval(File.read('activesupport.gemspec'))
Gem::PackageTask.new(spec) do |p|
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index b46a331f6a..ffc2c2074e 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -21,8 +21,8 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
s.add_dependency('i18n', '~> 0.6', '>= 0.6.4')
- s.add_dependency 'multi_json', '~> 1.3'
+ s.add_dependency 'json', '~> 1.7'
s.add_dependency 'tzinfo', '~> 0.3.37'
- s.add_dependency 'minitest', '~> 4.2'
+ s.add_dependency 'minitest', '~> 5.0'
s.add_dependency 'thread_safe','~> 0.1'
end
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 6413502b53..805b7a714f 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -45,15 +45,5 @@ module ActiveSupport
yield
end
end
-
- # Silence the logger during the execution of the block.
- def silence
- message = "ActiveSupport::Benchmarkable#silence is deprecated. It will be removed from Rails 4.1."
- ActiveSupport::Deprecation.warn message
- old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger
- yield
- ensure
- logger.level = old_logger_level if logger
- end
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 6fe7e0f4fb..85b7669353 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,9 +1,9 @@
-require 'thread_safe'
require 'active_support/concern'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
+require 'thread'
module ActiveSupport
# Callbacks are code hooks that are run at key points in an object's lifecycle.
@@ -76,8 +76,14 @@ module ActiveSupport
# save
# end
def run_callbacks(kind, &block)
- runner_name = self.class.__define_callbacks(kind, self)
- send(runner_name, &block)
+ cbs = send("_#{kind}_callbacks")
+ if cbs.empty?
+ yield if block_given?
+ else
+ runner = cbs.compile
+ e = Filters::Environment.new(self, false, nil, block)
+ runner.call(e).value
+ end
end
private
@@ -88,204 +94,306 @@ module ActiveSupport
def halted_callback_hook(filter)
end
- class Callback #:nodoc:#
- @@_callback_sequence = 0
+ module Filters
+ Environment = Struct.new(:target, :halted, :value, :run_block)
- class Basic < Callback
+ class End
+ def call(env)
+ block = env.run_block
+ env.value = !env.halted && (!block || block.call)
+ env
+ end
end
+ ENDING = End.new
- class Object < Callback
- def duplicates?(other)
- false
+ class Before
+ def self.build(next_callback, user_callback, user_conditions, chain_config, filter)
+ halted_lambda = chain_config[:terminator]
+
+ if chain_config.key?(:terminator) && user_conditions.any?
+ halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
+ elsif chain_config.key? :terminator
+ halting(next_callback, user_callback, halted_lambda, filter)
+ elsif user_conditions.any?
+ conditional(next_callback, user_callback, user_conditions)
+ else
+ simple next_callback, user_callback
+ end
end
- end
- def self.build(chain, filter, kind, options, _klass)
- klass = case filter
- when Array, Symbol, String
- Callback::Basic
- else
- Callback::Object
- end
- klass.new chain, filter, kind, options, _klass
- end
+ private
- attr_accessor :chain, :kind, :options, :klass, :raw_filter
+ def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
+ lambda { |env|
+ target = env.target
+ value = env.value
+ halted = env.halted
- def initialize(chain, filter, kind, options, klass)
- @chain, @kind, @klass = chain, kind, klass
- deprecate_per_key_option(options)
- normalize_options!(options)
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
+ result = user_callback.call target, value
+ env.halted = halted_lambda.call(target, result)
+ if env.halted
+ target.send :halted_callback_hook, filter
+ end
+ end
+ next_callback.call env
+ }
+ end
- @raw_filter, @options = filter, options
- @key = compute_identifier filter
- @source = _compile_source(filter)
- recompile_options!
- end
+ def self.halting(next_callback, user_callback, halted_lambda, filter)
+ lambda { |env|
+ target = env.target
+ value = env.value
+ halted = env.halted
+
+ unless halted
+ result = user_callback.call target, value
+ env.halted = halted_lambda.call(target, result)
+ if env.halted
+ target.send :halted_callback_hook, filter
+ end
+ end
+ next_callback.call env
+ }
+ end
- def filter
- @key
- end
+ def self.conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ target = env.target
+ value = env.value
- def deprecate_per_key_option(options)
- if options[:per_key]
- raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead."
+ if user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call target, value
+ end
+ next_callback.call env
+ }
end
- end
- def clone(chain, klass)
- obj = super()
- obj.chain = chain
- obj.klass = klass
- obj.options = @options.dup
- obj.options[:if] = @options[:if].dup
- obj.options[:unless] = @options[:unless].dup
- obj
+ def self.simple(next_callback, user_callback)
+ lambda { |env|
+ user_callback.call env.target, env.value
+ next_callback.call env
+ }
+ end
end
- def normalize_options!(options)
- options[:if] = Array(options[:if])
- options[:unless] = Array(options[:unless])
- end
+ class After
+ def self.build(next_callback, user_callback, user_conditions, chain_config)
+ if chain_config[:skip_after_callbacks_if_terminated]
+ if chain_config.key?(:terminator) && user_conditions.any?
+ halting_and_conditional(next_callback, user_callback, user_conditions)
+ elsif chain_config.key?(:terminator)
+ halting(next_callback, user_callback)
+ elsif user_conditions.any?
+ conditional next_callback, user_callback, user_conditions
+ else
+ simple next_callback, user_callback
+ end
+ else
+ if user_conditions.any?
+ conditional next_callback, user_callback, user_conditions
+ else
+ simple next_callback, user_callback
+ end
+ end
+ end
- def name
- chain.name
- end
+ private
- def next_id
- @@_callback_sequence += 1
- end
+ def self.halting_and_conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ env = next_callback.call env
+ target = env.target
+ value = env.value
+ halted = env.halted
- def matches?(_kind, _filter)
- @kind == _kind && filter == _filter
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call target, value
+ end
+ env
+ }
+ end
+
+ def self.halting(next_callback, user_callback)
+ lambda { |env|
+ env = next_callback.call env
+ unless env.halted
+ user_callback.call env.target, env.value
+ end
+ env
+ }
+ end
+
+ def self.conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ env = next_callback.call env
+ target = env.target
+ value = env.value
+
+ if user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call target, value
+ end
+ env
+ }
+ end
+
+ def self.simple(next_callback, user_callback)
+ lambda { |env|
+ env = next_callback.call env
+ user_callback.call env.target, env.value
+ env
+ }
+ end
end
- def duplicates?(other)
- return false unless self.class == other.class
+ class Around
+ def self.build(next_callback, user_callback, user_conditions, chain_config)
+ if chain_config.key?(:terminator) && user_conditions.any?
+ halting_and_conditional(next_callback, user_callback, user_conditions)
+ elsif chain_config.key? :terminator
+ halting(next_callback, user_callback)
+ elsif user_conditions.any?
+ conditional(next_callback, user_callback, user_conditions)
+ else
+ simple(next_callback, user_callback)
+ end
+ end
+
+ private
+
+ def self.halting_and_conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ target = env.target
+ value = env.value
+ halted = env.halted
+
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call(target, value) {
+ env = next_callback.call env
+ env.value
+ }
+ env
+ else
+ next_callback.call env
+ end
+ }
+ end
+
+ def self.halting(next_callback, user_callback)
+ lambda { |env|
+ target = env.target
+ value = env.value
+
+ unless env.halted
+ user_callback.call(target, value) {
+ env = next_callback.call env
+ env.value
+ }
+ env
+ else
+ next_callback.call env
+ end
+ }
+ end
+
+ def self.conditional(next_callback, user_callback, user_conditions)
+ lambda { |env|
+ target = env.target
+ value = env.value
+
+ if user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call(target, value) {
+ env = next_callback.call env
+ env.value
+ }
+ env
+ else
+ next_callback.call env
+ end
+ }
+ end
- matches?(other.kind, other.filter)
+ def self.simple(next_callback, user_callback)
+ lambda { |env|
+ user_callback.call(env.target, env.value) {
+ env = next_callback.call env
+ env.value
+ }
+ env
+ }
+ end
end
+ end
- def _update_filter(filter_options, new_options)
- filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
- filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
+ class Callback #:nodoc:#
+ def self.build(chain, filter, kind, options)
+ new chain.name, filter, kind, options, chain.config
end
- def recompile!(_options)
- deprecate_per_key_option(_options)
- _update_filter(self.options, _options)
+ attr_accessor :kind, :name
+ attr_reader :chain_config
- recompile_options!
+ def initialize(name, filter, kind, options, chain_config)
+ @chain_config = chain_config
+ @name = name
+ @kind = kind
+ @filter = filter
+ @key = compute_identifier filter
+ @if = Array(options[:if])
+ @unless = Array(options[:unless])
end
- # Wraps code with filter
- def apply(code)
- case @kind
- when :before
- <<-RUBY_EVAL
- if !halted && #{@compiled_options}
- # This double assignment is to prevent warnings in 1.9.3 as
- # the `result` variable is not always used except if the
- # terminator code refers to it.
- result = result = #{@source}
- halted = (#{chain.config[:terminator]})
- if halted
- halted_callback_hook(#{@raw_filter.inspect.inspect})
- end
- end
- #{code}
- RUBY_EVAL
- when :after
- <<-RUBY_EVAL
- #{code}
- if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options}
- #{@source}
- end
- RUBY_EVAL
- when :around
- name = define_conditional_callback
- <<-RUBY_EVAL
- #{name}(halted) do
- #{code}
- value
- end
- RUBY_EVAL
- end
+ def filter; @key; end
+ def raw_filter; @filter; end
+
+ def merge(chain, new_options)
+ options = {
+ :if => @if.dup,
+ :unless => @unless.dup
+ }
+
+ options[:if].concat Array(new_options.fetch(:unless, []))
+ options[:unless].concat Array(new_options.fetch(:if, []))
+
+ self.class.build chain, @filter, @kind, options
end
- private
+ def matches?(_kind, _filter)
+ @kind == _kind && filter == _filter
+ end
- def compute_identifier(filter)
- case filter
- when String, ::Proc
- filter.object_id
+ def duplicates?(other)
+ case @filter
+ when Symbol, String
+ matches?(other.kind, other.filter)
else
- filter
+ false
end
end
- # Compile around filters with conditions into proxy methods
- # that contain the conditions.
- #
- # For `set_callback :save, :around, :filter_name, if: :condition':
- #
- # def _conditional_callback_save_17
- # if condition
- # filter_name do
- # yield self
- # end
- # else
- # yield self
- # end
- # end
- def define_conditional_callback
- name = "_conditional_callback_#{@kind}_#{next_id}"
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}(halted)
- if #{@compiled_options} && !halted
- #{@source} do
- yield self
- end
- else
- yield self
- end
- end
- RUBY_EVAL
- name
- end
-
- # Options support the same options as filters themselves (and support
- # symbols, string, procs, and objects), so compile a conditional
- # expression based on the options.
- def recompile_options!
- conditions = ["true"]
-
- unless options[:if].empty?
- conditions << Array(_compile_source(options[:if]))
- end
+ # Wraps code with filter
+ def apply(next_callback)
+ user_conditions = conditions_lambdas
+ user_callback = make_lambda @filter
- unless options[:unless].empty?
- conditions << Array(_compile_source(options[:unless])).map {|f| "!#{f}"}
+ case kind
+ when :before
+ Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter)
+ when :after
+ Filters::After.build(next_callback, user_callback, user_conditions, chain_config)
+ when :around
+ Filters::Around.build(next_callback, user_callback, user_conditions, chain_config)
end
-
- @compiled_options = conditions.flatten.join(" && ")
end
- def _method_name_for_object_filter(kind, filter, append_next_id = true)
- class_name = filter.kind_of?(Class) ? filter.to_s : filter.class.to_s
- class_name.gsub!(/<|>|#/, '')
- class_name.gsub!(/\/|:/, "_")
+ private
- method_name = "_callback_#{kind}_#{class_name}"
- method_name << "_#{next_id}" if append_next_id
- method_name
+ def invert_lambda(l)
+ lambda { |*args, &blk| !l.call(*args, &blk) }
end
# Filters support:
#
- # Arrays:: Used in conditions. This is used to specify
- # multiple conditions. Used internally to
- # merge conditions from skip_* filters.
# Symbols:: A method to call.
# Strings:: Some content to evaluate.
# Procs:: A proc to call with the object.
@@ -294,87 +402,105 @@ module ActiveSupport
# All of these objects are compiled into methods and handled
# the same after this point:
#
- # Arrays:: Merged together into a single filter.
# Symbols:: Already methods.
# Strings:: class_eval'ed into methods.
# Procs:: define_method'ed into methods.
# Objects::
# a method is created that calls the before_foo method
# on the object.
- def _compile_source(filter)
+ def make_lambda(filter)
case filter
- when Array
- filter.map {|f| _compile_source(f)}
when Symbol
- filter
+ lambda { |target, _, &blk| target.send filter, &blk }
when String
- "(#{filter})"
+ l = eval "lambda { |value| #{filter} }"
+ lambda { |target, value| target.instance_exec(value, &l) }
when ::Proc
- method_name = "_callback_#{@kind}_#{next_id}"
- @klass.send(:define_method, method_name, &filter)
- return method_name if filter.arity <= 0
+ if filter.arity > 1
+ return lambda { |target, _, &block|
+ raise ArgumentError unless block
+ target.instance_exec(target, block, &filter)
+ }
+ end
- method_name << (filter.arity == 1 ? "(self)" : "(self, ::Proc.new)")
+ if filter.arity <= 0
+ lambda { |target, _| target.instance_exec(&filter) }
+ else
+ lambda { |target, _| target.instance_exec(target, &filter) }
+ end
else
- method_name = _method_name_for_object_filter(kind, filter)
- @klass.send(:define_method, "#{method_name}_object") { filter }
-
- _normalize_legacy_filter(kind, filter)
- scopes = Array(chain.config[:scope])
+ scopes = Array(chain_config[:scope])
method_to_call = scopes.map{ |s| public_send(s) }.join("_")
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{method_name}(&blk)
- #{method_name}_object.send(:#{method_to_call}, self, &blk)
- end
- RUBY_EVAL
-
- method_name
+ lambda { |target, _, &blk|
+ filter.public_send method_to_call, target, &blk
+ }
end
end
- def _normalize_legacy_filter(kind, filter)
- if !filter.respond_to?(kind) && filter.respond_to?(:filter)
- message = "Filter object with #filter method is deprecated. Define method corresponding " \
- "to filter type (#before, #after or #around)."
- ActiveSupport::Deprecation.warn message
- filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{kind}(context, &block) filter(context, &block) end
- RUBY_EVAL
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around)
- message = "Filter object with #before and #after methods is deprecated. Define #around method instead."
- ActiveSupport::Deprecation.warn message
- def filter.around(context)
- should_continue = before(context)
- yield if should_continue
- after(context)
- end
+ def compute_identifier(filter)
+ case filter
+ when String, ::Proc
+ filter.object_id
+ else
+ filter
end
end
+
+ def conditions_lambdas
+ @if.map { |c| make_lambda c } +
+ @unless.map { |c| invert_lambda make_lambda c }
+ end
end
# An Array with a compile method.
- class CallbackChain < Array #:nodoc:#
+ class CallbackChain #:nodoc:#
+ include Enumerable
+
attr_reader :name, :config
def initialize(name, config)
@name = name
@config = {
- :terminator => "false",
:scope => [ :kind ]
}.merge!(config)
+ @chain = []
+ @callbacks = nil
+ @mutex = Mutex.new
+ end
+
+ def each(&block); @chain.each(&block); end
+ def index(o); @chain.index(o); end
+ def empty?; @chain.empty?; end
+
+ def insert(index, o)
+ @callbacks = nil
+ @chain.insert(index, o)
+ end
+
+ def delete(o)
+ @callbacks = nil
+ @chain.delete(o)
+ end
+
+ def clear
+ @callbacks = nil
+ @chain.clear
+ self
+ end
+
+ def initialize_copy(other)
+ @callbacks = nil
+ @chain = other.chain.dup
+ @mutex = Mutex.new
end
def compile
- method = ["value = nil", "halted = false"]
- callbacks = "value = !halted && (!block_given? || yield)"
- reverse_each do |callback|
- callbacks = callback.apply(callbacks)
+ @callbacks || @mutex.synchronize do
+ @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback|
+ callback.apply chain
+ end
end
- method << callbacks
-
- method << "value"
- method.join("\n")
end
def append(*callbacks)
@@ -385,69 +511,45 @@ module ActiveSupport
callbacks.each { |c| prepend_one(c) }
end
+ protected
+ def chain; @chain; end
+
private
def append_one(callback)
+ @callbacks = nil
remove_duplicates(callback)
- push(callback)
+ @chain.push(callback)
end
def prepend_one(callback)
+ @callbacks = nil
remove_duplicates(callback)
- unshift(callback)
+ @chain.unshift(callback)
end
def remove_duplicates(callback)
- delete_if { |c| callback.duplicates?(c) }
+ @callbacks = nil
+ @chain.delete_if { |c| callback.duplicates?(c) }
end
end
module ClassMethods
- # This method defines callback chain method for the given kind
- # if it was not yet defined.
- # This generated method plays caching role.
- def __define_callbacks(kind, object) #:nodoc:
- name = __callback_runner_name(kind)
- unless object.respond_to?(name, true)
- str = object.send("_#{kind}_callbacks").compile
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}() #{str} end
- protected :#{name}
- RUBY_EVAL
- end
- name
- end
-
- def __reset_runner(symbol)
- name = __callback_runner_name(symbol)
- undef_method(name) if method_defined?(name)
- end
-
- def __callback_runner_name_cache
- @__callback_runner_name_cache ||= ThreadSafe::Cache.new {|cache, kind| cache[kind] = __generate_callback_runner_name(kind) }
- end
-
- def __generate_callback_runner_name(kind)
- "_run__#{self.name.hash.abs}__#{kind}__callbacks"
- end
-
- def __callback_runner_name(kind)
- __callback_runner_name_cache[kind]
- end
-
- # This is used internally to append, prepend and skip callbacks to the
- # CallbackChain.
- def __update_callbacks(name, filters = [], block = nil) #:nodoc:
+ def normalize_callback_params(filters, block) # :nodoc:
type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
+ [type, filters, options.dup]
+ end
+ # This is used internally to append, prepend and skip callbacks to the
+ # CallbackChain.
+ def __update_callbacks(name) #:nodoc:
([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)
+ chain = target.get_callbacks name
+ yield target, chain.dup
end
end
@@ -487,16 +589,15 @@ module ActiveSupport
# * <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
-
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
- mapped ||= filters.map do |filter|
- Callback.build(chain, filter, type, options.dup, self)
- end
+ type, filters, options = normalize_callback_params(filter_list, block)
+ self_chain = get_callbacks name
+ mapped = filters.map do |filter|
+ Callback.build(self_chain, filter, type, options)
+ end
+ __update_callbacks(name) do |target, chain|
options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
-
- target.send("_#{name}_callbacks=", chain)
+ target.set_callbacks name, chain
end
end
@@ -508,36 +609,34 @@ module ActiveSupport
# skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
# end
def skip_callback(name, *filter_list, &block)
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
+ type, filters, options = normalize_callback_params(filter_list, block)
+
+ __update_callbacks(name) do |target, chain|
filters.each do |filter|
filter = chain.find {|c| c.matches?(type, filter) }
if filter && options.any?
- new_filter = filter.clone(chain, self)
+ new_filter = filter.merge(chain, options)
chain.insert(chain.index(filter), new_filter)
- new_filter.recompile!(options)
end
chain.delete(filter)
end
- target.send("_#{name}_callbacks=", chain)
+ target.set_callbacks name, chain
end
end
# Remove all set callbacks for the given event.
def reset_callbacks(symbol)
- callbacks = send("_#{symbol}_callbacks")
+ callbacks = get_callbacks symbol
ActiveSupport::DescendantsTracker.descendants(self).each do |target|
- chain = target.send("_#{symbol}_callbacks").dup
+ chain = target.get_callbacks(symbol).dup
callbacks.each { |c| chain.delete(c) }
- target.send("_#{symbol}_callbacks=", chain)
- target.__reset_runner(symbol)
+ target.set_callbacks symbol, chain
end
- self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
-
- __reset_runner(symbol)
+ self.set_callbacks symbol, callbacks.dup.clear
end
# Define sets of events in the object lifecycle that support callbacks.
@@ -609,11 +708,28 @@ module ActiveSupport
# would call <tt>Audit#save</tt>.
def define_callbacks(*callbacks)
config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
+ if config.key?(:terminator) && String === config[:terminator]
+ ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda"
+ value = config[:terminator]
+ l = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__
+ config[:terminator] = lambda { |target, result| target.instance_exec(result, &l) }
+ end
+
callbacks.each do |callback|
class_attribute "_#{callback}_callbacks"
- send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
+ set_callbacks callback, CallbackChain.new(callback, config)
end
end
+
+ protected
+
+ def get_callbacks(name)
+ send "_#{name}_callbacks"
+ end
+
+ def set_callbacks(name, callbacks)
+ send "_#{name}_callbacks=", callbacks
+ end
end
end
end
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index eeeba60839..b6ae86b583 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -98,6 +98,12 @@ module ActiveSupport
# include Bar # works, Bar takes care now of its dependencies
# end
module Concern
+ class MultipleIncludedBlocks < StandardError #:nodoc:
+ def initialize
+ super "Cannot define multiple 'included' blocks for a Concern"
+ end
+ end
+
def self.extended(base) #:nodoc:
base.instance_variable_set("@_dependencies", [])
end
@@ -117,6 +123,8 @@ module ActiveSupport
def included(base = nil, &block)
if base.nil?
+ raise MultipleIncludedBlocks if instance_variable_defined?("@_included_block")
+
@_included_block = block
else
super
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 6d49b7b6e1..6fa9967a28 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -71,7 +71,7 @@ class Class
def class_attribute(*attrs)
options = attrs.extract_options!
# double assignment is used to avoid "assigned but unused variable" warning
- instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, 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)
instance_predicate = options.fetch(:instance_predicate, true)
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 106a65610c..06e4847e82 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -119,4 +119,15 @@ class Date
options.fetch(:day, day)
)
end
+
+ # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there.
+ def compare_with_coercion(other)
+ if other.is_a?(Time)
+ self.to_datetime <=> other
+ else
+ compare_without_coercion(other)
+ end
+ end
+ alias_method :compare_without_coercion, :<=>
+ alias_method :<=>, :compare_with_coercion
end
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index c7a8348b1d..56c79c04bd 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/aliasing'
+
module Marshal
class << self
def load_with_autoloading(source)
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 3af66aaf2f..3a07401c8a 100644
--- a/activesupport/lib/active_support/core_ext/range/include_range.rb
+++ b/activesupport/lib/active_support/core_ext/range/include_range.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/aliasing'
+
class Range
# Extends the default Range#include? to support range comparisons.
# (1..5).include?(1..5) # => true
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index d2a2db32bb..6691fc0995 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -15,6 +15,7 @@ class String
# "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
# "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
# "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC
+ # "12/13/2012".to_time # => ArgumentError: argument out of range
def to_time(form = :local)
parts = Date._parse(self, false)
return if parts.empty?
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 0b506a6030..56e8a5f98d 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -185,7 +185,7 @@ class String
#
# Singular names are not handled correctly.
#
- # 'business'.classify # => "Busines"
+ # 'business'.classify # => "Business"
def classify
ActiveSupport::Inflector.classify(self)
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 1b20592e4c..0a81a8393d 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -91,7 +91,7 @@ module ActiveSupport
#
# This value can be later fetched using either +:key+ or +'key'+.
def []=(key, value)
- regular_writer(convert_key(key), convert_value(value))
+ regular_writer(convert_key(key), convert_value(value, for: :assignment))
end
alias_method :store, :[]=
@@ -229,7 +229,11 @@ module ActiveSupport
# Convert to a regular hash with string keys.
def to_hash
- Hash.new(default).merge!(self)
+ _new_hash= {}
+ each do |key, value|
+ _new_hash[convert_key(key)] = convert_value(value, for: :to_hash)
+ end
+ Hash.new(default).merge!(_new_hash)
end
protected
@@ -237,12 +241,18 @@ module ActiveSupport
key.kind_of?(Symbol) ? key.to_s : key
end
- def convert_value(value)
+ def convert_value(value, options = {})
if value.is_a? Hash
- value.nested_under_indifferent_access
+ if options[:for] == :to_hash
+ value.to_hash
+ else
+ value.nested_under_indifferent_access
+ end
elsif value.is_a?(Array)
- value = value.dup if value.frozen?
- value.map! { |e| convert_value(e) }
+ unless options[:for] == :assignment
+ value = value.dup
+ end
+ value.map! { |e| convert_value(e, options) }
else
value
end
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index a4a32b2ad0..30833a4cb1 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -1,6 +1,6 @@
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/module/delegation'
-require 'multi_json'
+require 'json'
module ActiveSupport
# Look for and parse json strings that look like ISO 8601 times.
@@ -13,8 +13,8 @@ module ActiveSupport
#
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
# => {"team" => "rails", "players" => "36"}
- def decode(json, options ={})
- data = MultiJson.load(json, options)
+ def decode(json, proc = nil, options = {})
+ data = ::JSON.load(json, proc, options)
if ActiveSupport.parse_json_times
convert_dates_from(data)
else
@@ -22,23 +22,6 @@ module ActiveSupport
end
end
- def engine
- MultiJson.adapter
- end
- alias :backend :engine
-
- def engine=(name)
- MultiJson.use(name)
- end
- alias :backend= :engine=
-
- def with_backend(name)
- old_backend, self.backend = backend, name
- yield
- ensure
- self.backend = old_backend
- end
-
# Returns the class of the error that will be raised when there is an
# error in decoding JSON. Using this method means you won't directly
# depend on the ActiveSupport's JSON implementation, in case it changes
@@ -50,7 +33,7 @@ module ActiveSupport
# Rails.logger.warn("Attempted to decode invalid JSON: #{some_string}")
# end
def parse_error
- MultiJson::DecodeError
+ ::JSON::ParserError
end
private
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 9bf1ea35b3..77b5d8d227 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,6 +1,7 @@
+#encoding: us-ascii
+
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/module/delegation'
-require 'active_support/json/variable'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
@@ -98,13 +99,18 @@ module ActiveSupport
"\010" => '\b',
"\f" => '\f',
"\n" => '\n',
+ "\xe2\x80\xa8" => '\u2028',
+ "\xe2\x80\xa9" => '\u2029',
"\r" => '\r',
"\t" => '\t',
'"' => '\"',
'\\' => '\\\\',
'>' => '\u003E',
'<' => '\u003C',
- '&' => '\u0026' }
+ '&' => '\u0026',
+ "#{0xe2.chr}#{0x80.chr}#{0xa8.chr}" => '\u2028',
+ "#{0xe2.chr}#{0x80.chr}#{0xa9.chr}" => '\u2029',
+ }
class << self
# If true, use ISO 8601 format for dates and times. Otherwise, fall back
@@ -121,9 +127,9 @@ module ActiveSupport
def escape_html_entities_in_json=(value)
self.escape_regex = \
if @escape_html_entities_in_json = value
- /[\x00-\x1F"\\><&]/
+ /\xe2\x80\xa8|\xe2\x80\xa9|[\x00-\x1F"\\><&]/
else
- /[\x00-\x1F"\\]/
+ /\xe2\x80\xa8|\xe2\x80\xa9|[\x00-\x1F"\\]/
end
end
diff --git a/activesupport/lib/active_support/json/variable.rb b/activesupport/lib/active_support/json/variable.rb
deleted file mode 100644
index d69dab6408..0000000000
--- a/activesupport/lib/active_support/json/variable.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'active_support/deprecation'
-
-module ActiveSupport
- module JSON
- # Deprecated: A string that returns itself as its JSON-encoded form.
- class Variable < String
- def initialize(*args)
- message = 'ActiveSupport::JSON::Variable is deprecated and will be removed in Rails 4.1. ' \
- 'For your own custom JSON literals, define #as_json and #encode_json yourself.'
- ActiveSupport::Deprecation.warn message
- super
- end
-
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) self end #:nodoc:
- end
- end
-end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index f1dfff738c..04e6b71580 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -145,7 +145,7 @@ module ActiveSupport
ncp << (HANGUL_TBASE + tindex) unless tindex == 0
decomposed.concat ncp
# if the codepoint is decomposable in with the current decomposition type
- elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatability)
+ elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatibility)
decomposed.concat decompose(type, ncp.dup)
else
decomposed << cp
@@ -263,9 +263,9 @@ module ActiveSupport
when :c
compose(reorder_characters(decompose(:canonical, codepoints)))
when :kd
- reorder_characters(decompose(:compatability, codepoints))
+ reorder_characters(decompose(:compatibility, codepoints))
when :kc
- compose(reorder_characters(decompose(:compatability, codepoints)))
+ compose(reorder_characters(decompose(:compatibility, codepoints)))
else
raise ArgumentError, "#{form} is not a valid normalization variant", caller
end.pack('U*')
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 7588fdb67c..99fe03e6d0 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -79,6 +79,13 @@ module ActiveSupport
def initialize(pattern, delegate)
@pattern = pattern
@delegate = delegate
+ @can_publish = delegate.respond_to?(:publish)
+ end
+
+ def publish(name, *args)
+ if @can_publish
+ @delegate.publish name, *args
+ end
end
def start(name, id, payload)
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index c9518bda79..e03bb4ca0f 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -40,6 +40,14 @@ module ActiveSupport
end
end
+ # +InheritableOptions+ provides a constructor to build an +OrderedOptions+
+ # hash inherited from the another hash.
+ #
+ # Use this if you already have some hash and you want to create a new one based on it.
+ #
+ # h = ActiveSupport::InheritableOptions.new({ girl: 'Mary', boy: 'John' })
+ # h.girl # => 'Mary'
+ # h.boy # => 'John'
class InheritableOptions < OrderedOptions
def initialize(parent = nil)
if parent.kind_of?(OrderedOptions)
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 8b392c36d0..2f27aff2bd 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,10 +1,9 @@
gem 'minitest' # make sure we get the gem, not stdlib
-require 'minitest/unit'
+require 'minitest'
require 'active_support/testing/tagged_logging'
require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/assertions'
require 'active_support/testing/deprecation'
-require 'active_support/testing/pending'
require 'active_support/testing/declarative'
require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
@@ -16,10 +15,28 @@ begin
rescue LoadError
end
+module Minitest # :nodoc:
+ class << self
+ remove_method :__run
+ end
+
+ def self.__run reporter, options # :nodoc:
+ # FIXME: MT5's runnables is not ordered. This is needed because
+ # we have have tests have cross-class order-dependent bugs.
+ suites = Runnable.runnables.sort_by { |ts| ts.name.to_s }
+
+ parallel, serial = suites.partition { |s| s.test_order == :parallel }
+
+ ParallelEach.new(parallel).map { |suite| suite.run reporter, options } +
+ serial.map { |suite| suite.run reporter, options }
+ end
+end
+
module ActiveSupport
- class TestCase < ::MiniTest::Unit::TestCase
- Assertion = MiniTest::Assertion
- alias_method :method_name, :__name__
+ class TestCase < ::Minitest::Test
+ Assertion = Minitest::Assertion
+
+ alias_method :method_name, :name
$tags = {}
def self.for_tag(tag)
@@ -36,7 +53,6 @@ module ActiveSupport
include ActiveSupport::Testing::SetupAndTeardown
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
- include ActiveSupport::Testing::Pending
extend ActiveSupport::Testing::Declarative
# test/unit backwards compatibility methods
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
index c446adc16d..5aa5f46310 100644
--- a/activesupport/lib/active_support/testing/autorun.rb
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -1,5 +1,5 @@
gem 'minitest'
-require 'minitest/unit'
+require 'minitest'
-MiniTest::Unit.autorun
+Minitest.autorun
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index e16b73a036..9c52ae7768 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -72,16 +72,12 @@ module ActiveSupport
end
end
- def run(runner)
- _run_class_setup
-
- serialized = run_in_isolation do |isolated_runner|
- super(isolated_runner)
+ def run
+ serialized = run_in_isolation do
+ super
end
- retval, proxy = Marshal.load(serialized)
- proxy.__replay__(runner)
- retval
+ Marshal.load(serialized)
end
module Forking
@@ -90,9 +86,8 @@ module ActiveSupport
pid = fork do
read.close
- proxy = ProxyTestResult.new
- retval = yield proxy
- write.puts [Marshal.dump([retval, proxy])].pack("m")
+ yield
+ write.puts [Marshal.dump(self.dup)].pack("m")
exit!
end
diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb
deleted file mode 100644
index b04bbbbaea..0000000000
--- a/activesupport/lib/active_support/testing/pending.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'active_support/deprecation'
-
-module ActiveSupport
- module Testing
- module Pending # :nodoc:
- unless defined?(Spec)
- def pending(description = "", &block)
- ActiveSupport::Deprecation.warn("#pending is deprecated and will be removed in Rails 4.1, please use #skip instead.")
- skip(description.blank? ? nil : description)
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb
index 9d43eb179f..f4cee64091 100644
--- a/activesupport/lib/active_support/testing/tagged_logging.rb
+++ b/activesupport/lib/active_support/testing/tagged_logging.rb
@@ -7,7 +7,7 @@ module ActiveSupport
def before_setup
if tagged_logger
- heading = "#{self.class}: #{__name__}"
+ heading = "#{self.class}: #{name}"
divider = '-' * heading.size
tagged_logger.info divider
tagged_logger.info heading
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 9659f141cb..f8e2ce22fa 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -525,7 +525,7 @@ module CallbacksTest
class CallbackTerminator
include ActiveSupport::Callbacks
- define_callbacks :save, :terminator => "result == :halt"
+ define_callbacks :save, :terminator => ->(_,result) { result == :halt }
set_callback :save, :before, :first
set_callback :save, :before, :second
@@ -718,7 +718,7 @@ module CallbacksTest
def test_termination_invokes_hook
terminator = CallbackTerminator.new
terminator.save
- assert_equal ":second", terminator.halted
+ assert_equal :second, terminator.halted
end
def test_block_never_called_if_terminated
@@ -772,22 +772,6 @@ module CallbacksTest
end
end
- class PerKeyOptionDeprecationTest < ActiveSupport::TestCase
-
- def test_per_key_option_deprecation
- assert_raise NotImplementedError do
- Phone.class_eval do
- set_callback :save, :before, :before_save1, :per_key => {:if => "true"}
- end
- end
- assert_raise NotImplementedError do
- Phone.class_eval do
- skip_callback :save, :before, :before_save1, :per_key => {:if => "true"}
- end
- end
- end
- end
-
class ExcludingDuplicatesCallbackTest < ActiveSupport::TestCase
def test_excludes_duplicates_in_separate_calls
model = DuplicatingCallbacks.new
@@ -802,6 +786,46 @@ module CallbacksTest
end
end
+ class CallbackProcTest < ActiveSupport::TestCase
+ def build_class(callback)
+ Class.new {
+ include ActiveSupport::Callbacks
+ define_callbacks :foo
+ set_callback :foo, :before, callback
+ def run; run_callbacks :foo; end
+ }
+ end
+
+ def test_proc_arity_0
+ calls = []
+ klass = build_class(->() { calls << :foo })
+ klass.new.run
+ assert_equal [:foo], calls
+ end
+
+ def test_proc_arity_1
+ calls = []
+ klass = build_class(->(o) { calls << o })
+ instance = klass.new
+ instance.run
+ assert_equal [instance], calls
+ end
+
+ def test_proc_arity_2
+ assert_raises(ArgumentError) do
+ klass = build_class(->(x,y) { })
+ klass.new.run
+ end
+ end
+
+ def test_proc_negative_called_with_empty_list
+ calls = []
+ klass = build_class(->(*args) { calls << args })
+ klass.new.run
+ assert_equal [[]], calls
+ end
+ end
+
class ConditionalTests < ActiveSupport::TestCase
def build_class(callback)
Class.new {
@@ -824,8 +848,9 @@ module CallbacksTest
include ActiveSupport::Callbacks
define_callbacks :foo, :scope => [:name]
set_callback :foo, :before, :foo, :if => callback
- def foo; end
def run; run_callbacks :foo; end
+ private
+ def foo; end
}
object = klass.new
object.run
@@ -873,6 +898,46 @@ module CallbacksTest
end
end
+ class ResetCallbackTest < ActiveSupport::TestCase
+ def build_class(memo)
+ klass = Class.new {
+ include ActiveSupport::Callbacks
+ define_callbacks :foo
+ set_callback :foo, :before, :hello
+ def run; run_callbacks :foo; end
+ }
+ klass.class_eval {
+ define_method(:hello) { memo << :hi }
+ }
+ klass
+ end
+
+ def test_reset_callbacks
+ events = []
+ klass = build_class events
+ klass.new.run
+ assert_equal 1, events.length
+
+ klass.reset_callbacks :foo
+ klass.new.run
+ assert_equal 1, events.length
+ end
+
+ def test_reset_impacts_subclasses
+ events = []
+ klass = build_class events
+ subclass = Class.new(klass) { set_callback :foo, :before, :world }
+ subclass.class_eval { define_method(:world) { events << :world } }
+
+ subclass.new.run
+ assert_equal 2, events.length
+
+ klass.reset_callbacks :foo
+ subclass.new.run
+ assert_equal 3, events.length
+ end
+ end
+
class CallbackTypeTest < ActiveSupport::TestCase
def build_class(callback, n = 10)
Class.new {
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index 912ce30c29..8e2c298fc6 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -91,4 +91,18 @@ class ConcernTest < ActiveSupport::TestCase
@klass.send(:include, Foo)
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
end
+
+ def test_raise_on_multiple_included_calls
+ assert_raises(ActiveSupport::Concern::MultipleIncludedBlocks) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ included do
+ end
+
+ included do
+ end
+ end
+ end
+ end
end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index efa7582ab0..384064d81f 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -96,6 +96,10 @@ class ArrayExtToSentenceTests < ActiveSupport::TestCase
assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options)
assert_equal({ words_connector: ' ' }, options)
end
+
+ def test_with_blank_elements
+ assert_equal ", one, , two, and three", [nil, 'one', '', 'two', 'three'].to_sentence
+ end
end
class ArrayExtToSTests < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index b1adf6d396..aa5e4461fd 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -48,6 +48,10 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_compare_to_time
+ assert Date.yesterday < Time.now
+ end
+
def test_to_datetime
assert_equal DateTime.civil(2005, 2, 21), Date.new(2005, 2, 21).to_datetime
assert_equal 0, Date.new(2005, 2, 21).to_datetime.offset # use UTC offset
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 30d95b75bc..39bd0a2dd4 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -490,6 +490,10 @@ class HashExtTest < ActiveSupport::TestCase
roundtrip = mixed_with_default.with_indifferent_access.to_hash
assert_equal @strings, roundtrip
assert_equal '1234', roundtrip.default
+ new_to_hash = @nested_mixed.with_indifferent_access.to_hash
+ assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
end
def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
@@ -499,9 +503,21 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal [1], hash[:a]
end
+ def test_with_indifferent_access_has_no_side_effects_on_existing_hash
+ hash = {content: [{:foo => :bar, 'bar' => 'baz'}]}
+ hash.with_indifferent_access
+
+ assert_equal [:foo, "bar"], hash[:content].first.keys
+ end
+
def test_indifferent_hash_with_array_of_hashes
hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access
assert_equal "1", hash[:urls][:url].first[:address]
+
+ hash = hash.to_hash
+ assert_not hash.instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
end
def test_should_preserve_array_subclass_when_value_is_array
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index f22ae3ccac..1da72eb17d 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -77,7 +77,7 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
assert_equal @dtnow.advance(:days => 1).advance(:months => 2), @dtnow + 1.day + 2.months
end
- def test_duration_after_convertion_is_no_longer_accurate
+ def test_duration_after_conversion_is_no_longer_accurate
assert_equal 30.days.to_i.since(@now), 1.month.to_i.since(@now)
assert_equal 365.25.days.to_f.since(@now), 1.year.to_f.since(@now)
assert_equal 30.days.to_i.since(@dtnow), 1.month.to_i.since(@dtnow)
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 3e2355ae23..2f8723a074 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -54,7 +54,7 @@ class RangeTest < ActiveSupport::TestCase
assert((1...10) === (1...10))
end
- def test_should_compare_other_with_exlusive_end
+ def test_should_compare_other_with_exclusive_end
assert((1..10) === (1...10))
end
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index a2d1d0dc39..34ed866848 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -55,35 +55,25 @@ class TestJSONDecoding < ActiveSupport::TestCase
%q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}
}
- backends = [:ok_json]
- backends << :json_gem if defined?(::JSON)
- backends << :yajl if defined?(::Yajl)
-
- backends.each do |backend|
- TESTS.each do |json, expected|
- test "json decodes #{json} with the #{backend} backend" do
- prev = ActiveSupport.parse_json_times
- ActiveSupport.parse_json_times = true
- silence_warnings do
- ActiveSupport::JSON.with_backend backend do
- assert_equal expected, ActiveSupport::JSON.decode(json)
- end
- end
- ActiveSupport.parse_json_times = prev
- end
- end
-
- test "json decodes time json with time parsing disabled with the #{backend} backend" do
+ TESTS.each do |json, expected|
+ test "json decodes #{json}" do
prev = ActiveSupport.parse_json_times
- ActiveSupport.parse_json_times = false
- expected = {"a" => "2007-01-01 01:12:34 Z"}
- ActiveSupport::JSON.with_backend backend do
- assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
+ ActiveSupport.parse_json_times = true
+ silence_warnings do
+ assert_equal expected, ActiveSupport::JSON.decode(json)
end
ActiveSupport.parse_json_times = prev
end
end
+ test "json decodes time json with time parsing disabled" do
+ prev = ActiveSupport.parse_json_times
+ ActiveSupport.parse_json_times = false
+ expected = {"a" => "2007-01-01 01:12:34 Z"}
+ assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
+ ActiveSupport.parse_json_times = prev
+ end
+
def test_failed_json_decoding
assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) }
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 8686dcf929..ed1326705c 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -45,8 +45,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")],
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
[ 'http://test.host/posts/1', %("http://test.host/posts/1")],
- [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
- %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F") ]]
+ [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\342\200\250\342\200\251",
+ %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F\\u2028\\u2029") ]]
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
@@ -96,13 +96,6 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
- def test_json_variable
- assert_deprecated do
- assert_equal ActiveSupport::JSON::Variable.new('foo'), 'foo'
- assert_equal ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")'
- end
- end
-
def test_hash_encoding
assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
@@ -195,7 +188,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_nothing_raised do
hash = {
"CHI" => {
- :dislay_name => "chicago",
+ :display_name => "chicago",
:latitude => 123.234
}
}
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index 33627a4e74..f729f0a95b 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -81,6 +81,20 @@ module Notifications
end
end
+ class TestSubscriber
+ attr_reader :starts, :finishes, :publishes
+
+ def initialize
+ @starts = []
+ @finishes = []
+ @publishes = []
+ end
+
+ def start(*args); @starts << args; end
+ def finish(*args); @finishes << args; end
+ def publish(*args); @publishes << args; end
+ end
+
class SyncPubSubTest < TestCase
def test_events_are_published_to_a_listener
@notifier.publish :foo
@@ -144,6 +158,14 @@ module Notifications
assert_equal [[:foo]], @another
end
+ def test_publish_with_subscriber
+ subscriber = TestSubscriber.new
+ @notifier.subscribe nil, subscriber
+ @notifier.publish :foo
+
+ assert_equal [[:foo]], subscriber.publishes
+ end
+
private
def event(*args)
args
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index e099e47e0e..ec9d231125 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -97,7 +97,7 @@ class RescuableTest < ActiveSupport::TestCase
assert_equal expected, result
end
- def test_children_should_inherit_rescue_defintions_from_parents_and_child_rescue_should_be_appended
+ def test_children_should_inherit_rescue_definitions_from_parents_and_child_rescue_should_be_appended
expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "CoolError"]
result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first}
assert_equal expected, result
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
deleted file mode 100644
index a1bdcf2396..0000000000
--- a/activesupport/test/test_case_test.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-require 'abstract_unit'
-
-module ActiveSupport
- class TestCaseTest < ActiveSupport::TestCase
- class FakeRunner
- attr_reader :puked
-
- def initialize
- @puked = []
- end
-
- def puke(klass, name, e)
- @puked << [klass, name, e]
- end
-
- def options
- nil
- end
-
- def record(*args)
- end
-
- def info_signal
- end
- end
-
- def test_standard_error_raised_within_setup_callback_is_puked
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- setup :bad_callback
- def bad_callback; raise 'oh noes' end
- def test_true; assert true end
- end
-
- test_name = 'test_true'
- fr = FakeRunner.new
-
- test = tc.new test_name
- test.run fr
- klass, name, exception = *fr.puked.first
-
- assert_equal tc, klass
- assert_equal test_name, name
- assert_equal 'oh noes', exception.message
- end
-
- def test_standard_error_raised_within_teardown_callback_is_puked
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- teardown :bad_callback
- def bad_callback; raise 'oh noes' end
- def test_true; assert true end
- end
-
- test_name = 'test_true'
- fr = FakeRunner.new
-
- test = tc.new test_name
- test.run fr
- klass, name, exception = *fr.puked.first
-
- assert_equal tc, klass
- assert_equal test_name, name
- assert_equal 'oh noes', exception.message
- end
-
- def test_passthrough_exception_raised_within_test_method_is_not_rescued
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- def test_which_raises_interrupt; raise Interrupt; end
- end
-
- test_name = 'test_which_raises_interrupt'
- fr = FakeRunner.new
-
- test = tc.new test_name
- assert_raises(Interrupt) { test.run fr }
- end
-
- def test_passthrough_exception_raised_within_setup_callback_is_not_rescued
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- setup :callback_which_raises_interrupt
- def callback_which_raises_interrupt; raise Interrupt; end
- def test_true; assert true end
- end
-
- test_name = 'test_true'
- fr = FakeRunner.new
-
- test = tc.new test_name
- assert_raises(Interrupt) { test.run fr }
- end
-
- def test_passthrough_exception_raised_within_teardown_callback_is_not_rescued
- tc = Class.new(TestCase) do
- def self.name; nil; end
-
- teardown :callback_which_raises_interrupt
- def callback_which_raises_interrupt; raise Interrupt; end
- def test_true; assert true end
- end
-
- test_name = 'test_true'
- fr = FakeRunner.new
-
- test = tc.new test_name
- assert_raises(Interrupt) { test.run fr }
- end
-
- def test_pending_deprecation
- assert_deprecated do
- pending "should use #skip instead"
- end
- end
- end
-end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 3e6ac811a4..68f9ec6c00 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -201,6 +201,6 @@ class TestCaseTaggedLoggingTest < ActiveSupport::TestCase
end
def test_logs_tagged_with_current_test_case
- assert_match "#{self.class}: #{__name__}\n", @out.string
+ assert_match "#{self.class}: #{name}\n", @out.string
end
end
diff --git a/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb
deleted file mode 100644
index 294d6595f7..0000000000
--- a/activesupport/test/ts_isolated.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'active_support/testing/autorun'
-require 'active_support/test_case'
-require 'rbconfig'
-require 'active_support/core_ext/kernel/reporting'
-
-class TestIsolated < ActiveSupport::TestCase
- ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
-
- Dir["#{File.dirname(__FILE__)}/**/*_test.rb"].each do |file|
- define_method("test #{file}") do
- command = "#{ruby} -Ilib:test #{file}"
- result = silence_stderr { `#{command}` }
- assert $?.to_i.zero?, "#{command}\n#{result}"
- end
- end
-end