aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md306
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/README.rdoc3
-rw-r--r--activesupport/activesupport.gemspec11
-rw-r--r--activesupport/lib/active_support.rb3
-rw-r--r--activesupport/lib/active_support/actionable_error.rb48
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb6
-rw-r--r--activesupport/lib/active_support/cache.rb28
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb23
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb4
-rw-r--r--activesupport/lib/active_support/cache/redis_cache_store.rb13
-rw-r--r--activesupport/lib/active_support/concern.rb25
-rw-r--r--activesupport/lib/active_support/configurable.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/array.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/array/prepend_and_append.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb25
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb46
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb29
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/agnostics.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/module/reachable.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range/compare_range.rb33
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb53
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb3
-rw-r--r--activesupport/lib/active_support/current_attributes.rb6
-rw-r--r--activesupport/lib/active_support/dependencies.rb5
-rw-r--r--activesupport/lib/active_support/dependencies/zeitwerk_integration.rb99
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb33
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb65
-rw-r--r--activesupport/lib/active_support/duration.rb6
-rw-r--r--activesupport/lib/active_support/encrypted_file.rb3
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb36
-rw-r--r--activesupport/lib/active_support/gem_version.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb22
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb3
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb2
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb29
-rw-r--r--activesupport/lib/active_support/json/decoding.rb46
-rw-r--r--activesupport/lib/active_support/key_generator.rb32
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb39
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb2
-rw-r--r--activesupport/lib/active_support/message_verifier.rb2
-rw-r--r--activesupport/lib/active_support/notifications.rb25
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb73
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb18
-rw-r--r--activesupport/lib/active_support/reloader.rb8
-rw-r--r--activesupport/lib/active_support/security_utils.rb2
-rw-r--r--activesupport/lib/active_support/subscriber.rb61
-rw-r--r--activesupport/lib/active_support/test_case.rb9
-rw-r--r--activesupport/lib/active_support/testing/method_call_assertions.rb14
-rw-r--r--activesupport/lib/active_support/testing/parallelization.rb47
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb8
-rw-r--r--activesupport/lib/active_support/xml_mini.rb7
-rw-r--r--activesupport/test/abstract_unit.rb6
-rw-r--r--activesupport/test/actionable_error_test.rb47
-rw-r--r--activesupport/test/autoloading_fixtures/raises_load_error.rb4
-rw-r--r--activesupport/test/cache/behaviors/cache_store_behavior.rb10
-rw-r--r--activesupport/test/cache/behaviors/connection_pool_behavior.rb56
-rw-r--r--activesupport/test/cache/stores/file_store_test.rb2
-rw-r--r--activesupport/test/cache/stores/mem_cache_store_test.rb23
-rw-r--r--activesupport/test/cache/stores/redis_cache_store_test.rb10
-rw-r--r--activesupport/test/callbacks_test.rb2
-rw-r--r--activesupport/test/constantize_test_cases.rb10
-rw-r--r--activesupport/test/core_ext/array/access_test.rb12
-rw-r--r--activesupport/test/core_ext/array/prepend_append_test.rb11
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb13
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb64
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb90
-rw-r--r--activesupport/test/core_ext/load_error_test.rb7
-rw-r--r--activesupport/test/core_ext/module/reachable_test.rb51
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/secure_random_test.rb20
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb11
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb2
-rw-r--r--activesupport/test/current_attributes_test.rb37
-rw-r--r--activesupport/test/dependencies_test.rb2
-rw-r--r--activesupport/test/deprecation/method_wrappers_test.rb15
-rw-r--r--activesupport/test/deprecation_test.rb7
-rw-r--r--activesupport/test/descendants_tracker_test_cases.rb9
-rw-r--r--activesupport/test/evented_file_update_checker_test.rb28
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb12
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb10
-rw-r--r--activesupport/test/inflector_test.rb12
-rw-r--r--activesupport/test/json/encoding_test.rb24
-rw-r--r--activesupport/test/notifications/evented_notification_test.rb33
-rw-r--r--activesupport/test/notifications/instrumenter_test.rb6
-rw-r--r--activesupport/test/notifications_test.rb37
-rw-r--r--activesupport/test/safe_buffer_test.rb60
-rw-r--r--activesupport/test/share_lock_test.rb104
-rw-r--r--activesupport/test/subscriber_test.rb23
-rw-r--r--activesupport/test/test_case_test.rb2
-rw-r--r--activesupport/test/time_travel_test.rb93
-rw-r--r--activesupport/test/time_zone_test.rb2
-rw-r--r--activesupport/test/transliterate_test.rb6
113 files changed, 1484 insertions, 1069 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index ba8aaf47f9..ccdf2c3040 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,296 +1,22 @@
-* If the same block is `included` multiple times for a Concern, an exception is no longer raised.
+* `truncate` would return the original string if it was too short to be truncated
+ and a frozen string if it were long enough to be truncated. Now truncate will
+ consistently return an unfrozen string regardless. This behavior is consistent
+ with `gsub` and `strip`.
- *Mark J. Titorenko*, *Vlad Bokov*
+ Before:
-* Fix bug where `#to_options` for `ActiveSupport::HashWithIndifferentAccess`
- would not act as alias for `#symbolize_keys`.
+ 'foobar'.truncate(5).frozen?
+ => true
+ 'foobar'.truncate(6).frozen?
+ => false
- *Nick Weiland*
+ After:
-* Improve the logic that detects non-autoloaded constants.
+ 'foobar'.truncate(5).frozen?
+ => false
+ 'foobar'.truncate(6).frozen?
+ => false
- *Jan Habermann*, *Xavier Noria*
+ *Jordan Thomas*
-* Deprecate `ActiveSupport::Multibyte::Unicode#pack_graphemes(array)` and `ActiveSuppport::Multibyte::Unicode#unpack_graphemes(string)`
- in favor of `array.flatten.pack("U*")` and `string.scan(/\X/).map(&:codepoints)`, respectively.
-
- *Francesco Rodrรญguez*
-
-* Deprecate `ActiveSupport::Multibyte::Chars.consumes?` in favor of `String#is_utf8?`.
-
- *Francesco Rodrรญguez*
-
-* Fix duration being rounded to a full second.
- ```
- time = DateTime.parse("2018-1-1")
- time += 0.51.seconds
- ```
- Will now correctly add 0.51 second and not 1 full second.
-
- *Edouard Chin*
-
-* Deprecate `ActiveSupport::Multibyte::Unicode#normalize` and `ActiveSuppport::Multibyte::Chars#normalize`
- in favor of `String#unicode_normalize`
-
- *Francesco Rodrรญguez*
-
-* Deprecate `ActiveSupport::Multibyte::Unicode#downcase/upcase/swapcase` in favor of
- `String#downcase/upcase/swapcase`.
-
- *Francesco Rodrรญguez*
-
-* Add `ActiveSupport::ParameterFilter`.
-
- *Yoshiyuki Kinjo*
-
-* Rename `Module#parent`, `Module#parents`, and `Module#parent_name` to
- `module_parent`, `module_parents`, and `module_parent_name`.
-
- *Gannon McGibbon*
-
-* Deprecate the use of `LoggerSilence` in favor of `ActiveSupport::LoggerSilence`
-
- *Edouard Chin*
-
-* Deprecate using negative limits in `String#first` and `String#last`.
-
- *Gannon McGibbon*, *Eric Turner*
-
-* Fix bug where `#without` for `ActiveSupport::HashWithIndifferentAccess` would fail
- with symbol arguments
-
- *Abraham Chan*
-
-* Treat `#delete_prefix`, `#delete_suffix` and `#unicode_normalize` results as non-`html_safe`.
- Ensure safety of arguments for `#insert`, `#[]=` and `#replace` calls on `html_safe` Strings.
-
- *Janosch Mรผller*
-
-* Changed `ActiveSupport::TaggedLogging.new` to return a new logger instance instead
- of mutating the one received as parameter.
-
- *Thierry Joyal*
-
-* Define `unfreeze_time` as an alias of `travel_back` in `ActiveSupport::Testing::TimeHelpers`.
-
- The alias is provided for symmetry with `freeze_time`.
-
- *Ryan Davidson*
-
-* Add support for tracing constant autoloads. Just throw
-
- ActiveSupport::Dependencies.logger = Rails.logger
- ActiveSupport::Dependencies.verbose = true
-
- in an initializer.
-
- *Xavier Noria*
-
-* Maintain `html_safe?` on html_safe strings when sliced.
-
- string = "<div>test</div>".html_safe
- string[-1..1].html_safe? # => true
-
- *Elom Gomez*, *Yumin Wong*
-
-* Add `Array#extract!`.
-
- The method removes and returns the elements for which the block returns a true value.
- If no block is given, an Enumerator is returned instead.
-
- numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
- numbers # => [0, 2, 4, 6, 8]
-
- *bogdanvlviv*
-
-* Support not to cache `nil` for `ActiveSupport::Cache#fetch`.
-
- cache.fetch('bar', skip_nil: true) { nil }
- cache.exist?('bar') # => false
-
- *Martin Hong*
-
-* Add "event object" support to the notification system.
- Before this change, end users were forced to create hand made artisanal
- event objects on their own, like this:
-
- ActiveSupport::Notifications.subscribe('wait') do |*args|
- @event = ActiveSupport::Notifications::Event.new(*args)
- end
-
- ActiveSupport::Notifications.instrument('wait') do
- sleep 1
- end
-
- @event.duration # => 1000.138
-
- After this change, if the block passed to `subscribe` only takes one
- parameter, the framework will yield an event object to the block. Now
- end users are no longer required to make their own:
-
- ActiveSupport::Notifications.subscribe('wait') do |event|
- @event = event
- end
-
- ActiveSupport::Notifications.instrument('wait') do
- sleep 1
- end
-
- p @event.allocations # => 7
- p @event.cpu_time # => 0.256
- p @event.idle_time # => 1003.2399
-
- Now you can enjoy event objects without making them yourself. Neat!
-
- *Aaron "t.lo" Patterson*
-
-* Add cpu_time, idle_time, and allocations to Event.
-
- *Eileen M. Uchitelle*, *Aaron Patterson*
-
-* RedisCacheStore: support key expiry in increment/decrement.
-
- Pass `:expires_in` to `#increment` and `#decrement` to set a Redis EXPIRE on the key.
-
- If the key is already set to expire, RedisCacheStore won't extend its expiry.
-
- Rails.cache.increment("some_key", 1, expires_in: 2.minutes)
-
- *Jason Lee*
-
-* Allow `Range#===` and `Range#cover?` on Range.
-
- `Range#cover?` can now accept a range argument like `Range#include?` and
- `Range#===`. `Range#===` works correctly on Ruby 2.6. `Range#include?` is moved
- into a new file, with these two methods.
-
- *Requiring active_support/core_ext/range/include_range is now deprecated.*
- *Use `require "active_support/core_ext/range/compare_range"` instead.*
-
- *utilum*
-
-* Add `index_with` to Enumerable.
-
- Allows creating a hash from an enumerable with the value from a passed block
- or a default argument.
-
- %i( title body ).index_with { |attr| post.public_send(attr) }
- # => { title: "hey", body: "what's up?" }
-
- %i( title body ).index_with(nil)
- # => { title: nil, body: nil }
-
- Closely linked with `index_by`, which creates a hash where the keys are extracted from a block.
-
- *Kasper Timm Hansen*
-
-* Fix bug where `ActiveSupport::Timezone.all` would fail when tzinfo data for
- any timezone defined in `ActiveSupport::TimeZone::MAPPING` is missing.
-
- *Dominik Sander*
-
-* Redis cache store: `delete_matched` no longer blocks the Redis server.
- (Switches from evaled Lua to a batched SCAN + DEL loop.)
-
- *Gleb Mazovetskiy*
-
-* Fix bug where `ActiveSupport::Cache` will massively inflate the storage
- size when compression is enabled (which is true by default). This patch
- does not attempt to repair existing data: please manually flush the cache
- to clear out the problematic entries.
-
- *Godfrey Chan*
-
-* Fix bug where `URI.unescape` would fail with mixed Unicode/escaped character input:
-
- URI.unescape("\xe3\x83\x90") # => "ใƒ"
- URI.unescape("%E3%83%90") # => "ใƒ"
- URI.unescape("\xe3\x83\x90%E3%83%90") # => Encoding::CompatibilityError
-
- *Ashe Connor*, *Aaron Patterson*
-
-* Add `before?` and `after?` methods to `Date`, `DateTime`,
- `Time`, and `TimeWithZone`.
-
- *Nick Holden*
-
-* `ActiveSupport::Inflector#ordinal` and `ActiveSupport::Inflector#ordinalize` now support
- translations through I18n.
-
- # locale/fr.rb
-
- {
- fr: {
- number: {
- nth: {
- ordinals: lambda do |_key, number:, **_options|
- if number.to_i.abs == 1
- 'er'
- else
- 'e'
- end
- end,
-
- ordinalized: lambda do |_key, number:, **_options|
- "#{number}#{ActiveSupport::Inflector.ordinal(number)}"
- end
- }
- }
- }
- }
-
-
- *Christian Blais*
-
-* Add `:private` option to ActiveSupport's `Module#delegate`
- in order to delegate methods as private:
-
- class User < ActiveRecord::Base
- has_one :profile
- delegate :date_of_birth, to: :profile, private: true
-
- def age
- Date.today.year - date_of_birth.year
- end
- end
-
- # User.new.age # => 29
- # User.new.date_of_birth
- # => NoMethodError: private method `date_of_birth' called for #<User:0x00000008221340>
-
- *Tomas Valent*
-
-* `String#truncate_bytes` to truncate a string to a maximum bytesize without
- breaking multibyte characters or grapheme clusters like ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ.
-
- *Jeremy Daer*
-
-* `String#strip_heredoc` preserves frozenness.
-
- "foo".freeze.strip_heredoc.frozen? # => true
-
- Fixes that frozen string literals would inadvertently become unfrozen:
-
- # frozen_string_literal: true
-
- foo = <<-MSG.strip_heredoc
- la la la
- MSG
-
- foo.frozen? # => false !??
-
- *Jeremy Daer*
-
-* Rails 6 requires Ruby 2.4.1 or newer.
-
- *Jeremy Daer*
-
-* Adds parallel testing to Rails.
-
- Parallelize your test suite with forked processes or threads.
-
- *Eileen M. Uchitelle*, *Aaron Patterson*
-
-
-Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 8f769c0767..315a4bc7c4 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2018 David Heinemeier Hansson
+Copyright (c) 2005-2019 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/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 448a2eeebb..7ecbafc911 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -9,13 +9,13 @@ Gem::Specification.new do |s|
s.summary = "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework."
s.description = "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing."
- s.required_ruby_version = ">= 2.4.1"
+ s.required_ruby_version = ">= 2.5.0"
s.license = "MIT"
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", "~> 2.1", ">= 2.1.4"
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index a4fb697669..9e242ddeaa 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2005-2018 David Heinemeier Hansson
+# Copyright (c) 2005-2019 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -34,6 +34,7 @@ module ActiveSupport
extend ActiveSupport::Autoload
autoload :Concern
+ autoload :ActionableError
autoload :CurrentAttributes
autoload :Dependencies
autoload :DescendantsTracker
diff --git a/activesupport/lib/active_support/actionable_error.rb b/activesupport/lib/active_support/actionable_error.rb
new file mode 100644
index 0000000000..7db14cd178
--- /dev/null
+++ b/activesupport/lib/active_support/actionable_error.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ # Actionable errors let's you define actions to resolve an error.
+ #
+ # To make an error actionable, include the <tt>ActiveSupport::ActionableError</tt>
+ # module and invoke the +action+ class macro to define the action. An action
+ # needs a name and a block to execute.
+ module ActionableError
+ extend Concern
+
+ class NonActionable < StandardError; end
+
+ included do
+ class_attribute :_actions, default: {}
+ end
+
+ def self.actions(error) # :nodoc:
+ case error
+ when ActionableError, -> it { Class === it && it < ActionableError }
+ error._actions
+ else
+ {}
+ end
+ end
+
+ def self.dispatch(error, name) # :nodoc:
+ actions(error).fetch(name).call
+ rescue KeyError
+ raise NonActionable, "Cannot find action \"#{name}\""
+ end
+
+ module ClassMethods
+ # Defines an action that can resolve the error.
+ #
+ # class PendingMigrationError < MigrationError
+ # include ActiveSupport::ActionableError
+ #
+ # action "Run pending migrations" do
+ # ActiveRecord::Tasks::DatabaseTasks.migrate
+ # end
+ # end
+ def action(name, &block)
+ _actions[name] = block
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index 62973eca58..02cbfbaee6 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -122,7 +122,11 @@ module ActiveSupport
end
def noise(backtrace)
- backtrace - silence(backtrace)
+ backtrace.select do |line|
+ @silencers.any? do |s|
+ s.call(line)
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index e8518645d9..e055135bb4 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -438,18 +438,18 @@ module ActiveSupport
options = merged_options(options)
instrument :read_multi, names, options do |payload|
- read_multi_entries(names, options).tap do |results|
- payload[:hits] = results.keys
- payload[:super_operation] = :fetch_multi
+ reads = read_multi_entries(names, options)
+ writes = {}
+ ordered = names.each_with_object({}) do |name, hash|
+ hash[name] = reads.fetch(name) { writes[name] = yield(name) }
+ end
- writes = {}
+ payload[:hits] = reads.keys
+ payload[:super_operation] = :fetch_multi
- (names - results.keys).each do |name|
- results[name] = writes[name] = yield(name)
- end
+ write_multi(writes, options)
- write_multi writes, options
- end
+ ordered
end
end
@@ -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 53a2b07536..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
@@ -108,12 +107,10 @@ module ActiveSupport
def lock_file(file_name, &block)
if File.exist?(file_name)
File.open(file_name, "r+") do |f|
- begin
- f.flock File::LOCK_EX
- yield
- ensure
- f.flock File::LOCK_UN
- end
+ f.flock File::LOCK_EX
+ yield
+ ensure
+ f.flock File::LOCK_UN
end
else
yield
@@ -156,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
@@ -169,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)
@@ -195,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/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb
index 9a55e49e27..bb092fcde9 100644
--- a/activesupport/lib/active_support/cache/redis_cache_store.rb
+++ b/activesupport/lib/active_support/cache/redis_cache_store.rb
@@ -152,12 +152,14 @@ module ActiveSupport
# Creates a new Redis cache store.
#
- # Handles three options: block provided to instantiate, single URL
- # provided, and multiple URLs provided.
+ # Handles four options: :redis block, :redis instance, single :url
+ # string, and multiple :url strings.
#
- # :redis Proc -> options[:redis].call
- # :url String -> Redis.new(url: โ€ฆ)
- # :url Array -> Redis::Distributed.new([{ url: โ€ฆ }, { url: โ€ฆ }, โ€ฆ])
+ # Option Class Result
+ # :redis Proc -> options[:redis].call
+ # :redis Object -> options[:redis]
+ # :url String -> Redis.new(url: โ€ฆ)
+ # :url Array -> Redis::Distributed.new([{ url: โ€ฆ }, { url: โ€ฆ }, โ€ฆ])
#
# No namespace is set by default. Provide one if the Redis cache
# server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
@@ -361,6 +363,7 @@ module ActiveSupport
def read_multi_mget(*names)
options = names.extract_options!
options = merged_options(options)
+ return {} if names == []
keys = names.map { |name| normalize_key(name, options) }
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index 5d356a0ab6..708c445031 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -110,7 +110,7 @@ module ActiveSupport
base.instance_variable_set(:@_dependencies, [])
end
- def append_features(base)
+ def append_features(base) #:nodoc:
if base.instance_variable_defined?(:@_dependencies)
base.instance_variable_get(:@_dependencies) << self
false
@@ -123,6 +123,9 @@ module ActiveSupport
end
end
+ # Evaluate given block in context of base class,
+ # so that you can write class macros here.
+ # When you define more than one +included+ block, it raises an exception.
def included(base = nil, &block)
if base.nil?
if instance_variable_defined?(:@_included_block)
@@ -137,6 +140,26 @@ module ActiveSupport
end
end
+ # Define class methods from given block.
+ # You can define private class methods as well.
+ #
+ # module Example
+ # extend ActiveSupport::Concern
+ #
+ # class_methods do
+ # def foo; puts 'foo'; end
+ #
+ # private
+ # def bar; puts 'bar'; end
+ # end
+ # end
+ #
+ # class Buzz
+ # include Example
+ # end
+ #
+ # Buzz.foo # => "foo"
+ # Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError)
def class_methods(&class_methods_module_definition)
mod = const_defined?(:ClassMethods, false) ?
const_get(:ClassMethods) :
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 9acf674c40..71c23dae9b 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -67,8 +67,8 @@ module ActiveSupport
# end
# # => NameError: invalid config attribute name
#
- # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
- # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ # To omit the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To omit the instance reader method, pass <tt>instance_reader: false</tt>.
#
# class User
# include ActiveSupport::Configurable
@@ -81,7 +81,7 @@ module ActiveSupport
# User.new.allowed_access = true # => NoMethodError
# User.new.allowed_access # => NoMethodError
#
- # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ # Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
#
# class User
# include ActiveSupport::Configurable
diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb
index a2569c798b..88b6567712 100644
--- a/activesupport/lib/active_support/core_ext/array.rb
+++ b/activesupport/lib/active_support/core_ext/array.rb
@@ -6,5 +6,4 @@ require "active_support/core_ext/array/conversions"
require "active_support/core_ext/array/extract"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/array/grouping"
-require "active_support/core_ext/array/prepend_and_append"
require "active_support/core_ext/array/inquiry"
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index b7ff7a3907..ea01e5891c 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/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
index 661971d7cd..ba3739f640 100644
--- a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
+++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
@@ -1,9 +1,5 @@
# frozen_string_literal: true
-class Array
- # The human way of thinking about adding stuff to the end of a list is with append.
- alias_method :append, :push unless [].respond_to?(:append)
+require "active_support/deprecation"
- # The human way of thinking about adding stuff to the beginning of a list is with prepend.
- alias_method :prepend, :unshift unless [].respond_to?(:prepend)
-end
+ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Array#append and Array#prepend natively, so requiring active_support/core_ext/array/prepend_and_append is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
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/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 05abd83221..e2e11545e2 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -134,7 +134,7 @@ module DateAndTime
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
# now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000
def beginning_of_quarter
- first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
+ first_quarter_month = month - (2 + month) % 3
beginning_of_month.change(month: first_quarter_month)
end
alias :at_beginning_of_quarter :beginning_of_quarter
@@ -149,7 +149,7 @@ module DateAndTime
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
# now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000
def end_of_quarter
- last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
+ last_quarter_month = month + (12 - month) % 3
beginning_of_month.change(month: last_quarter_month).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
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/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb
index 28c8d86b9b..5cb858af5c 100644
--- a/activesupport/lib/active_support/core_ext/hash/compact.rb
+++ b/activesupport/lib/active_support/core_ext/hash/compact.rb
@@ -2,4 +2,4 @@
require "active_support/deprecation"
-ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Hash#compact and Hash#compact! natively, so requiring active_support/core_ext/hash/compact is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
+ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Hash#compact and Hash#compact! natively, so requiring active_support/core_ext/hash/compact is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
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/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 6258610c98..5013812460 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -10,7 +10,7 @@ class Hash
# This is useful for limiting a set of parameters to everything but a few known toggles:
# @person.update(params[:person].except(:admin))
def except(*keys)
- dup.except!(*keys)
+ slice(*self.keys - keys)
end
# Removes the given keys from hash and returns it.
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index bdf196ec3d..7d3495db2c 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,35 +1,6 @@
# frozen_string_literal: true
class Hash
- # Returns a new hash with all keys converted using the +block+ operation.
- #
- # hash = { name: 'Rob', age: '28' }
- #
- # hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"}
- #
- # If you do not provide a +block+, it will return an Enumerator
- # for chaining with other methods:
- #
- # hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
- def transform_keys
- return enum_for(:transform_keys) { size } unless block_given?
- result = {}
- each_key do |key|
- result[yield(key)] = self[key]
- end
- result
- end unless method_defined? :transform_keys
-
- # Destructively converts all keys using the +block+ operations.
- # Same as +transform_keys+ but modifies +self+.
- def transform_keys!
- return enum_for(:transform_keys!) { size } unless block_given?
- keys.each do |key|
- self[yield(key)] = delete(key)
- end
- self
- end unless method_defined? :transform_keys!
-
# Returns a new hash with all keys converted to strings.
#
# hash = { name: 'Rob', age: '28' }
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index e07a5498ff..3d0f8a1e62 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -1,29 +1,6 @@
# frozen_string_literal: true
class Hash
- # Slices a hash to include only the given keys. Returns a hash containing
- # the given keys.
- #
- # { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b)
- # # => {:a=>1, :b=>2}
- #
- # This is useful for limiting an options hash to valid keys before
- # passing to a method:
- #
- # def search(criteria = {})
- # criteria.assert_valid_keys(:mass, :velocity, :time)
- # end
- #
- # search(options.slice(:mass, :velocity, :time))
- #
- # If you have an array of keys you want to limit to, you should splat them:
- #
- # valid_keys = [:mass, :velocity, :time]
- # search(options.slice(*valid_keys))
- def slice(*keys)
- keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
- end unless method_defined?(:slice)
-
# Replaces the hash with only the given keys.
# Returns a hash containing the removed key/value pairs.
#
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
index fc15130c9e..e4aeb0e891 100644
--- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -2,4 +2,4 @@
require "active_support/deprecation"
-ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Hash#transform_values natively, so requiring active_support/core_ext/hash/transform_values is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
+ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Hash#transform_values natively, so requiring active_support/core_ext/hash/transform_values is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
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/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 5850e0193f..cc1926e022 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -24,7 +24,7 @@ class Module
# end
# # => NameError: invalid attribute name: 1_Badname
#
- # If you want to opt out the creation on the instance reader method, pass
+ # To omit the instance reader method, pass
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
#
# module HairColors
@@ -91,7 +91,7 @@ class Module
# Person.new.hair_colors = [:blonde, :red]
# HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red]
#
- # If you want to opt out the instance writer method, pass
+ # To omit the instance writer method, pass
# <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
#
# module HairColors
@@ -166,8 +166,8 @@ class Module
# Citizen.new.hair_colors << :blue
# Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
#
- # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
- # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ # To omit the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To omit the instance reader method, pass <tt>instance_reader: false</tt>.
#
# module HairColors
# mattr_accessor :hair_colors, instance_writer: false, instance_reader: false
@@ -180,7 +180,7 @@ class Module
# Person.new.hair_colors = [:brown] # => NoMethodError
# Person.new.hair_colors # => NoMethodError
#
- # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ # Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
#
# module HairColors
# mattr_accessor :hair_colors, instance_accessor: false
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
index cb996e9e6d..a6e87aeb68 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -25,7 +25,7 @@ class Module
# end
# # => NameError: invalid attribute name: 1_Badname
#
- # If you want to opt out of the creation of the instance reader method, pass
+ # To omit the instance reader method, pass
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
#
# class Current
@@ -66,7 +66,7 @@ class Module
# Current.user = "DHH"
# Thread.current[:attr_Current_user] # => "DHH"
#
- # If you want to opt out of the creation of the instance writer method, pass
+ # To omit the instance writer method, pass
# <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
#
# class Current
@@ -118,8 +118,8 @@ class Module
# Customer.user # => "Rafael"
# Account.user # => "DHH"
#
- # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
- # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ # To omit the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To omit the instance reader method, pass <tt>instance_reader: false</tt>.
#
# class Current
# thread_mattr_accessor :user, instance_writer: false, instance_reader: false
@@ -128,7 +128,7 @@ class Module
# Current.new.user = "DHH" # => NoMethodError
# Current.new.user # => NoMethodError
#
- # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ # Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
#
# class Current
# thread_mattr_accessor :user, instance_accessor: false
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 3128539112..9fe7f8fe01 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -19,7 +19,7 @@ class Module
# public methods as your own.
#
# ==== Options
- # * <tt>:to</tt> - Specifies the target object
+ # * <tt>:to</tt> - Specifies the target object name as a symbol or string
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
# * <tt>:allow_nil</tt> - If set to true, prevents a +Module::DelegationError+
# from being raised
@@ -170,7 +170,7 @@ class Module
# The target method must be public, otherwise it will raise +NoMethodError+.
def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil)
unless to
- raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)."
+ raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
end
if prefix == true && /^[^a-z_]/.match?(to)
@@ -243,7 +243,7 @@ class Module
# end
#
# def person
- # @event.detail.person || @event.creator
+ # detail.person || creator
# end
#
# private
@@ -266,7 +266,7 @@ class Module
# end
#
# def person
- # @event.detail.person || @event.creator
+ # detail.person || creator
# end
# end
#
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/numeric/inquiry.rb b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
index e05e40825b..6b5240d051 100644
--- a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
@@ -2,4 +2,4 @@
require "active_support/deprecation"
-ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Numeric#positive? and Numeric#negative? natively, so requiring active_support/core_ext/numeric/inquiry is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
+ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Numeric#positive? and Numeric#negative? natively, so requiring active_support/core_ext/numeric/inquiry is no longer necessary. Requiring it will raise LoadError in Rails 6.1."
diff --git a/activesupport/lib/active_support/core_ext/range/compare_range.rb b/activesupport/lib/active_support/core_ext/range/compare_range.rb
index 6f6d2a27bb..ea1dc29a76 100644
--- a/activesupport/lib/active_support/core_ext/range/compare_range.rb
+++ b/activesupport/lib/active_support/core_ext/range/compare_range.rb
@@ -3,9 +3,10 @@
module ActiveSupport
module CompareWithRange
# Extends the default Range#=== to support range comparisons.
- # (1..5) === (1..5) # => true
- # (1..5) === (2..3) # => true
- # (1..5) === (2..6) # => false
+ # (1..5) === (1..5) # => true
+ # (1..5) === (2..3) # => true
+ # (1..5) === (1...6) # => true
+ # (1..5) === (2..6) # => false
#
# The native Range#=== behavior is untouched.
# ('a'..'f') === ('c') # => true
@@ -13,17 +14,20 @@ module ActiveSupport
def ===(value)
if value.is_a?(::Range)
# 1...10 includes 1..9 but it does not include 1..10.
+ # 1..10 includes 1...11 but it does not include 1...12.
operator = exclude_end? && !value.exclude_end? ? :< : :<=
- super(value.first) && value.last.send(operator, last)
+ value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
+ super(value.first) && value_max.send(operator, last)
else
super
end
end
# Extends the default Range#include? to support range comparisons.
- # (1..5).include?(1..5) # => true
- # (1..5).include?(2..3) # => true
- # (1..5).include?(2..6) # => false
+ # (1..5).include?(1..5) # => true
+ # (1..5).include?(2..3) # => true
+ # (1..5).include?(1...6) # => true
+ # (1..5).include?(2..6) # => false
#
# The native Range#include? behavior is untouched.
# ('a'..'f').include?('c') # => true
@@ -31,17 +35,20 @@ module ActiveSupport
def include?(value)
if value.is_a?(::Range)
# 1...10 includes 1..9 but it does not include 1..10.
+ # 1..10 includes 1...11 but it does not include 1...12.
operator = exclude_end? && !value.exclude_end? ? :< : :<=
- super(value.first) && value.last.send(operator, last)
+ value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
+ super(value.first) && value_max.send(operator, last)
else
super
end
end
# Extends the default Range#cover? to support range comparisons.
- # (1..5).cover?(1..5) # => true
- # (1..5).cover?(2..3) # => true
- # (1..5).cover?(2..6) # => false
+ # (1..5).cover?(1..5) # => true
+ # (1..5).cover?(2..3) # => true
+ # (1..5).cover?(1...6) # => true
+ # (1..5).cover?(2..6) # => false
#
# The native Range#cover? behavior is untouched.
# ('a'..'f').cover?('c') # => true
@@ -49,8 +56,10 @@ module ActiveSupport
def cover?(value)
if value.is_a?(::Range)
# 1...10 covers 1..9 but it does not cover 1..10.
+ # 1..10 covers 1...11 but it does not cover 1...12.
operator = exclude_end? && !value.exclude_end? ? :< : :<=
- super(value.first) && value.last.send(operator, last)
+ value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
+ super(value.first) && value_max.send(operator, last)
else
super
end
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
index b4a491f5fd..ef812f7e1a 100644
--- a/activesupport/lib/active_support/core_ext/securerandom.rb
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -4,17 +4,18 @@ require "securerandom"
module SecureRandom
BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"]
+ BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a
+
# SecureRandom.base58 generates a random base58 string.
#
- # The argument _n_ specifies the length, of the random string to be generated.
+ # The argument _n_ specifies the length of the random string to be generated.
#
# If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
#
- # The result may contain alphanumeric characters except 0, O, I and l
+ # The result may contain alphanumeric characters except 0, O, I and l.
#
# p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
# p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
- #
def self.base58(n = 16)
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
idx = byte % 64
@@ -22,4 +23,23 @@ module SecureRandom
BASE58_ALPHABET[idx]
end.join
end
+
+ # SecureRandom.base36 generates a random base36 string in lowercase.
+ #
+ # The argument _n_ specifies the length of the random string to be generated.
+ #
+ # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
+ # This method can be used over +base58+ if a deterministic case key is necessary.
+ #
+ # The result will contain alphanumeric characters in lowercase.
+ #
+ # p SecureRandom.base36 # => "4kugl2pdqmscqtje"
+ # p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
+ def self.base36(n = 16)
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
+ idx = byte % 64
+ idx = SecureRandom.random_number(36) if idx >= 36
+ BASE36_ALPHABET[idx]
+ end.join
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index df0e79afa8..7f28bd52f2 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -75,7 +75,7 @@ class String
length_with_room_for_omission
end
- "#{self[0, stop]}#{omission}"
+ +"#{self[0, stop]}#{omission}"
end
# Truncates +text+ to at most <tt>bytesize</tt> bytes in length without
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 8af301734a..5eb8d9f99b 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
+ # the configured <tt>I18n.locale</tt>.
+ #
# 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/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 3a80de4617..645b1fea17 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -135,10 +135,12 @@ module ActiveSupport #:nodoc:
class SafeBuffer < String
UNSAFE_STRING_METHODS = %w(
capitalize chomp chop delete delete_prefix delete_suffix
- downcase gsub lstrip next reverse rstrip slice squeeze strip
- sub succ swapcase tr tr_s unicode_normalize upcase
+ downcase lstrip next reverse rstrip slice squeeze strip
+ succ swapcase tr tr_s unicode_normalize upcase
)
+ UNSAFE_STRING_METHODS_WITH_BACKREF = %w(gsub sub)
+
alias_method :original_concat, :concat
private :original_concat
@@ -199,14 +201,24 @@ module ActiveSupport #:nodoc:
super(html_escape_interpolated_argument(value))
end
- def []=(index, value)
- super(index, html_escape_interpolated_argument(value))
+ def []=(*args)
+ if args.count == 3
+ super(args[0], args[1], html_escape_interpolated_argument(args[2]))
+ else
+ super(args[0], html_escape_interpolated_argument(args[1]))
+ end
end
def +(other)
dup.concat(other)
end
+ def *(*)
+ new_safe_buffer = super
+ new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
+ new_safe_buffer
+ end
+
def %(args)
case args
when Hash
@@ -249,11 +261,44 @@ module ActiveSupport #:nodoc:
end
end
+ UNSAFE_STRING_METHODS_WITH_BACKREF.each do |unsafe_method|
+ if unsafe_method.respond_to?(unsafe_method)
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
+ def #{unsafe_method}(*args, &block) # def gsub(*args, &block)
+ if block # if block
+ to_str.#{unsafe_method}(*args) { |*params| # to_str.gsub(*args) { |*params|
+ set_block_back_references(block, $~) # set_block_back_references(block, $~)
+ block.call(*params) # block.call(*params)
+ } # }
+ else # else
+ to_str.#{unsafe_method}(*args) # to_str.gsub(*args)
+ end # end
+ end # end
+
+ def #{unsafe_method}!(*args, &block) # def gsub!(*args, &block)
+ @html_safe = false # @html_safe = false
+ if block # if block
+ super(*args) { |*params| # super(*args) { |*params|
+ set_block_back_references(block, $~) # set_block_back_references(block, $~)
+ block.call(*params) # block.call(*params)
+ } # }
+ else # else
+ super # super
+ end # end
+ end # end
+ EOT
+ end
+ end
+
private
def html_escape_interpolated_argument(arg)
(!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
end
+
+ def set_block_back_references(block, match_data)
+ block.binding.eval("proc { |m| $~ = m }").call(match_data)
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 120768dec5..f09a6271ad 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -170,8 +170,7 @@ class Time
options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
end
- d = to_date.advance(options)
- d = d.gregorian if d.julian?
+ d = to_date.gregorian.advance(options)
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/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.rb b/activesupport/lib/active_support/dependencies.rb
index d5d00b5e6e..82f07c085e 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -70,6 +70,11 @@ module ActiveSupport #:nodoc:
# only once. All directories in this set must also be present in +autoload_paths+.
mattr_accessor :autoload_once_paths, default: []
+ # This is a private set that collects all eager load paths during bootstrap.
+ # Useful for Zeitwerk integration. Its public interface is the config.* path
+ # accessors of each engine.
+ mattr_accessor :_eager_load_paths, default: Set.new
+
# An array of qualified constant names that have been loaded. Adding a name
# to this array will cause it to be unloaded the next time Dependencies are
# cleared.
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..fd39ad6e55
--- /dev/null
+++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+require "set"
+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
+ rescue Zeitwerk::ReloadingDisabledError
+ raise "reloading is disabled because config.cache_classes is true"
+ 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.unloadable_cpaths
+ end
+
+ def autoloaded?(object)
+ cpath = object.is_a?(Module) ? object.name : object.to_s
+ Rails.autoloaders.main.unloadable_cpath?(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(enable_reloading:)
+ setup_autoloaders(enable_reloading)
+ freeze_paths
+ decorate_dependencies
+ end
+
+ private
+
+ def setup_autoloaders(enable_reloading)
+ Dependencies.autoload_paths.each do |autoload_path|
+ # Zeitwerk only accepts existing directories in `push_dir` to
+ # prevent misconfigurations.
+ next unless File.directory?(autoload_path)
+
+ autoloader = \
+ autoload_once?(autoload_path) ? Rails.autoloaders.once : Rails.autoloaders.main
+
+ autoloader.push_dir(autoload_path)
+ autoloader.do_not_eager_load(autoload_path) unless eager_load?(autoload_path)
+ end
+
+ Rails.autoloaders.main.enable_reloading if enable_reloading
+ Rails.autoloaders.each(&:setup)
+ end
+
+ def autoload_once?(autoload_path)
+ Dependencies.autoload_once_paths.include?(autoload_path)
+ end
+
+ def eager_load?(autoload_path)
+ Dependencies._eager_load_paths.member?(autoload_path)
+ end
+
+ def freeze_paths
+ Dependencies.autoload_paths.freeze
+ Dependencies.autoload_once_paths.freeze
+ Dependencies._eager_load_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/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 7271ab565b..16db547016 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -35,7 +35,7 @@ module ActiveSupport
# and the second is a library name.
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = "6.1", gem_name = "Rails")
+ def initialize(deprecation_horizon = "6.2", gem_name = "Rails")
self.gem_name = gem_name
self.deprecation_horizon = deprecation_horizon
# By default, warnings are not silenced and debugging is off.
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 81482092fe..7c0a54a1d0 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/module/redefine_method"
module ActiveSupport
class Deprecation
@@ -52,27 +53,25 @@ module ActiveSupport
options = method_names.extract_options!
deprecator = options.delete(:deprecator) || self
method_names += options.keys
+ mod = nil
method_names.each do |method_name|
- aliased_method, punctuation = method_name.to_s.sub(/([?!=])$/, ""), $1
- with_method = "#{aliased_method}_with_deprecation#{punctuation}"
- without_method = "#{aliased_method}_without_deprecation#{punctuation}"
-
- target_module.send(:define_method, with_method) do |*args, &block|
- deprecator.deprecation_warning(method_name, options[method_name])
- send(without_method, *args, &block)
- end
-
- target_module.send(:alias_method, without_method, method_name)
- target_module.send(:alias_method, method_name, with_method)
-
- case
- when target_module.protected_method_defined?(without_method)
- target_module.send(:protected, method_name)
- when target_module.private_method_defined?(without_method)
- target_module.send(:private, method_name)
+ if target_module.method_defined?(method_name) || target_module.private_method_defined?(method_name)
+ method = target_module.instance_method(method_name)
+ target_module.redefine_method(method_name) do |*args, &block|
+ deprecator.deprecation_warning(method_name, options[method_name])
+ method.bind(self).call(*args, &block)
+ end
+ else
+ mod ||= Module.new
+ mod.define_method(method_name) do |*args, &block|
+ deprecator.deprecation_warning(method_name, options[method_name])
+ super(*args, &block)
+ end
end
end
+
+ target_module.prepend(mod) if mod
end
end
end
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
index a4cee788b6..21565138a7 100644
--- a/activesupport/lib/active_support/descendants_tracker.rb
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "weakref"
+
module ActiveSupport
# This module provides an internal implementation to track descendants
# which is faster than iterating through ObjectSpace.
@@ -8,7 +10,8 @@ module ActiveSupport
class << self
def direct_descendants(klass)
- @@direct_descendants[klass] || []
+ descendants = @@direct_descendants[klass]
+ descendants ? descendants.to_a : []
end
def descendants(klass)
@@ -20,10 +23,10 @@ module ActiveSupport
def clear
if defined? ActiveSupport::Dependencies
@@direct_descendants.each do |klass, descendants|
- if ActiveSupport::Dependencies.autoloaded?(klass)
+ if Dependencies.autoloaded?(klass)
@@direct_descendants.delete(klass)
else
- descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ descendants.reject! { |v| Dependencies.autoloaded?(v) }
end
end
else
@@ -34,16 +37,19 @@ module ActiveSupport
# This is the only method that is not thread safe, but is only ever called
# during the eager loading phase.
def store_inherited(klass, descendant)
- (@@direct_descendants[klass] ||= []) << descendant
+ (@@direct_descendants[klass] ||= DescendantsArray.new) << descendant
end
private
- def accumulate_descendants(klass, acc)
- if direct_descendants = @@direct_descendants[klass]
- acc.concat(direct_descendants)
- direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) }
+
+ def accumulate_descendants(klass, acc)
+ if direct_descendants = @@direct_descendants[klass]
+ direct_descendants.each do |direct_descendant|
+ acc << direct_descendant
+ accumulate_descendants(direct_descendant, acc)
+ end
+ end
end
- end
end
def inherited(base)
@@ -58,5 +64,46 @@ module ActiveSupport
def descendants
DescendantsTracker.descendants(self)
end
+
+ # DescendantsArray is an array that contains weak references to classes.
+ class DescendantsArray # :nodoc:
+ include Enumerable
+
+ def initialize
+ @refs = []
+ end
+
+ def initialize_copy(orig)
+ @refs = @refs.dup
+ end
+
+ def <<(klass)
+ cleanup!
+ @refs << WeakRef.new(klass)
+ end
+
+ def each
+ @refs.each do |ref|
+ yield ref.__getobj__
+ rescue WeakRef::RefError
+ end
+ end
+
+ def refs_size
+ @refs.size
+ end
+
+ def cleanup!
+ @refs.delete_if { |ref| !ref.weakref_alive? }
+ end
+
+ def reject!
+ @refs.reject! do |ref|
+ yield ref.__getobj__
+ rescue WeakRef::RefError
+ true
+ end
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 314c926ac0..a30bd11a87 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -4,7 +4,6 @@ require "active_support/core_ext/array/conversions"
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/object/acts_like"
require "active_support/core_ext/string/filters"
-require "active_support/deprecation"
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
@@ -214,8 +213,11 @@ module ActiveSupport
end
def coerce(other) #:nodoc:
- if Scalar === other
+ case other
+ when Scalar
[other, self]
+ when Duration
+ [Scalar.new(other.value), self]
else
[Scalar.new(other), self]
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/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index 97e982eb05..3893b0de0e 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -52,16 +52,17 @@ module ActiveSupport
@pid = Process.pid
@boot_mutex = Mutex.new
- if (@dtw = directories_to_watch).any?
+ dtw = directories_to_watch
+ @dtw, @missing = dtw.partition(&:exist?)
+
+ if @dtw.any?
# Loading listen triggers warnings. These are originated by a legit
# usage of attr_* macros for private attributes, but adds a lot of noise
# to our test suite. Thus, we lazy load it and disable warnings locally.
silence_warnings do
- begin
- require "listen"
- rescue LoadError => e
- raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
- end
+ require "listen"
+ rescue LoadError => e
+ raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
end
end
boot!
@@ -75,6 +76,19 @@ module ActiveSupport
@updated.make_true
end
end
+
+ if @missing.any?(&:exist?)
+ @boot_mutex.synchronize do
+ appeared, @missing = @missing.partition(&:exist?)
+ shutdown!
+
+ @dtw += appeared
+ boot!
+
+ @updated.make_true
+ end
+ end
+
@updated.true?
end
@@ -96,6 +110,10 @@ module ActiveSupport
Listen.to(*@dtw, &method(:changed)).start
end
+ def shutdown!
+ Listen.stop
+ end
+
def changed(modified, added, removed)
unless updated?
@updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
@@ -113,7 +131,9 @@ module ActiveSupport
ext = @ph.normalize_extension(file.extname)
file.dirname.ascend do |dir|
- if @dirs.fetch(dir, []).include?(ext)
+ matching = @dirs[dir]
+
+ if matching && (matching.empty? || matching.include?(ext))
break true
elsif dir == @lcsp || dir.root?
break false
@@ -123,7 +143,7 @@ module ActiveSupport
end
def directories_to_watch
- dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) }
+ dtw = @files.map(&:dirname) + @dirs.keys
dtw.compact!
dtw.uniq!
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index c951ad16a3..b1ac3e7969 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveSupport
module VERSION
MAJOR = 6
- MINOR = 0
+ MINOR = 1
TINY = 0
PRE = "alpha"
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index aa81ad6250..5981763f0e 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:
#
@@ -212,8 +225,8 @@ module ActiveSupport
# hash[:a] = 'x'
# hash[:b] = 'y'
# hash.values_at('a', 'b') # => ["x", "y"]
- def values_at(*indices)
- indices.collect { |key| self[convert_key(key)] }
+ def values_at(*keys)
+ super(*keys.map { |key| convert_key(key) })
end
# Returns an array of the values at the specified indices, but also
@@ -226,7 +239,7 @@ module ActiveSupport
# hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"]
# hash.fetch_values('a', 'c') # => KeyError: key not found: "c"
def fetch_values(*indices, &block)
- indices.collect { |key| fetch(key, &block) }
+ super(*indices.map { |key| convert_key(key) }, &block)
end
# Returns a shallow copy of the hash.
@@ -280,6 +293,9 @@ module ActiveSupport
super(convert_key(key))
end
+ def except(*keys)
+ slice(*self.keys - keys.map { |key| convert_key(key) })
+ end
alias_method :without, :except
def stringify_keys!; self end
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 584930e413..8faa93a3e4 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -97,7 +97,8 @@ module I18n
If you desire the default locale to be included in the defaults, please
explicitly configure it with `config.i18n.fallbacks.defaults =
[I18n.default_locale]` or `config.i18n.fallbacks = [I18n.default_locale,
- {...}]`
+ {...}]`. If you want to opt-in to the new behavior, use
+ `config.i18n.fallbacks.defaults = [nil, {...}]`.
MSG
args.unshift I18n.default_locale
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 2b86d233e5..88cdd99dbd 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "concurrent/map"
-require "active_support/core_ext/array/prepend_and_append"
require "active_support/i18n"
require "active_support/deprecation"
@@ -66,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/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 1af9833d46..ee193add6f 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -328,6 +328,8 @@ module ActiveSupport
e.name.to_s == camel_cased_word.to_s)
rescue ArgumentError => e
raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message)
+ rescue LoadError => e
+ raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(e.message)
end
# Returns the suffix that should be added to a number to denote the position
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 0d2a17970f..ec6e9ccb59 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
@@ -75,8 +74,8 @@ module ActiveSupport
#
# To use a custom separator, override the +separator+ argument.
#
- # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
- # parameterize("^trรจs|Jolie__ ", separator: '_') # => "tres_jolie"
+ # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
+ # parameterize("^trรจs|Jolie__ ", separator: '_') # => "tres_jolie"
#
# To preserve the case of the characters in a string, use the +preserve_case+ argument.
#
@@ -85,13 +84,17 @@ module ActiveSupport
#
# It preserves dashes and underscores unless they are used as separators:
#
- # parameterize("^trรจs|Jolie__ ") # => "tres-jolie__"
- # parameterize("^trรจs|Jolie-- ", separator: "_") # => "tres_jolie--"
- # parameterize("^trรจs_Jolie-- ", separator: ".") # => "tres_jolie--"
+ # parameterize("^trรจs|Jolie__ ") # => "tres-jolie__"
+ # parameterize("^trรจs|Jolie-- ", separator: "_") # => "tres_jolie--"
+ # parameterize("^trรจs_Jolie-- ", separator: ".") # => "tres_jolie--"
#
- def parameterize(string, separator: "-", preserve_case: false)
+ # 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
+ # the configured <tt>I18n.locale<tt>.
+ 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: locale)
# Turn unwanted chars into the separator.
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 8c0e016dc5..402a3fbe60 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -45,32 +45,32 @@ module ActiveSupport
private
- def convert_dates_from(data)
- case data
- when nil
- nil
- when DATE_REGEX
- begin
- Date.parse(data)
- rescue ArgumentError
+ def convert_dates_from(data)
+ case data
+ when nil
+ nil
+ when DATE_REGEX
+ begin
+ Date.parse(data)
+ rescue ArgumentError
+ data
+ end
+ when DATETIME_REGEX
+ begin
+ Time.zone.parse(data)
+ rescue ArgumentError
+ data
+ end
+ when Array
+ data.map! { |d| convert_dates_from(d) }
+ when Hash
+ data.each do |key, value|
+ data[key] = convert_dates_from(value)
+ end
+ else
data
end
- when DATETIME_REGEX
- begin
- Time.zone.parse(data)
- rescue ArgumentError
- data
- end
- when Array
- data.map! { |d| convert_dates_from(d) }
- when Hash
- data.each do |key, value|
- data[key] = convert_dates_from(value)
- end
- else
- data
end
- end
end
end
end
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/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 0f7be06c8e..938cfdb914 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -5,8 +5,8 @@ require "active_support/core_ext/class/attribute"
require "active_support/subscriber"
module ActiveSupport
- # ActiveSupport::LogSubscriber is an object set to consume
- # ActiveSupport::Notifications with the sole purpose of logging them.
+ # <tt>ActiveSupport::LogSubscriber</tt> is an object set to consume
+ # <tt>ActiveSupport::Notifications</tt> with the sole purpose of logging them.
# The log subscriber dispatches notifications to a registered object based
# on its given namespace.
#
@@ -16,7 +16,7 @@ module ActiveSupport
# module ActiveRecord
# class LogSubscriber < ActiveSupport::LogSubscriber
# def sql(event)
- # "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
+ # info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
# end
# end
# end
@@ -29,13 +29,36 @@ module ActiveSupport
# subscriber, the line above should be called after your
# <tt>ActiveRecord::LogSubscriber</tt> definition.
#
- # After configured, whenever a "sql.active_record" notification is published,
- # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
- # the sql method.
+ # After configured, whenever a <tt>"sql.active_record"</tt> notification is published,
+ # it will properly dispatch the event
+ # (<tt>ActiveSupport::Notifications::Event</tt>) to the sql method.
+ #
+ # Being an <tt>ActiveSupport::Notifications</tt> consumer,
+ # <tt>ActiveSupport::LogSubscriber</tt> exposes a simple interface to check if
+ # instrumented code raises an exception. It is common to log a different
+ # message in case of an error, and this can be achieved by extending
+ # the previous example:
+ #
+ # module ActiveRecord
+ # class LogSubscriber < ActiveSupport::LogSubscriber
+ # def sql(event)
+ # exception = event.payload[:exception]
+ #
+ # if exception
+ # exception_object = event.payload[:exception_object]
+ #
+ # error "[ERROR] #{event.payload[:name]}: #{exception.join(', ')} " \
+ # "(#{exception_object.backtrace.first})"
+ # else
+ # # standard logger code
+ # end
+ # end
+ # end
+ # end
#
# Log subscriber also has some helpers to deal with logging and automatically
- # flushes all logs when the request finishes (via action_dispatch.callback
- # notification) in a Rails environment.
+ # flushes all logs when the request finishes
+ # (via <tt>action_dispatch.callback</tt> notification) in a Rails environment.
class LogSubscriber < Subscriber
# Embed in a String to clear all previous ANSI sequences.
CLEAR = "\e[0m"
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 01cc363e2b..d9e93b530c 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -67,9 +67,12 @@ module ActiveSupport
# have a key <tt>:exception</tt> with an array of two elements as value: a string with
# the name of the exception class, and the exception message.
# The <tt>:exception_object</tt> key of the payload will have the exception
- # itself as the value.
+ # itself as the value:
#
- # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
+ # event.payload[:exception] # => ["ArgumentError", "Invalid value"]
+ # event.payload[:exception_object] # => #<ArgumentError: Invalid value>
+ #
+ # As the earlier example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
# is able to take the arguments as they come and provide an object-oriented
# interface to that data.
#
@@ -150,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
@@ -177,7 +189,7 @@ module ActiveSupport
# names, or by passing a Regexp to match all events that match a pattern.
#
# ActiveSupport::Notifications.subscribe(/render/) do |*args|
- # ...
+ # @event = ActiveSupport::Notifications::Event.new(*args)
# end
#
# The +block+ will receive five parameters with information about the event:
@@ -189,6 +201,13 @@ module ActiveSupport
# id # => String, unique ID for the instrumenter that fired the event
# payload # => Hash, the payload
# end
+ #
+ # If the block passed to the method only takes one parameter,
+ # it will yield an event object to the block:
+ #
+ # ActiveSupport::Notifications.subscribe(/render/) do |event|
+ # @event = event
+ # end
def subscribe(*args, &block)
notifier.subscribe(*args, &block)
end
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..12546511a8 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -13,14 +13,15 @@ module ActiveSupport
@notifier = notifier
end
- # Instrument the given block by measuring the time taken to execute it
- # and publish it. Notice that events get sent even if an error occurs
- # in the passed-in block.
+ # Given a block, instrument it by measuring the time taken to execute
+ # and publish it. Without a block, simply send a message via the
+ # notifier. Notice that events get sent even if an error occurs in the
+ # passed-in block.
def instrument(name, payload = {})
# some of the listeners might have state
listeners_state = start name, payload
begin
- yield payload
+ yield payload if block_given?
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
payload[:exception_object] = e
@@ -67,9 +68,8 @@ module ActiveSupport
@transaction_id = transaction_id
@end = ending
@children = []
- @duration = nil
- @cpu_time_start = nil
- @cpu_time_finish = nil
+ @cpu_time_start = 0
+ @cpu_time_finish = 0
@allocation_count_start = 0
@allocation_count_finish = 0
end
@@ -124,7 +124,7 @@ module ActiveSupport
#
# @event.duration # => 1000.138
def duration
- @duration ||= 1000.0 * (self.end - time)
+ 1000.0 * (self.end - time)
end
def <<(event)
@@ -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/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
index fea18e9712..2f81cd4f80 100644
--- a/activesupport/lib/active_support/reloader.rb
+++ b/activesupport/lib/active_support/reloader.rb
@@ -50,11 +50,9 @@ module ActiveSupport
def self.reload!
executor.wrap do
new.tap do |instance|
- begin
- instance.run!
- ensure
- instance.complete!
- end
+ instance.run!
+ ensure
+ instance.complete!
end
end
prepare!
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index 20b6b9cd3f..5e455fca57 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -24,7 +24,7 @@ module ActiveSupport
# The values are first processed by SHA256, so that we don't leak length info
# via timing attacks.
def secure_compare(a, b)
- fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b
+ fixed_length_secure_compare(::Digest::SHA256.digest(a), ::Digest::SHA256.digest(b)) && a == b
end
module_function :secure_compare
end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index f3e902f9dd..c3cd175a52 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -24,6 +24,10 @@ module ActiveSupport
# After configured, whenever a "sql.active_record" notification is published,
# it will properly dispatch the event (ActiveSupport::Notifications::Event) to
# the +sql+ method.
+ #
+ # We can detach a subscriber as well:
+ #
+ # ActiveRecord::StatsSubscriber.detach_from(:active_record)
class Subscriber
class << self
# Attach the subscriber to a namespace.
@@ -40,6 +44,25 @@ module ActiveSupport
end
end
+ # Detach the subscriber from a namespace.
+ def detach_from(namespace, notifier = ActiveSupport::Notifications)
+ @namespace = namespace
+ @subscriber = find_attached_subscriber
+ @notifier = notifier
+
+ return unless subscriber
+
+ subscribers.delete(subscriber)
+
+ # Remove event subscribers of all existing methods on the class.
+ subscriber.public_methods(false).each do |event|
+ remove_event_subscriber(event)
+ end
+
+ # Reset notifier so that event subscribers will not add for new methods added to the class.
+ @notifier = nil
+ end
+
# Adds event subscribers for all new methods added to the class.
def method_added(event)
# Only public methods are added as subscribers, and only if a notifier
@@ -58,15 +81,41 @@ module ActiveSupport
attr_reader :subscriber, :notifier, :namespace
def add_event_subscriber(event) # :doc:
- return if %w{ start finish }.include?(event.to_s)
+ return if invalid_event?(event.to_s)
- pattern = "#{event}.#{namespace}"
+ pattern = prepare_pattern(event)
# Don't add multiple subscribers (eg. if methods are redefined).
- return if subscriber.patterns.include?(pattern)
+ return if pattern_subscribed?(pattern)
+
+ subscriber.patterns[pattern] = notifier.subscribe(pattern, subscriber)
+ end
+
+ def remove_event_subscriber(event) # :doc:
+ return if invalid_event?(event.to_s)
+
+ pattern = prepare_pattern(event)
+
+ return unless pattern_subscribed?(pattern)
+
+ notifier.unsubscribe(subscriber.patterns[pattern])
+ subscriber.patterns.delete(pattern)
+ end
+
+ def find_attached_subscriber
+ subscribers.find { |attached_subscriber| attached_subscriber.instance_of?(self) }
+ end
+
+ def invalid_event?(event)
+ %w{ start finish }.include?(event.to_s)
+ end
+
+ def prepare_pattern(event)
+ "#{event}.#{namespace}"
+ end
- subscriber.patterns << pattern
- notifier.subscribe(pattern, subscriber)
+ def pattern_subscribed?(pattern)
+ subscriber.patterns.key?(pattern)
end
end
@@ -74,7 +123,7 @@ module ActiveSupport
def initialize
@queue_key = [self.class.name, object_id].join "-"
- @patterns = []
+ @patterns = {}
super
end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index ef12c6b9b0..7be4108ed7 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -12,6 +12,7 @@ require "active_support/testing/constant_lookup"
require "active_support/testing/time_helpers"
require "active_support/testing/file_fixtures"
require "active_support/testing/parallelization"
+require "concurrent/utility/processor_counter"
module ActiveSupport
class TestCase < ::Minitest::Test
@@ -58,16 +59,20 @@ module ActiveSupport
# If the number of workers is set to +1+ or fewer, the tests will not be
# parallelized.
#
+ # If +workers+ is set to +:number_of_processors+, the number of workers will be
+ # set to the actual core count on the machine you are on.
+ #
# The default parallelization method is to fork processes. If you'd like to
# use threads instead you can pass <tt>with: :threads</tt> to the +parallelize+
# method. Note the threaded parallelization does not create multiple
# database and will not work with system tests at this time.
#
- # parallelize(workers: 2, with: :threads)
+ # parallelize(workers: :number_of_processors, with: :threads)
#
# The threaded parallelization uses minitest's parallel executor directly.
# The processes parallelization uses a Ruby DRb server.
- def parallelize(workers: 2, with: :processes)
+ def parallelize(workers: :number_of_processors, with: :processes)
+ workers = Concurrent.physical_processor_count if workers == :number_of_processors
workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"]
return if workers <= 1
diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb
index eba41aa907..03c38be481 100644
--- a/activesupport/lib/active_support/testing/method_call_assertions.rb
+++ b/activesupport/lib/active_support/testing/method_call_assertions.rb
@@ -35,18 +35,16 @@ module ActiveSupport
assert_called(object, method_name, message, times: 0, &block)
end
- # TODO: No need to resort to #send once support for Ruby 2.4 is
- # dropped.
def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil)
times_called = 0
- klass.send(:define_method, "stubbed_#{method_name}") do |*|
+ klass.define_method("stubbed_#{method_name}") do |*|
times_called += 1
returns
end
- klass.send(:alias_method, "original_#{method_name}", method_name)
- klass.send(:alias_method, method_name, "stubbed_#{method_name}")
+ klass.alias_method "original_#{method_name}", method_name
+ klass.alias_method method_name, "stubbed_#{method_name}"
yield
@@ -55,9 +53,9 @@ module ActiveSupport
assert_equal times, times_called, error
ensure
- klass.send(:alias_method, method_name, "original_#{method_name}")
- klass.send(:undef_method, "original_#{method_name}")
- klass.send(:undef_method, "stubbed_#{method_name}")
+ klass.alias_method method_name, "original_#{method_name}"
+ klass.undef_method "original_#{method_name}"
+ klass.undef_method "stubbed_#{method_name}"
end
def assert_not_called_on_instance_of(klass, method_name, message = nil, &block)
diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb
index c5d3a88131..e760bf5ce3 100644
--- a/activesupport/lib/active_support/testing/parallelization.rb
+++ b/activesupport/lib/active_support/testing/parallelization.rb
@@ -69,31 +69,35 @@ module ActiveSupport
def start
@pool = @queue_size.times.map do |worker|
fork do
- begin
- DRb.stop_service
+ DRb.stop_service
+ begin
after_fork(worker)
+ rescue => setup_exception; end
+
+ queue = DRbObject.new_with_uri(@url)
+
+ while job = queue.pop
+ klass = job[0]
+ method = job[1]
+ reporter = job[2]
+ result = klass.with_info_handler reporter do
+ Minitest.run_one_method(klass, method)
+ end
+
+ add_setup_exception(result, setup_exception) if setup_exception
- queue = DRbObject.new_with_uri(@url)
-
- while job = queue.pop
- klass = job[0]
- method = job[1]
- reporter = job[2]
- result = Minitest.run_one_method(klass, method)
-
- begin
- queue.record(reporter, result)
- rescue DRb::DRbConnError
- result.failures.each do |failure|
- failure.exception = DRb::DRbRemoteError.new(failure.exception)
- end
- queue.record(reporter, result)
+ begin
+ queue.record(reporter, result)
+ rescue DRb::DRbConnError
+ result.failures.each do |failure|
+ failure.exception = DRb::DRbRemoteError.new(failure.exception)
end
+ queue.record(reporter, result)
end
- ensure
- run_cleanup(worker)
end
+ ensure
+ run_cleanup(worker)
end
end
end
@@ -106,6 +110,11 @@ module ActiveSupport
@queue_size.times { @queue << nil }
@pool.each { |pid| Process.waitpid pid }
end
+
+ private
+ def add_setup_exception(result, setup_exception)
+ result.failures.prepend Minitest::UnexpectedError.new(setup_exception)
+ end
end
end
end
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index f160e66971..5a3fa9346c 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -22,7 +22,7 @@ module ActiveSupport
@stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name)
- object.singleton_class.send :alias_method, new_name, method_name
+ object.singleton_class.alias_method new_name, method_name
object.define_singleton_method(method_name, &block)
end
@@ -43,9 +43,9 @@ module ActiveSupport
def unstub_object(stub)
singleton_class = stub.object.singleton_class
- singleton_class.send :silence_redefinition_of_method, stub.method_name
- singleton_class.send :alias_method, stub.method_name, stub.original_method
- singleton_class.send :undef_method, stub.original_method
+ singleton_class.silence_redefinition_of_method stub.method_name
+ singleton_class.alias_method stub.method_name, stub.original_method
+ singleton_class.undef_method stub.original_method
end
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index be298bf0a1..075cd4ed8b 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -3,6 +3,7 @@
require "time"
require "base64"
require "bigdecimal"
+require "bigdecimal/util"
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/date_time/calculations"
@@ -68,11 +69,7 @@ module ActiveSupport
"float" => Proc.new { |float| float.to_f },
"decimal" => Proc.new do |number|
if String === number
- begin
- BigDecimal(number)
- rescue ArgumentError
- BigDecimal(number.to_f.to_s)
- end
+ number.to_d
else
BigDecimal(number)
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 168d3655d3..01e60abd99 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -39,8 +39,6 @@ class ActiveSupport::TestCase
def jruby_skip(message = "")
skip message if defined?(JRUBY_VERSION)
end
-
- def frozen_error_class
- Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError
- end
end
+
+require_relative "../../tools/test_common"
diff --git a/activesupport/test/actionable_error_test.rb b/activesupport/test/actionable_error_test.rb
new file mode 100644
index 0000000000..63046b937c
--- /dev/null
+++ b/activesupport/test/actionable_error_test.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/actionable_error"
+
+class ActionableErrorTest < ActiveSupport::TestCase
+ NonActionableError = Class.new(StandardError)
+
+ class DispatchableError < StandardError
+ include ActiveSupport::ActionableError
+
+ class_attribute :flip1, default: false
+ class_attribute :flip2, default: false
+
+ action "Flip 1" do
+ self.flip1 = true
+ end
+
+ action "Flip 2" do
+ self.flip2 = true
+ end
+ end
+
+ test "returns all action of an actionable error" do
+ assert_equal ["Flip 1", "Flip 2"], ActiveSupport::ActionableError.actions(DispatchableError).keys
+ assert_equal ["Flip 1", "Flip 2"], ActiveSupport::ActionableError.actions(DispatchableError.new).keys
+ end
+
+ test "returns no actions for non-actionable errors" do
+ assert ActiveSupport::ActionableError.actions(Exception).empty?
+ assert ActiveSupport::ActionableError.actions(Exception.new).empty?
+ end
+
+ test "dispatches actions from error and name" do
+ assert_changes "DispatchableError.flip1", from: false, to: true do
+ ActiveSupport::ActionableError.dispatch DispatchableError, "Flip 1"
+ end
+ end
+
+ test "cannot dispatch missing actions" do
+ err = assert_raises ActiveSupport::ActionableError::NonActionable do
+ ActiveSupport::ActionableError.dispatch NonActionableError, "action"
+ end
+
+ assert_equal 'Cannot find action "action"', err.to_s
+ end
+end
diff --git a/activesupport/test/autoloading_fixtures/raises_load_error.rb b/activesupport/test/autoloading_fixtures/raises_load_error.rb
new file mode 100644
index 0000000000..f97be29b71
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/raises_load_error.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+# raises a load error typical of the dynamic code that manually raises load errors
+raise LoadError, "required gem not present kind of error"
diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb
index 9f54b1e7de..a696760bb2 100644
--- a/activesupport/test/cache/behaviors/cache_store_behavior.rb
+++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb
@@ -130,7 +130,7 @@ module CacheStoreBehavior
assert_equal("fufu", @cache.read("fu"))
end
- def test_multi_with_objects
+ def test_fetch_multi_with_objects
cache_struct = Struct.new(:cache_key, :title)
foo = cache_struct.new("foo", "FOO!")
bar = cache_struct.new("bar")
@@ -142,6 +142,14 @@ module CacheStoreBehavior
assert_equal({ foo => "FOO!", bar => "BAM!" }, values)
end
+ def test_fetch_multi_returns_ordered_names
+ @cache.write("bam", "BAM")
+
+ values = @cache.fetch_multi("foo", "bar", "bam") { |key| key.upcase }
+
+ assert_equal(%w(foo bar bam), values.keys)
+ end
+
def test_fetch_multi_without_block
assert_raises(ArgumentError) do
@cache.fetch_multi("foo")
diff --git a/activesupport/test/cache/behaviors/connection_pool_behavior.rb b/activesupport/test/cache/behaviors/connection_pool_behavior.rb
index 4d1901a173..aed04d07d4 100644
--- a/activesupport/test/cache/behaviors/connection_pool_behavior.rb
+++ b/activesupport/test/cache/behaviors/connection_pool_behavior.rb
@@ -7,24 +7,22 @@ module ConnectionPoolBehavior
threads = []
emulating_latency do
- begin
- cache = ActiveSupport::Cache.lookup_store(store, { pool_size: 2, pool_timeout: 1 }.merge(store_options))
- cache.clear
-
- assert_raises Timeout::Error do
- # One of the three threads will fail in 1 second because our pool size
- # is only two.
- 3.times do
- threads << Thread.new do
- cache.read("latency")
- end
+ cache = ActiveSupport::Cache.lookup_store(*store, { pool_size: 2, pool_timeout: 1 }.merge(store_options))
+ cache.clear
+
+ assert_raises Timeout::Error do
+ # One of the three threads will fail in 1 second because our pool size
+ # is only two.
+ 3.times do
+ threads << Thread.new do
+ cache.read("latency")
end
-
- threads.each(&:join)
end
- ensure
- threads.each(&:kill)
+
+ threads.each(&:join)
end
+ ensure
+ threads.each(&:kill)
end
ensure
Thread.report_on_exception = original_report_on_exception
@@ -34,24 +32,22 @@ module ConnectionPoolBehavior
threads = []
emulating_latency do
- begin
- cache = ActiveSupport::Cache.lookup_store(store, store_options)
- cache.clear
-
- assert_nothing_raised do
- # Default connection pool size is 5, assuming 10 will make sure that
- # the connection pool isn't used at all.
- 10.times do
- threads << Thread.new do
- cache.read("latency")
- end
+ cache = ActiveSupport::Cache.lookup_store(*store, store_options)
+ cache.clear
+
+ assert_nothing_raised do
+ # Default connection pool size is 5, assuming 10 will make sure that
+ # the connection pool isn't used at all.
+ 10.times do
+ threads << Thread.new do
+ cache.read("latency")
end
-
- threads.each(&:join)
end
- ensure
- threads.each(&:kill)
+
+ threads.each(&:join)
end
+ ensure
+ threads.each(&:kill)
end
end
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..790534cd3c 100644
--- a/activesupport/test/cache/stores/redis_cache_store_test.rb
+++ b/activesupport/test/cache/stores/redis_cache_store_test.rb
@@ -140,6 +140,12 @@ module ActiveSupport::Cache::RedisCacheStoreTests
end
end
+ def test_fetch_multi_without_names
+ assert_not_called(@cache.redis, :mget) do
+ @cache.fetch_multi() { }
+ end
+ end
+
def test_increment_expires_in
assert_called_with @cache.redis, :incrby, [ "#{@namespace}:foo", 1 ] do
assert_called_with @cache.redis, :expire, [ "#{@namespace}:foo", 60 ] do
@@ -187,7 +193,7 @@ module ActiveSupport::Cache::RedisCacheStoreTests
private
def store
- :redis_cache_store
+ [:redis_cache_store]
end
def emulating_latency
@@ -204,7 +210,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/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 466b364e9d..79098b2a7d 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -31,7 +31,7 @@ module CallbacksTest
def callback_object(callback_method)
klass = Class.new
- klass.send(:define_method, callback_method) do |model|
+ klass.define_method(callback_method) do |model|
model.history << [:"#{callback_method}_save", :object]
end
klass.new
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index 2c6145940b..cdb8441b81 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -112,6 +112,16 @@ module ConstantizeTestCases
assert_nil yield("A::Object::B")
assert_nil yield("A::Object::Object::Object::B")
+ with_autoloading_fixtures do
+ assert_nil yield("Em")
+ end
+
+ assert_raises(LoadError) do
+ with_autoloading_fixtures do
+ yield("RaisesLoadError")
+ end
+ end
+
assert_raises(NameError) do
with_autoloading_fixtures do
yield("RaisesNameError")
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/array/prepend_append_test.rb b/activesupport/test/core_ext/array/prepend_append_test.rb
index c34acd66ad..8573dbd5a6 100644
--- a/activesupport/test/core_ext/array/prepend_append_test.rb
+++ b/activesupport/test/core_ext/array/prepend_append_test.rb
@@ -1,14 +1,11 @@
# frozen_string_literal: true
require "abstract_unit"
-require "active_support/core_ext/array"
class PrependAppendTest < ActiveSupport::TestCase
- def test_append
- assert_equal [1, 2], [1].append(2)
- end
-
- def test_prepend
- assert_equal [2, 1], [1].prepend(2)
+ def test_requiring_prepend_and_append_is_deprecated
+ assert_deprecated do
+ require "active_support/core_ext/array/prepend_and_append"
+ end
end
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/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
deleted file mode 100644
index b9e41f7b25..0000000000
--- a/activesupport/test/core_ext/hash/transform_keys_test.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-# frozen_string_literal: true
-
-require "abstract_unit"
-require "active_support/core_ext/hash/keys"
-
-class TransformKeysTest < ActiveSupport::TestCase
- test "transform_keys returns a new hash with the keys computed from the block" do
- original = { a: "a", b: "b" }
- mapped = original.transform_keys { |k| "#{k}!".to_sym }
-
- assert_equal({ a: "a", b: "b" }, original)
- assert_equal({ a!: "a", b!: "b" }, mapped)
- end
-
- test "transform_keys! modifies the keys of the original" do
- original = { a: "a", b: "b" }
- mapped = original.transform_keys! { |k| "#{k}!".to_sym }
-
- assert_equal({ a!: "a", b!: "b" }, original)
- assert_same original, mapped
- end
-
- test "transform_keys returns a sized Enumerator if no block is given" do
- original = { a: "a", b: "b" }
- enumerator = original.transform_keys
- assert_equal original.size, enumerator.size
- assert_equal Enumerator, enumerator.class
- end
-
- test "transform_keys! returns a sized Enumerator if no block is given" do
- original = { a: "a", b: "b" }
- enumerator = original.transform_keys!
- assert_equal original.size, enumerator.size
- assert_equal Enumerator, enumerator.class
- end
-
- test "transform_keys is chainable with Enumerable methods" do
- original = { a: "a", b: "b" }
- mapped = original.transform_keys.with_index { |k, i| [k, i].join.to_sym }
- assert_equal({ a0: "a", b1: "b" }, mapped)
- end
-
- test "transform_keys! is chainable with Enumerable methods" do
- original = { a: "a", b: "b" }
- original.transform_keys!.with_index { |k, i| [k, i].join.to_sym }
- assert_equal({ a0: "a", b1: "b" }, original)
- end
-
- test "transform_keys returns a Hash instance when self is inherited from Hash" do
- class HashDescendant < ::Hash
- def initialize(elements = nil)
- super(elements)
- (elements || {}).each_pair { |key, value| self[key] = value }
- end
- end
-
- original = HashDescendant.new(a: "a", b: "b")
- mapped = original.transform_keys { |k| "#{k}!".to_sym }
-
- assert_equal({ a: "a", b: "b" }, original)
- assert_equal({ a!: "a", b!: "b" }, mapped)
- assert_equal(::Hash, mapped.class)
- end
-end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index f4f0dd6b31..8572d56722 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -31,10 +31,10 @@ class HashExtTest < ActiveSupport::TestCase
def test_methods
h = {}
- assert_respond_to h, :transform_keys
- assert_respond_to h, :transform_keys!
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
@@ -49,18 +49,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_respond_to h, :except!
end
- def test_transform_keys
- assert_equal @upcase_strings, @strings.transform_keys { |key| key.to_s.upcase }
- assert_equal @upcase_strings, @symbols.transform_keys { |key| key.to_s.upcase }
- assert_equal @upcase_strings, @mixed.transform_keys { |key| key.to_s.upcase }
- end
-
- def test_transform_keys_not_mutates
- transformed_hash = @mixed.dup
- transformed_hash.transform_keys { |key| key.to_s.upcase }
- assert_equal @mixed, transformed_hash
- end
-
def test_deep_transform_keys
assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys { |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys { |key| key.to_s.upcase }
@@ -76,19 +64,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_mixed, transformed_hash
end
- def test_transform_keys!
- assert_equal @upcase_strings, @symbols.dup.transform_keys! { |key| key.to_s.upcase }
- assert_equal @upcase_strings, @strings.dup.transform_keys! { |key| key.to_s.upcase }
- assert_equal @upcase_strings, @mixed.dup.transform_keys! { |key| key.to_s.upcase }
- end
-
- def test_transform_keys_with_bang_mutates
- transformed_hash = @mixed.dup
- transformed_hash.transform_keys! { |key| key.to_s.upcase }
- assert_equal @upcase_strings, transformed_hash
- assert_equal({ :a => 1, "b" => 2 }, @mixed)
- end
-
def test_deep_transform_keys!
assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys! { |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys! { |key| key.to_s.upcase }
@@ -105,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
@@ -337,30 +337,16 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, merged
end
- def test_slice
- original = { a: "x", b: "y", c: 10 }
- expected = { a: "x", b: "y" }
-
- # Should return a new hash with only the given keys.
- assert_equal expected, original.slice(:a, :b)
- assert_not_equal expected, original
- end
-
def test_slice_inplace
original = { a: "x", b: "y", c: 10 }
- expected = { c: 10 }
-
- # Should replace the hash with only the given keys.
- assert_equal expected, original.slice!(:a, :b)
- end
+ expected_return = { c: 10 }
+ expected_original = { a: "x", b: "y" }
- def test_slice_with_an_array_key
- original = { :a => "x", :b => "y", :c => 10, [:a, :b] => "an array key" }
- expected = { [:a, :b] => "an array key", :c => 10 }
+ # Should return a hash containing the removed key/value pairs.
+ assert_equal expected_return, original.slice!(:a, :b)
- # Should return a new hash with only the given keys when given an array key.
- assert_equal expected, original.slice([:a, :b], :c)
- assert_not_equal expected, original
+ # Should replace the hash with only the given keys.
+ assert_equal expected_original, original
end
def test_slice_inplace_with_an_array_key
@@ -371,14 +357,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, original.slice!([:a, :b], :c)
end
- def test_slice_with_splatted_keys
- original = { :a => "x", :b => "y", :c => 10, [:a, :b] => "an array key" }
- expected = { a: "x", b: "y" }
-
- # Should grab each of the splatted keys.
- assert_equal expected, original.slice(*[:a, :b])
- end
-
def test_slice_bang_does_not_override_default
hash = Hash.new(0)
hash.update(a: 1, b: 2)
@@ -444,7 +422,7 @@ class HashExtTest < ActiveSupport::TestCase
original.freeze
assert_nothing_raised { original.except(:a) }
- assert_raise(frozen_error_class) { original.except!(:a) }
+ assert_raise(FrozenError) { original.except!(:a) }
end
def test_except_does_not_delete_values_in_original
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index 126aa51cb4..6d3726e407 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -13,10 +13,9 @@ class TestLoadError < ActiveSupport::TestCase
end
def test_path
- begin load "nor/this/one.rb"
- rescue LoadError => e
- assert_equal "nor/this/one.rb", e.path
- end
+ load "nor/this/one.rb"
+ rescue LoadError => e
+ assert_equal "nor/this/one.rb", e.path
end
def test_is_missing_with_nil_path
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/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 4b8efb8a93..16d6a4c2f2 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -57,7 +57,7 @@ class RangeTest < ActiveSupport::TestCase
end
def test_should_include_other_with_exclusive_end
- assert((1..10).include?(1...10))
+ assert((1..10).include?(1...11))
end
def test_should_compare_identical_inclusive
@@ -69,7 +69,7 @@ class RangeTest < ActiveSupport::TestCase
end
def test_should_compare_other_with_exclusive_end
- assert((1..10) === (1...10))
+ assert((1..10) === (1...11))
end
def test_exclusive_end_should_not_include_identical_with_inclusive_end
@@ -93,6 +93,10 @@ class RangeTest < ActiveSupport::TestCase
assert range.method(:include?) != range.method(:cover?)
end
+ def test_should_cover_other_with_exclusive_end
+ assert((1..10).cover?(1...11))
+ end
+
def test_overlaps_on_time
time_range_1 = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30)
time_range_2 = Time.utc(2005, 12, 10, 17, 00)..Time.utc(2005, 12, 10, 18, 00)
diff --git a/activesupport/test/core_ext/secure_random_test.rb b/activesupport/test/core_ext/secure_random_test.rb
index 7067fb524c..4b73233971 100644
--- a/activesupport/test/core_ext/secure_random_test.rb
+++ b/activesupport/test/core_ext/secure_random_test.rb
@@ -19,4 +19,24 @@ class SecureRandomTest < ActiveSupport::TestCase
assert_not_equal s1, s2
assert_equal 24, s1.length
end
+
+ def test_base36
+ s1 = SecureRandom.base36
+ s2 = SecureRandom.base36
+
+ assert_not_equal s1, s2
+ assert_equal 16, s1.length
+ assert_match(/^[a-z0-9]+$/, s1)
+ assert_match(/^[a-z0-9]+$/, s2)
+ end
+
+ def test_base36_with_length
+ s1 = SecureRandom.base36(24)
+ s2 = SecureRandom.base36(24)
+
+ assert_not_equal s1, s2
+ assert_equal 24, s1.length
+ assert_match(/^[a-z0-9]+$/, s1)
+ assert_match(/^[a-z0-9]+$/, s2)
+ end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 2468fe3603..c5a000b67a 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -206,6 +206,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_parameterize_with_locale
+ word = "Fรผnf autos"
+ I18n.backend.store_translations(:de, i18n: { transliterate: { rule: { "รผ" => "ue" } } })
+ assert_equal("fuenf-autos", word.parameterize(locale: :de))
+ end
+
def test_humanize
UnderscoreToHuman.each do |underscore, human|
assert_equal(human, underscore.humanize)
@@ -285,6 +291,11 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, omission: "[...]", separator: /\s/)
end
+ def test_truncate_returns_frozen_string
+ assert_not "Hello World!".truncate(12).frozen?
+ assert_not "Hello World!!".truncate(12).frozen?
+ end
+
def test_truncate_bytes
assert_equal "๐Ÿ‘๐Ÿ‘๐Ÿ‘๐Ÿ‘", "๐Ÿ‘๐Ÿ‘๐Ÿ‘๐Ÿ‘".truncate_bytes(16)
assert_equal "๐Ÿ‘๐Ÿ‘๐Ÿ‘๐Ÿ‘", "๐Ÿ‘๐Ÿ‘๐Ÿ‘๐Ÿ‘".truncate_bytes(16, omission: nil)
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 7078f3506d..590b81b770 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -514,6 +514,8 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(1582, 10, 15, 15, 15, 10), Time.local(1582, 10, 14, 15, 15, 10).advance(days: 1)
assert_equal Time.local(1582, 10, 5, 15, 15, 10), Time.local(1582, 10, 4, 15, 15, 10).advance(days: 1)
assert_equal Time.local(1582, 10, 4, 15, 15, 10), Time.local(1582, 10, 5, 15, 15, 10).advance(days: -1)
+ assert_equal Time.local(999, 10, 4, 15, 15, 10), Time.local(1000, 10, 4, 15, 15, 10).advance(years: -1)
+ assert_equal Time.local(1000, 10, 4, 15, 15, 10), Time.local(999, 10, 4, 15, 15, 10).advance(years: 1)
end
def test_last_week
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/deprecation/method_wrappers_test.rb b/activesupport/test/deprecation/method_wrappers_test.rb
index b04bce7a11..0aa3233aab 100644
--- a/activesupport/test/deprecation/method_wrappers_test.rb
+++ b/activesupport/test/deprecation/method_wrappers_test.rb
@@ -21,6 +21,13 @@ class MethodWrappersTest < ActiveSupport::TestCase
end
end
+ def test_deprecate_methods_without_alternate_method
+ warning = /old_method is deprecated and will be removed from Rails \d.\d./
+ ActiveSupport::Deprecation.deprecate_methods(@klass, :old_method)
+
+ assert_deprecated(warning) { assert_equal "abc", @klass.new.old_method }
+ end
+
def test_deprecate_methods_warning_default
warning = /old_method is deprecated and will be removed from Rails \d.\d \(use new_method instead\)/
ActiveSupport::Deprecation.deprecate_methods(@klass, old_method: :new_method)
@@ -82,12 +89,4 @@ class MethodWrappersTest < ActiveSupport::TestCase
warning = /old_method is deprecated and will be removed from Rails \d.\d \(use new_method instead\)/
assert_deprecated(warning) { assert_equal "abc", @klass.old_method }
end
-
- def test_method_with_without_deprecation_is_exposed
- ActiveSupport::Deprecation.deprecate_methods(@klass, old_method: :new_method)
-
- warning = /old_method is deprecated and will be removed from Rails \d.\d \(use new_method instead\)/
- assert_deprecated(warning) { assert_equal "abc", @klass.new.old_method_with_deprecation }
- assert_equal "abc", @klass.new.old_method_without_deprecation
- end
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 105153584d..f25c704586 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -31,6 +31,9 @@ class Deprecatee
def f=(v); end
deprecate :f=
+ deprecate :g
+ def g; end
+
module B
C = 1
end
@@ -425,6 +428,10 @@ class DeprecationTest < ActiveSupport::TestCase
end
end
+ def test_deprecate_work_before_define_method
+ assert_deprecated { @dtc.g }
+ end
+
private
def deprecator_with_messages
klass = Class.new(ActiveSupport::Deprecation)
diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb
index 2c94c3c56c..f8752688d2 100644
--- a/activesupport/test/descendants_tracker_test_cases.rb
+++ b/activesupport/test/descendants_tracker_test_cases.rb
@@ -27,6 +27,15 @@ module DescendantsTrackerTestCases
assert_equal_sets [], Child2.descendants
end
+ def test_descendants_with_garbage_collected_classes
+ 1.times do
+ child_klass = Class.new(Parent)
+ assert_equal_sets [Child1, Grandchild1, Grandchild2, Child2, child_klass], Parent.descendants
+ end
+ GC.start
+ assert_equal_sets [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
+ end
+
def test_direct_descendants
assert_equal_sets [Child1, Child2], Parent.direct_descendants
assert_equal_sets [Grandchild1, Grandchild2], Child1.direct_descendants
diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb
index a557608986..b2d5eb94c2 100644
--- a/activesupport/test/evented_file_update_checker_test.rb
+++ b/activesupport/test/evented_file_update_checker_test.rb
@@ -76,6 +76,34 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
Process.wait(pid)
end
+
+ test "updated should become true when nonexistent directory is added later" do
+ Dir.mktmpdir do |dir|
+ watched_dir = File.join(dir, "app")
+ unwatched_dir = File.join(dir, "node_modules")
+ not_exist_watched_dir = File.join(dir, "test")
+
+ Dir.mkdir(watched_dir)
+ Dir.mkdir(unwatched_dir)
+
+ checker = new_checker([], watched_dir => ".rb", not_exist_watched_dir => ".rb") { }
+
+ FileUtils.touch(File.join(watched_dir, "a.rb"))
+ wait
+ assert_predicate checker, :updated?
+ assert checker.execute_if_updated
+
+ Dir.mkdir(not_exist_watched_dir)
+ wait
+ assert_predicate checker, :updated?
+ assert checker.execute_if_updated
+
+ FileUtils.touch(File.join(unwatched_dir, "a.rb"))
+ wait
+ assert_not_predicate checker, :updated?
+ assert_not checker.execute_if_updated
+ end
+ end
end
class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
index 72683816b3..84d89fa0a7 100644
--- a/activesupport/test/file_update_checker_shared_tests.rb
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -186,6 +186,18 @@ module FileUpdateCheckerSharedTests
assert_equal 1, i
end
+ test "should execute the block if files change in a watched directory any extensions" do
+ i = 0
+
+ checker = new_checker([], tmpdir => []) { i += 1 }
+
+ touch(tmpfile("foo.rb"))
+ wait
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
test "should execute the block if files change in a watched directory several extensions" do
i = 0
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
index 90f7107b5c..af96231801 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..ddf765a220 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))
@@ -310,6 +304,12 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ def test_parameterize_with_locale
+ word = "Fรผnf autos"
+ I18n.backend.store_translations(:de, i18n: { transliterate: { rule: { "รผ" => "ue" } } })
+ assert_equal("fuenf-autos", ActiveSupport::Inflector.parameterize(word, locale: :de))
+ end
+
def test_classify
ClassNameToTableName.each do |class_name, table_name|
assert_equal(class_name, ActiveSupport::Inflector.classify(table_name))
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 8062873386..7589ffd0ea 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -21,20 +21,18 @@ class TestJSONEncoding < ActiveSupport::TestCase
JSONTest::EncodingTestCases.constants.each do |class_tests|
define_method("test_#{class_tests[0..-6].underscore}") do
- begin
- prev = ActiveSupport.use_standard_json_time_format
-
- standard_class_tests = /Standard/.match?(class_tests)
-
- ActiveSupport.escape_html_entities_in_json = !standard_class_tests
- ActiveSupport.use_standard_json_time_format = standard_class_tests
- JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
- assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
- end
- ensure
- ActiveSupport.escape_html_entities_in_json = false
- ActiveSupport.use_standard_json_time_format = prev
+ prev = ActiveSupport.use_standard_json_time_format
+
+ standard_class_tests = /Standard/.match?(class_tests)
+
+ ActiveSupport.escape_html_entities_in_json = !standard_class_tests
+ ActiveSupport.use_standard_json_time_format = standard_class_tests
+ JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
+ assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
end
+ ensure
+ ActiveSupport.escape_html_entities_in_json = false
+ ActiveSupport.use_standard_json_time_format = prev
end
end
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/instrumenter_test.rb b/activesupport/test/notifications/instrumenter_test.rb
index d5c9e82e9f..9729ad5c89 100644
--- a/activesupport/test/notifications/instrumenter_test.rb
+++ b/activesupport/test/notifications/instrumenter_test.rb
@@ -44,6 +44,12 @@ module ActiveSupport
assert_equal Hash[result: 2], payload
end
+ def test_instrument_works_without_a_block
+ instrumenter.instrument("no.block", payload)
+ assert_equal 1, notifier.finishes.size
+ assert_equal "no.block", notifier.finishes.first.first
+ end
+
def test_start
instrumenter.start("foo", payload)
assert_equal [["foo", instrumenter.id, payload]], notifier.starts
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index 54fd4345fb..d3c660a014 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -26,7 +26,7 @@ module Notifications
end
end
- class SubscribeEventObjects < TestCase
+ class SubscribeEventObjectsTest < TestCase
def test_subscribe_events
events = []
@notifier.subscribe do |event|
@@ -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
@@ -291,15 +310,23 @@ module Notifications
assert_in_delta 10.0, event.duration, 0.00001
end
+ def test_event_cpu_time_does_not_raise_error_when_start_or_finished_not_called
+ time = Time.now
+ event = event(:foo, time, time + 0.01, random_id, {})
+
+ assert_equal 0, event.cpu_time
+ end
+
+
def test_events_consumes_information_given_as_payload
- event = event(:foo, Time.now, Time.now + 1, random_id, payload: :bar)
+ event = event(:foo, Concurrent.monotonic_time, Concurrent.monotonic_time + 1, random_id, payload: :bar)
assert_equal Hash[payload: :bar], event.payload
end
def test_event_is_parent_based_on_children
- time = Time.utc(2009, 01, 01, 0, 0, 1)
+ time = Concurrent.monotonic_time
- parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, random_id, {})
+ parent = event(:foo, Concurrent.monotonic_time, Concurrent.monotonic_time + 100, random_id, {})
child = event(:foo, time, time + 10, random_id, {})
not_child = event(:foo, time, time + 100, random_id, {})
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 49a3951623..f475e05c9a 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -112,12 +112,52 @@ class SafeBufferTest < ActiveSupport::TestCase
end
end
+ test "can assign value into zero-index" do
+ buffer = ActiveSupport::SafeBuffer.new("012345")
+
+ buffer[0] = "<"
+
+ assert_equal "&lt;12345", buffer
+ end
+
+ test "can assign value into non zero-index" do
+ buffer = ActiveSupport::SafeBuffer.new("012345")
+
+ buffer[2] = "<"
+
+ assert_equal "01&lt;345", buffer
+ end
+
+ test "can assign value into slice" do
+ buffer = ActiveSupport::SafeBuffer.new("012345")
+
+ buffer[0, 3] = "<"
+
+ assert_equal "&lt;345", buffer
+ end
+
+ test "can assign value into offset slice" do
+ buffer = ActiveSupport::SafeBuffer.new("012345")
+
+ buffer[1, 3] = "<"
+
+ assert_equal "0&lt;45", buffer
+ end
+
test "Should escape dirty buffers on add" do
clean = "hello".html_safe
@buffer.gsub!("", "<>")
assert_equal "hello&lt;&gt;", clean + @buffer
end
+ test "Should preserve html_safe? status on multiplication" do
+ multiplied_safe_buffer = "<br />".html_safe * 2
+ assert_predicate multiplied_safe_buffer, :html_safe?
+
+ multiplied_unsafe_buffer = @buffer.gsub("", "<>") * 2
+ assert_not_predicate multiplied_unsafe_buffer, :html_safe?
+ end
+
test "Should concat as a normal string when safe" do
clean = "hello".html_safe
@buffer.gsub!("", "<>")
@@ -151,7 +191,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
@@ -216,4 +256,22 @@ class SafeBufferTest < ActiveSupport::TestCase
x = "Hello".html_safe
assert_nil x[/a/, 1]
end
+
+ test "Should set back references" do
+ a = "foo123".html_safe
+ a2 = a.sub(/([a-z]+)([0-9]+)/) { $2 + $1 }
+ assert_equal "123foo", a2
+ assert_not_predicate a2, :html_safe?
+ a.sub!(/([a-z]+)([0-9]+)/) { $2 + $1 }
+ assert_equal "123foo", a
+ assert_not_predicate a, :html_safe?
+
+ b = "foo123 bar456".html_safe
+ b2 = b.gsub(/([a-z]+)([0-9]+)/) { $2 + $1 }
+ assert_equal "123foo 456bar", b2
+ assert_not_predicate b2, :html_safe?
+ b.gsub!(/([a-z]+)([0-9]+)/) { $2 + $1 }
+ assert_equal "123foo 456bar", b
+ assert_not_predicate b, :html_safe?
+ end
end
diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb
index 34479020e1..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
@@ -115,68 +115,66 @@ class ShareLockTest < ActiveSupport::TestCase
def test_exclusive_conflicting_purpose
[true, false].each do |use_upgrading|
with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
- begin
- together = Concurrent::CyclicBarrier.new(2)
- conflicting_exclusive_threads = [
- Thread.new do
- @lock.send(use_upgrading ? :sharing : :tap) do
- together.wait
- @lock.exclusive(purpose: :red, compatible: [:green, :purple]) { }
- end
- end,
- Thread.new do
- @lock.send(use_upgrading ? :sharing : :tap) do
- together.wait
- @lock.exclusive(purpose: :blue, compatible: [:green]) { }
- end
+ together = Concurrent::CyclicBarrier.new(2)
+ conflicting_exclusive_threads = [
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ together.wait
+ @lock.exclusive(purpose: :red, compatible: [:green, :purple]) { }
+ end
+ end,
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ together.wait
+ @lock.exclusive(purpose: :blue, compatible: [:green]) { }
end
- ]
-
- assert_threads_stuck conflicting_exclusive_threads # wait for threads to get into their respective `exclusive {}` blocks
-
- # This thread will be stuck as long as any other thread is in
- # a sharing block. While it's blocked, it holds no lock, so it
- # doesn't interfere with any other attempts.
- no_purpose_thread = Thread.new do
- @lock.exclusive { }
end
- assert_threads_stuck no_purpose_thread
+ ]
- # This thread is compatible with both of the "primary"
- # attempts above. It's initially stuck on the outer share
- # lock, but as soon as that's released, it can run --
- # regardless of whether those threads hold share locks.
- compatible_thread = Thread.new do
- @lock.exclusive(purpose: :green, compatible: []) { }
- end
- assert_threads_stuck compatible_thread
+ assert_threads_stuck conflicting_exclusive_threads # wait for threads to get into their respective `exclusive {}` blocks
- assert_threads_stuck conflicting_exclusive_threads
+ # This thread will be stuck as long as any other thread is in
+ # a sharing block. While it's blocked, it holds no lock, so it
+ # doesn't interfere with any other attempts.
+ no_purpose_thread = Thread.new do
+ @lock.exclusive { }
+ end
+ assert_threads_stuck no_purpose_thread
+
+ # This thread is compatible with both of the "primary"
+ # attempts above. It's initially stuck on the outer share
+ # lock, but as soon as that's released, it can run --
+ # regardless of whether those threads hold share locks.
+ compatible_thread = Thread.new do
+ @lock.exclusive(purpose: :green, compatible: []) { }
+ end
+ assert_threads_stuck compatible_thread
- sharing_thread_release_latch.count_down
+ assert_threads_stuck conflicting_exclusive_threads
- assert_threads_not_stuck compatible_thread # compatible thread is now able to squeak through
+ sharing_thread_release_latch.count_down
- if use_upgrading
- # The "primary" threads both each hold a share lock, and are
- # mutually incompatible; they're still stuck.
- assert_threads_stuck conflicting_exclusive_threads
+ assert_threads_not_stuck compatible_thread # compatible thread is now able to squeak through
- # The thread without a specified purpose is also stuck; it's
- # not compatible with anything.
- assert_threads_stuck no_purpose_thread
- else
- # As the primaries didn't hold a share lock, as soon as the
- # outer one was released, all the exclusive locks are free
- # to be acquired in turn.
+ if use_upgrading
+ # The "primary" threads both each hold a share lock, and are
+ # mutually incompatible; they're still stuck.
+ assert_threads_stuck conflicting_exclusive_threads
- assert_threads_not_stuck conflicting_exclusive_threads
- assert_threads_not_stuck no_purpose_thread
- end
- ensure
- conflicting_exclusive_threads.each(&:kill)
- no_purpose_thread.kill
+ # The thread without a specified purpose is also stuck; it's
+ # not compatible with anything.
+ assert_threads_stuck no_purpose_thread
+ else
+ # As the primaries didn't hold a share lock, as soon as the
+ # outer one was released, all the exclusive locks are free
+ # to be acquired in turn.
+
+ assert_threads_not_stuck conflicting_exclusive_threads
+ assert_threads_not_stuck no_purpose_thread
end
+ ensure
+ conflicting_exclusive_threads.each(&:kill)
+ no_purpose_thread.kill
end
end
end
diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb
index 6b012e43af..bc8d8f1c13 100644
--- a/activesupport/test/subscriber_test.rb
+++ b/activesupport/test/subscriber_test.rb
@@ -23,6 +23,21 @@ class TestSubscriber < ActiveSupport::Subscriber
end
end
+class TestSubscriber2 < ActiveSupport::Subscriber
+ attach_to :doodle
+ detach_from :doodle
+
+ cattr_reader :events
+
+ def self.clear
+ @@events = []
+ end
+
+ def open_party(event)
+ events << event
+ end
+end
+
# Monkey patch subscriber to test that only one subscriber per method is added.
class TestSubscriber
remove_method :open_party
@@ -34,6 +49,7 @@ end
class SubscriberTest < ActiveSupport::TestCase
def setup
TestSubscriber.clear
+ TestSubscriber2.clear
end
def test_attaches_subscribers
@@ -53,4 +69,11 @@ class SubscriberTest < ActiveSupport::TestCase
assert_equal [], TestSubscriber.events
end
+
+ def test_detaches_subscribers
+ ActiveSupport::Notifications.instrument("open_party.doodle")
+
+ assert_equal [], TestSubscriber2.events
+ assert_equal 1, TestSubscriber.events.size
+ end
end
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 8698c66e6d..56cd2665e0 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -104,7 +104,7 @@ class AssertionsTest < ActiveSupport::TestCase
def test_expression_is_evaluated_in_the_appropriate_scope
silence_warnings do
local_scope = "foo"
- local_scope = local_scope # to suppress unused variable warning
+ _ = local_scope # to suppress unused variable warning
assert_difference("local_scope; @object.num") { @object.increment }
end
end
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index 8c47f2cdc7..a1f84bf69e 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -3,24 +3,25 @@
require "abstract_unit"
require "active_support/core_ext/date_time"
require "active_support/core_ext/numeric/time"
+require "time_zone_test_helpers"
class TimeTravelTest < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
class TimeSubclass < ::Time; end
class DateSubclass < ::Date; end
class DateTimeSubclass < ::DateTime; end
def test_time_helper_travel
Time.stub(:now, Time.now) do
- begin
- expected_time = Time.now + 1.day
- travel 1.day
+ expected_time = Time.now + 1.day
+ travel 1.day
- assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
- assert_equal expected_time.to_date, Date.today
- assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
- ensure
- travel_back
- end
+ assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ assert_equal expected_time.to_date, Date.today
+ assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
+ ensure
+ travel_back
end
end
@@ -42,16 +43,14 @@ class TimeTravelTest < ActiveSupport::TestCase
def test_time_helper_travel_to
Time.stub(:now, Time.now) do
- begin
- expected_time = Time.new(2004, 11, 24, 01, 04, 44)
- travel_to expected_time
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ travel_to expected_time
- assert_equal expected_time, Time.now
- assert_equal Date.new(2004, 11, 24), Date.today
- assert_equal expected_time.to_datetime, DateTime.now
- ensure
- travel_back
- end
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
+ ensure
+ travel_back
end
end
@@ -71,23 +70,35 @@ class TimeTravelTest < ActiveSupport::TestCase
end
end
+ def test_time_helper_travel_to_with_time_zone
+ with_env_tz "US/Eastern" do
+ with_tz_default ActiveSupport::TimeZone["UTC"] do
+ Time.stub(:now, Time.now) do
+ expected_time = 5.minutes.ago
+
+ travel_to 5.minutes.ago do
+ assert_equal expected_time.to_s(:db), Time.zone.now.to_s(:db)
+ end
+ end
+ end
+ end
+ end
+
def test_time_helper_travel_back
Time.stub(:now, Time.now) do
- begin
- expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
- travel_to expected_time
- assert_equal expected_time, Time.now
- assert_equal Date.new(2004, 11, 24), Date.today
- assert_equal expected_time.to_datetime, DateTime.now
- travel_back
+ travel_to expected_time
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
+ travel_back
- assert_not_equal expected_time, Time.now
- assert_not_equal Date.new(2004, 11, 24), Date.today
- assert_not_equal expected_time.to_datetime, DateTime.now
- ensure
- travel_back
- end
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ assert_not_equal expected_time.to_datetime, DateTime.now
+ ensure
+ travel_back
end
end
@@ -122,24 +133,22 @@ class TimeTravelTest < ActiveSupport::TestCase
def test_time_helper_travel_to_with_subsequent_calls
Time.stub(:now, Time.now) do
- begin
- initial_expected_time = Time.new(2004, 11, 24, 01, 04, 44)
- subsequent_expected_time = Time.new(2004, 10, 24, 01, 04, 44)
- assert_nothing_raised do
- travel_to initial_expected_time
- travel_to subsequent_expected_time
+ initial_expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ subsequent_expected_time = Time.new(2004, 10, 24, 01, 04, 44)
+ assert_nothing_raised do
+ travel_to initial_expected_time
+ travel_to subsequent_expected_time
- assert_equal subsequent_expected_time, Time.now
+ assert_equal subsequent_expected_time, Time.now
- travel_back
- end
- ensure
travel_back
end
+ ensure
+ travel_back
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))
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index 7d19447598..9e29a93ea0 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -30,6 +30,12 @@ class TransliterateTest < ActiveSupport::TestCase
I18n.locale = default_locale
end
+ def test_transliterate_respects_the_locale_argument
+ char = [117, 776].pack("U*") # "รผ" as ASCII "u" plus COMBINING DIAERESIS
+ I18n.backend.store_translations(:de, i18n: { transliterate: { rule: { "รผ" => "ue" } } })
+ assert_equal "ue", ActiveSupport::Inflector.transliterate(char, locale: :de)
+ end
+
def test_transliterate_should_allow_a_custom_replacement_char
assert_equal "a*b", ActiveSupport::Inflector.transliterate("a็ดขb", "*")
end