activesupport 7.2.2.1 → 8.1.3
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 +422 -145
- data/README.rdoc +1 -1
- data/lib/active_support/backtrace_cleaner.rb +73 -2
- data/lib/active_support/benchmark.rb +21 -0
- data/lib/active_support/benchmarkable.rb +3 -2
- data/lib/active_support/broadcast_logger.rb +61 -74
- data/lib/active_support/cache/file_store.rb +14 -4
- data/lib/active_support/cache/mem_cache_store.rb +30 -29
- data/lib/active_support/cache/memory_store.rb +11 -5
- data/lib/active_support/cache/null_store.rb +2 -2
- data/lib/active_support/cache/redis_cache_store.rb +43 -34
- 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 +88 -20
- data/lib/active_support/callbacks.rb +28 -13
- data/lib/active_support/class_attribute.rb +33 -0
- data/lib/active_support/code_generator.rb +9 -0
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +34 -0
- data/lib/active_support/configuration_file.rb +15 -6
- 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 +0 -15
- data/lib/active_support/core_ext/big_decimal.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +26 -20
- data/lib/active_support/core_ext/class.rb +2 -2
- data/lib/active_support/core_ext/date/conversions.rb +2 -0
- 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 +25 -8
- 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/except.rb +0 -12
- 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/attr_internal.rb +3 -4
- 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 +24 -11
- 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/inflections.rb +1 -1
- data/lib/active_support/core_ext/string/multibyte.rb +12 -3
- data/lib/active_support/core_ext/string/output_safety.rb +29 -13
- 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/thread/backtrace/location.rb +2 -7
- data/lib/active_support/core_ext/time/calculations.rb +7 -2
- data/lib/active_support/core_ext/time/compatibility.rb +2 -19
- data/lib/active_support/core_ext/time/conversions.rb +2 -0
- data/lib/active_support/core_ext/time.rb +5 -5
- data/lib/active_support/current_attributes/test_helper.rb +2 -2
- data/lib/active_support/current_attributes.rb +27 -17
- data/lib/active_support/delegation.rb +25 -44
- data/lib/active_support/dependencies/interlock.rb +11 -5
- data/lib/active_support/dependencies.rb +6 -2
- data/lib/active_support/deprecation/reporting.rb +4 -21
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/duration.rb +14 -10
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/encrypted_configuration.rb +20 -2
- data/lib/active_support/error_reporter.rb +81 -4
- 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 -2
- data/lib/active_support/execution_context.rb +75 -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 +4 -4
- data/lib/active_support/gzip.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +61 -38
- data/lib/active_support/i18n_railtie.rb +19 -11
- data/lib/active_support/inflector/inflections.rb +34 -16
- data/lib/active_support/inflector/methods.rb +3 -3
- data/lib/active_support/inflector/transliterate.rb +6 -8
- data/lib/active_support/isolated_execution_state.rb +17 -17
- data/lib/active_support/json/decoding.rb +6 -4
- data/lib/active_support/json/encoding.rb +159 -21
- 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 +6 -1
- 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 -43
- data/lib/active_support/notifications/instrumenter.rb +1 -1
- data/lib/active_support/number_helper/number_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +17 -2
- data/lib/active_support/number_helper.rb +22 -0
- data/lib/active_support/railtie.rb +32 -9
- 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 +7 -0
- data/lib/active_support/tagged_logging.rb +5 -0
- data/lib/active_support/test_case.rb +67 -6
- data/lib/active_support/testing/assertions.rb +118 -27
- data/lib/active_support/testing/autorun.rb +5 -0
- data/lib/active_support/testing/error_reporter_assertions.rb +17 -0
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/isolation.rb +0 -2
- 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 +9 -3
- 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 +9 -4
- data/lib/active_support/time_with_zone.rb +36 -23
- data/lib/active_support/values/time_zone.rb +19 -10
- data/lib/active_support/xml_mini.rb +3 -2
- data/lib/active_support.rb +21 -9
- metadata +35 -16
- data/lib/active_support/core_ext/range/each.rb +0 -24
- data/lib/active_support/proxy_object.rb +0 -20
- data/lib/active_support/testing/strict_warnings.rb +0 -43
|
@@ -4,9 +4,9 @@ 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
|
-
require "thread"
|
|
10
10
|
|
|
11
11
|
module ActiveSupport
|
|
12
12
|
# = Active Support \Callbacks
|
|
@@ -67,7 +67,7 @@ module ActiveSupport
|
|
|
67
67
|
|
|
68
68
|
included do
|
|
69
69
|
extend ActiveSupport::DescendantsTracker
|
|
70
|
-
class_attribute :__callbacks, instance_writer: false, default: {}
|
|
70
|
+
class_attribute :__callbacks, instance_writer: false, instance_predicate: false, default: {}
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
CALLBACK_FILTER_TYPES = [:before, :after, :around].freeze
|
|
@@ -499,9 +499,10 @@ module ActiveSupport
|
|
|
499
499
|
when Conditionals::Value
|
|
500
500
|
ProcCall.new(filter)
|
|
501
501
|
when ::Proc
|
|
502
|
-
|
|
502
|
+
case filter.arity
|
|
503
|
+
when 2
|
|
503
504
|
InstanceExec2.new(filter)
|
|
504
|
-
|
|
505
|
+
when 1, -2
|
|
505
506
|
InstanceExec1.new(filter)
|
|
506
507
|
else
|
|
507
508
|
InstanceExec0.new(filter)
|
|
@@ -573,7 +574,7 @@ module ActiveSupport
|
|
|
573
574
|
@name = name
|
|
574
575
|
@config = {
|
|
575
576
|
scope: [:kind],
|
|
576
|
-
terminator:
|
|
577
|
+
terminator: DEFAULT_TERMINATOR
|
|
577
578
|
}.merge!(config)
|
|
578
579
|
@chain = []
|
|
579
580
|
@all_callbacks = nil
|
|
@@ -661,8 +662,8 @@ module ActiveSupport
|
|
|
661
662
|
@chain.delete_if { |c| callback.duplicates?(c) }
|
|
662
663
|
end
|
|
663
664
|
|
|
664
|
-
|
|
665
|
-
|
|
665
|
+
class DefaultTerminator # :nodoc:
|
|
666
|
+
def call(target, result_lambda)
|
|
666
667
|
terminate = true
|
|
667
668
|
catch(:abort) do
|
|
668
669
|
result_lambda.call
|
|
@@ -671,6 +672,7 @@ module ActiveSupport
|
|
|
671
672
|
terminate
|
|
672
673
|
end
|
|
673
674
|
end
|
|
675
|
+
DEFAULT_TERMINATOR = DefaultTerminator.new.freeze
|
|
674
676
|
end
|
|
675
677
|
|
|
676
678
|
module ClassMethods
|
|
@@ -904,12 +906,13 @@ module ActiveSupport
|
|
|
904
906
|
names.each do |name|
|
|
905
907
|
name = name.to_sym
|
|
906
908
|
|
|
907
|
-
([self] + self.descendants).each do |target|
|
|
908
|
-
target.set_callbacks name, CallbackChain.new(name, options)
|
|
909
|
-
end
|
|
910
|
-
|
|
911
909
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
912
|
-
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)
|
|
913
916
|
run_callbacks #{name.inspect}, &block
|
|
914
917
|
end
|
|
915
918
|
|
|
@@ -925,6 +928,10 @@ module ActiveSupport
|
|
|
925
928
|
__callbacks[#{name.inspect}]
|
|
926
929
|
end
|
|
927
930
|
RUBY
|
|
931
|
+
|
|
932
|
+
([self] + self.descendants).each do |target|
|
|
933
|
+
target.set_callbacks name, CallbackChain.new(name, options)
|
|
934
|
+
end
|
|
928
935
|
end
|
|
929
936
|
end
|
|
930
937
|
|
|
@@ -934,9 +941,17 @@ module ActiveSupport
|
|
|
934
941
|
end
|
|
935
942
|
|
|
936
943
|
def set_callbacks(name, callbacks) # :nodoc:
|
|
937
|
-
|
|
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)
|
|
938
948
|
self.__callbacks = __callbacks.dup
|
|
939
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
|
|
940
955
|
self.__callbacks[name.to_sym] = callbacks
|
|
941
956
|
self.__callbacks
|
|
942
957
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module ClassAttribute # :nodoc:
|
|
5
|
+
class << self
|
|
6
|
+
def redefine(owner, name, namespaced_name, value)
|
|
7
|
+
if owner.singleton_class?
|
|
8
|
+
if owner.attached_object.is_a?(Module)
|
|
9
|
+
redefine_method(owner, namespaced_name, private: true) { value }
|
|
10
|
+
else
|
|
11
|
+
redefine_method(owner, name) { value }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
redefine_method(owner.singleton_class, namespaced_name, private: true) { value }
|
|
16
|
+
|
|
17
|
+
redefine_method(owner.singleton_class, "#{namespaced_name}=", private: true) do |new_value|
|
|
18
|
+
if owner.equal?(self)
|
|
19
|
+
value = new_value
|
|
20
|
+
else
|
|
21
|
+
::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, new_value)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def redefine_method(owner, name, private: false, &block)
|
|
27
|
+
owner.silence_redefinition_of_method(name)
|
|
28
|
+
owner.define_method(name, &block)
|
|
29
|
+
owner.send(:private, name) if private
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -55,6 +55,11 @@ module ActiveSupport
|
|
|
55
55
|
@path = path
|
|
56
56
|
@line = line
|
|
57
57
|
@namespaces = Hash.new { |h, k| h[k] = MethodSet.new(k) }
|
|
58
|
+
@sources = []
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def class_eval
|
|
62
|
+
yield @sources
|
|
58
63
|
end
|
|
59
64
|
|
|
60
65
|
def define_cached_method(canonical_name, namespace:, as: nil, &block)
|
|
@@ -65,6 +70,10 @@ module ActiveSupport
|
|
|
65
70
|
@namespaces.each_value do |method_set|
|
|
66
71
|
method_set.apply(@owner, @path, @line - 1)
|
|
67
72
|
end
|
|
73
|
+
|
|
74
|
+
unless @sources.empty?
|
|
75
|
+
@owner.class_eval("# frozen_string_literal: true\n" + @sources.join(";"), @path, @line - 1)
|
|
76
|
+
end
|
|
68
77
|
end
|
|
69
78
|
end
|
|
70
79
|
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
|
|
@@ -19,11 +19,20 @@ module ActiveSupport
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def parse(context: nil, **options)
|
|
22
|
-
source = render(context)
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
source = @content.include?("<%") ? render(context) : @content
|
|
23
|
+
|
|
24
|
+
if source == @content
|
|
25
|
+
if YAML.respond_to?(:unsafe_load)
|
|
26
|
+
YAML.unsafe_load_file(@content_path, **options) || {}
|
|
27
|
+
else
|
|
28
|
+
YAML.load_file(@content_path, **options) || {}
|
|
29
|
+
end
|
|
25
30
|
else
|
|
26
|
-
YAML.
|
|
31
|
+
if YAML.respond_to?(:unsafe_load)
|
|
32
|
+
YAML.unsafe_load(source, **options) || {}
|
|
33
|
+
else
|
|
34
|
+
YAML.load(source, **options) || {}
|
|
35
|
+
end
|
|
27
36
|
end
|
|
28
37
|
rescue Psych::SyntaxError => error
|
|
29
38
|
raise "YAML syntax error occurred while parsing #{@content_path}. " \
|
|
@@ -33,8 +42,7 @@ module ActiveSupport
|
|
|
33
42
|
|
|
34
43
|
private
|
|
35
44
|
def read(content_path)
|
|
36
|
-
require "yaml"
|
|
37
|
-
require "erb"
|
|
45
|
+
require "yaml" unless defined?(YAML)
|
|
38
46
|
|
|
39
47
|
File.read(content_path).tap do |content|
|
|
40
48
|
if content.include?("\u00A0")
|
|
@@ -44,6 +52,7 @@ module ActiveSupport
|
|
|
44
52
|
end
|
|
45
53
|
|
|
46
54
|
def render(context)
|
|
55
|
+
require "erb" unless defined?(ERB)
|
|
47
56
|
erb = ERB.new(@content).tap { |e| e.filename = @content_path }
|
|
48
57
|
context ? erb.result(context) : erb.result
|
|
49
58
|
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.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
require_relative "array/wrap"
|
|
4
|
+
require_relative "array/access"
|
|
5
|
+
require_relative "array/conversions"
|
|
6
|
+
require_relative "array/extract"
|
|
7
|
+
require_relative "array/extract_options"
|
|
8
|
+
require_relative "array/grouping"
|
|
9
|
+
require_relative "array/inquiry"
|
|
@@ -1,16 +1 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "benchmark"
|
|
4
|
-
|
|
5
|
-
class << Benchmark
|
|
6
|
-
# Benchmark realtime in milliseconds.
|
|
7
|
-
#
|
|
8
|
-
# Benchmark.realtime { User.all }
|
|
9
|
-
# # => 8.0e-05
|
|
10
|
-
#
|
|
11
|
-
# Benchmark.ms { User.all }
|
|
12
|
-
# # => 0.074
|
|
13
|
-
def ms(&block)
|
|
14
|
-
1000 * realtime(&block)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/core_ext/module/redefine_method"
|
|
4
|
+
require "active_support/class_attribute"
|
|
4
5
|
|
|
5
6
|
class Class
|
|
6
7
|
# Declare a class-level attribute whose value is inheritable by subclasses.
|
|
@@ -83,32 +84,39 @@ class Class
|
|
|
83
84
|
#
|
|
84
85
|
# class_attribute :settings, default: {}
|
|
85
86
|
def class_attribute(*attrs, instance_accessor: true,
|
|
86
|
-
instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil
|
|
87
|
-
|
|
87
|
+
instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil
|
|
88
|
+
)
|
|
88
89
|
class_methods, methods = [], []
|
|
89
90
|
attrs.each do |name|
|
|
90
91
|
unless name.is_a?(Symbol) || name.is_a?(String)
|
|
91
92
|
raise TypeError, "#{name.inspect} is not a symbol nor a string"
|
|
92
93
|
end
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
RUBY
|
|
95
|
+
name = name.to_sym
|
|
96
|
+
namespaced_name = :"__class_attr_#{name}"
|
|
97
|
+
::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, default)
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
defined?(@#{name}) ? @#{name} : self.class.#{name}
|
|
102
|
-
end
|
|
103
|
-
RUBY
|
|
99
|
+
class_methods << "def #{name}; #{namespaced_name}; end"
|
|
100
|
+
class_methods << "def #{name}=(value); self.#{namespaced_name} = value; end"
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
if singleton_class?
|
|
103
|
+
methods << <<~RUBY if instance_reader
|
|
104
|
+
silence_redefinition_of_method(:#{name})
|
|
105
|
+
def #{name}
|
|
106
|
+
self.singleton_class.#{name}
|
|
107
|
+
end
|
|
108
|
+
RUBY
|
|
109
|
+
else
|
|
110
|
+
methods << <<~RUBY if instance_reader
|
|
111
|
+
silence_redefinition_of_method def #{name}
|
|
112
|
+
if defined?(@#{name})
|
|
113
|
+
@#{name}
|
|
114
|
+
else
|
|
115
|
+
self.class.#{name}
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
RUBY
|
|
119
|
+
end
|
|
112
120
|
|
|
113
121
|
methods << <<~RUBY if instance_writer
|
|
114
122
|
silence_redefinition_of_method(:#{name}=)
|
|
@@ -125,7 +133,5 @@ class Class
|
|
|
125
133
|
|
|
126
134
|
location = caller_locations(1, 1).first
|
|
127
135
|
class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno)
|
|
128
|
-
|
|
129
|
-
attrs.each { |name| public_send("#{name}=", default) }
|
|
130
136
|
end
|
|
131
137
|
end
|