activesupport 7.1.3.2 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1126
  3. data/lib/active_support/array_inquirer.rb +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +15 -3
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +19 -18
  8. data/lib/active_support/cache/file_store.rb +27 -12
  9. data/lib/active_support/cache/mem_cache_store.rb +16 -74
  10. data/lib/active_support/cache/memory_store.rb +8 -3
  11. data/lib/active_support/cache/redis_cache_store.rb +21 -15
  12. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  13. data/lib/active_support/cache.rb +76 -78
  14. data/lib/active_support/callbacks.rb +79 -116
  15. data/lib/active_support/class_attribute.rb +33 -0
  16. data/lib/active_support/code_generator.rb +24 -10
  17. data/lib/active_support/concurrency/share_lock.rb +0 -1
  18. data/lib/active_support/configuration_file.rb +15 -6
  19. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  20. data/lib/active_support/core_ext/benchmark.rb +6 -9
  21. data/lib/active_support/core_ext/class/attribute.rb +24 -20
  22. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  23. data/lib/active_support/core_ext/date/blank.rb +4 -0
  24. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  25. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  26. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  27. data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
  28. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  29. data/lib/active_support/core_ext/enumerable.rb +8 -3
  30. data/lib/active_support/core_ext/erb/util.rb +7 -2
  31. data/lib/active_support/core_ext/hash/except.rb +0 -12
  32. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  33. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  34. data/lib/active_support/core_ext/module/delegation.rb +20 -148
  35. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  36. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  37. data/lib/active_support/core_ext/object/blank.rb +45 -1
  38. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  39. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  40. data/lib/active_support/core_ext/object/json.rb +21 -13
  41. data/lib/active_support/core_ext/object/with.rb +5 -3
  42. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  43. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  44. data/lib/active_support/core_ext/securerandom.rb +4 -4
  45. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  46. data/lib/active_support/core_ext/string/filters.rb +1 -1
  47. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  48. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  49. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  50. data/lib/active_support/core_ext/time/calculations.rb +32 -30
  51. data/lib/active_support/core_ext/time/compatibility.rb +24 -0
  52. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  53. data/lib/active_support/core_ext/time/zones.rb +1 -1
  54. data/lib/active_support/core_ext.rb +0 -1
  55. data/lib/active_support/current_attributes.rb +38 -40
  56. data/lib/active_support/delegation.rb +200 -0
  57. data/lib/active_support/dependencies/autoload.rb +0 -12
  58. data/lib/active_support/dependencies.rb +0 -1
  59. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  60. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  61. data/lib/active_support/deprecation/reporting.rb +3 -17
  62. data/lib/active_support/deprecation.rb +8 -5
  63. data/lib/active_support/descendants_tracker.rb +9 -87
  64. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  65. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  66. data/lib/active_support/duration.rb +25 -16
  67. data/lib/active_support/encrypted_configuration.rb +20 -2
  68. data/lib/active_support/encrypted_file.rb +1 -1
  69. data/lib/active_support/error_reporter.rb +65 -3
  70. data/lib/active_support/evented_file_update_checker.rb +0 -2
  71. data/lib/active_support/execution_wrapper.rb +0 -1
  72. data/lib/active_support/file_update_checker.rb +1 -1
  73. data/lib/active_support/fork_tracker.rb +2 -38
  74. data/lib/active_support/gem_version.rb +4 -4
  75. data/lib/active_support/hash_with_indifferent_access.rb +21 -23
  76. data/lib/active_support/html_safe_translation.rb +7 -4
  77. data/lib/active_support/i18n_railtie.rb +19 -11
  78. data/lib/active_support/isolated_execution_state.rb +0 -2
  79. data/lib/active_support/json/encoding.rb +3 -3
  80. data/lib/active_support/log_subscriber.rb +1 -12
  81. data/lib/active_support/logger.rb +15 -2
  82. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  83. data/lib/active_support/message_pack/extensions.rb +15 -2
  84. data/lib/active_support/message_verifier.rb +12 -0
  85. data/lib/active_support/messages/codec.rb +1 -1
  86. data/lib/active_support/multibyte/chars.rb +2 -2
  87. data/lib/active_support/notifications/fanout.rb +4 -8
  88. data/lib/active_support/notifications/instrumenter.rb +32 -21
  89. data/lib/active_support/notifications.rb +28 -27
  90. data/lib/active_support/number_helper/number_converter.rb +2 -2
  91. data/lib/active_support/number_helper.rb +22 -0
  92. data/lib/active_support/option_merger.rb +2 -2
  93. data/lib/active_support/ordered_options.rb +53 -15
  94. data/lib/active_support/railtie.rb +8 -11
  95. data/lib/active_support/string_inquirer.rb +1 -1
  96. data/lib/active_support/subscriber.rb +1 -0
  97. data/lib/active_support/syntax_error_proxy.rb +1 -11
  98. data/lib/active_support/tagged_logging.rb +9 -1
  99. data/lib/active_support/test_case.rb +3 -1
  100. data/lib/active_support/testing/assertions.rb +79 -21
  101. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  102. data/lib/active_support/testing/deprecation.rb +5 -12
  103. data/lib/active_support/testing/isolation.rb +19 -9
  104. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  105. data/lib/active_support/testing/parallelization/server.rb +3 -0
  106. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  107. data/lib/active_support/testing/strict_warnings.rb +8 -4
  108. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  109. data/lib/active_support/testing/time_helpers.rb +4 -3
  110. data/lib/active_support/time_with_zone.rb +30 -17
  111. data/lib/active_support/values/time_zone.rb +25 -14
  112. data/lib/active_support/xml_mini.rb +11 -2
  113. data/lib/active_support.rb +12 -4
  114. metadata +68 -19
  115. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  116. data/lib/active_support/proxy_object.rb +0 -17
  117. data/lib/active_support/ruby_features.rb +0 -7
@@ -52,7 +52,7 @@ module ActiveSupport
52
52
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
53
53
  end
54
54
 
55
- @format_version = 6.1
55
+ @format_version = 7.0
56
56
 
57
57
  class << self
58
58
  attr_accessor :format_version
@@ -86,13 +86,7 @@ module ActiveSupport
86
86
  case store
87
87
  when Symbol
88
88
  options = parameters.extract_options!
89
- # clean this up once Ruby 2.7 support is dropped
90
- # see https://github.com/rails/rails/pull/41522#discussion_r581186602
91
- if options.empty?
92
- retrieve_store_class(store).new(*parameters)
93
- else
94
- retrieve_store_class(store).new(*parameters, **options)
95
- end
89
+ retrieve_store_class(store).new(*parameters, **options)
96
90
  when Array
97
91
  lookup_store(*store)
98
92
  when nil
@@ -166,7 +160,7 @@ module ActiveSupport
166
160
  # cache = ActiveSupport::Cache::MemoryStore.new
167
161
  #
168
162
  # cache.read('city') # => nil
169
- # cache.write('city', "Duckburgh")
163
+ # cache.write('city', "Duckburgh") # => true
170
164
  # cache.read('city') # => "Duckburgh"
171
165
  #
172
166
  # cache.write('not serializable', Proc.new {}) # => TypeError
@@ -206,24 +200,6 @@ module ActiveSupport
206
200
  def retrieve_pool_options(options)
207
201
  if options.key?(:pool)
208
202
  pool_options = options.delete(:pool)
209
- elsif options.key?(:pool_size) || options.key?(:pool_timeout)
210
- pool_options = {}
211
-
212
- if options.key?(:pool_size)
213
- ActiveSupport.deprecator.warn(<<~MSG)
214
- Using :pool_size is deprecated and will be removed in Rails 7.2.
215
- Use `pool: { size: #{options[:pool_size].inspect} }` instead.
216
- MSG
217
- pool_options[:size] = options.delete(:pool_size)
218
- end
219
-
220
- if options.key?(:pool_timeout)
221
- ActiveSupport.deprecator.warn(<<~MSG)
222
- Using :pool_timeout is deprecated and will be removed in Rails 7.2.
223
- Use `pool: { timeout: #{options[:pool_timeout].inspect} }` instead.
224
- MSG
225
- pool_options[:timeout] = options.delete(:pool_timeout)
226
- end
227
203
  else
228
204
  pool_options = true
229
205
  end
@@ -310,7 +286,7 @@ module ActiveSupport
310
286
  # <tt>coder: nil</tt> to avoid the overhead of safeguarding against
311
287
  # mutation.
312
288
  #
313
- # The +:coder+ option is mutally exclusive with the +:serializer+ and
289
+ # The +:coder+ option is mutually exclusive with the +:serializer+ and
314
290
  # +:compressor+ options. Specifying them together will raise an
315
291
  # +ArgumentError+.
316
292
  #
@@ -344,7 +320,7 @@ module ActiveSupport
344
320
 
345
321
  # Silences the logger within a block.
346
322
  def mute
347
- previous_silence, @silence = defined?(@silence) && @silence, true
323
+ previous_silence, @silence = @silence, true
348
324
  yield
349
325
  ensure
350
326
  @silence = previous_silence
@@ -411,31 +387,47 @@ module ActiveSupport
411
387
  # has elapsed.
412
388
  #
413
389
  # # Set all values to expire after one minute.
414
- # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
390
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
415
391
  #
416
- # cache.write('foo', 'original value')
392
+ # cache.write("foo", "original value")
417
393
  # val_1 = nil
418
394
  # val_2 = nil
419
- # sleep 60
395
+ # p cache.read("foo") # => "original value"
420
396
  #
421
- # Thread.new do
422
- # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
397
+ # sleep 1 # wait until the cache expires
398
+ #
399
+ # t1 = Thread.new do
400
+ # # fetch does the following:
401
+ # # 1. gets an recent expired entry
402
+ # # 2. extends the expiry by 2 seconds (race_condition_ttl)
403
+ # # 3. regenerates the new value
404
+ # val_1 = cache.fetch("foo", race_condition_ttl: 2) do
423
405
  # sleep 1
424
- # 'new value 1'
406
+ # "new value 1"
425
407
  # end
426
408
  # end
427
409
  #
428
- # Thread.new do
429
- # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
430
- # 'new value 2'
431
- # end
410
+ # # Wait until t1 extends the expiry of the entry
411
+ # # but before generating the new value
412
+ # sleep 0.1
413
+ #
414
+ # val_2 = cache.fetch("foo", race_condition_ttl: 2) do
415
+ # # This block won't be executed because t1 extended the expiry
416
+ # "new value 2"
432
417
  # end
433
418
  #
434
- # cache.fetch('foo') # => "original value"
435
- # sleep 10 # First thread extended the life of cache by another 10 seconds
436
- # cache.fetch('foo') # => "new value 1"
437
- # val_1 # => "new value 1"
438
- # val_2 # => "original value"
419
+ # t1.join
420
+ #
421
+ # p val_1 # => "new value 1"
422
+ # p val_2 # => "original value"
423
+ # p cache.fetch("foo") # => "new value 1"
424
+ #
425
+ # # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
426
+ # # We have waited 2 seconds already (sleep(1) + t1.join) thus we need to wait 1
427
+ # # more second to see the entry expire.
428
+ # sleep 1
429
+ #
430
+ # p cache.fetch("foo") # => nil
439
431
  #
440
432
  # ==== Dynamic Options
441
433
  #
@@ -456,7 +448,7 @@ module ActiveSupport
456
448
 
457
449
  entry = nil
458
450
  unless options[:force]
459
- instrument(:read, name, options) do |payload|
451
+ instrument(:read, key, options) do |payload|
460
452
  cached_entry = read_entry(key, **options, event: payload)
461
453
  entry = handle_expired_entry(cached_entry, key, options)
462
454
  if entry
@@ -478,7 +470,7 @@ module ActiveSupport
478
470
  if entry
479
471
  get_entry_value(entry, name, options)
480
472
  else
481
- save_block_result_to_cache(name, options, &block)
473
+ save_block_result_to_cache(name, key, options, &block)
482
474
  end
483
475
  elsif options && options[:force]
484
476
  raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -508,7 +500,7 @@ module ActiveSupport
508
500
  key = normalize_key(name, options)
509
501
  version = normalize_version(name, options)
510
502
 
511
- instrument(:read, name, options) do |payload|
503
+ instrument(:read, key, options) do |payload|
512
504
  entry = read_entry(key, **options, event: payload)
513
505
 
514
506
  if entry
@@ -546,10 +538,11 @@ module ActiveSupport
546
538
 
547
539
  options = names.extract_options!
548
540
  options = merged_options(options)
541
+ keys = names.map { |name| normalize_key(name, options) }
549
542
 
550
- instrument_multi :read_multi, names, options do |payload|
543
+ instrument_multi :read_multi, keys, options do |payload|
551
544
  read_multi_entries(names, **options, event: payload).tap do |results|
552
- payload[:hits] = results.keys
545
+ payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
553
546
  end
554
547
  end
555
548
  end
@@ -559,8 +552,9 @@ module ActiveSupport
559
552
  return hash if hash.empty?
560
553
 
561
554
  options = merged_options(options)
555
+ normalized_hash = hash.transform_keys { |key| normalize_key(key, options) }
562
556
 
563
- instrument_multi :write_multi, hash, options do |payload|
557
+ instrument_multi :write_multi, normalized_hash, options do |payload|
564
558
  entries = hash.each_with_object({}) do |(name, value), memo|
565
559
  memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
566
560
  end
@@ -604,32 +598,37 @@ module ActiveSupport
604
598
 
605
599
  options = names.extract_options!
606
600
  options = merged_options(options)
607
-
608
- instrument_multi :read_multi, names, options do |payload|
601
+ keys = names.map { |name| normalize_key(name, options) }
602
+ writes = {}
603
+ ordered = instrument_multi :read_multi, keys, options do |payload|
609
604
  if options[:force]
610
605
  reads = {}
611
606
  else
612
607
  reads = read_multi_entries(names, **options)
613
608
  end
614
609
 
615
- writes = {}
616
610
  ordered = names.index_with do |name|
617
611
  reads.fetch(name) { writes[name] = yield(name) }
618
612
  end
619
613
  writes.compact! if options[:skip_nil]
620
614
 
621
- payload[:hits] = reads.keys
615
+ payload[:hits] = reads.keys.map { |name| normalize_key(name, options) }
622
616
  payload[:super_operation] = :fetch_multi
623
617
 
624
- write_multi(writes, options)
625
-
626
618
  ordered
627
619
  end
620
+
621
+ write_multi(writes, options)
622
+
623
+ ordered
628
624
  end
629
625
 
630
626
  # Writes the value to the cache with the key. The value must be supported
631
627
  # by the +coder+'s +dump+ and +load+ methods.
632
628
  #
629
+ # Returns +true+ if the write succeeded, +nil+ if there was an error talking
630
+ # to the cache backend, or +false+ if the write failed for another reason.
631
+ #
633
632
  # By default, cache entries larger than 1kB are compressed. Compression
634
633
  # allows more data to be stored in the same memory footprint, leading to
635
634
  # fewer cache evictions and higher hit rates.
@@ -662,10 +661,11 @@ module ActiveSupport
662
661
  # Other options will be handled by the specific cache store implementation.
663
662
  def write(name, value, options = nil)
664
663
  options = merged_options(options)
664
+ key = normalize_key(name, options)
665
665
 
666
- instrument(:write, name, options) do
666
+ instrument(:write, key, options) do
667
667
  entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
668
- write_entry(normalize_key(name, options), entry, **options)
668
+ write_entry(key, entry, **options)
669
669
  end
670
670
  end
671
671
 
@@ -675,9 +675,10 @@ module ActiveSupport
675
675
  # Options are passed to the underlying cache implementation.
676
676
  def delete(name, options = nil)
677
677
  options = merged_options(options)
678
+ key = normalize_key(name, options)
678
679
 
679
- instrument(:delete, name) do
680
- delete_entry(normalize_key(name, options), **options)
680
+ instrument(:delete, key, options) do
681
+ delete_entry(key, **options)
681
682
  end
682
683
  end
683
684
 
@@ -691,7 +692,7 @@ module ActiveSupport
691
692
  options = merged_options(options)
692
693
  names.map! { |key| normalize_key(key, options) }
693
694
 
694
- instrument_multi :delete_multi, names do
695
+ instrument_multi(:delete_multi, names, options) do
695
696
  delete_multi_entries(names, **options)
696
697
  end
697
698
  end
@@ -701,9 +702,10 @@ module ActiveSupport
701
702
  # Options are passed to the underlying cache implementation.
702
703
  def exist?(name, options = nil)
703
704
  options = merged_options(options)
705
+ key = normalize_key(name, options)
704
706
 
705
- instrument(:exist?, name) do |payload|
706
- entry = read_entry(normalize_key(name, options), **options, event: payload)
707
+ instrument(:exist?, key) do |payload|
708
+ entry = read_entry(key, **options, event: payload)
707
709
  (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
708
710
  end
709
711
  end
@@ -761,14 +763,6 @@ module ActiveSupport
761
763
  private
762
764
  def default_serializer
763
765
  case Cache.format_version
764
- when 6.1
765
- ActiveSupport.deprecator.warn <<~EOM
766
- Support for `config.active_support.cache_format_version = 6.1` has been deprecated and will be removed in Rails 7.2.
767
-
768
- Check the Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
769
- for more information on how to upgrade.
770
- EOM
771
- Cache::SerializerWithFallback[:marshal_6_1]
772
766
  when 7.0
773
767
  Cache::SerializerWithFallback[:marshal_7_0]
774
768
  when 7.1
@@ -951,9 +945,12 @@ module ActiveSupport
951
945
  #
952
946
  # namespace_key 'foo', namespace: -> { 'cache' }
953
947
  # # => 'cache:foo'
954
- def namespace_key(key, options = nil)
955
- options = merged_options(options)
956
- namespace = options[:namespace]
948
+ def namespace_key(key, call_options = nil)
949
+ namespace = if call_options&.key?(:namespace)
950
+ call_options[:namespace]
951
+ else
952
+ options[:namespace]
953
+ end
957
954
 
958
955
  if namespace.respond_to?(:call)
959
956
  namespace = namespace.call
@@ -1016,7 +1013,7 @@ module ActiveSupport
1016
1013
  if multi
1017
1014
  ": #{payload[:key].size} key(s) specified"
1018
1015
  elsif payload[:key]
1019
- ": #{normalize_key(payload[:key], options)}"
1016
+ ": #{payload[:key]}"
1020
1017
  end
1021
1018
 
1022
1019
  debug_options = " (#{options.inspect})" unless options.blank?
@@ -1038,7 +1035,8 @@ module ActiveSupport
1038
1035
  # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
1039
1036
  # for a brief period while the entry is being recalculated.
1040
1037
  entry.expires_at = Time.now.to_f + race_ttl
1041
- write_entry(key, entry, expires_in: race_ttl * 2)
1038
+ options[:expires_in] = race_ttl * 2
1039
+ write_entry(key, entry, **options)
1042
1040
  else
1043
1041
  delete_entry(key, **options)
1044
1042
  end
@@ -1052,10 +1050,10 @@ module ActiveSupport
1052
1050
  entry.value
1053
1051
  end
1054
1052
 
1055
- def save_block_result_to_cache(name, options)
1053
+ def save_block_result_to_cache(name, key, options)
1056
1054
  options = options.dup
1057
1055
 
1058
- result = instrument(:generate, name, options) do
1056
+ result = instrument(:generate, key, options) do
1059
1057
  yield(name, WriteOptions.new(options))
1060
1058
  end
1061
1059
 
@@ -6,7 +6,6 @@ require "active_support/core_ext/array/extract_options"
6
6
  require "active_support/core_ext/class/attribute"
7
7
  require "active_support/core_ext/string/filters"
8
8
  require "active_support/core_ext/object/blank"
9
- require "thread"
10
9
 
11
10
  module ActiveSupport
12
11
  # = Active Support \Callbacks
@@ -67,7 +66,7 @@ module ActiveSupport
67
66
 
68
67
  included do
69
68
  extend ActiveSupport::DescendantsTracker
70
- class_attribute :__callbacks, instance_writer: false, default: {}
69
+ class_attribute :__callbacks, instance_writer: false, instance_predicate: false, default: {}
71
70
  end
72
71
 
73
72
  CALLBACK_FILTER_TYPES = [:before, :after, :around].freeze
@@ -150,7 +149,7 @@ module ActiveSupport
150
149
  def halted_callback_hook(filter, name)
151
150
  end
152
151
 
153
- module Conditionals # :nodoc:
152
+ module Conditionals # :nodoc: all
154
153
  class Value
155
154
  def initialize(&block)
156
155
  @block = block
@@ -159,128 +158,76 @@ module ActiveSupport
159
158
  end
160
159
  end
161
160
 
162
- module Filters
161
+ module Filters # :nodoc: all
163
162
  Environment = Struct.new(:target, :halted, :value)
164
163
 
165
164
  class Before
166
- def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter, name)
165
+ def initialize(user_callback, user_conditions, chain_config, filter, name)
167
166
  halted_lambda = chain_config[:terminator]
168
-
169
- if user_conditions.any?
170
- halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
171
- else
172
- halting(callback_sequence, user_callback, halted_lambda, filter, name)
173
- end
167
+ @user_callback, @user_conditions, @halted_lambda, @filter, @name = user_callback, user_conditions, halted_lambda, filter, name
168
+ freeze
174
169
  end
170
+ attr_reader :user_callback, :user_conditions, :halted_lambda, :filter, :name
175
171
 
176
- def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
177
- callback_sequence.before do |env|
178
- target = env.target
179
- value = env.value
180
- halted = env.halted
172
+ def call(env)
173
+ target = env.target
174
+ value = env.value
175
+ halted = env.halted
181
176
 
182
- if !halted && user_conditions.all? { |c| c.call(target, value) }
183
- result_lambda = -> { user_callback.call target, value }
184
- env.halted = halted_lambda.call(target, result_lambda)
185
- if env.halted
186
- target.send :halted_callback_hook, filter, name
187
- end
177
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
178
+ result_lambda = -> { user_callback.call target, value }
179
+ env.halted = halted_lambda.call(target, result_lambda)
180
+ if env.halted
181
+ target.send :halted_callback_hook, filter, name
188
182
  end
189
-
190
- env
191
183
  end
192
- end
193
- private_class_method :halting_and_conditional
194
-
195
- def self.halting(callback_sequence, user_callback, halted_lambda, filter, name)
196
- callback_sequence.before do |env|
197
- target = env.target
198
- value = env.value
199
- halted = env.halted
200
184
 
201
- unless halted
202
- result_lambda = -> { user_callback.call target, value }
203
- env.halted = halted_lambda.call(target, result_lambda)
204
- if env.halted
205
- target.send :halted_callback_hook, filter, name
206
- end
207
- end
185
+ env
186
+ end
208
187
 
209
- env
210
- end
188
+ def apply(callback_sequence)
189
+ callback_sequence.before(self)
211
190
  end
212
- private_class_method :halting
213
191
  end
214
192
 
215
193
  class After
216
- def self.build(callback_sequence, user_callback, user_conditions, chain_config)
217
- if chain_config[:skip_after_callbacks_if_terminated]
218
- if user_conditions.any?
219
- halting_and_conditional(callback_sequence, user_callback, user_conditions)
220
- else
221
- halting(callback_sequence, user_callback)
222
- end
223
- else
224
- if user_conditions.any?
225
- conditional callback_sequence, user_callback, user_conditions
226
- else
227
- simple callback_sequence, user_callback
228
- end
229
- end
194
+ attr_reader :user_callback, :user_conditions, :halting
195
+ def initialize(user_callback, user_conditions, chain_config)
196
+ halting = chain_config[:skip_after_callbacks_if_terminated]
197
+ @user_callback, @user_conditions, @halting = user_callback, user_conditions, halting
198
+ freeze
230
199
  end
231
200
 
232
- def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
233
- callback_sequence.after do |env|
234
- target = env.target
235
- value = env.value
236
- halted = env.halted
237
-
238
- if !halted && user_conditions.all? { |c| c.call(target, value) }
239
- user_callback.call target, value
240
- end
201
+ def call(env)
202
+ target = env.target
203
+ value = env.value
204
+ halted = env.halted
241
205
 
242
- env
206
+ if (!halted || !@halting) && user_conditions.all? { |c| c.call(target, value) }
207
+ user_callback.call target, value
243
208
  end
244
- end
245
- private_class_method :halting_and_conditional
246
-
247
- def self.halting(callback_sequence, user_callback)
248
- callback_sequence.after do |env|
249
- unless env.halted
250
- user_callback.call env.target, env.value
251
- end
252
209
 
253
- env
254
- end
210
+ env
255
211
  end
256
- private_class_method :halting
257
-
258
- def self.conditional(callback_sequence, user_callback, user_conditions)
259
- callback_sequence.after do |env|
260
- target = env.target
261
- value = env.value
262
212
 
263
- if user_conditions.all? { |c| c.call(target, value) }
264
- user_callback.call target, value
265
- end
266
-
267
- env
268
- end
213
+ def apply(callback_sequence)
214
+ callback_sequence.after(self)
269
215
  end
270
- private_class_method :conditional
216
+ end
271
217
 
272
- def self.simple(callback_sequence, user_callback)
273
- callback_sequence.after do |env|
274
- user_callback.call env.target, env.value
218
+ class Around
219
+ def initialize(user_callback, user_conditions)
220
+ @user_callback, @user_conditions = user_callback, user_conditions
221
+ freeze
222
+ end
275
223
 
276
- env
277
- end
224
+ def apply(callback_sequence)
225
+ callback_sequence.around(@user_callback, @user_conditions)
278
226
  end
279
- private_class_method :simple
280
227
  end
281
228
  end
282
229
 
283
- class Callback # :nodoc:#
230
+ class Callback # :nodoc:
284
231
  def self.build(chain, filter, kind, options)
285
232
  if filter.is_a?(String)
286
233
  raise ArgumentError, <<-MSG.squish
@@ -302,6 +249,8 @@ module ActiveSupport
302
249
  @filter = filter
303
250
  @if = check_conditionals(options[:if])
304
251
  @unless = check_conditionals(options[:unless])
252
+
253
+ compiled
305
254
  end
306
255
 
307
256
  def merge_conditional_options(chain, if_option:, unless_option:)
@@ -329,19 +278,26 @@ module ActiveSupport
329
278
  end
330
279
  end
331
280
 
281
+ def compiled
282
+ @compiled ||=
283
+ begin
284
+ user_conditions = conditions_lambdas
285
+ user_callback = CallTemplate.build(@filter, self)
286
+
287
+ case kind
288
+ when :before
289
+ Filters::Before.new(user_callback.make_lambda, user_conditions, chain_config, @filter, name)
290
+ when :after
291
+ Filters::After.new(user_callback.make_lambda, user_conditions, chain_config)
292
+ when :around
293
+ Filters::Around.new(user_callback, user_conditions)
294
+ end
295
+ end
296
+ end
297
+
332
298
  # Wraps code with filter
333
299
  def apply(callback_sequence)
334
- user_conditions = conditions_lambdas
335
- user_callback = CallTemplate.build(@filter, self)
336
-
337
- case kind
338
- when :before
339
- Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter, name)
340
- when :after
341
- Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config)
342
- when :around
343
- callback_sequence.around(user_callback, user_conditions)
344
- end
300
+ compiled.apply(callback_sequence)
345
301
  end
346
302
 
347
303
  def current_scopes
@@ -368,14 +324,16 @@ module ActiveSupport
368
324
  end
369
325
 
370
326
  def conditions_lambdas
371
- @if.map { |c| CallTemplate.build(c, self).make_lambda } +
327
+ conditions =
328
+ @if.map { |c| CallTemplate.build(c, self).make_lambda } +
372
329
  @unless.map { |c| CallTemplate.build(c, self).inverted_lambda }
330
+ conditions.empty? ? EMPTY_ARRAY : conditions
373
331
  end
374
332
  end
375
333
 
376
334
  # A future invocation of user-supplied code (either as a callback,
377
335
  # or a condition filter).
378
- module CallTemplate # :nodoc:
336
+ module CallTemplate # :nodoc: all
379
337
  class MethodCall
380
338
  def initialize(method)
381
339
  @method_name = method
@@ -562,16 +520,18 @@ module ActiveSupport
562
520
  @call_template = call_template
563
521
  @user_conditions = user_conditions
564
522
 
565
- @before = []
566
- @after = []
523
+ @before = nil
524
+ @after = nil
567
525
  end
568
526
 
569
- def before(&before)
527
+ def before(before)
528
+ @before ||= []
570
529
  @before.unshift(before)
571
530
  self
572
531
  end
573
532
 
574
- def after(&after)
533
+ def after(after)
534
+ @after ||= []
575
535
  @after.push(after)
576
536
  self
577
537
  end
@@ -595,11 +555,11 @@ module ActiveSupport
595
555
  end
596
556
 
597
557
  def invoke_before(arg)
598
- @before.each { |b| b.call(arg) }
558
+ @before&.each { |b| b.call(arg) }
599
559
  end
600
560
 
601
561
  def invoke_after(arg)
602
- @after.each { |a| a.call(arg) }
562
+ @after&.each { |a| a.call(arg) }
603
563
  end
604
564
  end
605
565
 
@@ -973,7 +933,10 @@ module ActiveSupport
973
933
  end
974
934
 
975
935
  def set_callbacks(name, callbacks) # :nodoc:
976
- unless singleton_class.method_defined?(:__callbacks, false)
936
+ # HACK: We're making assumption on how `class_attribute` is implemented
937
+ # to save constantly duping the callback hash. If this desync with class_attribute
938
+ # we'll lose the optimization, but won't cause an actual behavior bug.
939
+ unless singleton_class.private_method_defined?(:__class_attr__callbacks, false)
977
940
  self.__callbacks = __callbacks.dup
978
941
  end
979
942
  self.__callbacks[name.to_sym] = callbacks
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module ClassAttribute # :nodoc:
5
+ class << self
6
+ def redefine(owner, name, namespaced_name, value)
7
+ if owner.singleton_class?
8
+ if owner.attached_object.is_a?(Module)
9
+ redefine_method(owner, namespaced_name, private: true) { value }
10
+ else
11
+ redefine_method(owner, name) { value }
12
+ end
13
+ end
14
+
15
+ redefine_method(owner.singleton_class, namespaced_name, private: true) { value }
16
+
17
+ redefine_method(owner.singleton_class, "#{namespaced_name}=", private: true) do |new_value|
18
+ if owner.equal?(self)
19
+ value = new_value
20
+ else
21
+ ::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, new_value)
22
+ end
23
+ end
24
+ end
25
+
26
+ def redefine_method(owner, name, private: false, &block)
27
+ owner.silence_redefinition_of_method(name)
28
+ owner.define_method(name, &block)
29
+ owner.send(:private, name) if private
30
+ end
31
+ end
32
+ end
33
+ end