diff options
Diffstat (limited to 'activesupport')
20 files changed, 251 insertions, 137 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index df144dd00b..cf32e2d7a0 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!` *DHH* + * `Time#change` now works with time values with offsets other than UTC or the local time zone. *Andrew White* * `ActiveSupport::Callbacks`: deprecate usage of filter object with `#before` and `#after` methods as `around` callback. *Bogdan Gusiev* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index fa38d5c1e3..836bc2f9cf 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -22,4 +22,5 @@ Gem::Specification.new do |s| s.add_dependency('i18n', '~> 0.6') s.add_dependency('multi_json', '~> 1.3') s.add_dependency('tzinfo', '~> 0.3.33') + s.add_dependency('minitest', '~> 3.2') end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 6cc875c69a..8f79334159 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -78,7 +78,7 @@ module ActiveSupport private # A hook invoked everytime a before callback is halted. - # This can be overriden in AS::Callback implementors in order + # This can be overridden in AS::Callback implementors in order # to provide better debugging/logging. def halted_callback_hook(filter) end diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb new file mode 100644 index 0000000000..465fedda80 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date.rb @@ -0,0 +1,5 @@ +require 'active_support/core_ext/date/acts_like' +require 'active_support/core_ext/date/calculations' +require 'active_support/core_ext/date/conversions' +require 'active_support/core_ext/date/zones' + diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb new file mode 100644 index 0000000000..e8a27b9f38 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date_time.rb @@ -0,0 +1,4 @@ +require 'active_support/core_ext/date_time/acts_like' +require 'active_support/core_ext/date_time/calculations' +require 'active_support/core_ext/date_time/conversions' +require 'active_support/core_ext/date_time/zones' diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 16a799ec03..9974b61078 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -5,6 +5,9 @@ class Object # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass. # + # This is also true if the receiving object does not implemented the tried method. It will + # return +nil+ in that case as well. + # # If try is called without a method to call, it will yield any given block with the object. # # Please also note that +try+ is defined on +Object+, therefore it won't work with @@ -31,6 +34,16 @@ class Object if a.empty? && block_given? yield self else + public_send(*a, &b) if respond_to?(a.first) + end + end + + # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and + # does not implemented the tried method. + def try!(*a, &b) + if a.empty? && block_given? + yield self + else public_send(*a, &b) end end @@ -50,4 +63,8 @@ class NilClass def try(*args) nil end + + def try!(*args) + nil + end end diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb new file mode 100644 index 0000000000..32cffe237d --- /dev/null +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -0,0 +1,5 @@ +require 'active_support/core_ext/time/acts_like' +require 'active_support/core_ext/time/calculations' +require 'active_support/core_ext/time/conversions' +require 'active_support/core_ext/time/marshal' +require 'active_support/core_ext/time/zones' diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 0a71fc117c..d0f574f2ba 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,6 +1,7 @@ require 'active_support/duration' require 'active_support/core_ext/time/conversions' require 'active_support/time_with_zone' +require 'active_support/core_ext/time/zones' class Time COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index e48866abe3..37bc3fae24 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/time/calculations' require 'active_support/time_with_zone' class Time @@ -27,11 +26,11 @@ class Time # around_filter :set_time_zone # # def set_time_zone - # old_time_zone = Time.zone - # Time.zone = current_user.time_zone if logged_in? - # yield - # ensure - # Time.zone = old_time_zone + # if logged_in? + # Time.use_zone(current_user.time_zone) { yield } + # else + # yield + # end # end # end def zone=(time_zone) diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 3e6c8893e9..5fd20673d8 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -1,12 +1,46 @@ require 'active_support/core_ext/hash/keys' module ActiveSupport - # This class has dubious semantics and we only have it so that - # people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt> - # and they get the same value for both keys. + # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered to be the same. + # + # rgb = ActiveSupport::HashWithIndifferentAccess.new + # + # rgb[:black] = '#000000' + # rgb[:black] # => '#000000' + # rgb['black'] # => '#000000' + # + # rgb['white'] = '#FFFFFF' + # rgb[:white] # => '#FFFFFF' + # rgb['white'] # => '#FFFFFF' + # + # Internally symbols are mapped to strings when used as keys in the entire + # writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This + # mapping belongs to the public interface. For example, given + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(:a => 1) + # + # you are guaranteed that the key is returned as a string: + # + # hash.keys # => ["a"] + # + # Technically other types of keys are accepted: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(:a => 1) + # hash[0] = 0 + # hash # => {"a"=>1, 0=>0} + # + # but this class is intended for use cases where strings or symbols are the + # expected keys and it is convenient to understand both as the same. For + # example the +params+ hash in Ruby on Rails. + # + # Note that core extensions define <tt>Hash#with_indifferent_access</tt>: + # + # rgb = {:black => '#000000', :white => '#FFFFFF'}.with_indifferent_access + # + # which may be handy. class HashWithIndifferentAccess < Hash - - # Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class. + # Returns true so that <tt>Array#extract_options!</tt> finds members of + # this class. def extractable_options? true end @@ -51,25 +85,32 @@ module ActiveSupport # Assigns a new value to the hash: # - # hash = HashWithIndifferentAccess.new + # hash = ActiveSupport::HashWithIndifferentAccess.new # hash[:key] = "value" # + # This value can be later fetched using either +:key+ or +"key"+. def []=(key, value) regular_writer(convert_key(key), convert_value(value)) end alias_method :store, :[]= - # Updates the instantized hash with values from the second: + # Updates the receiver in-place merging in the hash passed as argument: # - # hash_1 = HashWithIndifferentAccess.new - # hash_1[:key] = "value" + # hash_1 = ActiveSupport::HashWithIndifferentAccess.new + # hash_2[:key] = "value" # - # hash_2 = HashWithIndifferentAccess.new + # hash_2 = ActiveSupport::HashWithIndifferentAccess.new # hash_2[:key] = "New Value!" # # hash_1.update(hash_2) # => {"key"=>"New Value!"} # + # The argument can be either an + # <tt>ActiveSupport::HashWithIndifferentAccess</tt> or a regular +Hash+. + # In either case the merge respects the semantics of indifferent access. + # + # If the argument is a regular hash with keys +:key+ and +"key"+ only one + # of the values end up in the receiver, but which was is unespecified. def update(other_hash) if other_hash.is_a? HashWithIndifferentAccess super(other_hash) @@ -83,10 +124,10 @@ module ActiveSupport # Checks the hash for a key matching the argument passed in: # - # hash = HashWithIndifferentAccess.new + # hash = ActiveSupport::HashWithIndifferentAccess.new # hash["key"] = "value" - # hash.key? :key # => true - # hash.key? "key" # => true + # hash.key?(:key) # => true + # hash.key?("key") # => true # def key?(key) super(convert_key(key)) @@ -96,14 +137,24 @@ module ActiveSupport alias_method :has_key?, :key? alias_method :member?, :key? - # Fetches the value for the specified key, same as doing hash[key] + # Same as <tt>Hash#fetch</tt> where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters.fetch("foo") # => 1 + # counters.fetch(:bar, 0) # => 0 + # counters.fetch(:bar) {|key| 0} # => 0 + # counters.fetch(:zoo) # => KeyError: key not found: "zoo" + # def fetch(key, *extras) super(convert_key(key), *extras) end # Returns an array of the values at the specified indices: # - # hash = HashWithIndifferentAccess.new + # hash = ActiveSupport::HashWithIndifferentAccess.new # hash[:a] = "x" # hash[:b] = "y" # hash.values_at("a", "b") # => ["x", "y"] @@ -119,23 +170,30 @@ module ActiveSupport end end - # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash. - # Does not overwrite the existing hash. + # This method has the same semantics of +update+, except it does not + # modify the receiver but rather returns a new hash with indifferent + # access with the result of the merge. def merge(hash) self.dup.update(hash) end - # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second. - # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a <tt>HashWithDifferentAccess</tt>. + # Like +merge+ but the other way around: Merges the receiver into the + # argument and returns a new hash with indifferent access as result: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash['a'] = nil + # hash.reverse_merge(:a => 0, :b => 1) # => {"a"=>nil, "b"=>1} + # def reverse_merge(other_hash) - super self.class.new_from_hash_copying_default(other_hash) + super(self.class.new_from_hash_copying_default(other_hash)) end + # Same semantics as +reverse_merge+ but modifies the receiver in-place. def reverse_merge!(other_hash) replace(reverse_merge( other_hash )) end - # Removes a specified key from the hash. + # Removes the specified key from the hash. def delete(key) super(convert_key(key)) end @@ -150,7 +208,7 @@ module ActiveSupport def deep_symbolize_keys; to_hash.deep_symbolize_keys end def to_options!; self end - # Convert to a Hash with String keys. + # Convert to a regular hash with string keys. def to_hash Hash.new(default).merge!(self) end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index c319e94bc6..389df58ec4 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -161,38 +161,67 @@ class Struct #:nodoc: end class TrueClass - def as_json(options = nil) self end #:nodoc: - def encode_json(encoder) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + to_s + end end class FalseClass - def as_json(options = nil) self end #:nodoc: - def encode_json(encoder) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + to_s + end end class NilClass - def as_json(options = nil) self end #:nodoc: - def encode_json(encoder) 'null' end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + 'null' + end end class String - def as_json(options = nil) self end #:nodoc: - def encode_json(encoder) encoder.escape(self) end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + encoder.escape(self) + end end class Symbol - def as_json(options = nil) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + to_s + end end class Numeric - def as_json(options = nil) self end #:nodoc: - def encode_json(encoder) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + to_s + end end class Float # Encoding Infinity or NaN to JSON should return "null". The default returns # "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]'). - def as_json(options = nil) finite? ? self : nil end #:nodoc: + def as_json(options = nil) #:nodoc: + finite? ? self : nil + end end class BigDecimal @@ -216,7 +245,9 @@ class BigDecimal end class Regexp - def as_json(options = nil) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + to_s + end end module Enumerable @@ -226,7 +257,9 @@ module Enumerable end class Range - def as_json(options = nil) to_s end #:nodoc: + def as_json(options = nil) #:nodoc: + to_s + end end class Array @@ -262,7 +295,7 @@ class Hash Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }] end - def encode_json(encoder) + def encode_json(encoder) #:nodoc: # values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be # processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields); diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index 5e080df518..9cd8ac36bc 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -16,7 +16,7 @@ module ActiveSupport module Formatter # :nodoc: # This method is invoked when a log event occurs def call(severity, timestamp, progname, msg) - super(severity, timestamp, progname, "#{tags_text}#{msg}") + super(severity, timestamp, progname, "#{tags_text} #{msg}".lstrip) end def clear! @@ -31,7 +31,7 @@ module ActiveSupport def tags_text tags = current_tags if tags.any? - tags.collect { |tag| "[#{tag}] " }.join + tags.collect { |tag| "[#{tag}]" }.join(' ') end end end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index e2b46a235a..d2b8e602f9 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -1,24 +1,21 @@ +gem 'minitest' # make sure we get the gem, not stdlib require 'minitest/spec' require 'active_support/testing/setup_and_teardown' require 'active_support/testing/assertions' require 'active_support/testing/deprecation' -require 'active_support/testing/declarative' require 'active_support/testing/isolation' -require 'active_support/testing/mochaing' +require 'active_support/testing/mocha_module' require 'active_support/core_ext/kernel/reporting' +require 'active_support/deprecation' module ActiveSupport class TestCase < ::MiniTest::Spec - if MiniTest::Unit::VERSION < '2.6.1' - class << self - alias :name :to_s - end - end + include ActiveSupport::Testing::MochaModule # Use AS::TestCase for the base class when describing a model register_spec_type(self) do |desc| - desc < ActiveRecord::Model + Class === desc && desc < ActiveRecord::Model end Assertion = MiniTest::Assertion @@ -38,7 +35,24 @@ module ActiveSupport include ActiveSupport::Testing::SetupAndTeardown include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation - extend ActiveSupport::Testing::Declarative + + def self.describe(text) + if block_given? + super + else + ActiveSupport::Deprecation.warn("`describe` without a block is deprecated, please switch to: `def self.name; #{text.inspect}; end`\n") + + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def self.name + "#{text}" + end + RUBY_EVAL + end + end + + class << self + alias :test :it + end # test/unit backwards compatibility methods alias :assert_raise :assert_raises diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb deleted file mode 100644 index 508e37254a..0000000000 --- a/activesupport/lib/active_support/testing/declarative.rb +++ /dev/null @@ -1,40 +0,0 @@ -module ActiveSupport - module Testing - module Declarative - - def self.extended(klass) #:nodoc: - klass.class_eval do - - unless method_defined?(:describe) - def self.describe(text) - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def self.name - "#{text}" - end - RUBY_EVAL - end - end - - end - end - - unless defined?(Spec) - # test "verify something" do - # ... - # end - def test(name, &block) - test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym - defined = instance_method(test_name) rescue false - raise "#{test_name} is already defined in #{self}" if defined - if block_given? - define_method(test_name, &block) - else - define_method(test_name) do - flunk "No implementation provided for #{name}" - end - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/testing/mocha_module.rb b/activesupport/lib/active_support/testing/mocha_module.rb new file mode 100644 index 0000000000..ed2942d23a --- /dev/null +++ b/activesupport/lib/active_support/testing/mocha_module.rb @@ -0,0 +1,22 @@ +module ActiveSupport + module Testing + module MochaModule + begin + require 'mocha_standalone' + include Mocha::API + + def before_setup + mocha_setup + super + end + + def after_teardown + super + mocha_verify + mocha_teardown + end + rescue LoadError + end + end + end +end diff --git a/activesupport/lib/active_support/testing/mochaing.rb b/activesupport/lib/active_support/testing/mochaing.rb deleted file mode 100644 index 4ad75a6681..0000000000 --- a/activesupport/lib/active_support/testing/mochaing.rb +++ /dev/null @@ -1,7 +0,0 @@ -begin - silence_warnings { require 'mocha' } -rescue LoadError - # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh. - Object.const_set :Mocha, Module.new - Mocha.const_set :ExpectationError, Class.new(StandardError) -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index 527fa555b7..a65148cf1f 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -4,20 +4,11 @@ require 'active_support/callbacks' module ActiveSupport module Testing module SetupAndTeardown - - PASSTHROUGH_EXCEPTIONS = [ - NoMemoryError, - SignalException, - Interrupt, - SystemExit - ] - extend ActiveSupport::Concern included do include ActiveSupport::Callbacks define_callbacks :setup, :teardown - end module ClassMethods @@ -30,28 +21,15 @@ module ActiveSupport end end - def run(runner) - result = '.' - begin - run_callbacks :setup do - result = super - end - rescue *PASSTHROUGH_EXCEPTIONS - raise - rescue Exception => e - result = runner.puke(self.class, method_name, e) - ensure - begin - run_callbacks :teardown - rescue *PASSTHROUGH_EXCEPTIONS - raise - rescue Exception => e - result = runner.puke(self.class, method_name, e) - end - end - result + def before_setup + super + run_callbacks :setup end + def after_teardown + run_callbacks :teardown + super + end end end end diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 57ed4a6b60..25ed962c23 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -18,8 +18,6 @@ end require 'minitest/autorun' require 'empty_bool' -silence_warnings { require 'mocha' } - ENV['NO_RELOAD'] = '1' require 'active_support' diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index 98ab82609e..ec7dd6d4fb 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -99,13 +99,25 @@ class ObjectTryTest < ActiveSupport::TestCase def test_nonexisting_method method = :undefined_method assert !@string.respond_to?(method) - assert_raise(NoMethodError) { @string.try(method) } + assert_nil @string.try(method) end def test_nonexisting_method_with_arguments method = :undefined_method assert !@string.respond_to?(method) - assert_raise(NoMethodError) { @string.try(method, 'llo', 'y') } + assert_nil @string.try(method, 'llo', 'y') + end + + def test_nonexisting_method_bang + method = :undefined_method + assert !@string.respond_to?(method) + assert_raise(NoMethodError) { @string.try!(method) } + end + + def test_nonexisting_method_with_arguments_bang + method = :undefined_method + assert !@string.respond_to?(method) + assert_raise(NoMethodError) { @string.try!(method, 'llo', 'y') } end def test_valid_method @@ -139,6 +151,18 @@ class ObjectTryTest < ActiveSupport::TestCase assert_equal false, ran end + def test_try_with_private_method_bang + klass = Class.new do + private + + def private_method + 'private method' + end + end + + assert_raise(NoMethodError) { klass.new.try!(:private_method) } + end + def test_try_with_private_method klass = Class.new do private @@ -148,6 +172,6 @@ class ObjectTryTest < ActiveSupport::TestCase end end - assert_raise(NoMethodError) { klass.new.try(:private_method) } + assert_nil klass.new.try(:private_method) end end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index a8d69d0ec3..ef289692bc 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -110,7 +110,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end %w{capitalize downcase lstrip reverse rstrip swapcase upcase}.each do |method| - class_eval(<<-EOTESTS) + class_eval(<<-EOTESTS, __FILE__, __LINE__ + 1) def test_#{method}_bang_should_return_self_when_modifying_wrapped_string chars = ' él piDió Un bUen café ' assert_equal chars.object_id, chars.send("#{method}!").object_id |