diff options
author | Ryan Bigg <radarlistener@gmail.com> | 2008-10-06 11:57:12 +1030 |
---|---|---|
committer | Ryan Bigg <radarlistener@gmail.com> | 2008-10-06 11:57:12 +1030 |
commit | a21d8f632fe8aa3bf4c1b83accc7a2dd230c28e7 (patch) | |
tree | bb0b0e9423d4e259de6041e661bba119f7871faa /activesupport | |
parent | b340337aaff5b59fdf2110207fec3e1c43f1380a (diff) | |
parent | 6090513cfb8acb5554a6653a6f2cb87648585d41 (diff) | |
download | rails-a21d8f632fe8aa3bf4c1b83accc7a2dd230c28e7.tar.gz rails-a21d8f632fe8aa3bf4c1b83accc7a2dd230c28e7.tar.bz2 rails-a21d8f632fe8aa3bf4c1b83accc7a2dd230c28e7.zip |
Merge branch 'master' of git@github.com:lifo/docrails
Diffstat (limited to 'activesupport')
-rw-r--r-- | activesupport/CHANGELOG | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support.rb | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/object/extending.rb | 45 | ||||
-rw-r--r-- | activesupport/lib/active_support/rescuable.rb | 108 | ||||
-rw-r--r-- | activesupport/lib/active_support/secure_random.rb | 4 | ||||
-rw-r--r-- | activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb | 11 | ||||
-rw-r--r-- | activesupport/test/rescuable_test.rb | 75 | ||||
-rw-r--r-- | activesupport/test/secure_random_test.rb | 4 | ||||
-rw-r--r-- | activesupport/test/test_test.rb | 18 |
9 files changed, 251 insertions, 18 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 9700a11531..2c6f4ed582 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. [Norbert Crombach, Pratik] + * Switch from String#chars to String#mb_chars for the unicode proxy. [Manfred Stienstra] This helps with 1.8.7 compatibility and also improves performance for some operations by reducing indirection. diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index b30faff06d..0ff09067ec 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -56,6 +56,8 @@ require 'active_support/time_with_zone' require 'active_support/secure_random' +require 'active_support/rescuable' + I18n.load_path << File.dirname(__FILE__) + '/active_support/locale/en-US.yml' Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector') diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb index 082e98a297..bbf6f8563b 100644 --- a/activesupport/lib/active_support/core_ext/object/extending.rb +++ b/activesupport/lib/active_support/core_ext/object/extending.rb @@ -3,22 +3,43 @@ class Object Class.remove_class(*subclasses_of(*superclasses)) end - # Exclude this class unless it's a subclass of our supers and is defined. - # We check defined? in case we find a removed class that has yet to be - # garbage collected. This also fails for anonymous classes -- please - # submit a patch if you have a workaround. - def subclasses_of(*superclasses) #:nodoc: - subclasses = [] - - superclasses.each do |sup| - ObjectSpace.each_object(class << sup; self; end) do |k| - if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id")) - subclasses << k + begin + ObjectSpace.each_object(Class.new) {} + + # Exclude this class unless it's a subclass of our supers and is defined. + # We check defined? in case we find a removed class that has yet to be + # garbage collected. This also fails for anonymous classes -- please + # submit a patch if you have a workaround. + def subclasses_of(*superclasses) #:nodoc: + subclasses = [] + + superclasses.each do |sup| + ObjectSpace.each_object(class << sup; self; end) do |k| + if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id")) + subclasses << k + end end end + + subclasses end + rescue RuntimeError + # JRuby and any implementations which cannot handle the objectspace traversal + # above fall back to this implementation + def subclasses_of(*superclasses) #:nodoc: + subclasses = [] - subclasses + superclasses.each do |sup| + ObjectSpace.each_object(Class) do |k| + if superclasses.any? { |superclass| k < superclass } && + (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id")) + subclasses << k + end + end + subclasses.uniq! + end + subclasses + end end def extended_by #:nodoc: diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb new file mode 100644 index 0000000000..f2bc12e832 --- /dev/null +++ b/activesupport/lib/active_support/rescuable.rb @@ -0,0 +1,108 @@ +module ActiveSupport + # Rescuable module adds support for easier exception handling. + module Rescuable + def self.included(base) # :nodoc: + base.class_inheritable_accessor :rescue_handlers + base.rescue_handlers = [] + + base.extend(ClassMethods) + end + + module ClassMethods + # Rescue exceptions raised in controller actions. + # + # <tt>rescue_from</tt> receives a series of exception classes or class + # names, and a trailing <tt>:with</tt> option with the name of a method + # or a Proc object to be called to handle them. Alternatively a block can + # be given. + # + # Handlers that take one argument will be called with the exception, so + # that the exception can be inspected when dealing with it. + # + # Handlers are inherited. They are searched from right to left, from + # bottom to top, and up the hierarchy. The handler of the first class for + # which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if + # any. + # + # class ApplicationController < ActionController::Base + # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception + # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors + # + # rescue_from 'MyAppError::Base' do |exception| + # render :xml => exception, :status => 500 + # end + # + # protected + # def deny_access + # ... + # end + # + # def show_errors(exception) + # exception.record.new_record? ? ... + # end + # end + def rescue_from(*klasses, &block) + options = klasses.extract_options! + + unless options.has_key?(:with) + if block_given? + options[:with] = block + else + raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument." + end + end + + klasses.each do |klass| + key = if klass.is_a?(Class) && klass <= Exception + klass.name + elsif klass.is_a?(String) + klass + else + raise ArgumentError, "#{klass} is neither an Exception nor a String" + end + + # put the new handler at the end because the list is read in reverse + rescue_handlers << [key, options[:with]] + end + end + end + + # Tries to rescue the exception by looking up and calling a registered handler. + def rescue_with_handler(exception) + if handler = handler_for_rescue(exception) + handler.arity != 0 ? handler.call(exception) : handler.call + true # don't rely on the return value of the handler + end + end + + def handler_for_rescue(exception) + # We go from right to left because pairs are pushed onto rescue_handlers + # as rescue_from declarations are found. + _, handler = Array(rescue_handlers).reverse.detect do |klass_name, handler| + # The purpose of allowing strings in rescue_from is to support the + # declaration of handler associations for exception classes whose + # definition is yet unknown. + # + # Since this loop needs the constants it would be inconsistent to + # assume they should exist at this point. An early raised exception + # could trigger some other handler and the array could include + # precisely a string whose corresponding constant has not yet been + # seen. This is why we are tolerant to unknown constants. + # + # Note that this tolerance only matters if the exception was given as + # a string, otherwise a NameError will be raised by the interpreter + # itself when rescue_from CONSTANT is executed. + klass = self.class.const_get(klass_name) rescue nil + klass ||= klass_name.constantize rescue nil + exception.is_a?(klass) if klass + end + + case handler + when Symbol + method(handler) + when Proc + handler.bind(self) + end + end + end +end diff --git a/activesupport/lib/active_support/secure_random.rb b/activesupport/lib/active_support/secure_random.rb index 688165f9a3..97971e8830 100644 --- a/activesupport/lib/active_support/secure_random.rb +++ b/activesupport/lib/active_support/secure_random.rb @@ -164,13 +164,13 @@ module ActiveSupport hex = n.to_s(16) hex = '0' + hex if (hex.length & 1) == 1 bin = [hex].pack("H*") - mask = bin[0].ord + mask = bin[0] mask |= mask >> 1 mask |= mask >> 2 mask |= mask >> 4 begin rnd = SecureRandom.random_bytes(bin.length) - rnd[0] = (rnd[0].ord & mask).chr + rnd[0] = rnd[0] & mask end until rnd < bin rnd.unpack("H*")[0].hex else diff --git a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb index 63d1ba6507..e5853bf828 100644 --- a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb +++ b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb @@ -37,15 +37,18 @@ module Test # end def assert_difference(expressions, difference = 1, message = nil, &block) expression_evaluations = Array(expressions).map do |expression| - lambda do + [expression, lambda do eval(expression, block.__send__(:binding)) - end + end] end - original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression.call } + original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression[1].call } yield expression_evaluations.each_with_index do |expression, i| - assert_equal original_values[i] + difference, expression.call, message + full_message = "" + full_message << "#{message}.\n" if message + full_message << "<#{expression[0]}> was the expression that failed" + assert_equal original_values[i] + difference, expression[1].call, full_message end end diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb new file mode 100644 index 0000000000..9f2b783b2e --- /dev/null +++ b/activesupport/test/rescuable_test.rb @@ -0,0 +1,75 @@ +require 'abstract_unit' + +class WraithAttack < StandardError +end + +class NuclearExplosion < StandardError +end + +class MadRonon < StandardError + attr_accessor :message + + def initialize(message) + @message = message + super() + end +end + +class Stargate + attr_accessor :result + + include ActiveSupport::Rescuable + + rescue_from WraithAttack, :with => :sos + + rescue_from NuclearExplosion do + @result = 'alldead' + end + + rescue_from MadRonon do |e| + @result = e.message + end + + def dispatch(method) + send(method) + rescue Exception => e + rescue_with_handler(e) + end + + def attack + raise WraithAttack + end + + def nuke + raise NuclearExplosion + end + + def ronanize + raise MadRonon.new("dex") + end + + def sos + @result = 'killed' + end +end + +class RescueableTest < Test::Unit::TestCase + def setup + @stargate = Stargate.new + end + + def test_rescue_from_with_method + @stargate.dispatch :attack + assert_equal 'killed', @stargate.result + end + + def test_rescue_from_with_block + @stargate.dispatch :nuke + assert_equal 'alldead', @stargate.result + end + + def test_rescue_from_with_block_with_args + @stargate.dispatch :ronanize + assert_equal 'dex', @stargate.result + end +end diff --git a/activesupport/test/secure_random_test.rb b/activesupport/test/secure_random_test.rb index b0b6c21a81..44694cd811 100644 --- a/activesupport/test/secure_random_test.rb +++ b/activesupport/test/secure_random_test.rb @@ -12,4 +12,8 @@ class SecureRandomTest < Test::Unit::TestCase b2 = ActiveSupport::SecureRandom.hex(64) assert_not_equal b1, b2 end + + def test_random_number + assert ActiveSupport::SecureRandom.random_number(5000) < 5000 + end end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 26a45af255..4e253848f6 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -60,6 +60,24 @@ class AssertDifferenceTest < Test::Unit::TestCase @object.increment end end + + def test_array_of_expressions_identify_failure + assert_difference ['@object.num', '1 + 1'] do + @object.increment + end + fail 'should not get to here' + rescue Test::Unit::AssertionFailedError => e + assert_equal "<1 + 1> was the expression that failed.\n<3> expected but was\n<2>.", e.message + end + + def test_array_of_expressions_identify_failure_when_message_provided + assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do + @object.increment + end + fail 'should not get to here' + rescue Test::Unit::AssertionFailedError => e + assert_equal "something went wrong.\n<1 + 1> was the expression that failed.\n<3> expected but was\n<2>.", e.message + end else def default_test; end end |