diff options
Diffstat (limited to 'activesupport')
48 files changed, 542 insertions, 225 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index d4eaee9f6d..ea84b54b3f 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,59 @@ +* Allow Array#excluding and Enumerable#excluding to deal with a passed array gracefully. + + [ 1, 2, 3, 4, 5 ].excluding([4, 5]) => [ 1, 2, 3 ] + + *DHH* + +* Renamed Array#without and Enumerable#without to Array#excluding and Enumerable#excluding, to create parity with + Array#including and Enumerable#including. Retained the old names as aliases. + + *DHH* + +* Added Array#including and Enumerable#including to conveniently enlarge a collection with more members using a method rather than an operator: + + [ 1, 2, 3 ].including(4, 5) => [ 1, 2, 3, 4, 5 ] + post.authors.including(Current.person) => All the authors plus the current person! + + *DHH* + +## Rails 6.0.0.beta2 (February 25, 2019) ## + +* New autoloading based on [Zeitwerk](https://github.com/fxn/zeitwerk). + + *Xavier Noria* + +* Revise `ActiveSupport::Notifications.unsubscribe` to correctly handle Regex or other multiple-pattern subscribers. + + *Zach Kemp* + +* Add `before_reset` callback to `CurrentAttributes` and define `after_reset` as an alias of `resets` for symmetry. + + *Rosa Gutierrez* + +* Remove the `` Kernel#` `` override that suppresses ENOENT and accidentally returns nil on Unix systems. + + *Akinori Musha* + +* Add `ActiveSupport::HashWithIndifferentAccess#assoc`. + + `assoc` can now be called with either a string or a symbol. + + *Stefan Schüßler* + +* Add `Hash#deep_transform_values`, and `Hash#deep_transform_values!`. + + *Guillermo Iguaran* + +## Rails 6.0.0.beta1 (January 18, 2019) ## + +* Remove deprecated `Module#reachable?` method. + + *Rafael Mendonça França* + +* Remove deprecated `#acronym_regex` method from `Inflections`. + + *Rafael Mendonça França* + * Fix `String#safe_constantize` throwing a `LoadError` for incorrectly cased constant references. *Keenan Brock* diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc index c770324be8..d8d25d86c8 100644 --- a/activesupport/README.rdoc +++ b/activesupport/README.rdoc @@ -5,6 +5,7 @@ extensions that were found useful for the Rails framework. These additions reside in this package so they can be loaded as needed in Ruby projects outside of Rails. +You can read more about the extensions in the {Active Support Core Extensions}[https://edgeguides.rubyonrails.org/active_support_core_extensions.html] guide. == Download and installation @@ -28,7 +29,7 @@ Active Support is released under the MIT license: API documentation is at: -* http://api.rubyonrails.org +* https://api.rubyonrails.org Bug reports for the Ruby on Rails project can be filed here: diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index bdd7bc70a0..2781d4e6f7 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.author = "David Heinemeier Hansson" s.email = "david@loudthinking.com" - s.homepage = "http://rubyonrails.org" + s.homepage = "https://rubyonrails.org" s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.rdoc", "lib/**/*"] s.require_path = "lib" @@ -30,8 +30,9 @@ Gem::Specification.new do |s| # NOTE: Please read our dependency guidelines before updating versions: # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves - s.add_dependency "i18n", ">= 0.7", "< 2" - s.add_dependency "tzinfo", "~> 1.1" - s.add_dependency "minitest", "~> 5.1" + s.add_dependency "i18n", ">= 0.7", "< 2" + s.add_dependency "tzinfo", "~> 1.1" + s.add_dependency "minitest", "~> 5.1" s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" + s.add_dependency "zeitwerk", "~> 1.3", ">= 1.3.2" end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 30a69c550b..e055135bb4 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -492,7 +492,7 @@ module ActiveSupport # # Options are passed to the underlying cache implementation. # - # All implementations may not support this method. + # Some implementations may not support this method. def delete_matched(matcher, options = nil) raise NotImplementedError.new("#{self.class.name} does not support delete_matched") end @@ -501,7 +501,7 @@ module ActiveSupport # # Options are passed to the underlying cache implementation. # - # All implementations may not support this method. + # Some implementations may not support this method. def increment(name, amount = 1, options = nil) raise NotImplementedError.new("#{self.class.name} does not support increment") end @@ -510,7 +510,7 @@ module ActiveSupport # # Options are passed to the underlying cache implementation. # - # All implementations may not support this method. + # Some implementations may not support this method. def decrement(name, amount = 1, options = nil) raise NotImplementedError.new("#{self.class.name} does not support decrement") end @@ -519,7 +519,7 @@ module ActiveSupport # # Options are passed to the underlying cache implementation. # - # All implementations may not support this method. + # Some implementations may not support this method. def cleanup(options = nil) raise NotImplementedError.new("#{self.class.name} does not support cleanup") end @@ -529,7 +529,7 @@ module ActiveSupport # # The options hash is passed to the underlying cache implementation. # - # All implementations may not support this method. + # Some implementations may not support this method. def clear(options = nil) raise NotImplementedError.new("#{self.class.name} does not support clear") end diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index de1fb1886c..f43894a1ea 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -18,7 +18,6 @@ module ActiveSupport DIR_FORMATTER = "%03X" FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write) FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room - EXCLUDED_DIRS = [".", ".."].freeze GITKEEP_FILES = [".gitkeep", ".keep"].freeze def initialize(cache_path, options = nil) @@ -35,7 +34,7 @@ module ActiveSupport # file store directory except for .keep or .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 = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES) + root_dirs = (Dir.children(cache_path) - GITKEEP_FILES) FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) }) rescue Errno::ENOENT end @@ -154,7 +153,7 @@ module ActiveSupport # Delete empty directories in the cache. def delete_empty_directories(dir) return if File.realpath(dir) == File.realpath(cache_path) - if exclude_from(dir, EXCLUDED_DIRS).empty? + if Dir.children(dir).empty? Dir.delete(dir) rescue nil delete_empty_directories(File.dirname(dir)) end @@ -167,8 +166,7 @@ module ActiveSupport def search_dir(dir, &callback) return if !File.exist?(dir) - Dir.foreach(dir) do |d| - next if EXCLUDED_DIRS.include?(d) + Dir.each_child(dir) do |d| name = File.join(dir, d) if File.directory?(name) search_dir(name, &callback) @@ -193,11 +191,6 @@ module ActiveSupport end end end - - # Exclude entries from source directory - def exclude_from(source, excludes) - Dir.entries(source).reject { |f| excludes.include?(f) } - end end end end diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 106b616529..629eb2dd70 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -62,13 +62,13 @@ module ActiveSupport return if pruning? @pruning = true begin - start_time = Time.now + start_time = Concurrent.monotonic_time cleanup instrument(:prune, target_size, from: @cache_size) do keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } } keys.each do |key| delete_entry(key, options) - return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time) + return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time) end end ensure diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index b7ff7a3907..10e4c6b09d 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -29,16 +29,28 @@ class Array end end - # Returns a copy of the Array without the specified elements. + # Returns a new array that includes the passed elements. # - # people = ["David", "Rafael", "Aaron", "Todd"] - # people.without "Aaron", "Todd" - # # => ["David", "Rafael"] + # [ 1, 2, 3 ].including(4, 5) => [ 1, 2, 3, 4, 5 ] + # [ [ 0, 1 ] ].including([ [ 1, 0 ] ]) => [ [ 0, 1 ], [ 1, 0 ] ] + def including(*elements) + self + elements.flatten(1) + end + + # Returns a copy of the Array excluding the specified elements. + # + # ["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") => ["David", "Rafael"] + # [ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ]) => [ [ 0, 1 ] ] # - # Note: This is an optimization of <tt>Enumerable#without</tt> that uses <tt>Array#-</tt> + # Note: This is an optimization of <tt>Enumerable#excluding</tt> that uses <tt>Array#-</tt> # instead of <tt>Array#reject</tt> for performance reasons. + def excluding(*elements) + self - elements.flatten(1) + end + + # Alias for #excluding. def without(*elements) - self - elements + excluding(*elements) end # Equal to <tt>self[1]</tt>. diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index fa33ff945f..255cbee55c 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -84,16 +84,17 @@ class Class # To set a default value for the attribute, pass <tt>default:</tt>, like so: # # class_attribute :settings, default: {} - def class_attribute(*attrs) - options = attrs.extract_options! - instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) - instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) - instance_predicate = options.fetch(:instance_predicate, true) - default_value = options.fetch(:default, nil) - + def class_attribute( + *attrs, + instance_accessor: true, + instance_reader: instance_accessor, + instance_writer: instance_accessor, + instance_predicate: true, + default: nil + ) attrs.each do |name| singleton_class.silence_redefinition_of_method(name) - define_singleton_method(name) { nil } + define_singleton_method(name) { default } singleton_class.silence_redefinition_of_method("#{name}?") define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate @@ -102,9 +103,7 @@ class Class singleton_class.silence_redefinition_of_method("#{name}=") define_singleton_method("#{name}=") do |val| - singleton_class.class_eval do - redefine_method(name) { val } - end + redefine_singleton_method(name) { val } if singleton_class? class_eval do @@ -137,10 +136,6 @@ class Class instance_variable_set ivar, val end end - - unless default_value.nil? - self.send("#{name}=", default_value) - end end end end diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 1cd7acb05d..d03a8d3997 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -110,12 +110,13 @@ class Date # Provides precise Date 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>. def advance(options) - options = options.dup d = self - d = d >> options.delete(:years) * 12 if options[:years] - d = d >> options.delete(:months) if options[:months] - d = d + options.delete(:weeks) * 7 if options[:weeks] - d = d + options.delete(:days) if options[:days] + + d = d >> options[:years] * 12 if options[:years] + d = d >> options[:months] if options[:months] + d = d + options[:weeks] * 7 if options[:weeks] + d = d + options[:days] if options[:days] + d end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index d87d63f287..4675c41936 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -97,23 +97,43 @@ module Enumerable end end + # Returns a new array that includes the passed elements. + # + # [ 1, 2, 3 ].including(4, 5) + # # => [ 1, 2, 3, 4, 5 ] + # + # ["David", "Rafael"].including %w[ Aaron Todd ] + # # => ["David", "Rafael", "Aaron", "Todd"] + def including(*elements) + to_a.including(*elements) + end + # The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the # collection does not include the object. def exclude?(object) !include?(object) end - # Returns a copy of the enumerable without the specified elements. + # Returns a copy of the enumerable excluding the specified elements. + # + # ["David", "Rafael", "Aaron", "Todd"].excluding "Aaron", "Todd" + # # => ["David", "Rafael"] # - # ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd" + # ["David", "Rafael", "Aaron", "Todd"].excluding %w[ Aaron Todd ] # # => ["David", "Rafael"] # - # {foo: 1, bar: 2, baz: 3}.without :bar + # {foo: 1, bar: 2, baz: 3}.excluding :bar # # => {foo: 1, baz: 3} - def without(*elements) + def excluding(*elements) + elements.flatten!(1) reject { |element| elements.include?(element) } end + # Alias for #excluding. + def without(*elements) + excluding(*elements) + end + # Convert an enumerable to an array based on the given key. # # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index c4b9e5f1a0..2f0901d853 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -2,6 +2,7 @@ require "active_support/core_ext/hash/conversions" require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/deep_transform_values" require "active_support/core_ext/hash/except" require "active_support/core_ext/hash/indifferent_access" require "active_support/core_ext/hash/keys" diff --git a/activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb b/activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb new file mode 100644 index 0000000000..720a1f67c8 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class Hash + # 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 and arrays. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_transform_values{ |value| value.to_s.upcase } + # # => {person: {name: "ROB", age: "28"}} + def deep_transform_values(&block) + _deep_transform_values_in_object(self, &block) + end + + # Destructively converts all values by using the block operation. + # This includes the values from the root hash and from all + # nested hashes and arrays. + def deep_transform_values!(&block) + _deep_transform_values_in_object!(self, &block) + end + + private + # support methods for deep transforming nested hashes and arrays + def _deep_transform_values_in_object(object, &block) + case object + when Hash + object.transform_values { |value| _deep_transform_values_in_object(value, &block) } + when Array + object.map { |e| _deep_transform_values_in_object(e, &block) } + else + yield(object) + end + end + + def _deep_transform_values_in_object!(object, &block) + case object + when Hash + object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) } + when Array + object.map! { |e| _deep_transform_values_in_object!(e, &block) } + else + yield(object) + end + end +end diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb index 0f4356fbdd..7708069301 100644 --- a/activesupport/lib/active_support/core_ext/kernel.rb +++ b/activesupport/lib/active_support/core_ext/kernel.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/kernel/agnostics" require "active_support/core_ext/kernel/concern" require "active_support/core_ext/kernel/reporting" require "active_support/core_ext/kernel/singleton_class" diff --git a/activesupport/lib/active_support/core_ext/kernel/agnostics.rb b/activesupport/lib/active_support/core_ext/kernel/agnostics.rb deleted file mode 100644 index 403b5f31f0..0000000000 --- a/activesupport/lib/active_support/core_ext/kernel/agnostics.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -class Object - # Makes backticks behave (somewhat more) similarly on all platforms. - # On win32 `nonexistent_command` raises Errno::ENOENT; on Unix, the - # spawned shell prints a message to stderr and sets $?. We emulate - # Unix on the former but not the latter. - def `(command) #:nodoc: - super - rescue Errno::ENOENT => e - STDERR.puts "#$0: #{e}" - end -end diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index d91e3fba6a..542af98c04 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -3,7 +3,6 @@ require "active_support/core_ext/module/aliasing" require "active_support/core_ext/module/introspection" 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/attribute_accessors_per_thread" require "active_support/core_ext/module/attr_internal" diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb index e9cbda5245..2020f5204c 100644 --- a/activesupport/lib/active_support/core_ext/module/reachable.rb +++ b/activesupport/lib/active_support/core_ext/module/reachable.rb @@ -3,9 +3,4 @@ require "active_support/core_ext/module/anonymous" require "active_support/core_ext/string/inflections" -class Module - def reachable? #:nodoc: - !anonymous? && name.safe_constantize.equal?(self) - end - deprecate :reachable? -end +ActiveSupport::Deprecation.warn("reachable is deprecated and will be removed from the framework.") diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 8af301734a..e63b38b227 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -162,6 +162,11 @@ class String # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. # + # If the optional parameter +locale+ is specified, + # the word will be parameterized as a word of that language. + # By default, this parameter is set to <tt>nil</tt> and it will use + # configured I18n.locale + # # class Person # def to_param # "#{id}-#{name.parameterize}" @@ -187,8 +192,8 @@ class String # # <%= link_to(@person.name, person_path) %> # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a> - def parameterize(separator: "-", preserve_case: false) - ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case) + def parameterize(separator: "-", preserve_case: false, locale: nil) + ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale) end # Creates the name of a table like Rails does for models to table names. This method diff --git a/activesupport/lib/active_support/current_attributes.rb b/activesupport/lib/active_support/current_attributes.rb index 3145ff87a1..67ebe102d7 100644 --- a/activesupport/lib/active_support/current_attributes.rb +++ b/activesupport/lib/active_support/current_attributes.rb @@ -119,10 +119,16 @@ module ActiveSupport end end + # Calls this block before #reset is called on the instance. Used for resetting external collaborators that depend on current values. + def before_reset(&block) + set_callback :reset, :before, &block + end + # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone. def resets(&block) set_callback :reset, :after, &block end + alias_method :after_reset, :resets delegate :set, :reset, to: :instance diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb new file mode 100644 index 0000000000..1e697e1ba5 --- /dev/null +++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/inflections" + +module ActiveSupport + module Dependencies + module ZeitwerkIntegration # :nodoc: all + module Decorations + def clear + Dependencies.unload_interlock do + Rails.autoloaders.main.reload + end + end + + def constantize(cpath) + ActiveSupport::Inflector.constantize(cpath) + end + + def safe_constantize(cpath) + ActiveSupport::Inflector.safe_constantize(cpath) + end + + def autoloaded_constants + (Rails.autoloaders.main.loaded + Rails.autoloaders.once.loaded).to_a + end + + def autoloaded?(object) + cpath = object.is_a?(Module) ? object.name : object.to_s + Rails.autoloaders.any? { |autoloader| autoloader.loaded?(cpath) } + end + + def verbose=(verbose) + l = verbose ? logger || Rails.logger : nil + Rails.autoloaders.each { |autoloader| autoloader.logger = l } + end + + def unhook! + :no_op + end + end + + module Inflector + def self.camelize(basename, _abspath) + basename.camelize + end + end + + class << self + def take_over + setup_autoloaders + freeze_autoload_paths + decorate_dependencies + end + + private + + def setup_autoloaders + Dependencies.autoload_paths.each do |autoload_path| + # Zeitwerk only accepts existing directories in `push_dir` to + # prevent misconfigurations. + next unless File.directory?(autoload_path) + + if autoload_once?(autoload_path) + Rails.autoloaders.once.push_dir(autoload_path) + else + Rails.autoloaders.main.push_dir(autoload_path) + end + end + + Rails.autoloaders.each(&:setup) + end + + def autoload_once?(autoload_path) + Dependencies.autoload_once_paths.include?(autoload_path) || + Gem.path.any? { |gem_path| autoload_path.to_s.start_with?(gem_path) } + end + + def freeze_autoload_paths + Dependencies.autoload_paths.freeze + Dependencies.autoload_once_paths.freeze + end + + def decorate_dependencies + Dependencies.unhook! + Dependencies.singleton_class.prepend(Decorations) + Object.class_eval { alias_method :require_dependency, :require } + end + end + end + end +end diff --git a/activesupport/lib/active_support/encrypted_file.rb b/activesupport/lib/active_support/encrypted_file.rb index c66f1b557e..2b7db568a5 100644 --- a/activesupport/lib/active_support/encrypted_file.rb +++ b/activesupport/lib/active_support/encrypted_file.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "pathname" +require "tmpdir" require "active_support/message_encryptor" module ActiveSupport @@ -67,7 +68,7 @@ module ActiveSupport write(updated_contents) if updated_contents != contents ensure - FileUtils.rm(tmp_path) if tmp_path.exist? + FileUtils.rm(tmp_path) if tmp_path&.exist? end diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index c951ad16a3..009bb895a6 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -10,7 +10,7 @@ module ActiveSupport MAJOR = 6 MINOR = 0 TINY = 0 - PRE = "alpha" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index f1af76019a..3a2b2652c4 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -164,6 +164,19 @@ module ActiveSupport super(convert_key(key)) end + # Same as <tt>Hash#assoc</tt> where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters.assoc('foo') # => ["foo", 1] + # counters.assoc(:foo) # => ["foo", 1] + # counters.assoc(:zoo) # => nil + def assoc(key) + super(convert_key(key)) + end + # Same as <tt>Hash#fetch</tt> where the key passed as argument can be # either a string or a symbol: # diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index fa087c4dd6..88cdd99dbd 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -65,8 +65,7 @@ module ActiveSupport @__instance__[locale] ||= new end - attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex - deprecate :acronym_regex + attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc: diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 0d2a17970f..09e6cbeaaf 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -51,19 +51,18 @@ module ActiveSupport # # Now you can have different transliterations for each locale: # - # I18n.locale = :en - # transliterate('Jürgen') + # transliterate('Jürgen', locale: :en) # # => "Jurgen" # - # I18n.locale = :de - # transliterate('Jürgen') + # transliterate('Jürgen', locale: :de) # # => "Juergen" - def transliterate(string, replacement = "?") + def transliterate(string, replacement = "?", locale: nil) raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String) I18n.transliterate( ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc), - replacement: replacement + replacement: replacement, + locale: locale ) end @@ -89,9 +88,9 @@ module ActiveSupport # parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--" # parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--" # - def parameterize(string, separator: "-", preserve_case: false) + def parameterize(string, separator: "-", preserve_case: false, locale: nil) # Replace accented chars with their ASCII equivalents. - parameterized_string = transliterate(string) + parameterized_string = transliterate(string, locale) # Turn unwanted chars into the separator. parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator) diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 00edcdd05a..8b61982883 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -38,36 +38,4 @@ module ActiveSupport @cache_keys[args.join] ||= @key_generator.generate_key(*args) end end - - class LegacyKeyGenerator # :nodoc: - SECRET_MIN_LENGTH = 30 # Characters - - def initialize(secret) - ensure_secret_secure(secret) - @secret = secret - end - - def generate_key(salt) - @secret - end - - private - - # To prevent users from using something insecure like "Password" we make sure that the - # 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. Set a secret_key_base of at least " \ - "#{SECRET_MIN_LENGTH} characters by running `rails credentials:edit`." - 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." - end - end - end end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 6f7302e732..7d6f8937f0 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -53,7 +53,7 @@ module ActiveSupport # crypt.encrypt_and_sign(parcel, expires_in: 1.month) # crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year) # - # Then the messages can be verified and returned upto the expire time. + # Then the messages can be verified and returned up to the expire time. # Thereafter, verifying returns +nil+. # # === Rotating keys diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 64c557bec6..c4a4afe95f 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -71,7 +71,7 @@ module ActiveSupport # @verifier.generate(parcel, expires_in: 1.month) # @verifier.generate(doowad, expires_at: Time.now.end_of_year) # - # Then the messages can be verified and returned upto the expire time. + # Then the messages can be verified and returned up to the expire time. # Thereafter, the +verified+ method returns +nil+ while +verify+ raises # <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>. # diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 7ccc333463..d9e93b530c 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -153,6 +153,15 @@ module ActiveSupport # # ActiveSupport::Notifications.unsubscribe("render") # + # Subscribers using a regexp or other pattern-matching object will remain subscribed + # to all events that match their original pattern, unless those events match a string + # passed to `unsubscribe`: + # + # subscriber = ActiveSupport::Notifications.subscribe(/render/) { } + # ActiveSupport::Notifications.unsubscribe('render_template.action_view') + # subscriber.matches?('render_template.action_view') # => false + # subscriber.matches?('render_partial.action_view') # => true + # # == Default Queue # # Notifications ships with a queue implementation that consumes and publishes events diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 4e4ca70942..c506b35b1e 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -2,6 +2,7 @@ require "mutex_m" require "concurrent/map" +require "set" module ActiveSupport module Notifications @@ -13,16 +14,22 @@ module ActiveSupport include Mutex_m def initialize - @subscribers = [] + @string_subscribers = Hash.new { |h, k| h[k] = [] } + @other_subscribers = [] @listeners_for = Concurrent::Map.new super end - def subscribe(pattern = nil, block = Proc.new) - subscriber = Subscribers.new pattern, block + def subscribe(pattern = nil, callable = nil, &block) + subscriber = Subscribers.new(pattern, callable || block) synchronize do - @subscribers << subscriber - @listeners_for.clear + if String === pattern + @string_subscribers[pattern] << subscriber + @listeners_for.delete(pattern) + else + @other_subscribers << subscriber + @listeners_for.clear + end end subscriber end @@ -31,12 +38,19 @@ module ActiveSupport synchronize do case subscriber_or_name when String - @subscribers.reject! { |s| s.matches?(subscriber_or_name) } + @string_subscribers[subscriber_or_name].clear + @listeners_for.delete(subscriber_or_name) + @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) } else - @subscribers.delete(subscriber_or_name) + pattern = subscriber_or_name.try(:pattern) + if String === pattern + @string_subscribers[pattern].delete(subscriber_or_name) + @listeners_for.delete(pattern) + else + @other_subscribers.delete(subscriber_or_name) + @listeners_for.clear + end end - - @listeners_for.clear end end @@ -56,7 +70,8 @@ module ActiveSupport # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics) @listeners_for[name] || synchronize do # use synchronisation when accessing @subscribers - @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } + @listeners_for[name] ||= + @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) } end end @@ -100,9 +115,33 @@ module ActiveSupport end end + class Matcher #:nodoc: + attr_reader :pattern, :exclusions + + def self.wrap(pattern) + return pattern if String === pattern + new(pattern) + end + + def initialize(pattern) + @pattern = pattern + @exclusions = Set.new + end + + def unsubscribe!(name) + exclusions << -name if pattern === name + end + + def ===(name) + pattern === name && !exclusions.include?(name) + end + end + class Evented #:nodoc: + attr_reader :pattern + def initialize(pattern, delegate) - @pattern = pattern + @pattern = Matcher.wrap(pattern) @delegate = delegate @can_publish = delegate.respond_to?(:publish) end @@ -122,11 +161,15 @@ module ActiveSupport end def subscribed_to?(name) - @pattern === name + pattern === name end def matches?(name) - @pattern && @pattern === name + pattern && pattern === name + end + + def unsubscribe!(name) + pattern.unsubscribe!(name) end end @@ -189,6 +232,10 @@ module ActiveSupport true end + def unsubscribe!(*) + false + end + alias :matches? :=== end end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 125c06f37a..00a57c38c9 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -137,7 +137,7 @@ module ActiveSupport private def now - Process.clock_gettime(Process::CLOCK_MONOTONIC) + Concurrent.monotonic_time end if clock_gettime_supported? diff --git a/activesupport/test/cache/behaviors/connection_pool_behavior.rb b/activesupport/test/cache/behaviors/connection_pool_behavior.rb index 5e66e0b202..aed04d07d4 100644 --- a/activesupport/test/cache/behaviors/connection_pool_behavior.rb +++ b/activesupport/test/cache/behaviors/connection_pool_behavior.rb @@ -7,7 +7,7 @@ module ConnectionPoolBehavior threads = [] emulating_latency do - cache = ActiveSupport::Cache.lookup_store(store, { pool_size: 2, pool_timeout: 1 }.merge(store_options)) + cache = ActiveSupport::Cache.lookup_store(*store, { pool_size: 2, pool_timeout: 1 }.merge(store_options)) cache.clear assert_raises Timeout::Error do @@ -32,7 +32,7 @@ module ConnectionPoolBehavior threads = [] emulating_latency do - cache = ActiveSupport::Cache.lookup_store(store, store_options) + cache = ActiveSupport::Cache.lookup_store(*store, store_options) cache.clear assert_nothing_raised do diff --git a/activesupport/test/cache/stores/file_store_test.rb b/activesupport/test/cache/stores/file_store_test.rb index f6855bb308..0364d9ab64 100644 --- a/activesupport/test/cache/stores/file_store_test.rb +++ b/activesupport/test/cache/stores/file_store_test.rb @@ -101,7 +101,7 @@ class FileStoreTest < ActiveSupport::TestCase end assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!" assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!" - assert_empty Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) } + assert_empty Dir.children(sub_cache_dir) end def test_log_exception_when_cache_read_fails diff --git a/activesupport/test/cache/stores/mem_cache_store_test.rb b/activesupport/test/cache/stores/mem_cache_store_test.rb index f426a37c66..0e472f5a1a 100644 --- a/activesupport/test/cache/stores/mem_cache_store_test.rb +++ b/activesupport/test/cache/stores/mem_cache_store_test.rb @@ -25,8 +25,9 @@ end class MemCacheStoreTest < ActiveSupport::TestCase begin - ss = Dalli::Client.new("localhost:11211").stats - raise Dalli::DalliError unless ss["localhost:11211"] + servers = ENV["MEMCACHE_SERVERS"] || "localhost:11211" + ss = Dalli::Client.new(servers).stats + raise Dalli::DalliError unless ss[servers] MEMCACHE_UP = true rescue Dalli::DalliError @@ -37,8 +38,8 @@ class MemCacheStoreTest < ActiveSupport::TestCase def setup skip "memcache server is not up" unless MEMCACHE_UP - @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, expires_in: 60) - @peek = ActiveSupport::Cache.lookup_store(:mem_cache_store) + @cache = ActiveSupport::Cache.lookup_store(*store, expires_in: 60) + @peek = ActiveSupport::Cache.lookup_store(*store) @data = @cache.instance_variable_get(:@data) @cache.clear @cache.silence! @@ -56,21 +57,21 @@ class MemCacheStoreTest < ActiveSupport::TestCase include FailureSafetyBehavior def test_raw_values - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache = ActiveSupport::Cache.lookup_store(*store, raw: true) cache.clear cache.write("foo", 2) assert_equal "2", cache.read("foo") end def test_raw_values_with_marshal - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache = ActiveSupport::Cache.lookup_store(*store, raw: true) cache.clear cache.write("foo", Marshal.dump([])) assert_equal [], cache.read("foo") end def test_local_cache_raw_values - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache = ActiveSupport::Cache.lookup_store(*store, raw: true) cache.clear cache.with_local_cache do cache.write("foo", 2) @@ -79,7 +80,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase end def test_increment_expires_in - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache = ActiveSupport::Cache.lookup_store(*store, raw: true) cache.clear assert_called_with cache.instance_variable_get(:@data), :incr, [ "foo", 1, 60 ] do cache.increment("foo", 1, expires_in: 60) @@ -87,7 +88,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase end def test_decrement_expires_in - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache = ActiveSupport::Cache.lookup_store(*store, raw: true) cache.clear assert_called_with cache.instance_variable_get(:@data), :decr, [ "foo", 1, 60 ] do cache.decrement("foo", 1, expires_in: 60) @@ -95,7 +96,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase end def test_local_cache_raw_values_with_marshal - cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true) + cache = ActiveSupport::Cache.lookup_store(*store, raw: true) cache.clear cache.with_local_cache do cache.write("foo", Marshal.dump([])) @@ -114,7 +115,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase private def store - :mem_cache_store + [:mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211"] end def emulating_latency diff --git a/activesupport/test/cache/stores/redis_cache_store_test.rb b/activesupport/test/cache/stores/redis_cache_store_test.rb index 305a2c184d..1d87f74347 100644 --- a/activesupport/test/cache/stores/redis_cache_store_test.rb +++ b/activesupport/test/cache/stores/redis_cache_store_test.rb @@ -187,7 +187,7 @@ module ActiveSupport::Cache::RedisCacheStoreTests private def store - :redis_cache_store + [:redis_cache_store] end def emulating_latency @@ -204,7 +204,7 @@ module ActiveSupport::Cache::RedisCacheStoreTests class RedisDistributedConnectionPoolBehaviourTest < ConnectionPoolBehaviourTest private def store_options - { url: %w[ redis://localhost:6379/0 redis://localhost:6379/0 ] } + { url: [ENV["REDIS_URL"] || "redis://localhost:6379/0"] * 2 } end end diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb index 8c217023cf..427b058925 100644 --- a/activesupport/test/core_ext/array/access_test.rb +++ b/activesupport/test/core_ext/array/access_test.rb @@ -32,6 +32,18 @@ class AccessTest < ActiveSupport::TestCase assert_equal array[-2], array.second_to_last end + def test_including + assert_equal [1, 2, 3, 4, 5], [1, 2, 4].including(3, 5).sort + assert_equal [1, 2, 3, 4, 5], [1, 2, 4].including([3, 5]).sort + assert_equal [[0, 1], [1, 0]], [[0, 1]].including([[1, 0]]) + end + + def test_excluding + assert_equal [1, 2, 4], [1, 2, 3, 4, 5].excluding(3, 5) + assert_equal [1, 2, 4], [1, 2, 3, 4, 5].excluding([3, 5]) + assert_equal [[0, 1]], [[0, 1], [1, 0]].excluding([[1, 0]]) + end + def test_without assert_equal [1, 2, 4], [1, 2, 3, 4, 5].without(3, 5) end diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index b63464a36a..381b5a1f32 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -217,11 +217,18 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal false, GenericEnumerable.new([ 1 ]).exclude?(1) end + def test_excluding + assert_equal [1, 2, 4], GenericEnumerable.new((1..5).to_a).excluding(3, 5) + assert_equal [3, 4, 5], GenericEnumerable.new((1..5).to_a).excluding([1, 2]) + assert_equal [[0, 1]], GenericEnumerable.new([[0, 1], [1, 0]]).excluding([[1, 0]]) + assert_equal [1, 2, 4], (1..5).to_a.excluding(3, 5) + assert_equal [1, 2, 4], (1..5).to_set.excluding(3, 5) + assert_equal({ foo: 1, baz: 3 }, { foo: 1, bar: 2, baz: 3 }.excluding(:bar)) + end + def test_without assert_equal [1, 2, 4], GenericEnumerable.new((1..5).to_a).without(3, 5) - assert_equal [1, 2, 4], (1..5).to_a.without(3, 5) - assert_equal [1, 2, 4], (1..5).to_set.without(3, 5) - assert_equal({ foo: 1, baz: 3 }, { foo: 1, bar: 2, baz: 3 }.without(:bar)) + assert_equal [3, 4, 5], GenericEnumerable.new((1..5).to_a).without([1, 2]) end def test_pluck diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index e8e0a1ae72..8572d56722 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -33,6 +33,8 @@ class HashExtTest < ActiveSupport::TestCase h = {} assert_respond_to h, :deep_transform_keys assert_respond_to h, :deep_transform_keys! + assert_respond_to h, :deep_transform_values + assert_respond_to h, :deep_transform_values! assert_respond_to h, :symbolize_keys assert_respond_to h, :symbolize_keys! assert_respond_to h, :deep_symbolize_keys @@ -78,6 +80,31 @@ class HashExtTest < ActiveSupport::TestCase assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed) end + def test_deep_transform_values + assert_equal({ "a" => "1", "b" => "2" }, @strings.deep_transform_values { |value| value.to_s }) + assert_equal({ "a" => { "b" => { "c" => "3" } } }, @nested_strings.deep_transform_values { |value| value.to_s }) + assert_equal({ "a" => [ { "b" => "2" }, { "c" => "3" }, "4" ] }, @string_array_of_hashes.deep_transform_values { |value| value.to_s }) + end + + def test_deep_transform_values_not_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_transform_values { |value| value.to_s } + assert_equal @nested_mixed, transformed_hash + end + + def test_deep_transform_values! + assert_equal({ "a" => "1", "b" => "2" }, @strings.deep_transform_values! { |value| value.to_s }) + assert_equal({ "a" => { "b" => { "c" => "3" } } }, @nested_strings.deep_transform_values! { |value| value.to_s }) + assert_equal({ "a" => [ { "b" => "2" }, { "c" => "3" }, "4" ] }, @string_array_of_hashes.deep_transform_values! { |value| value.to_s }) + end + + def test_deep_transform_values_with_bang_mutates + transformed_hash = @nested_mixed.deep_dup + transformed_hash.deep_transform_values! { |value| value.to_s } + assert_equal({ "a" => { b: { "c" => "3" } } }, transformed_hash) + assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed) + end + def test_symbolize_keys assert_equal @symbols, @symbols.symbolize_keys assert_equal @symbols, @strings.symbolize_keys diff --git a/activesupport/test/core_ext/module/reachable_test.rb b/activesupport/test/core_ext/module/reachable_test.rb deleted file mode 100644 index f356d46957..0000000000 --- a/activesupport/test/core_ext/module/reachable_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require "abstract_unit" -require "active_support/core_ext/module/reachable" - -class AnonymousTest < ActiveSupport::TestCase - test "an anonymous class or module is not reachable" do - assert_deprecated do - assert_not_predicate Module.new, :reachable? - assert_not_predicate Class.new, :reachable? - end - end - - test "ordinary named classes or modules are reachable" do - assert_deprecated do - assert_predicate Kernel, :reachable? - assert_predicate Object, :reachable? - end - end - - test "a named class or module whose constant has gone is not reachable" do - c = eval "class C; end; C" - m = eval "module M; end; M" - - self.class.send(:remove_const, :C) - self.class.send(:remove_const, :M) - - assert_deprecated do - assert_not_predicate c, :reachable? - assert_not_predicate m, :reachable? - end - end - - test "a named class or module whose constants store different objects are not reachable" do - c = eval "class C; end; C" - m = eval "module M; end; M" - - self.class.send(:remove_const, :C) - self.class.send(:remove_const, :M) - - eval "class C; end" - eval "module M; end" - - assert_deprecated do - assert_predicate C, :reachable? - assert_predicate M, :reachable? - assert_not_predicate c, :reachable? - assert_not_predicate m, :reachable? - end - end -end diff --git a/activesupport/test/current_attributes_test.rb b/activesupport/test/current_attributes_test.rb index 1669f08f68..adbdc646bc 100644 --- a/activesupport/test/current_attributes_test.rb +++ b/activesupport/test/current_attributes_test.rb @@ -3,13 +3,18 @@ require "abstract_unit" class CurrentAttributesTest < ActiveSupport::TestCase - Person = Struct.new(:name, :time_zone) + Person = Struct.new(:id, :name, :time_zone) class Current < ActiveSupport::CurrentAttributes attribute :world, :account, :person, :request delegate :time_zone, to: :person - resets { Time.zone = "UTC" } + before_reset { Session.previous = person.try(:id) } + + resets do + Time.zone = "UTC" + Session.current = nil + end def account=(account) super @@ -19,6 +24,7 @@ class CurrentAttributesTest < ActiveSupport::TestCase def person=(person) super Time.zone = person.try(:time_zone) + Session.current = person.try(:id) end def request @@ -30,9 +36,14 @@ class CurrentAttributesTest < ActiveSupport::TestCase end end + class Session < ActiveSupport::CurrentAttributes + attribute :current, :previous + end + setup do @original_time_zone = Time.zone Current.reset + Session.reset end teardown do @@ -56,16 +67,28 @@ class CurrentAttributesTest < ActiveSupport::TestCase end test "set auxiliary class via overwritten method" do - Current.person = Person.new("David", "Central Time (US & Canada)") + Current.person = Person.new(42, "David", "Central Time (US & Canada)") assert_equal "Central Time (US & Canada)", Time.zone.name + assert_equal 42, Session.current end - test "resets auxiliary class via callback" do - Current.person = Person.new("David", "Central Time (US & Canada)") + test "resets auxiliary classes via callback" do + Current.person = Person.new(42, "David", "Central Time (US & Canada)") assert_equal "Central Time (US & Canada)", Time.zone.name Current.reset assert_equal "UTC", Time.zone.name + assert_nil Session.current + end + + test "set auxiliary class based on current attributes via before callback" do + Current.person = Person.new(42, "David", "Central Time (US & Canada)") + assert_nil Session.previous + assert_equal 42, Session.current + + Current.reset + assert_equal 42, Session.previous + assert_nil Session.current end test "set attribute only via scope" do @@ -92,13 +115,13 @@ class CurrentAttributesTest < ActiveSupport::TestCase end test "delegation" do - Current.person = Person.new("David", "Central Time (US & Canada)") + Current.person = Person.new(42, "David", "Central Time (US & Canada)") assert_equal "Central Time (US & Canada)", Current.time_zone assert_equal "Central Time (US & Canada)", Current.instance.time_zone end test "all methods forward to the instance" do - Current.person = Person.new("David", "Central Time (US & Canada)") + Current.person = Person.new(42, "David", "Central Time (US & Canada)") assert_equal "David, in Central Time (US & Canada)", Current.intro assert_equal "David, in Central Time (US & Canada)", Current.instance.intro end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index b1b3070891..d4e709137e 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -841,7 +841,7 @@ class DependenciesTest < ActiveSupport::TestCase remove_constants(:C) end - def test_new_contants_in_without_constants + def test_new_constants_in_without_constants assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { }) assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? } end diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb index f81e0dc70f..1d6a07ec5f 100644 --- a/activesupport/test/hash_with_indifferent_access_test.rb +++ b/activesupport/test/hash_with_indifferent_access_test.rb @@ -447,6 +447,14 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings end + def test_indifferent_assoc + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + key, value = indifferent_strings.assoc(:a) + + assert_equal("a", key) + assert_equal(1, value) + end + def test_indifferent_compact hash_contain_nil_value = @strings.merge("z" => nil) hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value) @@ -478,7 +486,7 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase assert_equal @strings, roundtrip assert_equal "1234", roundtrip.default - # Ensure nested hashes are not HashWithIndiffereneAccess + # Ensure nested hashes are not HashWithIndifferentAccess new_to_hash = @nested_mixed.with_indifferent_access.to_hash assert_not new_to_hash.instance_of?(HashWithIndifferentAccess) assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess) diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 5e50acf5db..c3e1faff5d 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -224,12 +224,6 @@ class InflectorTest < ActiveSupport::TestCase assert_equal("json_html_api", ActiveSupport::Inflector.underscore("JSONHTMLAPI")) end - def test_acronym_regexp_is_deprecated - assert_deprecated do - ActiveSupport::Inflector.inflections.acronym_regex - end - end - def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) diff --git a/activesupport/test/notifications/evented_notification_test.rb b/activesupport/test/notifications/evented_notification_test.rb index 4beb8194b9..ab2a9b8659 100644 --- a/activesupport/test/notifications/evented_notification_test.rb +++ b/activesupport/test/notifications/evented_notification_test.rb @@ -84,6 +84,39 @@ module ActiveSupport [:finish, "hi", 1, {}] ], listener.events end + + def test_listen_to_regexp + notifier = Fanout.new + listener = Listener.new + notifier.subscribe(/[a-z]*.world/, listener) + notifier.start("hi.world", 1, {}) + notifier.finish("hi.world", 2, {}) + notifier.start("hello.world", 1, {}) + notifier.finish("hello.world", 2, {}) + + assert_equal [ + [:start, "hi.world", 1, {}], + [:finish, "hi.world", 2, {}], + [:start, "hello.world", 1, {}], + [:finish, "hello.world", 2, {}] + ], listener.events + end + + def test_listen_to_regexp_with_exclusions + notifier = Fanout.new + listener = Listener.new + notifier.subscribe(/[a-z]*.world/, listener) + notifier.unsubscribe("hi.world") + notifier.start("hi.world", 1, {}) + notifier.finish("hi.world", 2, {}) + notifier.start("hello.world", 1, {}) + notifier.finish("hello.world", 2, {}) + + assert_equal [ + [:start, "hello.world", 1, {}], + [:finish, "hello.world", 2, {}] + ], listener.events + end end end end diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 4e0aef2cc7..bb20d26a25 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -81,7 +81,7 @@ module Notifications assert_equal expected, events end - def test_subsribing_to_instrumentation_while_inside_it + def test_subscribing_to_instrumentation_while_inside_it # the repro requires that there are no evented subscribers for the "foo" event, # so we have to duplicate some of the setup code old_notifier = ActiveSupport::Notifications.notifier @@ -128,6 +128,25 @@ module Notifications assert_equal [["named.subscription", :foo], ["named.subscription", :foo]], @events end + def test_unsubscribing_by_name_leaves_regexp_matched_subscriptions + @matched_events = [] + @notifier.subscribe(/subscription/) { |*args| @matched_events << event(*args) } + @notifier.publish("named.subscription", :before) + @notifier.wait + [@events, @named_events, @matched_events].each do |collector| + assert_includes(collector, ["named.subscription", :before]) + end + @notifier.unsubscribe("named.subscription") + @notifier.publish("named.subscription", :after) + @notifier.publish("other.subscription", :after) + @notifier.wait + assert_includes(@events, ["named.subscription", :after]) + assert_includes(@events, ["other.subscription", :after]) + assert_includes(@matched_events, ["other.subscription", :after]) + assert_not_includes(@matched_events, ["named.subscription", :after]) + assert_not_includes(@named_events, ["named.subscription", :after]) + end + private def event(*args) args diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index 49a3951623..08d04e3223 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -151,7 +151,7 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal "", ActiveSupport::SafeBuffer.new("foo").clone_empty end - test "clone_empty keeps the original dirtyness" do + test "clone_empty keeps the original dirtiness" do assert_predicate @buffer.clone_empty, :html_safe? assert_not_predicate @buffer.gsub!("", "").clone_empty, :html_safe? end diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index 30a1ddad3f..a40c813fe3 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -38,7 +38,7 @@ class ShareLockTest < ActiveSupport::TestCase end end - def test_multiple_exlusives_are_able_to_progress + def test_multiple_exclusives_are_able_to_progress with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| exclusive_threads = (1..2).map do Thread.new do diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb index 9c61ab0ab5..a1f84bf69e 100644 --- a/activesupport/test/time_travel_test.rb +++ b/activesupport/test/time_travel_test.rb @@ -148,7 +148,7 @@ class TimeTravelTest < ActiveSupport::TestCase end end - def test_travel_to_will_reset_the_usec_to_avoid_mysql_rouding + def test_travel_to_will_reset_the_usec_to_avoid_mysql_rounding Time.stub(:now, Time.now) do travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do assert_equal 50, Time.now.sec diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 6d45a6726d..f948c363df 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -32,7 +32,7 @@ class TimeZoneTest < ActiveSupport::TestCase end end - def test_period_for_local_with_ambigiuous_time + def test_period_for_local_with_ambiguous_time zone = ActiveSupport::TimeZone["Moscow"] period = zone.period_for_local(Time.utc(2015, 1, 1)) assert_equal period, zone.period_for_local(Time.utc(2014, 10, 26, 1, 0, 0)) |