diff options
Diffstat (limited to 'activesupport')
11 files changed, 178 insertions, 505 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 4c7b134c35..e494f66c5a 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,495 +1,23 @@ -* Introduce `ActiveSupport::ActionableError`. - - Actionable errors let's you dispatch actions from Rails' error pages. This - can help you save time if you have a clear action for the resolution of - common development errors. - - The de-facto example are pending migrations. Every time pending migrations - are found, a middleware raises an error. With actionable errors, you can - run the migrations right from the error page. Other examples include Rails - plugins that need to run a rake task to setup themselves. They can now - raise actionable errors to run the setup straight from the error pages. - - Here is how to define an actionable error: - - ```ruby - class PendingMigrationError < MigrationError #:nodoc: - include ActiveSupport::ActionableError - - action "Run pending migrations" do - ActiveRecord::Tasks::DatabaseTasks.migrate - end - end - ``` - - To make an error actionable, include the `ActiveSupport::ActionableError` - module and invoke the `action` class macro to define the action. An action - needs a name and a procedure to execute. The name is shown as the name of a - button on the error pages. Once clicked, it will invoke the given - procedure. - - *Vipul A M*, *Yao Jie*, *Genadi Samokovarov* - -* Preserve `html_safe?` status on `ActiveSupport::SafeBuffer#*`. - - Before: - - ("<br />".html_safe * 2).html_safe? #=> nil - - After: - - ("<br />".html_safe * 2).html_safe? #=> true - - *Ryo Nakamura* - -* Calling test methods with `with_info_handler` method to allow minitest-hooks - plugin to work. - - *Mauri Mustonen* - -* The Zeitwerk compatibility interface for `ActiveSupport::Dependencies` no - longer implements `autoloaded_constants` or `autoloaded?` (undocumented, - anyway). Experience shows introspection does not have many use cases, and - troubleshooting is done by logging. With this design trade-off we are able - to use even less memory in all environments. - - *Xavier Noria* - -* Depends on Zeitwerk 2, which stores less metadata if reloading is disabled - and hence uses less memory when `config.cache_classes` is `true`, a standard - setup in production. - - *Xavier Noria* - -* In `:zeitwerk` mode, eager load directories in engines and applications only - if present in their respective `config.eager_load_paths`. - - A common use case for this is adding `lib` to `config.autoload_paths`, but - not to `config.eager_load_paths`. In that configuration, for example, files - in the `lib` directory should not be eager loaded. - - *Xavier Noria* - -* Fix bug in Range comparisons when comparing to an excluded-end Range +* `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`. Before: - (1..10).cover?(1...11) # => false + 'foobar'.truncate(5).frozen? + # => true + 'foobar'.truncate(6).frozen? + # => false After: - (1..10).cover?(1...11) # => true - - With the same change for `Range#include?` and `Range#===`. - - *Owen Stephens* - -* Use weak references in descendants tracker to allow anonymous subclasses to - be garbage collected. - - *Edgars Beigarts* - -* Update `ActiveSupport::Notifications::Instrumenter#instrument` to make - passing a block optional. This will let users use - `ActiveSupport::Notifications` messaging features outside of - instrumentation. - - *Ali Ibrahim* - -* Fix `Time#advance` to work with dates before 1001-03-07 - - Before: - - Time.utc(1001, 3, 6).advance(years: -1) # => 1000-03-05 00:00:00 UTC - - After - - Time.utc(1001, 3, 6).advance(years: -1) # => 1000-03-06 00:00:00 UTC - - Note that this doesn't affect `DateTime#advance` as that doesn't use a proleptic calendar. - - *Andrew White* - -* In Zeitwerk mode, engines are now managed by the `main` autoloader. Engines may reference application constants, if the application is reloaded and we do not reload engines, they won't use the reloaded application code. - - *Xavier Noria* - -* Add support for supplying `locale` to `transliterate` and `parameterize`. - - I18n.backend.store_translations(:de, i18n: { transliterate: { rule: { "ü" => "ue" } } }) - - ActiveSupport::Inflector.transliterate("ü", locale: :de) # => "ue" - "Fünf autos".parameterize(locale: :de) # => "fuenf-autos" - ActiveSupport::Inflector.parameterize("Fünf autos", locale: :de) # => "fuenf-autos" - - *Kaan Ozkan*, *Sharang Dashputre* - -* Allow `Array#excluding` and `Enumerable#excluding` to deal with a passed array gracefully. - - [ 1, 2, 3, 4, 5 ].excluding([4, 5]) # => [ 1, 2, 3 ] - - *DHH* - -* Renamed `Array#without` and `Enumerable#without` to `Array#excluding` and `Enumerable#excluding`, to create parity with - `Array#including` and `Enumerable#including`. Retained the old names as aliases. - - *DHH* - -* Added `Array#including` and `Enumerable#including` to conveniently enlarge a collection with more members using a method rather than an operator: - - [ 1, 2, 3 ].including(4, 5) # => [ 1, 2, 3, 4, 5 ] - post.authors.including(Current.person) # => All the authors plus the current person! - - *DHH* - - -## Rails 6.0.0.beta3 (March 11, 2019) ## - -* No changes. - - -## Rails 6.0.0.beta2 (February 25, 2019) ## - -* New autoloading based on [Zeitwerk](https://github.com/fxn/zeitwerk). - - *Xavier Noria* - -* Revise `ActiveSupport::Notifications.unsubscribe` to correctly handle Regex or other multiple-pattern subscribers. - - *Zach Kemp* - -* Add `before_reset` callback to `CurrentAttributes` and define `after_reset` as an alias of `resets` for symmetry. - - *Rosa Gutierrez* - -* Remove the `` Kernel#` `` override that suppresses ENOENT and accidentally returns nil on Unix systems. - - *Akinori Musha* - -* Add `ActiveSupport::HashWithIndifferentAccess#assoc`. - - `assoc` can now be called with either a string or a symbol. - - *Stefan Schüßler* - -* Add `Hash#deep_transform_values`, and `Hash#deep_transform_values!`. - - *Guillermo Iguaran* - - -## Rails 6.0.0.beta1 (January 18, 2019) ## - -* Remove deprecated `Module#reachable?` method. - - *Rafael Mendonça França* - -* Remove deprecated `#acronym_regex` method from `Inflections`. - - *Rafael Mendonça França* - -* Fix `String#safe_constantize` throwing a `LoadError` for incorrectly cased constant references. - - *Keenan Brock* - -* Preserve key order passed to `ActiveSupport::CacheStore#fetch_multi`. - - `fetch_multi(*names)` now returns its results in the same order as the `*names` requested, rather than returning cache hits followed by cache misses. - - *Gannon McGibbon* - -* If the same block is `included` multiple times for a Concern, an exception is no longer raised. - - *Mark J. Titorenko*, *Vlad Bokov* - -* Fix bug where `#to_options` for `ActiveSupport::HashWithIndifferentAccess` - would not act as alias for `#symbolize_keys`. - - *Nick Weiland* - -* Improve the logic that detects non-autoloaded constants. - - *Jan Habermann*, *Xavier Noria* - -* 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.5.0 or newer. - - *Jeremy Daer*, *Kasper Timm Hansen* - -* Adds parallel testing to Rails. - - Parallelize your test suite with forked processes or threads. + 'foobar'.truncate(5).frozen? + # => false + 'foobar'.truncate(6).frozen? + # => false - *Eileen M. Uchitelle*, *Aaron Patterson* + *Jordan Thomas* -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/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 5652f2d1cc..9fe7f8fe01 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -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) 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/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/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index cf17922d7d..b1ac3e7969 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -8,9 +8,9 @@ module ActiveSupport module VERSION MAJOR = 6 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta3" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 42ae7e9b7b..5981763f0e 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -69,7 +69,7 @@ module ActiveSupport super() update(constructor) - hash = constructor.to_hash + hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash self.default = hash.default if hash.default self.default_proc = hash.default_proc if hash.default_proc else diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index d9e93b530c..555c0ad1d3 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -38,6 +38,19 @@ module ActiveSupport # payload # => Hash, the payload # end # + # Here, the +start+ and +finish+ values represent wall-clock time. If you are + # concerned about accuracy, you can register a monotonic subscriber. + # + # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload| + # name # => String, name of the event (such as 'render' from above) + # start # => Monotonic time, when the instrumented block started execution + # finish # => Monotonic time, when the instrumented block ended execution + # id # => String, unique ID for the instrumenter that fired the event + # payload # => Hash, the payload + # end + # + # The +start+ and +finish+ values above represent monotonic time. + # # For instance, let's store all "render" events in an array: # # events = [] @@ -135,6 +148,16 @@ module ActiveSupport # during the execution of the block. The callback is unsubscribed automatically # after that. # + # To record +started+ and +finished+ values with monotonic time, + # specify the optional <tt>:monotonic</tt> option to the + # <tt>subscribed</tt> method. The <tt>:monotonic</tt> option is set + # to +false+ by default. + # + # callback = lambda {|name, started, finished, unique_id, payload| ... } + # ActiveSupport::Notifications.subscribed(callback, "sql.active_record", monotonic: true) do + # ... + # end + # # === Manual Unsubscription # # The +subscribe+ method returns a subscriber object: @@ -209,11 +232,17 @@ module ActiveSupport # @event = event # end def subscribe(*args, &block) - notifier.subscribe(*args, &block) + pattern, callback = *args + notifier.subscribe(pattern, callback, false, &block) + end + + def monotonic_subscribe(*args, &block) + pattern, callback = *args + notifier.subscribe(pattern, callback, true, &block) end - def subscribed(callback, *args, &block) - subscriber = subscribe(*args, &callback) + def subscribed(callback, pattern, monotonic: false, &block) + subscriber = notifier.subscribe(pattern, callback, monotonic) yield ensure unsubscribe(subscriber) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 8812b67f63..c37bec4ee5 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -20,8 +20,8 @@ module ActiveSupport super end - def subscribe(pattern = nil, callable = nil, &block) - subscriber = Subscribers.new(pattern, callable || block) + def subscribe(pattern = nil, callable = nil, monotonic = false, &block) + subscriber = Subscribers.new(monotonic, pattern, callable || block) synchronize do if String === pattern @string_subscribers[pattern] << subscriber @@ -84,8 +84,8 @@ module ActiveSupport end module Subscribers # :nodoc: - def self.new(pattern, listener) - subscriber_class = Timed + def self.new(monotonic, pattern, listener) + subscriber_class = monotonic ? MonotonicTimed : Timed if listener.respond_to?(:start) && listener.respond_to?(:finish) subscriber_class = Evented @@ -180,12 +180,29 @@ module ActiveSupport def start(name, id, payload) timestack = Thread.current[:_timestack] ||= [] - timestack.push Concurrent.monotonic_time + timestack.push Time.now end def finish(name, id, payload) timestack = Thread.current[:_timestack] started = timestack.pop + @delegate.call(name, started, Time.now, id, payload) + end + end + + class MonotonicTimed < Evented # :nodoc: + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + timestack = Thread.current[:_timestack_monotonic] ||= [] + timestack.push Concurrent.monotonic_time + end + + def finish(name, id, payload) + timestack = Thread.current[:_timestack_monotonic] + started = timestack.pop @delegate.call(name, started, Concurrent.monotonic_time, id, payload) end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 4ffa33aa61..c5a000b67a 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -291,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/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb index 1d6a07ec5f..af96231801 100644 --- a/activesupport/test/hash_with_indifferent_access_test.rb +++ b/activesupport/test/hash_with_indifferent_access_test.rb @@ -836,4 +836,32 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase assert_equal 3, hash_wia[:foo] assert_equal 3, hash_wia[:bar] end + + def test_should_copy_the_default_when_converting_non_hash_to_hash_with_indifferent_access + non_hash = Object.new + + def non_hash.to_hash + h = { foo: :bar } + h.default = :baz + h + end + + hash_wia = HashWithIndifferentAccess.new(non_hash) + assert_equal :bar, hash_wia[:foo] + assert_equal :baz, hash_wia[:missing] + end + + def test_should_copy_the_default_proc_when_converting_non_hash_to_hash_with_indifferent_access + non_hash = Object.new + + def non_hash.to_hash + h = { foo: :bar } + h.default_proc = ->(hash, key) { hash[key] = :baz } + h + end + + hash_wia = HashWithIndifferentAccess.new(non_hash) + assert_equal :bar, hash_wia[:foo] + assert_equal :baz, hash_wia[:missing] + end end diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 0af59764b5..3b98749f1b 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -62,6 +62,38 @@ module Notifications end end + class TimedAndMonotonicTimedSubscriberTest < TestCase + def test_subscribe + event_name = "foo" + class_of_started = nil + class_of_finished = nil + + ActiveSupport::Notifications.subscribe(event_name) do |name, started, finished, unique_id, data| + class_of_started = started.class + class_of_finished = finished.class + end + + ActiveSupport::Notifications.instrument(event_name) + + assert_equal [Time, Time], [class_of_started, class_of_finished] + end + + def test_monotonic_subscribe + event_name = "foo" + class_of_started = nil + class_of_finished = nil + + ActiveSupport::Notifications.monotonic_subscribe(event_name) do |name, started, finished, unique_id, data| + class_of_started = started.class + class_of_finished = finished.class + end + + ActiveSupport::Notifications.instrument(event_name) + + assert_equal [Float, Float], [class_of_started, class_of_finished] + end + end + class SubscribedTest < TestCase def test_subscribed name = "foo" @@ -95,6 +127,42 @@ module Notifications ensure ActiveSupport::Notifications.notifier = old_notifier end + + def test_timed_subscribed + event_name = "foo" + class_of_started = nil + class_of_finished = nil + callback = lambda do |name, started, finished, unique_id, data| + class_of_started = started.class + class_of_finished = finished.class + end + + ActiveSupport::Notifications.subscribed(callback, event_name) do + ActiveSupport::Notifications.instrument(event_name) + end + + ActiveSupport::Notifications.instrument(event_name) + + assert_equal [Time, Time], [class_of_started, class_of_finished] + end + + def test_monotonic_timed_subscribed + event_name = "foo" + class_of_started = nil + class_of_finished = nil + callback = lambda do |name, started, finished, unique_id, data| + class_of_started = started.class + class_of_finished = finished.class + end + + ActiveSupport::Notifications.subscribed(callback, event_name, monotonic: true) do + ActiveSupport::Notifications.instrument(event_name) + end + + ActiveSupport::Notifications.instrument(event_name) + + assert_equal [Float, Float], [class_of_started, class_of_finished] + end end class UnsubscribeTest < TestCase @@ -302,7 +370,7 @@ module Notifications class EventTest < TestCase def test_events_are_initialized_with_details - time = Concurrent.monotonic_time + time = Time.now event = event(:foo, time, time + 0.01, random_id, {}) assert_equal :foo, event.name @@ -310,15 +378,13 @@ module Notifications assert_in_delta 10.0, event.duration, 0.00001 end - def test_event_cpu_time_and_idle_time_when_start_and_finish_is_not_called - time = Concurrent.monotonic_time + 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 - assert_in_delta 10.0, event.idle_time, 0.00001 end - def test_events_consumes_information_given_as_payload event = event(:foo, Concurrent.monotonic_time, Concurrent.monotonic_time + 1, random_id, payload: :bar) assert_equal Hash[payload: :bar], event.payload |