activesupport 8.0.0 → 8.1.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +391 -67
- data/README.rdoc +1 -1
- data/lib/active_support/backtrace_cleaner.rb +72 -1
- data/lib/active_support/broadcast_logger.rb +61 -74
- data/lib/active_support/cache/file_store.rb +2 -2
- data/lib/active_support/cache/mem_cache_store.rb +26 -28
- data/lib/active_support/cache/memory_store.rb +5 -5
- data/lib/active_support/cache/null_store.rb +2 -2
- data/lib/active_support/cache/redis_cache_store.rb +37 -31
- data/lib/active_support/cache/strategy/local_cache.rb +72 -27
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
- data/lib/active_support/cache.rb +72 -9
- data/lib/active_support/callbacks.rb +27 -11
- data/lib/active_support/class_attribute.rb +15 -8
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +34 -0
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array/conversions.rb +3 -3
- data/lib/active_support/core_ext/array.rb +7 -7
- data/lib/active_support/core_ext/benchmark.rb +4 -11
- data/lib/active_support/core_ext/big_decimal.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +20 -5
- data/lib/active_support/core_ext/class.rb +2 -2
- data/lib/active_support/core_ext/date.rb +5 -5
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -35
- data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
- data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
- data/lib/active_support/core_ext/date_time.rb +5 -5
- data/lib/active_support/core_ext/digest.rb +1 -1
- data/lib/active_support/core_ext/enumerable.rb +17 -5
- data/lib/active_support/core_ext/erb/util.rb +5 -5
- data/lib/active_support/core_ext/file.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
- data/lib/active_support/core_ext/hash.rb +8 -8
- data/lib/active_support/core_ext/integer.rb +3 -3
- data/lib/active_support/core_ext/kernel.rb +3 -3
- data/lib/active_support/core_ext/module/introspection.rb +3 -0
- data/lib/active_support/core_ext/module.rb +11 -11
- data/lib/active_support/core_ext/numeric.rb +3 -3
- data/lib/active_support/core_ext/object/json.rb +8 -1
- data/lib/active_support/core_ext/object/to_query.rb +7 -1
- data/lib/active_support/core_ext/object/try.rb +2 -2
- data/lib/active_support/core_ext/object.rb +13 -13
- data/lib/active_support/core_ext/pathname.rb +2 -2
- data/lib/active_support/core_ext/range/overlap.rb +3 -3
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +4 -4
- data/lib/active_support/core_ext/securerandom.rb +24 -8
- data/lib/active_support/core_ext/string/filters.rb +3 -3
- data/lib/active_support/core_ext/string/multibyte.rb +12 -3
- data/lib/active_support/core_ext/string/output_safety.rb +19 -12
- data/lib/active_support/core_ext/string.rb +13 -13
- data/lib/active_support/core_ext/symbol.rb +1 -1
- data/lib/active_support/core_ext/time/calculations.rb +0 -7
- data/lib/active_support/core_ext/time/compatibility.rb +2 -27
- data/lib/active_support/core_ext/time.rb +5 -5
- data/lib/active_support/core_ext.rb +1 -1
- data/lib/active_support/current_attributes/test_helper.rb +2 -2
- data/lib/active_support/current_attributes.rb +27 -17
- data/lib/active_support/dependencies/interlock.rb +11 -5
- data/lib/active_support/dependencies.rb +6 -1
- data/lib/active_support/deprecation/reporting.rb +4 -2
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/error_reporter.rb +61 -8
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +592 -0
- data/lib/active_support/evented_file_update_checker.rb +5 -1
- data/lib/active_support/execution_context.rb +64 -7
- data/lib/active_support/execution_wrapper.rb +1 -1
- data/lib/active_support/file_update_checker.rb +8 -6
- data/lib/active_support/gem_version.rb +1 -1
- data/lib/active_support/gzip.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +47 -24
- data/lib/active_support/i18n_railtie.rb +1 -2
- data/lib/active_support/inflector/inflections.rb +31 -15
- data/lib/active_support/inflector/transliterate.rb +6 -8
- data/lib/active_support/isolated_execution_state.rb +12 -15
- data/lib/active_support/json/decoding.rb +6 -4
- data/lib/active_support/json/encoding.rb +155 -19
- data/lib/active_support/lazy_load_hooks.rb +1 -1
- data/lib/active_support/log_subscriber.rb +2 -6
- data/lib/active_support/logger_thread_safe_level.rb +6 -3
- data/lib/active_support/message_encryptors.rb +54 -2
- data/lib/active_support/message_pack/extensions.rb +5 -0
- data/lib/active_support/message_verifier.rb +9 -0
- data/lib/active_support/message_verifiers.rb +57 -3
- data/lib/active_support/messages/rotation_coordinator.rb +9 -0
- data/lib/active_support/messages/rotator.rb +10 -0
- data/lib/active_support/multibyte/chars.rb +12 -2
- data/lib/active_support/multibyte.rb +4 -0
- data/lib/active_support/notifications/fanout.rb +64 -42
- data/lib/active_support/notifications/instrumenter.rb +1 -1
- data/lib/active_support/railtie.rb +32 -13
- data/lib/active_support/structured_event_subscriber.rb +99 -0
- data/lib/active_support/subscriber.rb +0 -5
- data/lib/active_support/syntax_error_proxy.rb +3 -0
- data/lib/active_support/test_case.rb +61 -6
- data/lib/active_support/testing/assertions.rb +34 -6
- data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +15 -2
- data/lib/active_support/testing/parallelization/worker.rb +4 -2
- data/lib/active_support/testing/parallelization.rb +25 -1
- data/lib/active_support/testing/tests_without_assertions.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +7 -3
- data/lib/active_support/time_with_zone.rb +23 -23
- data/lib/active_support/values/time_zone.rb +8 -1
- data/lib/active_support/xml_mini.rb +3 -2
- data/lib/active_support.rb +20 -15
- metadata +27 -23
- data/lib/active_support/core_ext/range/each.rb +0 -24
- data/lib/active_support/testing/strict_warnings.rb +0 -43
|
@@ -11,11 +11,12 @@ module ActiveSupport
|
|
|
11
11
|
# This class wraps up local storage for middlewares. Only the middleware method should
|
|
12
12
|
# construct them.
|
|
13
13
|
class Middleware # :nodoc:
|
|
14
|
-
attr_reader :name
|
|
14
|
+
attr_reader :name
|
|
15
|
+
attr_accessor :cache
|
|
15
16
|
|
|
16
|
-
def initialize(name,
|
|
17
|
+
def initialize(name, cache)
|
|
17
18
|
@name = name
|
|
18
|
-
@
|
|
19
|
+
@cache = cache
|
|
19
20
|
@app = nil
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -25,18 +26,17 @@ module ActiveSupport
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def call(env)
|
|
28
|
-
|
|
29
|
+
cache.new_local_cache
|
|
29
30
|
response = @app.call(env)
|
|
30
31
|
response[2] = ::Rack::BodyProxy.new(response[2]) do
|
|
31
|
-
|
|
32
|
+
cache.unset_local_cache
|
|
32
33
|
end
|
|
33
34
|
cleanup_on_body_close = true
|
|
34
35
|
response
|
|
35
36
|
rescue Rack::Utils::InvalidParameterError
|
|
36
37
|
[400, {}, []]
|
|
37
38
|
ensure
|
|
38
|
-
|
|
39
|
-
cleanup_on_body_close
|
|
39
|
+
cache.unset_local_cache unless cleanup_on_body_close
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
end
|
data/lib/active_support/cache.rb
CHANGED
|
@@ -35,6 +35,8 @@ module ActiveSupport
|
|
|
35
35
|
:race_condition_ttl,
|
|
36
36
|
:serializer,
|
|
37
37
|
:skip_nil,
|
|
38
|
+
:raw,
|
|
39
|
+
:max_key_size,
|
|
38
40
|
]
|
|
39
41
|
|
|
40
42
|
# Mapping of canonical option names to aliases that a store will recognize.
|
|
@@ -186,6 +188,12 @@ module ActiveSupport
|
|
|
186
188
|
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
|
|
187
189
|
#
|
|
188
190
|
class Store
|
|
191
|
+
# Default +ConnectionPool+ options
|
|
192
|
+
DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
|
|
193
|
+
|
|
194
|
+
# Keys are truncated with the Active Support digest if they exceed the limit.
|
|
195
|
+
MAX_KEY_SIZE = 250
|
|
196
|
+
|
|
189
197
|
cattr_accessor :logger, instance_writer: true
|
|
190
198
|
cattr_accessor :raise_on_invalid_cache_expiration_time, default: false
|
|
191
199
|
|
|
@@ -194,9 +202,6 @@ module ActiveSupport
|
|
|
194
202
|
|
|
195
203
|
class << self
|
|
196
204
|
private
|
|
197
|
-
DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze
|
|
198
|
-
private_constant :DEFAULT_POOL_OPTIONS
|
|
199
|
-
|
|
200
205
|
def retrieve_pool_options(options)
|
|
201
206
|
if options.key?(:pool)
|
|
202
207
|
pool_options = options.delete(:pool)
|
|
@@ -298,6 +303,9 @@ module ActiveSupport
|
|
|
298
303
|
@options[:compress] = true unless @options.key?(:compress)
|
|
299
304
|
@options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT
|
|
300
305
|
|
|
306
|
+
@max_key_size = @options.delete(:max_key_size)
|
|
307
|
+
@max_key_size = MAX_KEY_SIZE if @max_key_size.nil? # allow 'false' as a value
|
|
308
|
+
|
|
301
309
|
@coder = @options.delete(:coder) do
|
|
302
310
|
legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer]
|
|
303
311
|
serializer = @options.delete(:serializer) || default_serializer
|
|
@@ -386,7 +394,7 @@ module ActiveSupport
|
|
|
386
394
|
# process can try to generate a new value after the extended time window
|
|
387
395
|
# has elapsed.
|
|
388
396
|
#
|
|
389
|
-
# # Set all values to expire after one
|
|
397
|
+
# # Set all values to expire after one second.
|
|
390
398
|
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
|
|
391
399
|
#
|
|
392
400
|
# cache.write("foo", "original value")
|
|
@@ -556,7 +564,7 @@ module ActiveSupport
|
|
|
556
564
|
|
|
557
565
|
instrument_multi :write_multi, normalized_hash, options do |payload|
|
|
558
566
|
entries = hash.each_with_object({}) do |(name, value), memo|
|
|
559
|
-
memo[normalize_key(name, options)] = Entry.new(value, **options
|
|
567
|
+
memo[normalize_key(name, options)] = Entry.new(value, **options, version: normalize_version(name, options))
|
|
560
568
|
end
|
|
561
569
|
|
|
562
570
|
write_multi_entries entries, **options
|
|
@@ -658,13 +666,15 @@ module ActiveSupport
|
|
|
658
666
|
# version, the read will be treated as a cache miss. This feature is
|
|
659
667
|
# used to support recyclable cache keys.
|
|
660
668
|
#
|
|
669
|
+
# * +:unless_exist+ - Prevents overwriting an existing cache entry.
|
|
670
|
+
#
|
|
661
671
|
# Other options will be handled by the specific cache store implementation.
|
|
662
672
|
def write(name, value, options = nil)
|
|
663
673
|
options = merged_options(options)
|
|
664
674
|
key = normalize_key(name, options)
|
|
665
675
|
|
|
666
676
|
instrument(:write, key, options) do
|
|
667
|
-
entry = Entry.new(value, **options
|
|
677
|
+
entry = Entry.new(value, **options, version: normalize_version(name, options))
|
|
668
678
|
write_entry(key, entry, **options)
|
|
669
679
|
end
|
|
670
680
|
end
|
|
@@ -741,6 +751,32 @@ module ActiveSupport
|
|
|
741
751
|
raise NotImplementedError.new("#{self.class.name} does not support decrement")
|
|
742
752
|
end
|
|
743
753
|
|
|
754
|
+
# Reads a counter that was set by #increment / #decrement.
|
|
755
|
+
#
|
|
756
|
+
# cache.write_counter("foo", 1)
|
|
757
|
+
# cache.read_counter("foo") # => 1
|
|
758
|
+
# cache.increment("foo")
|
|
759
|
+
# cache.read_counter("foo") # => 2
|
|
760
|
+
#
|
|
761
|
+
# Options are passed to the underlying cache implementation.
|
|
762
|
+
def read_counter(name, **options)
|
|
763
|
+
options = merged_options(options).merge(raw: true)
|
|
764
|
+
read(name, **options)&.to_i
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# Writes a counter that can then be modified by #increment / #decrement.
|
|
768
|
+
#
|
|
769
|
+
# cache.write_counter("foo", 1)
|
|
770
|
+
# cache.read_counter("foo") # => 1
|
|
771
|
+
# cache.increment("foo")
|
|
772
|
+
# cache.read_counter("foo") # => 2
|
|
773
|
+
#
|
|
774
|
+
# Options are passed to the underlying cache implementation.
|
|
775
|
+
def write_counter(name, value, **options)
|
|
776
|
+
options = merged_options(options).merge(raw: true)
|
|
777
|
+
write(name, value.to_i, **options)
|
|
778
|
+
end
|
|
779
|
+
|
|
744
780
|
# Cleans up the cache by removing expired entries.
|
|
745
781
|
#
|
|
746
782
|
# Options are passed to the underlying cache implementation.
|
|
@@ -760,6 +796,17 @@ module ActiveSupport
|
|
|
760
796
|
raise NotImplementedError.new("#{self.class.name} does not support clear")
|
|
761
797
|
end
|
|
762
798
|
|
|
799
|
+
# Get the current namespace
|
|
800
|
+
def namespace
|
|
801
|
+
@options[:namespace]
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
# Set the current namespace. Note, this will be ignored if custom
|
|
805
|
+
# options are passed to cache wills with a namespace key.
|
|
806
|
+
def namespace=(namespace)
|
|
807
|
+
@options[:namespace] = namespace
|
|
808
|
+
end
|
|
809
|
+
|
|
763
810
|
private
|
|
764
811
|
def default_serializer
|
|
765
812
|
case Cache.format_version
|
|
@@ -926,16 +973,33 @@ module ActiveSupport
|
|
|
926
973
|
options
|
|
927
974
|
end
|
|
928
975
|
|
|
929
|
-
# Expands and
|
|
976
|
+
# Expands, namespaces and truncates the cache key.
|
|
930
977
|
# Raises an exception when the key is +nil+ or an empty string.
|
|
931
978
|
# May be overridden by cache stores to do additional normalization.
|
|
932
979
|
def normalize_key(key, options = nil)
|
|
980
|
+
key = expand_and_namespace_key(key, options)
|
|
981
|
+
truncate_key(key)
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
def expand_and_namespace_key(key, options = nil)
|
|
933
985
|
str_key = expanded_key(key)
|
|
934
986
|
raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty?
|
|
935
987
|
|
|
936
988
|
namespace_key str_key, options
|
|
937
989
|
end
|
|
938
990
|
|
|
991
|
+
def truncate_key(key)
|
|
992
|
+
if key && @max_key_size && key.bytesize > @max_key_size
|
|
993
|
+
suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
|
|
994
|
+
truncate_at = @max_key_size - suffix.bytesize
|
|
995
|
+
key = key.byteslice(0, truncate_at)
|
|
996
|
+
key.scrub!("")
|
|
997
|
+
"#{key}#{suffix}"
|
|
998
|
+
else
|
|
999
|
+
key
|
|
1000
|
+
end
|
|
1001
|
+
end
|
|
1002
|
+
|
|
939
1003
|
# Prefix the key with a namespace string:
|
|
940
1004
|
#
|
|
941
1005
|
# namespace_key 'foo', namespace: 'cache'
|
|
@@ -1035,8 +1099,7 @@ module ActiveSupport
|
|
|
1035
1099
|
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
|
|
1036
1100
|
# for a brief period while the entry is being recalculated.
|
|
1037
1101
|
entry.expires_at = Time.now.to_f + race_ttl
|
|
1038
|
-
options
|
|
1039
|
-
write_entry(key, entry, **options)
|
|
1102
|
+
write_entry(key, entry, **options, expires_in: race_ttl * 2)
|
|
1040
1103
|
else
|
|
1041
1104
|
delete_entry(key, **options)
|
|
1042
1105
|
end
|
|
@@ -4,6 +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/module/redefine_method"
|
|
7
8
|
require "active_support/core_ext/string/filters"
|
|
8
9
|
require "active_support/core_ext/object/blank"
|
|
9
10
|
|
|
@@ -498,9 +499,10 @@ module ActiveSupport
|
|
|
498
499
|
when Conditionals::Value
|
|
499
500
|
ProcCall.new(filter)
|
|
500
501
|
when ::Proc
|
|
501
|
-
|
|
502
|
+
case filter.arity
|
|
503
|
+
when 2
|
|
502
504
|
InstanceExec2.new(filter)
|
|
503
|
-
|
|
505
|
+
when 1, -2
|
|
504
506
|
InstanceExec1.new(filter)
|
|
505
507
|
else
|
|
506
508
|
InstanceExec0.new(filter)
|
|
@@ -572,7 +574,7 @@ module ActiveSupport
|
|
|
572
574
|
@name = name
|
|
573
575
|
@config = {
|
|
574
576
|
scope: [:kind],
|
|
575
|
-
terminator:
|
|
577
|
+
terminator: DEFAULT_TERMINATOR
|
|
576
578
|
}.merge!(config)
|
|
577
579
|
@chain = []
|
|
578
580
|
@all_callbacks = nil
|
|
@@ -660,8 +662,8 @@ module ActiveSupport
|
|
|
660
662
|
@chain.delete_if { |c| callback.duplicates?(c) }
|
|
661
663
|
end
|
|
662
664
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
+
class DefaultTerminator # :nodoc:
|
|
666
|
+
def call(target, result_lambda)
|
|
665
667
|
terminate = true
|
|
666
668
|
catch(:abort) do
|
|
667
669
|
result_lambda.call
|
|
@@ -670,6 +672,7 @@ module ActiveSupport
|
|
|
670
672
|
terminate
|
|
671
673
|
end
|
|
672
674
|
end
|
|
675
|
+
DEFAULT_TERMINATOR = DefaultTerminator.new.freeze
|
|
673
676
|
end
|
|
674
677
|
|
|
675
678
|
module ClassMethods
|
|
@@ -903,12 +906,13 @@ module ActiveSupport
|
|
|
903
906
|
names.each do |name|
|
|
904
907
|
name = name.to_sym
|
|
905
908
|
|
|
906
|
-
([self] + self.descendants).each do |target|
|
|
907
|
-
target.set_callbacks name, CallbackChain.new(name, options)
|
|
908
|
-
end
|
|
909
|
-
|
|
910
909
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
911
|
-
def _run_#{name}_callbacks
|
|
910
|
+
def _run_#{name}_callbacks
|
|
911
|
+
yield if block_given?
|
|
912
|
+
end
|
|
913
|
+
silence_redefinition_of_method(:_run_#{name}_callbacks)
|
|
914
|
+
|
|
915
|
+
def _run_#{name}_callbacks!(&block)
|
|
912
916
|
run_callbacks #{name.inspect}, &block
|
|
913
917
|
end
|
|
914
918
|
|
|
@@ -924,6 +928,10 @@ module ActiveSupport
|
|
|
924
928
|
__callbacks[#{name.inspect}]
|
|
925
929
|
end
|
|
926
930
|
RUBY
|
|
931
|
+
|
|
932
|
+
([self] + self.descendants).each do |target|
|
|
933
|
+
target.set_callbacks name, CallbackChain.new(name, options)
|
|
934
|
+
end
|
|
927
935
|
end
|
|
928
936
|
end
|
|
929
937
|
|
|
@@ -933,9 +941,17 @@ module ActiveSupport
|
|
|
933
941
|
end
|
|
934
942
|
|
|
935
943
|
def set_callbacks(name, callbacks) # :nodoc:
|
|
936
|
-
|
|
944
|
+
# HACK: We're making assumption on how `class_attribute` is implemented
|
|
945
|
+
# to save constantly duping the callback hash. If this desync with class_attribute
|
|
946
|
+
# we'll lose the optimization, but won't cause an actual behavior bug.
|
|
947
|
+
unless singleton_class.private_method_defined?(:__class_attr__callbacks, false)
|
|
937
948
|
self.__callbacks = __callbacks.dup
|
|
938
949
|
end
|
|
950
|
+
name = name.to_sym
|
|
951
|
+
callbacks_was = self.__callbacks[name.to_sym]
|
|
952
|
+
if (callbacks_was.nil? || callbacks_was.empty?) && !callbacks.empty?
|
|
953
|
+
alias_method("_run_#{name}_callbacks", "_run_#{name}_callbacks!")
|
|
954
|
+
end
|
|
939
955
|
self.__callbacks[name.to_sym] = callbacks
|
|
940
956
|
self.__callbacks
|
|
941
957
|
end
|
|
@@ -3,23 +3,30 @@
|
|
|
3
3
|
module ActiveSupport
|
|
4
4
|
module ClassAttribute # :nodoc:
|
|
5
5
|
class << self
|
|
6
|
-
def redefine(owner, name, value)
|
|
6
|
+
def redefine(owner, name, namespaced_name, value)
|
|
7
7
|
if owner.singleton_class?
|
|
8
|
-
owner.
|
|
9
|
-
|
|
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
|
|
10
13
|
end
|
|
11
14
|
|
|
12
|
-
owner.
|
|
13
|
-
owner.singleton_class.send(:public, name)
|
|
15
|
+
redefine_method(owner.singleton_class, namespaced_name, private: true) { value }
|
|
14
16
|
|
|
15
|
-
owner.
|
|
17
|
+
redefine_method(owner.singleton_class, "#{namespaced_name}=", private: true) do |new_value|
|
|
16
18
|
if owner.equal?(self)
|
|
17
19
|
value = new_value
|
|
18
20
|
else
|
|
19
|
-
::ActiveSupport::ClassAttribute.redefine(self, name, new_value)
|
|
21
|
+
::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, new_value)
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
|
-
|
|
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
|
|
23
30
|
end
|
|
24
31
|
end
|
|
25
32
|
end
|
|
@@ -4,69 +4,15 @@ require "monitor"
|
|
|
4
4
|
|
|
5
5
|
module ActiveSupport
|
|
6
6
|
module Concurrency
|
|
7
|
-
module LoadInterlockAwareMonitorMixin # :nodoc:
|
|
8
|
-
EXCEPTION_NEVER = { Exception => :never }.freeze
|
|
9
|
-
EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze
|
|
10
|
-
private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE
|
|
11
|
-
|
|
12
|
-
# Enters an exclusive section, but allows dependency loading while blocked
|
|
13
|
-
def mon_enter
|
|
14
|
-
mon_try_enter ||
|
|
15
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super }
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def synchronize(&block)
|
|
19
|
-
Thread.handle_interrupt(EXCEPTION_NEVER) do
|
|
20
|
-
mon_enter
|
|
21
|
-
|
|
22
|
-
begin
|
|
23
|
-
Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block)
|
|
24
|
-
ensure
|
|
25
|
-
mon_exit
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
7
|
# A monitor that will permit dependency loading while blocked waiting for
|
|
31
8
|
# the lock.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@owner = nil
|
|
41
|
-
@count = 0
|
|
42
|
-
@mutex = Mutex.new
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
def mon_try_enter
|
|
47
|
-
if @owner != Thread.current
|
|
48
|
-
return false unless @mutex.try_lock
|
|
49
|
-
@owner = Thread.current
|
|
50
|
-
end
|
|
51
|
-
@count += 1
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def mon_enter
|
|
55
|
-
@mutex.lock if @owner != Thread.current
|
|
56
|
-
@owner = Thread.current
|
|
57
|
-
@count += 1
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def mon_exit
|
|
61
|
-
unless @owner == Thread.current
|
|
62
|
-
raise ThreadError, "current thread not owner"
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
@count -= 1
|
|
66
|
-
return unless @count == 0
|
|
67
|
-
@owner = nil
|
|
68
|
-
@mutex.unlock
|
|
69
|
-
end
|
|
70
|
-
end
|
|
9
|
+
LoadInterlockAwareMonitor = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
|
|
10
|
+
"ActiveSupport::Concurrency::LoadInterlockAwareMonitor",
|
|
11
|
+
"::Monitor",
|
|
12
|
+
ActiveSupport.deprecator,
|
|
13
|
+
message: "ActiveSupport::Concurrency::LoadInterlockAwareMonitor is deprecated and will be " \
|
|
14
|
+
"removed in Rails 9.0. Use Monitor directly instead, as the loading interlock is " \
|
|
15
|
+
"no longer used."
|
|
16
|
+
)
|
|
71
17
|
end
|
|
72
18
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module Concurrency
|
|
5
|
+
class ThreadMonitor # :nodoc:
|
|
6
|
+
EXCEPTION_NEVER = { Exception => :never }.freeze
|
|
7
|
+
EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze
|
|
8
|
+
private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@owner = nil
|
|
12
|
+
@count = 0
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def synchronize(&block)
|
|
17
|
+
Thread.handle_interrupt(EXCEPTION_NEVER) do
|
|
18
|
+
mon_enter
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block)
|
|
22
|
+
ensure
|
|
23
|
+
mon_exit
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
def mon_try_enter
|
|
30
|
+
if @owner != Thread.current
|
|
31
|
+
return false unless @mutex.try_lock
|
|
32
|
+
@owner = Thread.current
|
|
33
|
+
end
|
|
34
|
+
@count += 1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def mon_enter
|
|
38
|
+
@mutex.lock if @owner != Thread.current
|
|
39
|
+
@owner = Thread.current
|
|
40
|
+
@count += 1
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def mon_exit
|
|
44
|
+
unless @owner == Thread.current
|
|
45
|
+
raise ThreadError, "current thread not owner"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@count -= 1
|
|
49
|
+
return unless @count == 0
|
|
50
|
+
@owner = nil
|
|
51
|
+
@mutex.unlock
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
ActiveSupport.deprecator.warn <<~MSG
|
|
4
|
+
ActiveSupport::Configurable is deprecated without replacement, and will be removed in Rails 8.2.
|
|
5
|
+
|
|
6
|
+
You can emulate the previous behavior with `class_attribute`.
|
|
7
|
+
MSG
|
|
8
|
+
|
|
3
9
|
require "active_support/concern"
|
|
4
10
|
require "active_support/ordered_options"
|
|
5
11
|
|
|
@@ -27,6 +33,19 @@ module ActiveSupport
|
|
|
27
33
|
end
|
|
28
34
|
|
|
29
35
|
module ClassMethods
|
|
36
|
+
# Reads and writes attributes from a configuration OrderedOptions.
|
|
37
|
+
#
|
|
38
|
+
# require "active_support/configurable"
|
|
39
|
+
#
|
|
40
|
+
# class User
|
|
41
|
+
# include ActiveSupport::Configurable
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# User.config.allowed_access = true
|
|
45
|
+
# User.config.level = 1
|
|
46
|
+
#
|
|
47
|
+
# User.config.allowed_access # => true
|
|
48
|
+
# User.config.level # => 1
|
|
30
49
|
def config
|
|
31
50
|
@_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config)
|
|
32
51
|
superclass.config.inheritable_copy
|
|
@@ -36,6 +55,21 @@ module ActiveSupport
|
|
|
36
55
|
end
|
|
37
56
|
end
|
|
38
57
|
|
|
58
|
+
# Configure values from within the passed block.
|
|
59
|
+
#
|
|
60
|
+
# require "active_support/configurable"
|
|
61
|
+
#
|
|
62
|
+
# class User
|
|
63
|
+
# include ActiveSupport::Configurable
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# User.allowed_access # => nil
|
|
67
|
+
#
|
|
68
|
+
# User.configure do |config|
|
|
69
|
+
# config.allowed_access = true
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
# User.allowed_access # => true
|
|
39
73
|
def configure
|
|
40
74
|
yield config
|
|
41
75
|
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
# Provides a DSL for declaring a continuous integration workflow that can be run either locally or in the cloud.
|
|
5
|
+
# Each step is timed, reports success/error, and is aggregated into a collective report that reports total runtime,
|
|
6
|
+
# as well as whether the entire run was successful or not.
|
|
7
|
+
#
|
|
8
|
+
# Example:
|
|
9
|
+
#
|
|
10
|
+
# ActiveSupport::ContinuousIntegration.run do
|
|
11
|
+
# step "Setup", "bin/setup --skip-server"
|
|
12
|
+
# step "Style: Ruby", "bin/rubocop"
|
|
13
|
+
# step "Security: Gem audit", "bin/bundler-audit"
|
|
14
|
+
# step "Tests: Rails", "bin/rails test test:system"
|
|
15
|
+
#
|
|
16
|
+
# if success?
|
|
17
|
+
# step "Signoff: Ready for merge and deploy", "gh signoff"
|
|
18
|
+
# else
|
|
19
|
+
# failure "Skipping signoff; CI failed.", "Fix the issues and try again."
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# Starting with Rails 8.1, a default `bin/ci` and `config/ci.rb` file are created to provide out-of-the-box CI.
|
|
24
|
+
class ContinuousIntegration
|
|
25
|
+
COLORS = {
|
|
26
|
+
banner: "\033[1;32m", # Green
|
|
27
|
+
title: "\033[1;35m", # Purple
|
|
28
|
+
subtitle: "\033[1;90m", # Medium Gray
|
|
29
|
+
error: "\033[1;31m", # Red
|
|
30
|
+
success: "\033[1;32m" # Green
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
attr_reader :results
|
|
34
|
+
|
|
35
|
+
# Perform a CI run. Execute each step, show their results and runtime, and exit with a non-zero status if there are any failures.
|
|
36
|
+
#
|
|
37
|
+
# Pass an optional title, subtitle, and a block that declares the steps to be executed.
|
|
38
|
+
#
|
|
39
|
+
# Sets the CI environment variable to "true" to allow for conditional behavior in the app, like enabling eager loading and disabling logging.
|
|
40
|
+
#
|
|
41
|
+
# Example:
|
|
42
|
+
#
|
|
43
|
+
# ActiveSupport::ContinuousIntegration.run do
|
|
44
|
+
# step "Setup", "bin/setup --skip-server"
|
|
45
|
+
# step "Style: Ruby", "bin/rubocop"
|
|
46
|
+
# step "Security: Gem audit", "bin/bundler-audit"
|
|
47
|
+
# step "Tests: Rails", "bin/rails test test:system"
|
|
48
|
+
#
|
|
49
|
+
# if success?
|
|
50
|
+
# step "Signoff: Ready for merge and deploy", "gh signoff"
|
|
51
|
+
# else
|
|
52
|
+
# failure "Skipping signoff; CI failed.", "Fix the issues and try again."
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
def self.run(title = "Continuous Integration", subtitle = "Running tests, style checks, and security audits", &block)
|
|
56
|
+
new.tap do |ci|
|
|
57
|
+
ENV["CI"] = "true"
|
|
58
|
+
ci.heading title, subtitle, padding: false
|
|
59
|
+
ci.report(title, &block)
|
|
60
|
+
abort unless ci.success?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def initialize
|
|
65
|
+
@results = []
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Declare a step with a title and a command. The command can either be given as a single string or as multiple
|
|
69
|
+
# strings that will be passed to `system` as individual arguments (and therefore correctly escaped for paths etc).
|
|
70
|
+
#
|
|
71
|
+
# Examples:
|
|
72
|
+
#
|
|
73
|
+
# step "Setup", "bin/setup"
|
|
74
|
+
# step "Single test", "bin/rails", "test", "--name", "test_that_is_one"
|
|
75
|
+
def step(title, *command)
|
|
76
|
+
heading title, command.join(" "), type: :title
|
|
77
|
+
report(title) { results << system(*command) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns true if all steps were successful.
|
|
81
|
+
def success?
|
|
82
|
+
results.all?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Display an error heading with the title and optional subtitle to reflect that the run failed.
|
|
86
|
+
def failure(title, subtitle = nil)
|
|
87
|
+
heading title, subtitle, type: :error
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Display a colorized heading followed by an optional subtitle.
|
|
91
|
+
#
|
|
92
|
+
# Examples:
|
|
93
|
+
#
|
|
94
|
+
# heading "Smoke Testing", "End-to-end tests verifying key functionality", padding: false
|
|
95
|
+
# heading "Skipping video encoding tests", "Install FFmpeg to run these tests", type: :error
|
|
96
|
+
#
|
|
97
|
+
# See ActiveSupport::ContinuousIntegration::COLORS for a complete list of options.
|
|
98
|
+
def heading(heading, subtitle = nil, type: :banner, padding: true)
|
|
99
|
+
echo "#{padding ? "\n\n" : ""}#{heading}", type: type
|
|
100
|
+
echo "#{subtitle}#{padding ? "\n" : ""}", type: :subtitle if subtitle
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Echo text to the terminal in the color corresponding to the type of the text.
|
|
104
|
+
#
|
|
105
|
+
# Examples:
|
|
106
|
+
#
|
|
107
|
+
# echo "This is going to be green!", type: :success
|
|
108
|
+
# echo "This is going to be red!", type: :error
|
|
109
|
+
#
|
|
110
|
+
# See ActiveSupport::ContinuousIntegration::COLORS for a complete list of options.
|
|
111
|
+
def echo(text, type:)
|
|
112
|
+
puts colorize(text, type)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# :nodoc:
|
|
116
|
+
def report(title, &block)
|
|
117
|
+
Signal.trap("INT") { abort colorize(:error, "\n❌ #{title} interrupted") }
|
|
118
|
+
|
|
119
|
+
ci = self.class.new
|
|
120
|
+
elapsed = timing { ci.instance_eval(&block) }
|
|
121
|
+
|
|
122
|
+
if ci.success?
|
|
123
|
+
echo "\n✅ #{title} passed in #{elapsed}", type: :success
|
|
124
|
+
else
|
|
125
|
+
echo "\n❌ #{title} failed in #{elapsed}", type: :error
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
results.concat ci.results
|
|
129
|
+
ensure
|
|
130
|
+
Signal.trap("INT", "-")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
def timing
|
|
135
|
+
started_at = Time.now.to_f
|
|
136
|
+
yield
|
|
137
|
+
min, sec = (Time.now.to_f - started_at).divmod(60)
|
|
138
|
+
"#{"#{min}m" if min > 0}%.2fs" % sec
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def colorize(text, type)
|
|
142
|
+
"#{COLORS.fetch(type)}#{text}\033[0m"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -16,11 +16,11 @@ class Array
|
|
|
16
16
|
# ==== Options
|
|
17
17
|
#
|
|
18
18
|
# * <tt>:words_connector</tt> - The sign or word used to join all but the last
|
|
19
|
-
# element in arrays with three or more elements (default: ", ").
|
|
19
|
+
# element in arrays with three or more elements (default: <tt>", "</tt>).
|
|
20
20
|
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element
|
|
21
|
-
# in arrays with three or more elements (default: ", and ").
|
|
21
|
+
# in arrays with three or more elements (default: <tt>", and "</tt>).
|
|
22
22
|
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements
|
|
23
|
-
# in arrays with two elements (default: " and ").
|
|
23
|
+
# in arrays with two elements (default: <tt>" and "</tt>).
|
|
24
24
|
# * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
|
|
25
25
|
# the connector options defined on the 'support.array' namespace in the
|
|
26
26
|
# corresponding dictionary file.
|