diff options
Diffstat (limited to 'activesupport/lib')
50 files changed, 532 insertions, 284 deletions
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index a40c6b559c..ab0054b339 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2005-2013 David Heinemeier Hansson +# Copyright (c) 2005-2014 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index c88ae3e661..d58578b7bc 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -22,7 +22,7 @@ module ActiveSupport # <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the # backtrace to a pristine state. If you need to reconfigure an existing # BacktraceCleaner so that it does not filter or modify the paths of any lines - # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!<tt> + # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt> # These two methods will give you a completely untouched backtrace. # # Inspired by the Quiet Backtrace gem by Thoughtbot. diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index d584d50da8..53154aef27 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -401,7 +401,7 @@ module ActiveSupport end end - # Return +true+ if the cache contains an entry for the given key. + # Returns +true+ if the cache contains an entry for the given key. # # Options are passed to the underlying cache implementation. def exist?(name, options = nil) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 5cd6065077..8ed60aebac 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -22,15 +22,15 @@ module ActiveSupport extend Strategy::LocalCache end - # Deletes all items from the cache. In this case it deletes all the entries in the specified - # file store directory except for .gitkeep. Be careful which directory is specified in your + # Deletes all items from the cache. In this case it deletes all the entries in the specified + # file store directory except for .gitkeep. Be careful which directory is specified in your # config file when using +FileStore+ because everything in that directory will be deleted. def clear(options = nil) root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)} FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) end - # Premptively iterates through all stored keys and removes the ones which have expired. + # Preemptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) search_dir(cache_path) do |fname| @@ -43,33 +43,13 @@ module ActiveSupport # Increments an already existing integer value that is stored in the cache. # If the key is not found nothing is done. def increment(name, amount = 1, options = nil) - file_name = key_file_path(namespaced_key(name, options)) - lock_file(file_name) do - options = merged_options(options) - if num = read(name, options) - num = num.to_i + amount - write(name, num, options) - num - else - nil - end - end + modify_value(name, amount, options) end # Decrements an already existing integer value that is stored in the cache. # If the key is not found nothing is done. def decrement(name, amount = 1, options = nil) - file_name = key_file_path(namespaced_key(name, options)) - lock_file(file_name) do - options = merged_options(options) - if num = read(name, options) - num = num.to_i - amount - write(name, num, options) - num - else - nil - end - end + modify_value(name, -amount, options) end def delete_matched(matcher, options = nil) @@ -184,6 +164,22 @@ module ActiveSupport end end end + + # Modifies the amount of an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def modify_value(name, amount, options) + file_name = key_file_path(namespaced_key(name, options)) + + lock_file(file_name) do + options = merged_options(options) + + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end + end + end end end end diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 512296554f..61b4f0b8b0 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -41,17 +41,15 @@ module ActiveSupport # # If no addresses are specified, then MemCacheStore will connect to # localhost port 11211 (the default memcached port). - # - # Instead of addresses one can pass in a MemCache-like object. For example: - # - # require 'memcached' # gem install memcached; uses C bindings to libmemcached - # ActiveSupport::Cache::MemCacheStore.new(Memcached::Rails.new("localhost:11211")) def initialize(*addresses) addresses = addresses.flatten options = addresses.extract_options! super(options) - if addresses.first.respond_to?(:get) + unless [String, Dalli::Client, NilClass].include?(addresses.first.class) + raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance." + end + if addresses.first.is_a?(Dalli::Client) @data = addresses.first else mem_cache_options = options.dup @@ -87,7 +85,7 @@ module ActiveSupport instrument(:increment, name, :amount => amount) do @data.incr(escape_key(namespaced_key(name, options)), amount) end - rescue Dalli::DalliError + rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger nil end @@ -101,7 +99,7 @@ module ActiveSupport instrument(:decrement, name, :amount => amount) do @data.decr(escape_key(namespaced_key(name, options)), amount) end - rescue Dalli::DalliError + rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger nil end diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 34ac91334a..8a0523d0e2 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -36,7 +36,7 @@ module ActiveSupport end end - # Premptively iterates through all stored keys and removes the ones which have expired. + # Preemptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) instrument(:cleanup, :size => @data.size) do diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index cea7eee924..4eaf57f385 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/string/inflections' +require 'rack/body_proxy' module ActiveSupport module Cache @@ -83,9 +84,14 @@ module ActiveSupport def call(env) LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new) - @app.call(env) - ensure + response = @app.call(env) + response[2] = ::Rack::BodyProxy.new(response[2]) do + LocalCacheRegistry.set_cache_for(local_cache_key, nil) + end + response + rescue Exception LocalCacheRegistry.set_cache_for(local_cache_key, nil) + raise end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index c3aac31323..e14ece7f35 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -7,14 +7,14 @@ 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. + # Callbacks are code hooks that are run at key points in an object's life cycle. # The typical use case is to have a base class define a set of callbacks # relevant to the other functionality it supplies, so that subclasses can # install callbacks that enhance or modify the base functionality without # needing to override or redefine methods of the base class. # # Mixing in this module allows you to define the events in the object's - # lifecycle that will support callbacks (via +ClassMethods.define_callbacks+), + # life cycle that will support callbacks (via +ClassMethods.define_callbacks+), # set the instance methods, procs, or callback objects to be called (via # +ClassMethods.set_callback+), and run the installed callbacks at the # appropriate times (via +run_callbacks+). @@ -89,7 +89,7 @@ module ActiveSupport private - # A hook invoked everytime a before callback is halted. + # A hook invoked every time a before callback is halted. # This can be overridden in AS::Callback implementors in order # to provide better debugging/logging. def halted_callback_hook(filter) @@ -577,7 +577,7 @@ module ActiveSupport # The callback can be specified as a symbol naming an instance method; as a # proc, lambda, or block; as a string to be instance evaluated; or as an # object that responds to a certain method determined by the <tt>:scope</tt> - # argument to +define_callback+. + # argument to +define_callbacks+. # # If a proc, lambda, or block is given, its body is evaluated in the context # of the current object. It can also optionally accept the current object as @@ -648,7 +648,7 @@ module ActiveSupport self.set_callbacks name, callbacks.dup.clear end - # Define sets of events in the object lifecycle that support callbacks. + # Define sets of events in the object life cycle that support callbacks. # # define_callbacks :validate # define_callbacks :initialize, :save, :destroy diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index e0d39d509f..3dd44e32d8 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -107,7 +107,7 @@ module ActiveSupport options = names.extract_options! names.each do |name| - raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/ + raise NameError.new('invalid config attribute name') unless name =~ /\A[_A-Za-z]\w*\z/ reader, reader_line = "def #{name}; config.#{name}; end", __LINE__ writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__ diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index ff870f5fd1..c2219beb5a 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -4,20 +4,21 @@ require 'active_support/core_ext/module/remove_method' class Class def superclass_delegating_accessor(name, options = {}) # Create private _name and _name= methods that can still be used if the public - # methods are overridden. This allows - _superclass_delegating_accessor("_#{name}") + # methods are overridden. + _superclass_delegating_accessor("_#{name}", options) - # Generate the public methods name, name=, and name? + # Generate the public methods name, name=, and name?. # These methods dispatch to the private _name, and _name= methods, making them - # overridable + # overridable. singleton_class.send(:define_method, name) { send("_#{name}") } singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") } singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) } - # If an instance_reader is needed, generate methods for name and name= on the - # class itself, so instances will be able to see them - define_method(name) { send("_#{name}") } if options[:instance_reader] != false - define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false + # If an instance_reader is needed, generate public instance methods name and name?. + if options[:instance_reader] != false + define_method(name) { send("_#{name}") } + define_method("#{name}?") { !!send("#{name}") } + end end private diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 6bc8f12176..df419a6e63 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,6 +1,7 @@ require 'date' require 'active_support/inflector/methods' require 'active_support/core_ext/date/zones' +require 'active_support/core_ext/module/remove_method' class Date DATE_FORMATS = { @@ -19,8 +20,10 @@ class Date # Ruby 1.9 has Date#to_time which converts to localtime only. remove_method :to_time - # Ruby 1.9 has Date#xmlschema which converts to a string without the time component. - remove_method :xmlschema + # Ruby 1.9 has Date#xmlschema which converts to a string without the time + # component. This removal may generate an issue on FreeBSD, that's why we + # need to use remove_possible_method here + remove_possible_method :xmlschema # Convert to a formatted string. See DATE_FORMATS for predefined formats. # @@ -37,12 +40,12 @@ class Date # date.to_formatted_s(:rfc822) # => "10 Nov 2007" # date.to_formatted_s(:iso8601) # => "2007-11-10" # - # == Adding your own time formats to to_formatted_s + # == Adding your own date formats to to_formatted_s # You can add your own formats to the Date::DATE_FORMATS hash. # Use the format name as the hash key and either a strftime string # or Proc instance that takes a date argument as the value. # - # # config/initializers/time_formats.rb + # # config/initializers/date_formats.rb # Date::DATE_FORMATS[:month_and_year] = '%B %Y' # Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") } def to_formatted_s(format = :default) diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 8e5d723074..73ad0aa097 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -151,7 +151,11 @@ class DateTime # Layers additional behavior on DateTime#<=> so that Time and # ActiveSupport::TimeWithZone instances can be compared with a DateTime. def <=>(other) - super other.to_datetime + if other.respond_to? :to_datetime + super other.to_datetime + else + nil + end end end diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index 686f12c6da..f68e1662f9 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/hash/compact' require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/except' diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb new file mode 100644 index 0000000000..6566215a4d --- /dev/null +++ b/activesupport/lib/active_support/core_ext/hash/compact.rb @@ -0,0 +1,20 @@ +class Hash + # Returns a hash with non +nil+ values. + # + # hash = { a: true, b: false, c: nil} + # hash.compact # => { a: true, b: false} + # hash # => { a: true, b: false, c: nil} + # { c: nil }.compact # => {} + def compact + self.select { |_, value| !value.nil? } + end + + # Replaces current hash with non +nil+ values. + # + # hash = { a: true, b: false, c: nil} + # hash.compact! # => { a: true, b: false} + # hash # => { a: true, b: false} + def compact! + self.reject! { |_, value| value.nil? } + end +end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 2684c772ea..7bea461c77 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -105,7 +105,7 @@ class Hash # hash = Hash.from_xml(xml) # # => {"hash"=>{"foo"=>1, "bar"=>2}} # - # DisallowedType is raise if the XML contains attributes with <tt>type="yaml"</tt> or + # DisallowedType is raised if the XML contains attributes with <tt>type="yaml"</tt> or # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML. def from_xml(xml, disallowed_types = nil) ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index d90e996ad4..682d089881 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -1,5 +1,5 @@ class Hash - # Return a hash that includes everything but the given keys. This is useful for + # Returns a hash that includes everything but the given keys. This is useful for # limiting a set of parameters to everything but a few known toggles: # # @person.update(params[:person].except(:admin)) diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index 93e716585b..3d41aa8572 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -1,5 +1,5 @@ class Hash - # Return a new hash with all keys converted using the block operation. + # Returns a new hash with all keys converted using the block operation. # # hash = { name: 'Rob', age: '28' } # @@ -22,7 +22,7 @@ class Hash self end - # Return a new hash with all keys converted to strings. + # Returns a new hash with all keys converted to strings. # # hash = { name: 'Rob', age: '28' } # @@ -38,7 +38,7 @@ class Hash transform_keys!{ |key| key.to_s } end - # Return a new hash with all keys converted to symbols, as long as + # Returns a new hash with all keys converted to symbols, as long as # they respond to +to_sym+. # # hash = { 'name' => 'Rob', 'age' => '28' } @@ -73,7 +73,7 @@ class Hash end end - # Return a new hash with all keys converted by the block operation. + # Returns a new hash with all keys converted by the block operation. # This includes the keys from the root hash and from all # nested hashes. # @@ -100,7 +100,7 @@ class Hash self end - # Return a new hash with all keys converted to strings. + # Returns a new hash with all keys converted to strings. # This includes the keys from the root hash and from all # nested hashes. # @@ -119,7 +119,7 @@ class Hash deep_transform_keys!{ |key| key.to_s } end - # Return a new hash with all keys converted to symbols, as long as + # Returns a new hash with all keys converted to symbols, as long as # they respond to +to_sym+. This includes the keys from the root hash # and from all nested hashes. # diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index df11737a6b..3b5e205244 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -48,6 +48,7 @@ module Kernel yield ensure stream.reopen(old_stream) + old_stream.close end # Blocks and ignores any exception passed as argument if raised within the block. diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index f2d4887df6..b4efff8b24 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/reachable' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/module/attr_internal' +require 'active_support/core_ext/module/concerning' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/module/remove_method' diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index f70a839074..d317df5079 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -49,7 +49,7 @@ class Module # include HairColors # end # - # Person.hair_colors # => [:brown, :black, :blonde, :red] + # + # Person.hair_colors # => [:brown, :black, :blonde, :red] def mattr_reader(*syms) options = syms.extract_options! syms.each do |sym| @@ -181,7 +181,7 @@ class Module # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods. # # module HairColors - # mattr_accessor :hair_colors, instance_acessor: false + # mattr_accessor :hair_colors, instance_accessor: false # end # # class Person diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb new file mode 100644 index 0000000000..b22dc5ff1e --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/concerning.rb @@ -0,0 +1,135 @@ +require 'active_support/concern' + +class Module + # = Bite-sized separation of concerns + # + # We often find ourselves with a medium-sized chunk of behavior that we'd + # like to extract, but only mix in to a single class. + # + # Extracting a plain old Ruby object to encapsulate it and collaborate or + # delegate to the original object is often a good choice, but when there's + # no additional state to encapsulate or we're making DSL-style declarations + # about the parent class, introducing new collaborators can obfuscate rather + # than simplify. + # + # The typical route is to just dump everything in a monolithic class, perhaps + # with a comment, as a least-bad alternative. Using modules in separate files + # means tedious sifting to get a big-picture view. + # + # = Dissatisfying ways to separate small concerns + # + # == Using comments: + # + # class Todo + # # Other todo implementation + # # ... + # + # ## Event tracking + # has_many :events + # + # before_create :track_creation + # after_destroy :track_deletion + # + # private + # def track_creation + # # ... + # end + # end + # + # == With an inline module: + # + # Noisy syntax. + # + # class Todo + # # Other todo implementation + # # ... + # + # module EventTracking + # extend ActiveSupport::Concern + # + # included do + # has_many :events + # before_create :track_creation + # after_destroy :track_deletion + # end + # + # private + # def track_creation + # # ... + # end + # end + # include EventTracking + # end + # + # == Mix-in noise exiled to its own file: + # + # Once our chunk of behavior starts pushing the scroll-to-understand it + # boundary, we give in and move it to a separate file. At this size, the + # overhead feels in good proportion to the size of our extraction, despite + # diluting our at-a-glance sense of how things really work. + # + # class Todo + # # Other todo implementation + # # ... + # + # include TodoEventTracking + # end + # + # = Introducing Module#concerning + # + # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to + # separate bite-sized concerns. + # + # class Todo + # # Other todo implementation + # # ... + # + # concerning :EventTracking do + # included do + # has_many :events + # before_create :track_creation + # after_destroy :track_deletion + # end + # + # private + # def track_creation + # # ... + # end + # end + # end + # + # Todo.ancestors + # # => Todo, Todo::EventTracking, Object + # + # This small step has some wonderful ripple effects. We can + # * grok the behavior of our class in one glance, + # * clean up monolithic junk-drawer classes by separating their concerns, and + # * stop leaning on protected/private for crude "this is internal stuff" modularity. + module Concerning + # Define a new concern and mix it in. + def concerning(topic, &block) + include concern(topic, &block) + end + + # A low-cruft shortcut to define a concern. + # + # concern :EventTracking do + # ... + # end + # + # is equivalent to + # + # module EventTracking + # extend ActiveSupport::Concern + # + # ... + # end + def concern(topic, &module_definition) + const_set topic, Module.new { + extend ::ActiveSupport::Concern + module_eval(&module_definition) + } + end + end + include Concerning +end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 58146cdf7a..f855833a24 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -178,30 +178,32 @@ class Module # whereas conceptually, from the user point of view, the delegator should # be doing one call. if allow_nil - module_eval(<<-EOS, file, line - 3) - def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) - _ = #{to} # _ = client - if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name) - _.#{method}(#{definition}) # _.name(*args, &block) - end # end - end # end - EOS + method_def = [ + "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block) + "_ = #{to}", # _ = client + "if !_.nil? || nil.respond_to?(:#{method})", # if !_.nil? || nil.respond_to?(:name) + " _.#{method}(#{definition})", # _.name(*args, &block) + "end", # end + "end" # end + ].join ';' else exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") - module_eval(<<-EOS, file, line - 2) - def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) - _ = #{to} # _ = client - _.#{method}(#{definition}) # _.name(*args, &block) - rescue NoMethodError => e # rescue NoMethodError => e - if _.nil? && e.name == :#{method} # if _.nil? && e.name == :name - #{exception} # # add helpful message to the exception - else # else - raise # raise - end # end - end # end - EOS + method_def = [ + "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block) + " _ = #{to}", # _ = client + " _.#{method}(#{definition})", # _.name(*args, &block) + "rescue NoMethodError => e", # rescue NoMethodError => e + " if _.nil? && e.name == :#{method}", # if _.nil? && e.name == :name + " #{exception}", # # add helpful message to the exception + " else", # else + " raise", # raise + " end", # end + "end" # end + ].join ';' end + + module_eval(method_def, file, line) end end end diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index f9ff4d9567..38e43478df 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -4,36 +4,42 @@ class Object # An object is blank if it's false, empty, or a whitespace string. # For example, '', ' ', +nil+, [], and {} are all blank. # - # This simplifies: + # This simplifies # - # if address.nil? || address.empty? + # address.nil? || address.empty? # - # ...to: + # to # - # if address.blank? + # address.blank? + # + # @return [true, false] def blank? - respond_to?(:empty?) ? empty? : !self + respond_to?(:empty?) ? !!empty? : !self end - # An object is present if it's not <tt>blank?</tt>. + # An object is present if it's not blank. + # + # @return [true, false] def present? !blank? end - # Returns object if it's <tt>present?</tt> otherwise returns +nil+. - # <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>. + # Returns the receiver if it's present otherwise returns +nil+. + # <tt>object.presence</tt> is equivalent to # - # This is handy for any representation of objects where blank is the same - # as not present at all. For example, this simplifies a common check for - # HTTP POST/query parameters: + # object.present? ? object : nil + # + # For example, something like # # state = params[:state] if params[:state].present? # country = params[:country] if params[:country].present? # region = state || country || 'US' # - # ...becomes: + # becomes # # region = params[:state].presence || params[:country].presence || 'US' + # + # @return [Object] def presence self if present? end @@ -43,6 +49,8 @@ class NilClass # +nil+ is blank: # # nil.blank? # => true + # + # @return [true] def blank? true end @@ -52,6 +60,8 @@ class FalseClass # +false+ is blank: # # false.blank? # => true + # + # @return [true] def blank? true end @@ -61,6 +71,8 @@ class TrueClass # +true+ is not blank: # # true.blank? # => false + # + # @return [false] def blank? false end @@ -71,6 +83,8 @@ class Array # # [].blank? # => true # [1,2,3].blank? # => false + # + # @return [true, false] alias_method :blank?, :empty? end @@ -79,18 +93,28 @@ class Hash # # {}.blank? # => true # { key: 'value' }.blank? # => false + # + # @return [true, false] alias_method :blank?, :empty? end class String + BLANK_RE = /\A[[:space:]]*\z/ + # A string is blank if it's empty or contains whitespaces only: # - # ''.blank? # => true - # ' '.blank? # => true - # ' '.blank? # => true - # ' something here '.blank? # => false + # ''.blank? # => true + # ' '.blank? # => true + # "\t\n\r".blank? # => true + # ' blah '.blank? # => false + # + # Unicode whitespace is supported: + # + # "\u00a0".blank? # => true + # + # @return [true, false] def blank? - self =~ /\A[[:space:]]*\z/ + BLANK_RE === self end end @@ -99,6 +123,8 @@ class Numeric #:nodoc: # # 1.blank? # => false # 0.blank? # => false + # + # @return [false] def blank? false end diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb index 1675145ffe..8e08cfbf26 100644 --- a/activesupport/lib/active_support/core_ext/object/json.rb +++ b/activesupport/lib/active_support/core_ext/object/json.rb @@ -16,12 +16,12 @@ require 'active_support/core_ext/module/aliasing' # otherwise they will always use to_json gem implementation, which is backwards incompatible in # several cases (for instance, the JSON implementation for Hash does not work) with inheritance # and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. -# +# # On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the # JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always # passes a ::JSON::State object as the only argument to to_json, we can detect that and forward the # calls to the original to_json method. -# +# # It should be noted that when using ::JSON.{generate,dump} directly, ActiveSupport's encoder is # bypassed completely. This means that as_json won't be invoked and the JSON gem will simply # ignore any options it does not natively understand. This also means that ::JSON.{generate,dump} @@ -163,7 +163,7 @@ end class Time def as_json(options = nil) #:nodoc: if ActiveSupport.use_standard_json_time_format - xmlschema(3) + xmlschema(ActiveSupport::JSON::Encoding.time_precision) else %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) end @@ -183,7 +183,7 @@ end class DateTime def as_json(options = nil) #:nodoc: if ActiveSupport.use_standard_json_time_format - xmlschema(3) + xmlschema(ActiveSupport::JSON::Encoding.time_precision) else strftime('%Y/%m/%d %H:%M:%S %z') end diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb index d51ea2e944..ecef78f55f 100644 --- a/activesupport/lib/active_support/core_ext/range/each.rb +++ b/activesupport/lib/active_support/core_ext/range/each.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/object/acts_like' class Range #:nodoc: @@ -17,7 +16,7 @@ class Range #:nodoc: private def ensure_iteration_allowed - if first.acts_like?(:time) + if first.is_a?(Time) raise TypeError, "can't iterate from #{first.class}" end end diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index d94e1bfca2..6018fd9641 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -59,7 +59,7 @@ class String # str.from(0).to(-1) # => "hello" # str.from(1).to(-2) # => "ell" def to(position) - self[0, position + 1] + self[0..position] end # Returns the first character. If a limit is supplied, returns a substring diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 1b2098fc84..eb02b6a442 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -70,9 +70,20 @@ class ERB # them inside a script tag to avoid XSS vulnerability: # # <script> - # var currentUser = <%= json_escape current_user.to_json %>; + # var currentUser = <%= raw json_escape(current_user.to_json) %>; # </script> # + # It is necessary to +raw+ the result of +json_escape+, so that quotation marks + # don't get converted to <tt>"</tt> entities. +json_escape+ doesn't + # automatically flag the result as HTML safe, since the raw value is unsafe to + # use inside HTML attributes. + # + # If you need to output JSON elsewhere in your HTML, you can just do something + # like this, as any unsafe characters (including quotation marks) will be + # automatically escaped for you: + # + # <div data-user-info="<%= current_user.to_json %>">...</div> + # # WARNING: this helper only works with valid JSON. Using this on non-JSON values # will open up serious XSS vulnerabilities. For example, if you replace the # +current_user.to_json+ in the example above with user input instead, the browser @@ -88,17 +99,6 @@ class ERB # is recommended that you always apply this helper (other libraries, such as the # JSON gem, do not provide this kind of protection by default; also some gems # might override +to_json+ to bypass Active Support's encoder). - # - # The output of this helper method is marked as HTML safe so that you can directly - # include it inside a <tt><script></tt> tag as shown above. - # - # However, it is NOT safe to use the output of this inside an HTML attribute, - # because quotation marks are not escaped. Doing so might break your page's layout. - # If you intend to use this inside an HTML attribute, you should use the - # +html_escape+ helper (or its +h+ alias) instead: - # - # <div data-user-info="<%= h current_user.to_json %>">...</div> - # def json_escape(s) result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) s.html_safe? ? result.html_safe : result @@ -183,15 +183,14 @@ module ActiveSupport #:nodoc: end def %(args) - args = Array(args).map do |arg| - if !html_safe? || arg.html_safe? - arg - else - ERB::Util.h(arg) - end + case args + when Hash + escaped_args = Hash[args.map { |k,arg| [k, html_escape_interpolated_argument(arg)] }] + else + escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) } end - self.class.new(super(args)) + self.class.new(super(escaped_args)) end def html_safe? @@ -224,6 +223,12 @@ module ActiveSupport #:nodoc: EOT end end + + private + + def html_escape_interpolated_argument(arg) + (!html_safe? || arg.html_safe?) ? arg : ERB::Util.h(arg) + end end end diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb index d5a420301a..ac1ffa4128 100644 --- a/activesupport/lib/active_support/core_ext/thread.rb +++ b/activesupport/lib/active_support/core_ext/thread.rb @@ -33,7 +33,7 @@ class Thread _locals[key.to_sym] = value end - # Returns an an array of the names of the thread-local variables (as Symbols). + # Returns an array of the names of the thread-local variables (as Symbols). # # thr = Thread.new do # Thread.current.thread_variable_set(:cat, 'meow') diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 5ebafcc26e..89cd7516cd 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -91,10 +91,11 @@ class Time end end - # Uses Date to provide precise Time calculations for years, months, and days. - # The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>, - # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, - # <tt>:minutes</tt>, <tt>:seconds</tt>. + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The +options+ parameter + # takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>, + # <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>, + # <tt>:seconds</tt>. def advance(options) unless options[:weeks].nil? options[:weeks], partial_weeks = options[:weeks].divmod(1) @@ -107,6 +108,7 @@ class Time end d = to_date.advance(options) + d = d.gregorian if d.julian? time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) seconds_to_advance = \ options.fetch(:seconds, 0) + diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 19d4ff51d7..6be19771f5 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -176,14 +176,22 @@ module ActiveSupport #:nodoc: end def const_missing(const_name) - # The interpreter does not pass nesting information, and in the - # case of anonymous modules we cannot even make the trade-off of - # assuming their name reflects the nesting. Resort to Object as - # the only meaningful guess we can make. - from_mod = anonymous? ? ::Object : self + from_mod = anonymous? ? guess_for_anonymous(const_name) : self Dependencies.load_missing_constant(from_mod, const_name) end + # Dependencies assumes the name of the module reflects the nesting (unless + # it can be proven that is not the case), and the path to the file that + # defines the constant. Anonymous modules cannot follow these conventions + # and we assume therefore the user wants to refer to a top-level constant. + def guess_for_anonymous(const_name) + if Object.const_defined?(const_name) + raise NameError, "#{const_name} cannot be autoloaded from an anonymous class or module" + else + Object + end + end + def unloadable(const_desc = self) super(const_desc) end @@ -456,8 +464,6 @@ module ActiveSupport #:nodoc: raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" end - raise NameError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name, false) - qualified_name = qualified_name_for from_mod, const_name path_suffix = qualified_name.underscore diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index f690eab604..594a4ca938 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -159,7 +159,7 @@ module ActiveSupport # # counters.fetch('foo') # => 1 # counters.fetch(:bar, 0) # => 0 - # counters.fetch(:bar) {|key| 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) @@ -172,7 +172,7 @@ module ActiveSupport # hash[:b] = 'y' # hash.values_at('a', 'b') # => ["x", "y"] def values_at(*indices) - indices.collect {|key| self[convert_key(key)]} + indices.collect { |key| self[convert_key(key)] } end # Returns an exact copy of the hash. @@ -228,7 +228,11 @@ module ActiveSupport def to_options!; self end def select(*args, &block) - dup.tap {|hash| hash.select!(*args, &block)} + dup.tap { |hash| hash.select!(*args, &block) } + end + + def reject(*args, &block) + dup.tap { |hash| hash.reject!(*args, &block) } end # Convert to a regular hash with string keys. diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index dcdea70443..23cd6716e3 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -8,6 +8,8 @@ module I18n config.i18n.railties_load_path = [] config.i18n.load_path = [] config.i18n.fallbacks = ActiveSupport::OrderedOptions.new + # Enforce I18n to check the available locales when setting a locale. + config.i18n.enforce_available_locales = true # Set the i18n configuration after initialization since a lot of # configuration is still usually done in application initializers. @@ -31,10 +33,11 @@ module I18n fallbacks = app.config.i18n.delete(:fallbacks) - if app.config.i18n.has_key?(:enforce_available_locales) - # this option needs to be set before `default_locale=` to work properly. - I18n.enforce_available_locales = app.config.i18n.delete(:enforce_available_locales) - end + # Avoid issues with setting the default_locale by disabling available locales + # check while configuring. + enforce_available_locales = app.config.i18n.delete(:enforce_available_locales) + enforce_available_locales = I18n.enforce_available_locales unless I18n.enforce_available_locales.nil? + I18n.enforce_available_locales = false app.config.i18n.each do |setting, value| case setting @@ -49,6 +52,9 @@ module I18n init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks) + # Restore available locales check so it will take place from now on. + I18n.enforce_available_locales = enforce_available_locales + reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! } app.reloaders << reloader ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated } diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 0f7ae98a8a..cdee4c2ca5 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -117,7 +117,8 @@ module ActiveSupport result.gsub!(/([a-z\d]*)/i) { |match| "#{inflections.acronyms[match] || match.downcase}" } - options.fetch(:capitalize, true) ? result.gsub(/^\w/) { $&.upcase } : result + result.gsub!(/^\w/) { $&.upcase } if options.fetch(:capitalize, true) + result end # Capitalizes all the words and replaces some characters in the string to diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 060dcb6995..f29d42276d 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/module/delegation' module ActiveSupport class << self delegate :use_standard_json_time_format, :use_standard_json_time_format=, + :time_precision, :time_precision=, :escape_html_entities_in_json, :escape_html_entities_in_json=, :encode_big_decimal_as_string, :encode_big_decimal_as_string=, :json_encoder, :json_encoder=, @@ -60,36 +61,31 @@ module ActiveSupport end # Mark these as private so we don't leak encoding-specific constructs - private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES, + private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES, :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString - # Recursively turn the given object into a "jsonified" Ruby data structure - # that the JSON gem understands - i.e. we want only Hash, Array, String, - # Numeric, true, false and nil in the final tree. Calls #as_json on it if - # it's not from one of these base types. - # - # This allows developers to implement #as_json withouth having to worry - # about what base types of objects they are allowed to return and having - # to remember calling #as_json recursively. - # - # By default, the options hash is not passed to the children data structures - # to avoid undesiarable result. Develoers must opt-in by implementing - # custom #as_json methods (e.g. Hash#as_json and Array#as_json). + # Convert an object into a "JSON-ready" representation composed of + # primitives like Hash, Array, String, Numeric, and true/false/nil. + # Recursively calls #as_json to the object to recursively build a + # fully JSON-ready object. + # + # This allows developers to implement #as_json without having to + # worry about what base types of objects they are allowed to return + # or having to remember to call #as_json recursively. + # + # Note: the +options+ hash passed to +object.to_json+ is only passed + # to +object.as_json+, not any of this method's recursive +#as_json+ + # calls. def jsonify(value) - if value.is_a?(Hash) - Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }] - elsif value.is_a?(Array) - value.map { |v| jsonify(v) } - elsif value.is_a?(String) + case value + when String EscapedString.new(value) - elsif value.is_a?(Numeric) + when Numeric, NilClass, TrueClass, FalseClass value - elsif value == true - true - elsif value == false - false - elsif value == nil - nil + when Hash + Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }] + when Array + value.map { |v| jsonify(v) } else jsonify value.as_json end @@ -110,6 +106,10 @@ module ActiveSupport # as a safety measure. attr_accessor :escape_html_entities_in_json + # Sets the precision of encoded time values. + # Defaults to 3 (equivalent to millisecond precision) + attr_accessor :time_precision + # Sets the encoder used by Rails to encode Ruby objects into JSON strings # in +Object#to_json+ and +ActiveSupport::JSON.encode+. attr_accessor :json_encoder @@ -166,6 +166,7 @@ module ActiveSupport self.use_standard_json_time_format = true self.escape_html_entities_in_json = true self.json_encoder = JSONGemEncoder + self.time_precision = 3 end end end diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 598c46bce5..51d2da3a79 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -57,18 +57,16 @@ module ActiveSupport # secret they've provided is at least 30 characters in length. def ensure_secret_secure(secret) if secret.blank? - raise ArgumentError, "A secret is required to generate an " + - "integrity hash for cookie session data. Use " + - "config.secret_key_base = \"some secret phrase of at " + - "least #{SECRET_MIN_LENGTH} characters\"" + - "in config/initializers/secret_token.rb" + raise ArgumentError, "A secret is required to generate an integrity hash " \ + "for cookie session data. Set a secret_key_base of at least " \ + "#{SECRET_MIN_LENGTH} characters in config/secrets.yml." end if secret.length < SECRET_MIN_LENGTH - raise ArgumentError, "Secret should be something secure, " + - "like \"#{SecureRandom.hex(16)}\". The value you " + - "provided, \"#{secret}\", is shorter than the minimum length " + - "of #{SECRET_MIN_LENGTH} characters" + raise ArgumentError, "Secret should be something secure, " \ + "like \"#{SecureRandom.hex(16)}\". The value you " \ + "provided, \"#{secret}\", is shorter than the minimum length " \ + "of #{SECRET_MIN_LENGTH} characters." end end end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index bffdfc6201..b019ad0dec 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -12,7 +12,7 @@ module ActiveSupport # This can be used in situations similar to the <tt>MessageVerifier</tt>, but # where you don't want users to be able to determine the value of the payload. # - # salt = SecureRandom.random_bytes(64) + # salt = SecureRandom.random_bytes(64) # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..." # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...> # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." @@ -76,12 +76,12 @@ module ActiveSupport encrypted_data = cipher.update(@serializer.dump(value)) encrypted_data << cipher.final - [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--") + "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" end def _decrypt(encrypted_message) cipher = new_cipher - encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.decode64(v)} + encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)} cipher.decrypt cipher.key = @secret @@ -91,7 +91,7 @@ module ActiveSupport decrypted_data << cipher.final @serializer.load(decrypted_data) - rescue OpenSSLCipherError, TypeError + rescue OpenSSLCipherError, TypeError, ArgumentError raise InvalidMessage end diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index e0cd92ae3c..8e6e1dcfeb 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -37,7 +37,12 @@ module ActiveSupport data, digest = signed_message.split("--") if data.present? && digest.present? && secure_compare(digest, generate_digest(data)) - @serializer.load(::Base64.decode64(data)) + begin + @serializer.load(::Base64.strict_decode64(data)) + rescue ArgumentError => argument_error + raise InvalidSignature if argument_error.message =~ %r{invalid base64} + raise + end else raise InvalidSignature end diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 1845c6ae38..84799c2399 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -11,7 +11,7 @@ module ActiveSupport NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation - UNICODE_VERSION = '6.2.0' + UNICODE_VERSION = '6.3.0' # The default normalization used for operations that require # normalization. It can be set to any of the normalizations @@ -212,37 +212,43 @@ module ActiveSupport codepoints end - # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent - # resulting in a valid UTF-8 string. - # - # Passing +true+ will forcibly tidy all bytes, assuming that the string's - # encoding is entirely CP1252 or ISO-8859-1. - def tidy_bytes(string, force = false) - return string if string.empty? - - if force - return string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) + # Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1. + if RUBY_VERSION >= '2.1' + # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent + # resulting in a valid UTF-8 string. + # + # Passing +true+ will forcibly tidy all bytes, assuming that the string's + # encoding is entirely CP1252 or ISO-8859-1. + def tidy_bytes(string, force = false) + return string if string.empty? + return recode_windows1252_chars(string) if force + string.scrub { |bad| recode_windows1252_chars(bad) } end + else + def tidy_bytes(string, force = false) + return string if string.empty? + return recode_windows1252_chars(string) if force + + # We can't transcode to the same format, so we choose a nearly-identical encoding. + # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to + # CP1252 when we get errors. The final string will be 'converted' back to UTF-8 + # before returning. + reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC) + + source = string.dup + out = ''.force_encoding(Encoding::UTF_8_MAC) + + loop do + reader.primitive_convert(source, out) + _, _, _, error_bytes, _ = reader.primitive_errinfo + break if error_bytes.nil? + out << error_bytes.encode(Encoding::UTF_8_MAC, Encoding::Windows_1252, invalid: :replace, undef: :replace) + end - # We can't transcode to the same format, so we choose a nearly-identical encoding. - # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to - # CP1252 when we get errors. The final string will be 'converted' back to UTF-8 - # before returning. - reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC) - - source = string.dup - out = ''.force_encoding(Encoding::UTF_8_MAC) + reader.finish - loop do - reader.primitive_convert(source, out) - _, _, _, error_bytes, _ = reader.primitive_errinfo - break if error_bytes.nil? - out << error_bytes.encode(Encoding::UTF_8_MAC, Encoding::Windows_1252, invalid: :replace, undef: :replace) + out.encode!(Encoding::UTF_8) end - - reader.finish - - out.encode!(Encoding::UTF_8) end # Returns the KC normalization of the string by default. NFKC is @@ -371,14 +377,8 @@ module ActiveSupport end.pack('U*') end - def tidy_byte(byte) - if byte < 160 - [database.cp1252[byte] || byte].pack("U").unpack("C*") - elsif byte < 192 - [194, byte] - else - [195, byte - 64] - end + def recode_windows1252_chars(string) + string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) end def database diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index c6658dba96..b169e3af01 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -1,4 +1,3 @@ - module ActiveSupport module NumberHelper extend ActiveSupport::Autoload @@ -343,6 +342,5 @@ module ActiveSupport def number_to_human(number, options = {}) NumberToHumanConverter.convert(number, options) end - end end diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb index d1335f6910..78d2c9ae6e 100644 --- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb @@ -1,6 +1,6 @@ module ActiveSupport module NumberHelper - class NumberToHumanSizeConverter < NumberConverter + class NumberToHumanSizeConverter < NumberConverter #:nodoc: STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb] self.namespace = :human diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb index 4c33c30772..af2ee56d91 100644 --- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb @@ -1,6 +1,6 @@ module ActiveSupport module NumberHelper - class NumberToPhoneConverter < NumberConverter + class NumberToPhoneConverter < NumberConverter #:nodoc: def convert str = country_code(opts[:country_code]) str << convert_to_phone_number(number.to_s.strip) diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb index 273667fdae..c42354fc83 100644 --- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -5,28 +5,50 @@ module ActiveSupport self.validate_float = true def convert - @number = Float(number) - precision = options.delete :precision significant = options.delete :significant + case number + when Float, String + @number = BigDecimal(number.to_s) + when Rational + if significant + @number = BigDecimal(number, digit_count(number.to_i) + precision) + else + @number = BigDecimal(number, precision) + end + else + @number = number.to_d + end + if significant && precision > 0 digits, rounded_number = digits_and_rounded_number(precision) precision -= digits precision = 0 if precision < 0 # don't let it be negative else - rounded_number = BigDecimal.new(number.to_s).round(precision).to_f + rounded_number = number.round(precision) + rounded_number = rounded_number.to_i if precision == 0 rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros end - delimited_number = NumberToDelimitedConverter.convert("%01.#{precision}f" % rounded_number, options) + formatted_string = + case rounded_number + when BigDecimal + s = rounded_number.to_s('F') + '0'*precision + a, b = s.split('.', 2) + a + '.' + b[0, precision] + else + "%01.#{precision}f" % rounded_number + end + + delimited_number = NumberToDelimitedConverter.convert(formatted_string, options) format_number(delimited_number) end private def digits_and_rounded_number(precision) - if number.zero? + if zero? [1, 0] else digits = digit_count(number) @@ -38,11 +60,11 @@ module ActiveSupport end def calculate_rounded_number(multiplier) - (BigDecimal.new(number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier + (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier end def digit_count(number) - (Math.log10(number.abs) + 1).floor + (Math.log10(absolute_number(number)) + 1).floor end def strip_insignificant_zeros @@ -57,6 +79,14 @@ module ActiveSupport number end end + + def absolute_number(number) + number.respond_to?(:abs) ? number.abs : number.to_d.abs + end + + def zero? + number.respond_to?(:zero?) ? number.zero? : number.to_d.zero? + end end end end diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 1a3693f766..58a2ce2105 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -28,6 +28,10 @@ module ActiveSupport coder.represent_seq '!omap', map { |k,v| { k => v } } end + def reject(*args, &block) + dup.tap { |hash| hash.reject!(*args, &block) } + end + def nested_under_indifferent_access self end diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index a5e7389d16..ca2e4d5625 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -32,12 +32,15 @@ module ActiveSupport # # If the class has an initializer, it must accept no arguments. module PerThreadRegistry + def self.extended(object) + object.instance_variable_set '@per_thread_registry_key', object.name.freeze + end + def instance - Thread.current[name] ||= new + Thread.current[@per_thread_registry_key] ||= new end protected - def method_missing(name, *args, &block) # :nodoc: # Caches the method definition as a singleton method of the receiver. define_singleton_method(name) do |*a, &b| diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb index a8342904dc..6c94c611b6 100644 --- a/activesupport/lib/active_support/testing/deprecation.rb +++ b/activesupport/lib/active_support/testing/deprecation.rb @@ -19,18 +19,17 @@ module ActiveSupport result end - private - def collect_deprecations - old_behavior = ActiveSupport::Deprecation.behavior - deprecations = [] - ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack| - deprecations << message - end - result = yield - [result, deprecations] - ensure - ActiveSupport::Deprecation.behavior = old_behavior + def collect_deprecations + old_behavior = ActiveSupport::Deprecation.behavior + deprecations = [] + ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack| + deprecations << message end + result = yield + [result, deprecations] + ensure + ActiveSupport::Deprecation.behavior = old_behavior + end end end end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index d5d31cecbe..908af176be 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,5 +1,4 @@ require 'rbconfig' -require 'minitest/parallel_each' module ActiveSupport module Testing @@ -7,11 +6,9 @@ module ActiveSupport require 'thread' def self.included(klass) #:nodoc: - klass.extend(Module.new { - def test_methods - ParallelEach.new super - end - }) + klass.class_eval do + parallelize_me! + end end def self.forking_env? @@ -40,6 +37,8 @@ module ActiveSupport module Forking def run_in_isolation(&blk) read, write = IO.pipe + read.binmode + write.binmode pid = fork do read.close diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 50db7da9d9..d459af1778 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -45,7 +45,7 @@ module ActiveSupport def initialize(utc_time, time_zone, local_time = nil, period = nil) @utc, @time_zone, @time = utc_time, time_zone, local_time - @period = @utc ? period : get_period_and_ensure_valid_local_time + @period = @utc ? period : get_period_and_ensure_valid_local_time(period) end # Returns a Time or DateTime instance that represents the time in +time_zone+. @@ -132,8 +132,8 @@ module ActiveSupport end def xmlschema(fraction_digits = 0) - fraction = if fraction_digits > 0 - (".%06i" % time.usec)[0, fraction_digits + 1] + fraction = if fraction_digits.to_i > 0 + (".%06i" % time.usec)[0, fraction_digits.to_i + 1] end "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}" @@ -154,7 +154,7 @@ module ActiveSupport # # => "2005/02/01 05:15:10 -1000" def as_json(options = nil) if ActiveSupport::JSON::Encoding.use_standard_json_time_format - xmlschema(3) + xmlschema(ActiveSupport::JSON::Encoding.time_precision) else %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) end @@ -292,6 +292,12 @@ module ActiveSupport end end + def change(options) + new_time = time.change(options) + periods = time_zone.periods_for_local(new_time) + self.class.new(nil, time_zone, new_time, periods.include?(period) ? period : nil) + end + %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method_name} # def month @@ -367,12 +373,12 @@ module ActiveSupport end private - def get_period_and_ensure_valid_local_time + def get_period_and_ensure_valid_local_time(period) # we don't want a Time.local instance enforcing its own DST rules as well, # so transfer time values to a utc constructor if necessary @time = transfer_time_values_to_utc_constructor(@time) unless @time.utc? begin - @time_zone.period_for_local(@time) + period || @time_zone.period_for_local(@time) rescue ::TZInfo::PeriodNotFound # time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again @time += 1.hour diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index a22e61d286..eb785d46ce 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -1,3 +1,4 @@ +require 'tzinfo' require 'thread_safe' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/try' @@ -208,8 +209,6 @@ module ActiveSupport # (GMT). Seconds were chosen as the offset unit because that is the unit # that Ruby uses to represent time zone offsets (see Time#utc_offset). def initialize(name, utc_offset = nil, tzinfo = nil) - self.class.send(:require_tzinfo) - @name = name @utc_offset = utc_offset @tzinfo = tzinfo || TimeZone.find_tzinfo(name) @@ -235,6 +234,7 @@ module ActiveSupport # Compare this time zone to the parameter. The two are compared first on # their offsets, and then by name. def <=>(zone) + return unless zone.respond_to? :utc_offset result = (utc_offset <=> zone.utc_offset) result = (name <=> zone.name) if result == 0 result @@ -352,6 +352,10 @@ module ActiveSupport tzinfo.period_for_local(time, dst) end + def periods_for_local(time) #:nodoc: + tzinfo.periods_for_local(time) + end + def self.find_tzinfo(name) TZInfo::TimezoneProxy.new(MAPPING[name] || name) end @@ -359,14 +363,14 @@ module ActiveSupport class << self alias_method :create, :new - # Return a TimeZone instance with the given name, or +nil+ if no + # Returns a TimeZone instance with the given name, or +nil+ if no # such TimeZone instance exists. (This exists to support the use of # this class with the +composed_of+ macro.) def new(name) self[name] end - # Return an array of all TimeZone objects. There are multiple + # Returns an array of all TimeZone objects. There are multiple # TimeZone objects per time zone, in many cases, to make it easier # for users to find their own time zone. def all @@ -389,7 +393,7 @@ module ActiveSupport case arg when String begin - lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset } + @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset } rescue TZInfo::InvalidTimezoneIdentifier nil end @@ -406,22 +410,6 @@ module ActiveSupport def us_zones @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } end - - protected - - def require_tzinfo - require 'tzinfo' unless defined?(::TZInfo) - rescue LoadError - $stderr.puts "You don't have tzinfo installed in your application. Please add it to your Gemfile and run bundle install" - raise - end - - private - - def lazy_zones_map - require_tzinfo - @lazy_zones_map - end end private diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat Binary files differindex 2571faa019..394ee95f4b 100644 --- a/activesupport/lib/active_support/values/unicode_tables.dat +++ b/activesupport/lib/active_support/values/unicode_tables.dat diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 8762330a6e..b3f0e7198d 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,7 +1,7 @@ module ActiveSupport # Returns the version of the currently loaded ActiveSupport as a Gem::Version def self.version - Gem::Version.new "4.1.0.beta" + Gem::Version.new "4.1.0.beta1" end module VERSION #:nodoc: |