activesupport 6.0.4 → 6.1.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +388 -460
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/array_inquirer.rb +4 -2
  5. data/lib/active_support/backtrace_cleaner.rb +3 -3
  6. data/lib/active_support/benchmarkable.rb +1 -1
  7. data/lib/active_support/cache/file_store.rb +3 -3
  8. data/lib/active_support/cache/mem_cache_store.rb +28 -18
  9. data/lib/active_support/cache/memory_store.rb +46 -26
  10. data/lib/active_support/cache/redis_cache_store.rb +25 -25
  11. data/lib/active_support/cache/strategy/local_cache.rb +20 -5
  12. data/lib/active_support/cache.rb +87 -40
  13. data/lib/active_support/callbacks.rb +65 -56
  14. data/lib/active_support/concern.rb +46 -2
  15. data/lib/active_support/configurable.rb +3 -3
  16. data/lib/active_support/configuration_file.rb +51 -0
  17. data/lib/active_support/core_ext/benchmark.rb +2 -2
  18. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  19. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  20. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  21. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  22. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  23. data/lib/active_support/core_ext/enumerable.rb +76 -4
  24. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  25. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  26. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  27. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  28. data/lib/active_support/core_ext/load_error.rb +1 -1
  29. data/lib/active_support/core_ext/marshal.rb +2 -0
  30. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  31. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  32. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  33. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  34. data/lib/active_support/core_ext/module/delegation.rb +38 -28
  35. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  36. data/lib/active_support/core_ext/name_error.rb +29 -2
  37. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  38. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  39. data/lib/active_support/core_ext/object/json.rb +12 -1
  40. data/lib/active_support/core_ext/object/try.rb +2 -2
  41. data/lib/active_support/core_ext/range/compare_range.rb +9 -3
  42. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  43. data/lib/active_support/core_ext/regexp.rb +8 -1
  44. data/lib/active_support/core_ext/string/access.rb +5 -24
  45. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  46. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  47. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  48. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  49. data/lib/active_support/core_ext/string/output_safety.rb +3 -4
  50. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  51. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  52. data/lib/active_support/core_ext/symbol.rb +3 -0
  53. data/lib/active_support/core_ext/time/calculations.rb +17 -0
  54. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  55. data/lib/active_support/core_ext/uri.rb +5 -1
  56. data/lib/active_support/core_ext.rb +1 -1
  57. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  58. data/lib/active_support/current_attributes.rb +8 -2
  59. data/lib/active_support/dependencies.rb +37 -18
  60. data/lib/active_support/deprecation/behaviors.rb +15 -2
  61. data/lib/active_support/deprecation/disallowed.rb +56 -0
  62. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  63. data/lib/active_support/deprecation/method_wrappers.rb +3 -2
  64. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  65. data/lib/active_support/deprecation/reporting.rb +50 -7
  66. data/lib/active_support/deprecation.rb +6 -1
  67. data/lib/active_support/descendants_tracker.rb +6 -2
  68. data/lib/active_support/duration/iso8601_serializer.rb +15 -9
  69. data/lib/active_support/duration.rb +71 -22
  70. data/lib/active_support/encrypted_file.rb +19 -2
  71. data/lib/active_support/environment_inquirer.rb +20 -0
  72. data/lib/active_support/evented_file_update_checker.rb +69 -133
  73. data/lib/active_support/fork_tracker.rb +64 -0
  74. data/lib/active_support/gem_version.rb +1 -1
  75. data/lib/active_support/hash_with_indifferent_access.rb +48 -24
  76. data/lib/active_support/i18n_railtie.rb +14 -19
  77. data/lib/active_support/inflector/inflections.rb +1 -2
  78. data/lib/active_support/inflector/methods.rb +35 -31
  79. data/lib/active_support/inflector/transliterate.rb +4 -4
  80. data/lib/active_support/json/decoding.rb +4 -4
  81. data/lib/active_support/json/encoding.rb +5 -1
  82. data/lib/active_support/key_generator.rb +1 -1
  83. data/lib/active_support/locale/en.yml +7 -3
  84. data/lib/active_support/log_subscriber.rb +8 -0
  85. data/lib/active_support/logger.rb +1 -1
  86. data/lib/active_support/logger_silence.rb +2 -26
  87. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  88. data/lib/active_support/message_encryptor.rb +4 -7
  89. data/lib/active_support/message_verifier.rb +5 -5
  90. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  91. data/lib/active_support/messages/rotator.rb +6 -5
  92. data/lib/active_support/multibyte/chars.rb +4 -42
  93. data/lib/active_support/multibyte/unicode.rb +9 -83
  94. data/lib/active_support/notifications/fanout.rb +23 -8
  95. data/lib/active_support/notifications/instrumenter.rb +6 -15
  96. data/lib/active_support/notifications.rb +32 -5
  97. data/lib/active_support/number_helper/number_converter.rb +1 -1
  98. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  99. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  100. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  101. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  102. data/lib/active_support/number_helper.rb +29 -14
  103. data/lib/active_support/option_merger.rb +2 -1
  104. data/lib/active_support/ordered_options.rb +8 -2
  105. data/lib/active_support/parameter_filter.rb +16 -11
  106. data/lib/active_support/per_thread_registry.rb +1 -1
  107. data/lib/active_support/rails.rb +1 -4
  108. data/lib/active_support/railtie.rb +23 -1
  109. data/lib/active_support/rescuable.rb +4 -4
  110. data/lib/active_support/secure_compare_rotator.rb +51 -0
  111. data/lib/active_support/security_utils.rb +19 -12
  112. data/lib/active_support/string_inquirer.rb +4 -2
  113. data/lib/active_support/subscriber.rb +12 -7
  114. data/lib/active_support/tagged_logging.rb +29 -4
  115. data/lib/active_support/testing/assertions.rb +18 -11
  116. data/lib/active_support/testing/parallelization/server.rb +78 -0
  117. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  118. data/lib/active_support/testing/parallelization.rb +12 -95
  119. data/lib/active_support/testing/time_helpers.rb +40 -3
  120. data/lib/active_support/time_with_zone.rb +67 -43
  121. data/lib/active_support/values/time_zone.rb +20 -10
  122. data/lib/active_support/xml_mini/rexml.rb +8 -1
  123. data/lib/active_support.rb +13 -1
  124. metadata +33 -35
  125. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  126. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  127. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  128. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  129. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  130. data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -3,10 +3,12 @@
3
3
  require "zlib"
4
4
  require "active_support/core_ext/array/extract_options"
5
5
  require "active_support/core_ext/array/wrap"
6
+ require "active_support/core_ext/enumerable"
6
7
  require "active_support/core_ext/module/attribute_accessors"
7
8
  require "active_support/core_ext/numeric/bytes"
8
9
  require "active_support/core_ext/numeric/time"
9
10
  require "active_support/core_ext/object/to_param"
11
+ require "active_support/core_ext/object/try"
10
12
  require "active_support/core_ext/string/inflections"
11
13
 
12
14
  module ActiveSupport
@@ -20,7 +22,7 @@ module ActiveSupport
20
22
 
21
23
  # These options mean something to all cache implementations. Individual cache
22
24
  # implementations may support additional options.
23
- UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
25
+ UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl, :coder]
24
26
 
25
27
  module Strategy
26
28
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
@@ -56,7 +58,13 @@ module ActiveSupport
56
58
  case store
57
59
  when Symbol
58
60
  options = parameters.extract_options!
59
- retrieve_store_class(store).new(*parameters, **options)
61
+ # clean this up once Ruby 2.7 support is dropped
62
+ # see https://github.com/rails/rails/pull/41522#discussion_r581186602
63
+ if options.empty?
64
+ retrieve_store_class(store).new(*parameters)
65
+ else
66
+ retrieve_store_class(store).new(*parameters, **options)
67
+ end
60
68
  when Array
61
69
  lookup_store(*store)
62
70
  when nil
@@ -79,7 +87,7 @@ module ActiveSupport
79
87
  #
80
88
  # The +key+ argument can also respond to +cache_key+ or +to_param+.
81
89
  def expand_cache_key(key, namespace = nil)
82
- expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
90
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
83
91
 
84
92
  if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
85
93
  expanded_cache_key << "#{prefix}/"
@@ -156,6 +164,8 @@ module ActiveSupport
156
164
  # threshold is configurable with the <tt>:compress_threshold</tt> option,
157
165
  # specified in bytes.
158
166
  class Store
167
+ DEFAULT_CODER = Marshal
168
+
159
169
  cattr_accessor :logger, instance_writer: true
160
170
 
161
171
  attr_reader :silence, :options
@@ -183,6 +193,7 @@ module ActiveSupport
183
193
  # namespace for the cache.
184
194
  def initialize(options = nil)
185
195
  @options = options ? options.dup : {}
196
+ @coder = @options.delete(:coder) { self.class::DEFAULT_CODER } || NullCoder
186
197
  end
187
198
 
188
199
  # Silences the logger.
@@ -319,7 +330,7 @@ module ActiveSupport
319
330
 
320
331
  entry = nil
321
332
  instrument(:read, name, options) do |payload|
322
- cached_entry = read_entry(key, **options) unless options[:force]
333
+ cached_entry = read_entry(key, **options, event: payload) unless options[:force]
323
334
  entry = handle_expired_entry(cached_entry, key, options)
324
335
  entry = nil if entry && entry.mismatched?(normalize_version(name, options))
325
336
  payload[:super_operation] = :fetch if payload
@@ -353,7 +364,7 @@ module ActiveSupport
353
364
  version = normalize_version(name, options)
354
365
 
355
366
  instrument(:read, name, options) do |payload|
356
- entry = read_entry(key, **options)
367
+ entry = read_entry(key, **options, event: payload)
357
368
 
358
369
  if entry
359
370
  if entry.expired?
@@ -385,7 +396,7 @@ module ActiveSupport
385
396
  options = merged_options(options)
386
397
 
387
398
  instrument :read_multi, names, options do |payload|
388
- read_multi_entries(names, **options).tap do |results|
399
+ read_multi_entries(names, **options, event: payload).tap do |results|
389
400
  payload[:hits] = results.keys
390
401
  end
391
402
  end
@@ -441,8 +452,8 @@ module ActiveSupport
441
452
  instrument :read_multi, names, options do |payload|
442
453
  reads = read_multi_entries(names, **options)
443
454
  writes = {}
444
- ordered = names.each_with_object({}) do |name, hash|
445
- hash[name] = reads.fetch(name) { writes[name] = yield(name) }
455
+ ordered = names.index_with do |name|
456
+ reads.fetch(name) { writes[name] = yield(name) }
446
457
  end
447
458
 
448
459
  payload[:hits] = reads.keys
@@ -477,14 +488,26 @@ module ActiveSupport
477
488
  end
478
489
  end
479
490
 
491
+ # Deletes multiple entries in the cache.
492
+ #
493
+ # Options are passed to the underlying cache implementation.
494
+ def delete_multi(names, options = nil)
495
+ options = merged_options(options)
496
+ names.map! { |key| normalize_key(key, options) }
497
+
498
+ instrument :delete_multi, names do
499
+ delete_multi_entries(names, **options)
500
+ end
501
+ end
502
+
480
503
  # Returns +true+ if the cache contains an entry for the given key.
481
504
  #
482
505
  # Options are passed to the underlying cache implementation.
483
506
  def exist?(name, options = nil)
484
507
  options = merged_options(options)
485
508
 
486
- instrument(:exist?, name) do
487
- entry = read_entry(normalize_key(name, options), **options)
509
+ instrument(:exist?, name) do |payload|
510
+ entry = read_entry(normalize_key(name, options), **options, event: payload)
488
511
  (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
489
512
  end
490
513
  end
@@ -567,26 +590,31 @@ module ActiveSupport
567
590
  raise NotImplementedError.new
568
591
  end
569
592
 
593
+ def serialize_entry(entry)
594
+ @coder.dump(entry)
595
+ end
596
+
597
+ def deserialize_entry(payload)
598
+ payload.nil? ? nil : @coder.load(payload)
599
+ end
600
+
570
601
  # Reads multiple entries from the cache implementation. Subclasses MAY
571
602
  # implement this method.
572
603
  def read_multi_entries(names, **options)
573
- results = {}
574
- names.each do |name|
575
- key = normalize_key(name, options)
604
+ names.each_with_object({}) do |name, results|
605
+ key = normalize_key(name, options)
606
+ entry = read_entry(key, **options)
607
+
608
+ next unless entry
609
+
576
610
  version = normalize_version(name, options)
577
- entry = read_entry(key, **options)
578
-
579
- if entry
580
- if entry.expired?
581
- delete_entry(key, **options)
582
- elsif entry.mismatched?(version)
583
- # Skip mismatched versions
584
- else
585
- results[name] = entry.value
586
- end
611
+
612
+ if entry.expired?
613
+ delete_entry(key, **options)
614
+ elsif !entry.mismatched?(version)
615
+ results[name] = entry.value
587
616
  end
588
617
  end
589
- results
590
618
  end
591
619
 
592
620
  # Writes multiple entries to the cache implementation. Subclasses MAY
@@ -603,6 +631,12 @@ module ActiveSupport
603
631
  raise NotImplementedError.new
604
632
  end
605
633
 
634
+ # Deletes multiples entries in the cache implementation. Subclasses MAY
635
+ # implement this method.
636
+ def delete_multi_entries(entries, **options)
637
+ entries.count { |key| delete_entry(key, **options) }
638
+ end
639
+
606
640
  # Merges the default options with ones specific to a method call.
607
641
  def merged_options(call_options)
608
642
  if call_options
@@ -639,6 +673,10 @@ module ActiveSupport
639
673
  namespace = namespace.call
640
674
  end
641
675
 
676
+ if key && key.encoding != Encoding::UTF_8
677
+ key = key.dup.force_encoding(Encoding::UTF_8)
678
+ end
679
+
642
680
  if namespace
643
681
  "#{namespace}:#{key}"
644
682
  else
@@ -655,15 +693,15 @@ module ActiveSupport
655
693
  case key
656
694
  when Array
657
695
  if key.size > 1
658
- key = key.collect { |element| expanded_key(element) }
696
+ key.collect { |element| expanded_key(element) }
659
697
  else
660
- key = expanded_key(key.first)
698
+ expanded_key(key.first)
661
699
  end
662
700
  when Hash
663
- key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
664
- end
665
-
666
- key.to_param
701
+ key.collect { |k, v| "#{k}=#{v}" }.sort!
702
+ else
703
+ key
704
+ end.to_param
667
705
  end
668
706
 
669
707
  def normalize_version(key, options = nil)
@@ -673,24 +711,21 @@ module ActiveSupport
673
711
  def expanded_version(key)
674
712
  case
675
713
  when key.respond_to?(:cache_version) then key.cache_version.to_param
676
- when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
714
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
677
715
  when key.respond_to?(:to_a) then expanded_version(key.to_a)
678
716
  end
679
717
  end
680
718
 
681
719
  def instrument(operation, key, options = nil)
682
- log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
720
+ if logger && logger.debug? && !silence?
721
+ logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
722
+ end
683
723
 
684
- payload = { key: key }
724
+ payload = { key: key, store: self.class.name }
685
725
  payload.merge!(options) if options.is_a?(Hash)
686
726
  ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
687
727
  end
688
728
 
689
- def log
690
- return unless logger && logger.debug? && !silence?
691
- logger.debug(yield)
692
- end
693
-
694
729
  def handle_expired_entry(entry, key, options)
695
730
  if entry && entry.expired?
696
731
  race_ttl = options[:race_condition_ttl].to_i
@@ -722,6 +757,18 @@ module ActiveSupport
722
757
  end
723
758
  end
724
759
 
760
+ module NullCoder # :nodoc:
761
+ class << self
762
+ def load(payload)
763
+ payload
764
+ end
765
+
766
+ def dump(entry)
767
+ entry
768
+ end
769
+ end
770
+ end
771
+
725
772
  # This class is used to represent cache entries. Cache entries have a value, an optional
726
773
  # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
727
774
  # on the cache. The version is used to support the :version option on the cache for rejecting
@@ -772,8 +819,8 @@ module ActiveSupport
772
819
  end
773
820
 
774
821
  # Returns the size of the cached value. This could be less than
775
- # <tt>value.size</tt> if the data is compressed.
776
- def size
822
+ # <tt>value.bytesize</tt> if the data is compressed.
823
+ def bytesize
777
824
  case value
778
825
  when NilClass
779
826
  0
@@ -4,10 +4,7 @@ require "active_support/concern"
4
4
  require "active_support/descendants_tracker"
5
5
  require "active_support/core_ext/array/extract_options"
6
6
  require "active_support/core_ext/class/attribute"
7
- require "active_support/core_ext/kernel/reporting"
8
- require "active_support/core_ext/kernel/singleton_class"
9
7
  require "active_support/core_ext/string/filters"
10
- require "active_support/deprecation"
11
8
  require "thread"
12
9
 
13
10
  module ActiveSupport
@@ -103,32 +100,6 @@ module ActiveSupport
103
100
  env = Filters::Environment.new(self, false, nil)
104
101
  next_sequence = callbacks.compile
105
102
 
106
- invoke_sequence = Proc.new do
107
- skipped = nil
108
- while true
109
- current = next_sequence
110
- current.invoke_before(env)
111
- if current.final?
112
- env.value = !env.halted && (!block_given? || yield)
113
- elsif current.skip?(env)
114
- (skipped ||= []) << current
115
- next_sequence = next_sequence.nested
116
- next
117
- else
118
- next_sequence = next_sequence.nested
119
- begin
120
- target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
121
- target.send(method, *arguments, &block)
122
- ensure
123
- next_sequence = current
124
- end
125
- end
126
- current.invoke_after(env)
127
- skipped.pop.invoke_after(env) while skipped && skipped.first
128
- break env.value
129
- end
130
- end
131
-
132
103
  # Common case: no 'around' callbacks defined
133
104
  if next_sequence.final?
134
105
  next_sequence.invoke_before(env)
@@ -136,6 +107,33 @@ module ActiveSupport
136
107
  next_sequence.invoke_after(env)
137
108
  env.value
138
109
  else
110
+ invoke_sequence = Proc.new do
111
+ skipped = nil
112
+
113
+ while true
114
+ current = next_sequence
115
+ current.invoke_before(env)
116
+ if current.final?
117
+ env.value = !env.halted && (!block_given? || yield)
118
+ elsif current.skip?(env)
119
+ (skipped ||= []) << current
120
+ next_sequence = next_sequence.nested
121
+ next
122
+ else
123
+ next_sequence = next_sequence.nested
124
+ begin
125
+ target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
126
+ target.send(method, *arguments, &block)
127
+ ensure
128
+ next_sequence = current
129
+ end
130
+ end
131
+ current.invoke_after(env)
132
+ skipped.pop.invoke_after(env) while skipped&.first
133
+ break env.value
134
+ end
135
+ end
136
+
139
137
  invoke_sequence.call
140
138
  end
141
139
  end
@@ -145,7 +143,7 @@ module ActiveSupport
145
143
  # A hook invoked every time a before callback is halted.
146
144
  # This can be overridden in ActiveSupport::Callbacks implementors in order
147
145
  # to provide better debugging/logging.
148
- def halted_callback_hook(filter)
146
+ def halted_callback_hook(filter, name)
149
147
  end
150
148
 
151
149
  module Conditionals # :nodoc:
@@ -161,17 +159,17 @@ module ActiveSupport
161
159
  Environment = Struct.new(:target, :halted, :value)
162
160
 
163
161
  class Before
164
- def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
162
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter, name)
165
163
  halted_lambda = chain_config[:terminator]
166
164
 
167
165
  if user_conditions.any?
168
- halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
166
+ halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
169
167
  else
170
- halting(callback_sequence, user_callback, halted_lambda, filter)
168
+ halting(callback_sequence, user_callback, halted_lambda, filter, name)
171
169
  end
172
170
  end
173
171
 
174
- def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
172
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
175
173
  callback_sequence.before do |env|
176
174
  target = env.target
177
175
  value = env.value
@@ -181,7 +179,7 @@ module ActiveSupport
181
179
  result_lambda = -> { user_callback.call target, value }
182
180
  env.halted = halted_lambda.call(target, result_lambda)
183
181
  if env.halted
184
- target.send :halted_callback_hook, filter
182
+ target.send :halted_callback_hook, filter, name
185
183
  end
186
184
  end
187
185
 
@@ -190,7 +188,7 @@ module ActiveSupport
190
188
  end
191
189
  private_class_method :halting_and_conditional
192
190
 
193
- def self.halting(callback_sequence, user_callback, halted_lambda, filter)
191
+ def self.halting(callback_sequence, user_callback, halted_lambda, filter, name)
194
192
  callback_sequence.before do |env|
195
193
  target = env.target
196
194
  value = env.value
@@ -199,9 +197,8 @@ module ActiveSupport
199
197
  unless halted
200
198
  result_lambda = -> { user_callback.call target, value }
201
199
  env.halted = halted_lambda.call(target, result_lambda)
202
-
203
200
  if env.halted
204
- target.send :halted_callback_hook, filter
201
+ target.send :halted_callback_hook, filter, name
205
202
  end
206
203
  end
207
204
 
@@ -300,8 +297,8 @@ module ActiveSupport
300
297
  @kind = kind
301
298
  @filter = filter
302
299
  @key = compute_identifier filter
303
- @if = check_conditionals(Array(options[:if]))
304
- @unless = check_conditionals(Array(options[:unless]))
300
+ @if = check_conditionals(options[:if])
301
+ @unless = check_conditionals(options[:unless])
305
302
  end
306
303
 
307
304
  def filter; @key; end
@@ -339,7 +336,7 @@ module ActiveSupport
339
336
 
340
337
  case kind
341
338
  when :before
342
- Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter)
339
+ Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter, name)
343
340
  when :after
344
341
  Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config)
345
342
  when :around
@@ -352,7 +349,13 @@ module ActiveSupport
352
349
  end
353
350
 
354
351
  private
352
+ EMPTY_ARRAY = [].freeze
353
+ private_constant :EMPTY_ARRAY
354
+
355
355
  def check_conditionals(conditionals)
356
+ return EMPTY_ARRAY if conditionals.blank?
357
+
358
+ conditionals = Array(conditionals)
356
359
  if conditionals.any? { |c| c.is_a?(String) }
357
360
  raise ArgumentError, <<-MSG.squish
358
361
  Passing string to be evaluated in :if and :unless conditional
@@ -361,7 +364,7 @@ module ActiveSupport
361
364
  MSG
362
365
  end
363
366
 
364
- conditionals
367
+ conditionals.freeze
365
368
  end
366
369
 
367
370
  def compute_identifier(filter)
@@ -403,21 +406,17 @@ module ActiveSupport
403
406
  # The actual invocation is left up to the caller to minimize
404
407
  # call stack pollution.
405
408
  def expand(target, value, block)
406
- result = @arguments.map { |arg|
409
+ expanded = [@override_target || target, @override_block || block, @method_name]
410
+
411
+ @arguments.each do |arg|
407
412
  case arg
408
- when :value; value
409
- when :target; target
410
- when :block; block || raise(ArgumentError)
413
+ when :value then expanded << value
414
+ when :target then expanded << target
415
+ when :block then expanded << (block || raise(ArgumentError))
411
416
  end
412
- }
413
-
414
- result.unshift @method_name
415
- result.unshift @override_block || block
416
- result.unshift @override_target || target
417
+ end
417
418
 
418
- # target, block, method, *arguments = result
419
- # target.send(method, *arguments, &block)
420
- result
419
+ expanded
421
420
  end
422
421
 
423
422
  # Return a lambda that will make this call when given the input
@@ -845,8 +844,18 @@ module ActiveSupport
845
844
  __callbacks[name.to_sym]
846
845
  end
847
846
 
848
- def set_callbacks(name, callbacks) # :nodoc:
849
- self.__callbacks = __callbacks.merge(name.to_sym => callbacks)
847
+ if Module.instance_method(:method_defined?).arity == 1 # Ruby 2.5 and older
848
+ def set_callbacks(name, callbacks) # :nodoc:
849
+ self.__callbacks = __callbacks.merge(name.to_sym => callbacks)
850
+ end
851
+ else # Ruby 2.6 and newer
852
+ def set_callbacks(name, callbacks) # :nodoc:
853
+ unless singleton_class.method_defined?(:__callbacks, false)
854
+ self.__callbacks = __callbacks.dup
855
+ end
856
+ self.__callbacks[name.to_sym] = callbacks
857
+ self.__callbacks
858
+ end
850
859
  end
851
860
  end
852
861
  end
@@ -19,7 +19,7 @@ module ActiveSupport
19
19
  # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
20
20
  # written as:
21
21
  #
22
- # require 'active_support/concern'
22
+ # require "active_support/concern"
23
23
  #
24
24
  # module M
25
25
  # extend ActiveSupport::Concern
@@ -76,7 +76,7 @@ module ActiveSupport
76
76
  # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
77
77
  # module dependencies are properly resolved:
78
78
  #
79
- # require 'active_support/concern'
79
+ # require "active_support/concern"
80
80
  #
81
81
  # module Foo
82
82
  # extend ActiveSupport::Concern
@@ -99,6 +99,14 @@ module ActiveSupport
99
99
  # class Host
100
100
  # include Bar # It works, now Bar takes care of its dependencies
101
101
  # end
102
+ #
103
+ # === Prepending concerns
104
+ #
105
+ # Just like <tt>include</tt>, concerns also support <tt>prepend</tt> with a corresponding
106
+ # <tt>prepended do</tt> callback. <tt>module ClassMethods</tt> or <tt>class_methods do</tt> are
107
+ # prepended as well.
108
+ #
109
+ # <tt>prepend</tt> is also used for any dependencies.
102
110
  module Concern
103
111
  class MultipleIncludedBlocks < StandardError #:nodoc:
104
112
  def initialize
@@ -106,6 +114,12 @@ module ActiveSupport
106
114
  end
107
115
  end
108
116
 
117
+ class MultiplePrependBlocks < StandardError #:nodoc:
118
+ def initialize
119
+ super "Cannot define multiple 'prepended' blocks for a Concern"
120
+ end
121
+ end
122
+
109
123
  def self.extended(base) #:nodoc:
110
124
  base.instance_variable_set(:@_dependencies, [])
111
125
  end
@@ -123,6 +137,19 @@ module ActiveSupport
123
137
  end
124
138
  end
125
139
 
140
+ def prepend_features(base) #:nodoc:
141
+ if base.instance_variable_defined?(:@_dependencies)
142
+ base.instance_variable_get(:@_dependencies).unshift self
143
+ false
144
+ else
145
+ return false if base < self
146
+ @_dependencies.each { |dep| base.prepend(dep) }
147
+ super
148
+ base.singleton_class.prepend const_get(:ClassMethods) if const_defined?(:ClassMethods)
149
+ base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block)
150
+ end
151
+ end
152
+
126
153
  # Evaluate given block in context of base class,
127
154
  # so that you can write class macros here.
128
155
  # When you define more than one +included+ block, it raises an exception.
@@ -140,6 +167,23 @@ module ActiveSupport
140
167
  end
141
168
  end
142
169
 
170
+ # Evaluate given block in context of base class,
171
+ # so that you can write class macros here.
172
+ # When you define more than one +prepended+ block, it raises an exception.
173
+ def prepended(base = nil, &block)
174
+ if base.nil?
175
+ if instance_variable_defined?(:@_prepended_block)
176
+ if @_prepended_block.source_location != block.source_location
177
+ raise MultiplePrependBlocks
178
+ end
179
+ else
180
+ @_prepended_block = block
181
+ end
182
+ else
183
+ super
184
+ end
185
+ end
186
+
143
187
  # Define class methods from given block.
144
188
  # You can define private class methods as well.
145
189
  #
@@ -5,7 +5,7 @@ require "active_support/ordered_options"
5
5
 
6
6
  module ActiveSupport
7
7
  # Configurable provides a <tt>config</tt> method to store and retrieve
8
- # configuration options as an <tt>OrderedHash</tt>.
8
+ # configuration options as an <tt>OrderedOptions</tt>.
9
9
  module Configurable
10
10
  extend ActiveSupport::Concern
11
11
 
@@ -124,9 +124,9 @@ module ActiveSupport
124
124
  private :config_accessor
125
125
  end
126
126
 
127
- # Reads and writes attributes from a configuration <tt>OrderedHash</tt>.
127
+ # Reads and writes attributes from a configuration <tt>OrderedOptions</tt>.
128
128
  #
129
- # require 'active_support/configurable'
129
+ # require "active_support/configurable"
130
130
  #
131
131
  # class User
132
132
  # include ActiveSupport::Configurable
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # Reads a YAML configuration file, evaluating any ERB, then
5
+ # parsing the resulting YAML.
6
+ #
7
+ # Warns in case of YAML confusing characters, like invisible
8
+ # non-breaking spaces.
9
+ class ConfigurationFile # :nodoc:
10
+ class FormatError < StandardError; end
11
+
12
+ def initialize(content_path)
13
+ @content_path = content_path.to_s
14
+ @content = read content_path
15
+ end
16
+
17
+ def self.parse(content_path, **options)
18
+ new(content_path).parse(**options)
19
+ end
20
+
21
+ def parse(context: nil, **options)
22
+ source = render(context)
23
+ if YAML.respond_to?(:unsafe_load)
24
+ YAML.unsafe_load(source, **options) || {}
25
+ else
26
+ YAML.load(source, **options) || {}
27
+ end
28
+ rescue Psych::SyntaxError => error
29
+ raise "YAML syntax error occurred while parsing #{@content_path}. " \
30
+ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
31
+ "Error: #{error.message}"
32
+ end
33
+
34
+ private
35
+ def read(content_path)
36
+ require "yaml"
37
+ require "erb"
38
+
39
+ File.read(content_path).tap do |content|
40
+ if content.include?("\u00A0")
41
+ warn "File contains invisible non-breaking spaces, you may want to remove those"
42
+ end
43
+ end
44
+ end
45
+
46
+ def render(context)
47
+ erb = ERB.new(@content).tap { |e| e.filename = @content_path }
48
+ context ? erb.result(context) : erb.result
49
+ end
50
+ end
51
+ end
@@ -10,7 +10,7 @@ class << Benchmark
10
10
  #
11
11
  # Benchmark.ms { User.all }
12
12
  # # => 0.074
13
- def ms
14
- 1000 * realtime { yield }
13
+ def ms(&block)
14
+ 1000 * realtime(&block)
15
15
  end
16
16
  end