activesupport 6.0.6.1 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +441 -455
  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/digest/uuid.rb +1 -0
  24. data/lib/active_support/core_ext/enumerable.rb +76 -4
  25. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  26. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  27. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  28. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  29. data/lib/active_support/core_ext/load_error.rb +1 -1
  30. data/lib/active_support/core_ext/marshal.rb +2 -0
  31. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  32. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  33. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  34. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  35. data/lib/active_support/core_ext/module/delegation.rb +38 -28
  36. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  37. data/lib/active_support/core_ext/name_error.rb +29 -2
  38. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  39. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  40. data/lib/active_support/core_ext/object/json.rb +12 -1
  41. data/lib/active_support/core_ext/object/try.rb +2 -2
  42. data/lib/active_support/core_ext/range/compare_range.rb +9 -3
  43. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  44. data/lib/active_support/core_ext/regexp.rb +8 -1
  45. data/lib/active_support/core_ext/string/access.rb +5 -24
  46. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  47. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  48. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  49. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  50. data/lib/active_support/core_ext/string/output_safety.rb +7 -4
  51. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  52. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  53. data/lib/active_support/core_ext/symbol.rb +3 -0
  54. data/lib/active_support/core_ext/time/calculations.rb +19 -0
  55. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  56. data/lib/active_support/core_ext/uri.rb +5 -1
  57. data/lib/active_support/core_ext.rb +1 -1
  58. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  59. data/lib/active_support/current_attributes.rb +9 -2
  60. data/lib/active_support/dependencies/zeitwerk_integration.rb +4 -1
  61. data/lib/active_support/dependencies.rb +37 -18
  62. data/lib/active_support/deprecation/behaviors.rb +15 -2
  63. data/lib/active_support/deprecation/disallowed.rb +56 -0
  64. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  65. data/lib/active_support/deprecation/method_wrappers.rb +3 -2
  66. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  67. data/lib/active_support/deprecation/reporting.rb +50 -7
  68. data/lib/active_support/deprecation.rb +6 -1
  69. data/lib/active_support/descendants_tracker.rb +6 -2
  70. data/lib/active_support/digest.rb +2 -0
  71. data/lib/active_support/duration/iso8601_serializer.rb +15 -9
  72. data/lib/active_support/duration.rb +75 -25
  73. data/lib/active_support/encrypted_file.rb +27 -11
  74. data/lib/active_support/environment_inquirer.rb +20 -0
  75. data/lib/active_support/evented_file_update_checker.rb +69 -133
  76. data/lib/active_support/fork_tracker.rb +64 -0
  77. data/lib/active_support/gem_version.rb +3 -3
  78. data/lib/active_support/hash_with_indifferent_access.rb +48 -24
  79. data/lib/active_support/i18n_railtie.rb +14 -19
  80. data/lib/active_support/inflector/inflections.rb +1 -2
  81. data/lib/active_support/inflector/methods.rb +36 -33
  82. data/lib/active_support/inflector/transliterate.rb +4 -4
  83. data/lib/active_support/json/decoding.rb +4 -4
  84. data/lib/active_support/json/encoding.rb +5 -1
  85. data/lib/active_support/key_generator.rb +1 -1
  86. data/lib/active_support/locale/en.yml +7 -3
  87. data/lib/active_support/log_subscriber.rb +8 -0
  88. data/lib/active_support/logger.rb +1 -1
  89. data/lib/active_support/logger_silence.rb +2 -26
  90. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  91. data/lib/active_support/message_encryptor.rb +4 -7
  92. data/lib/active_support/message_verifier.rb +5 -5
  93. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  94. data/lib/active_support/messages/rotator.rb +6 -5
  95. data/lib/active_support/multibyte/chars.rb +4 -42
  96. data/lib/active_support/multibyte/unicode.rb +9 -83
  97. data/lib/active_support/notifications/fanout.rb +23 -8
  98. data/lib/active_support/notifications/instrumenter.rb +6 -15
  99. data/lib/active_support/notifications.rb +32 -5
  100. data/lib/active_support/number_helper/number_converter.rb +1 -1
  101. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  102. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  103. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  104. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  105. data/lib/active_support/number_helper.rb +29 -14
  106. data/lib/active_support/option_merger.rb +2 -1
  107. data/lib/active_support/ordered_options.rb +8 -2
  108. data/lib/active_support/parameter_filter.rb +16 -11
  109. data/lib/active_support/per_thread_registry.rb +2 -1
  110. data/lib/active_support/rails.rb +1 -4
  111. data/lib/active_support/railtie.rb +23 -1
  112. data/lib/active_support/rescuable.rb +4 -4
  113. data/lib/active_support/secure_compare_rotator.rb +51 -0
  114. data/lib/active_support/security_utils.rb +19 -12
  115. data/lib/active_support/string_inquirer.rb +4 -2
  116. data/lib/active_support/subscriber.rb +12 -7
  117. data/lib/active_support/tagged_logging.rb +30 -5
  118. data/lib/active_support/testing/assertions.rb +18 -11
  119. data/lib/active_support/testing/parallelization/server.rb +78 -0
  120. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  121. data/lib/active_support/testing/parallelization.rb +12 -95
  122. data/lib/active_support/testing/time_helpers.rb +40 -3
  123. data/lib/active_support/time_with_zone.rb +67 -43
  124. data/lib/active_support/values/time_zone.rb +22 -10
  125. data/lib/active_support/xml_mini/rexml.rb +8 -1
  126. data/lib/active_support.rb +13 -1
  127. metadata +34 -36
  128. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  129. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  130. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  131. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  132. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  133. 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