activesupport 5.0.0 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +343 -590
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -4
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/all.rb +5 -3
- data/lib/active_support/array_inquirer.rb +11 -5
- data/lib/active_support/backtrace_cleaner.rb +33 -5
- data/lib/active_support/benchmarkable.rb +5 -3
- data/lib/active_support/builder.rb +3 -1
- data/lib/active_support/cache/file_store.rb +45 -53
- data/lib/active_support/cache/mem_cache_store.rb +81 -79
- data/lib/active_support/cache/memory_store.rb +69 -41
- data/lib/active_support/cache/null_store.rb +11 -4
- data/lib/active_support/cache/redis_cache_store.rb +493 -0
- data/lib/active_support/cache/strategy/local_cache.rb +74 -37
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
- data/lib/active_support/cache.rb +332 -161
- data/lib/active_support/callbacks.rb +657 -586
- data/lib/active_support/concern.rb +79 -6
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +35 -0
- data/lib/active_support/concurrency/share_lock.rb +59 -19
- data/lib/active_support/configurable.rb +15 -17
- data/lib/active_support/configuration_file.rb +46 -0
- data/lib/active_support/core_ext/array/access.rb +21 -7
- data/lib/active_support/core_ext/array/conversions.rb +20 -18
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/extract_options.rb +2 -0
- data/lib/active_support/core_ext/array/grouping.rb +3 -1
- data/lib/active_support/core_ext/array/inquiry.rb +3 -1
- data/lib/active_support/core_ext/array/wrap.rb +2 -0
- data/lib/active_support/core_ext/array.rb +9 -7
- data/lib/active_support/core_ext/benchmark.rb +5 -3
- data/lib/active_support/core_ext/big_decimal/conversions.rb +6 -6
- data/lib/active_support/core_ext/big_decimal.rb +3 -1
- data/lib/active_support/core_ext/class/attribute.rb +52 -49
- data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
- data/lib/active_support/core_ext/class/subclasses.rb +18 -26
- data/lib/active_support/core_ext/class.rb +4 -2
- data/lib/active_support/core_ext/date/acts_like.rb +3 -1
- data/lib/active_support/core_ext/date/blank.rb +3 -1
- data/lib/active_support/core_ext/date/calculations.rb +16 -13
- data/lib/active_support/core_ext/date/conversions.rb +23 -21
- data/lib/active_support/core_ext/date/zones.rb +4 -2
- data/lib/active_support/core_ext/date.rb +7 -5
- data/lib/active_support/core_ext/date_and_time/calculations.rb +82 -53
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -5
- data/lib/active_support/core_ext/date_and_time/zones.rb +9 -9
- data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
- data/lib/active_support/core_ext/date_time/blank.rb +3 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +23 -11
- data/lib/active_support/core_ext/date_time/compatibility.rb +15 -2
- data/lib/active_support/core_ext/date_time/conversions.rb +14 -13
- data/lib/active_support/core_ext/date_time.rb +7 -5
- data/lib/active_support/core_ext/digest/uuid.rb +7 -5
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +165 -29
- data/lib/active_support/core_ext/file/atomic.rb +7 -5
- data/lib/active_support/core_ext/file.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +40 -39
- data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +4 -2
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -2
- data/lib/active_support/core_ext/hash/keys.rb +9 -36
- data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
- data/lib/active_support/core_ext/hash/slice.rb +8 -29
- data/lib/active_support/core_ext/hash.rb +10 -9
- data/lib/active_support/core_ext/integer/inflections.rb +3 -1
- data/lib/active_support/core_ext/integer/multiple.rb +3 -1
- data/lib/active_support/core_ext/integer/time.rb +11 -18
- data/lib/active_support/core_ext/integer.rb +5 -3
- data/lib/active_support/core_ext/kernel/concern.rb +3 -1
- data/lib/active_support/core_ext/kernel/reporting.rb +3 -1
- data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
- data/lib/active_support/core_ext/kernel.rb +5 -4
- data/lib/active_support/core_ext/load_error.rb +2 -23
- data/lib/active_support/core_ext/marshal.rb +6 -2
- data/lib/active_support/core_ext/module/aliasing.rb +5 -48
- data/lib/active_support/core_ext/module/anonymous.rb +2 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +7 -5
- data/lib/active_support/core_ext/module/attribute_accessors.rb +53 -59
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +31 -24
- data/lib/active_support/core_ext/module/concerning.rb +16 -11
- data/lib/active_support/core_ext/module/delegation.rb +159 -44
- data/lib/active_support/core_ext/module/deprecation.rb +2 -0
- data/lib/active_support/core_ext/module/introspection.rb +23 -26
- data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
- data/lib/active_support/core_ext/module/remove_method.rb +5 -23
- data/lib/active_support/core_ext/module.rb +13 -12
- data/lib/active_support/core_ext/name_error.rb +36 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +129 -134
- data/lib/active_support/core_ext/numeric/time.rb +18 -26
- data/lib/active_support/core_ext/numeric.rb +5 -4
- data/lib/active_support/core_ext/object/acts_like.rb +12 -1
- data/lib/active_support/core_ext/object/blank.rb +14 -2
- data/lib/active_support/core_ext/object/conversions.rb +6 -4
- data/lib/active_support/core_ext/object/deep_dup.rb +4 -2
- data/lib/active_support/core_ext/object/duplicable.rb +13 -62
- data/lib/active_support/core_ext/object/inclusion.rb +3 -1
- data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
- data/lib/active_support/core_ext/object/json.rb +42 -15
- data/lib/active_support/core_ext/object/to_param.rb +3 -1
- data/lib/active_support/core_ext/object/to_query.rb +10 -5
- data/lib/active_support/core_ext/object/try.rb +20 -8
- data/lib/active_support/core_ext/object/with_options.rb +15 -2
- data/lib/active_support/core_ext/object.rb +14 -12
- data/lib/active_support/core_ext/range/compare_range.rb +82 -0
- data/lib/active_support/core_ext/range/conversions.rb +35 -25
- data/lib/active_support/core_ext/range/each.rb +5 -2
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +28 -0
- data/lib/active_support/core_ext/range/overlaps.rb +2 -0
- data/lib/active_support/core_ext/range.rb +7 -4
- data/lib/active_support/core_ext/regexp.rb +10 -1
- data/lib/active_support/core_ext/securerandom.rb +28 -6
- data/lib/active_support/core_ext/string/access.rb +9 -18
- data/lib/active_support/core_ext/string/behavior.rb +2 -0
- data/lib/active_support/core_ext/string/conversions.rb +5 -2
- data/lib/active_support/core_ext/string/exclude.rb +2 -0
- data/lib/active_support/core_ext/string/filters.rb +47 -4
- data/lib/active_support/core_ext/string/indent.rb +6 -4
- data/lib/active_support/core_ext/string/inflections.rb +78 -29
- data/lib/active_support/core_ext/string/inquiry.rb +4 -1
- data/lib/active_support/core_ext/string/multibyte.rb +10 -5
- data/lib/active_support/core_ext/string/output_safety.rb +86 -31
- data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
- data/lib/active_support/core_ext/string/strip.rb +5 -1
- data/lib/active_support/core_ext/string/zones.rb +4 -2
- data/lib/active_support/core_ext/string.rb +15 -13
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/time/acts_like.rb +3 -1
- data/lib/active_support/core_ext/time/calculations.rb +117 -45
- data/lib/active_support/core_ext/time/compatibility.rb +13 -2
- data/lib/active_support/core_ext/time/conversions.rb +18 -12
- data/lib/active_support/core_ext/time/zones.rb +9 -7
- data/lib/active_support/core_ext/time.rb +7 -5
- data/lib/active_support/core_ext/uri.rb +12 -7
- data/lib/active_support/core_ext.rb +3 -2
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +208 -0
- data/lib/active_support/dependencies/autoload.rb +2 -0
- data/lib/active_support/dependencies/interlock.rb +7 -1
- data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
- data/lib/active_support/dependencies.rb +172 -98
- data/lib/active_support/deprecation/behaviors.rb +45 -13
- data/lib/active_support/deprecation/constant_accessor.rb +52 -0
- data/lib/active_support/deprecation/disallowed.rb +56 -0
- data/lib/active_support/deprecation/instance_delegator.rb +16 -2
- data/lib/active_support/deprecation/method_wrappers.rb +32 -17
- data/lib/active_support/deprecation/proxy_wrappers.rb +35 -7
- data/lib/active_support/deprecation/reporting.rb +61 -16
- data/lib/active_support/deprecation.rb +17 -9
- data/lib/active_support/descendants_tracker.rb +61 -9
- data/lib/active_support/digest.rb +20 -0
- data/lib/active_support/duration/iso8601_parser.rb +67 -66
- data/lib/active_support/duration/iso8601_serializer.rb +25 -17
- data/lib/active_support/duration.rb +349 -46
- data/lib/active_support/encrypted_configuration.rb +45 -0
- data/lib/active_support/encrypted_file.rb +117 -0
- data/lib/active_support/environment_inquirer.rb +20 -0
- data/lib/active_support/evented_file_update_checker.rb +88 -112
- data/lib/active_support/execution_wrapper.rb +25 -13
- data/lib/active_support/executor.rb +3 -1
- data/lib/active_support/file_update_checker.rb +56 -51
- data/lib/active_support/fork_tracker.rb +62 -0
- data/lib/active_support/gem_version.rb +4 -2
- data/lib/active_support/gzip.rb +7 -5
- data/lib/active_support/hash_with_indifferent_access.rb +153 -49
- data/lib/active_support/i18n.rb +9 -6
- data/lib/active_support/i18n_railtie.rb +30 -20
- data/lib/active_support/inflections.rb +13 -11
- data/lib/active_support/inflector/inflections.rb +28 -15
- data/lib/active_support/inflector/methods.rb +120 -109
- data/lib/active_support/inflector/transliterate.rb +60 -25
- data/lib/active_support/inflector.rb +7 -5
- data/lib/active_support/json/decoding.rb +30 -29
- data/lib/active_support/json/encoding.rb +22 -11
- data/lib/active_support/json.rb +4 -2
- data/lib/active_support/key_generator.rb +6 -36
- data/lib/active_support/lazy_load_hooks.rb +53 -20
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/locale/en.yml +7 -3
- data/lib/active_support/log_subscriber/test_helper.rb +11 -9
- data/lib/active_support/log_subscriber.rb +51 -18
- data/lib/active_support/logger.rb +9 -22
- data/lib/active_support/logger_silence.rb +14 -21
- data/lib/active_support/logger_thread_safe_level.rb +55 -8
- data/lib/active_support/message_encryptor.rb +170 -53
- data/lib/active_support/message_verifier.rb +91 -20
- data/lib/active_support/messages/metadata.rb +80 -0
- data/lib/active_support/messages/rotation_configuration.rb +23 -0
- data/lib/active_support/messages/rotator.rb +57 -0
- data/lib/active_support/multibyte/chars.rb +24 -78
- data/lib/active_support/multibyte/unicode.rb +21 -352
- data/lib/active_support/multibyte.rb +4 -2
- data/lib/active_support/notifications/fanout.rb +121 -19
- data/lib/active_support/notifications/instrumenter.rb +78 -14
- data/lib/active_support/notifications.rb +80 -12
- data/lib/active_support/number_helper/number_converter.rb +17 -16
- data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +5 -3
- data/lib/active_support/number_helper/number_to_human_converter.rb +13 -12
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +11 -13
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +5 -4
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +18 -55
- data/lib/active_support/number_helper/rounding_helper.rb +50 -0
- data/lib/active_support/number_helper.rb +45 -16
- data/lib/active_support/option_merger.rb +25 -4
- data/lib/active_support/ordered_hash.rb +6 -4
- data/lib/active_support/ordered_options.rb +23 -9
- data/lib/active_support/parameter_filter.rb +133 -0
- data/lib/active_support/per_thread_registry.rb +7 -5
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +8 -9
- data/lib/active_support/railtie.rb +62 -11
- data/lib/active_support/reloader.rb +12 -11
- data/lib/active_support/rescuable.rb +20 -11
- data/lib/active_support/secure_compare_rotator.rb +51 -0
- data/lib/active_support/security_utils.rb +26 -15
- data/lib/active_support/string_inquirer.rb +12 -3
- data/lib/active_support/subscriber.rb +77 -23
- data/lib/active_support/tagged_logging.rb +52 -17
- data/lib/active_support/test_case.rb +106 -29
- data/lib/active_support/testing/assertions.rb +144 -8
- data/lib/active_support/testing/autorun.rb +5 -10
- data/lib/active_support/testing/constant_lookup.rb +2 -1
- data/lib/active_support/testing/declarative.rb +3 -1
- data/lib/active_support/testing/deprecation.rb +4 -2
- data/lib/active_support/testing/file_fixtures.rb +4 -0
- data/lib/active_support/testing/isolation.rb +19 -24
- data/lib/active_support/testing/method_call_assertions.rb +31 -2
- data/lib/active_support/testing/parallelization/server.rb +78 -0
- data/lib/active_support/testing/parallelization/worker.rb +100 -0
- data/lib/active_support/testing/parallelization.rb +51 -0
- data/lib/active_support/testing/setup_and_teardown.rb +13 -8
- data/lib/active_support/testing/stream.rb +30 -29
- data/lib/active_support/testing/tagged_logging.rb +3 -1
- data/lib/active_support/testing/time_helpers.rb +125 -24
- data/lib/active_support/time.rb +14 -12
- data/lib/active_support/time_with_zone.rb +142 -55
- data/lib/active_support/values/time_zone.rb +160 -53
- data/lib/active_support/version.rb +3 -1
- data/lib/active_support/xml_mini/jdom.rb +115 -114
- data/lib/active_support/xml_mini/libxml.rb +15 -14
- data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
- data/lib/active_support/xml_mini/nokogiri.rb +13 -13
- data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
- data/lib/active_support/xml_mini/rexml.rb +18 -9
- data/lib/active_support/xml_mini.rb +44 -42
- data/lib/active_support.rb +19 -10
- metadata +79 -37
- data/lib/active_support/concurrency/latch.rb +0 -19
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
- data/lib/active_support/core_ext/hash/compact.rb +0 -20
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -29
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
- data/lib/active_support/core_ext/kernel/debugger.rb +0 -3
- data/lib/active_support/core_ext/module/method_transplanting.rb +0 -3
- data/lib/active_support/core_ext/module/qualified_const.rb +0 -70
- data/lib/active_support/core_ext/module/reachable.rb +0 -8
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -26
- data/lib/active_support/core_ext/range/include_range.rb +0 -23
- data/lib/active_support/core_ext/struct.rb +0 -3
- data/lib/active_support/core_ext/time/marshal.rb +0 -3
- data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,13 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require 'active_support/deprecation'
|
10
|
-
require 'thread'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/descendants_tracker"
|
5
|
+
require "active_support/core_ext/array/extract_options"
|
6
|
+
require "active_support/core_ext/class/attribute"
|
7
|
+
require "active_support/core_ext/string/filters"
|
8
|
+
require "thread"
|
11
9
|
|
12
10
|
module ActiveSupport
|
13
11
|
# Callbacks are code hooks that are run at key points in an object's life cycle.
|
@@ -22,6 +20,9 @@ module ActiveSupport
|
|
22
20
|
# +ClassMethods.set_callback+), and run the installed callbacks at the
|
23
21
|
# appropriate times (via +run_callbacks+).
|
24
22
|
#
|
23
|
+
# By default callbacks are halted by throwing +:abort+.
|
24
|
+
# See +ClassMethods.define_callbacks+ for details.
|
25
|
+
#
|
25
26
|
# Three kinds of callbacks are supported: before callbacks, run before a
|
26
27
|
# certain event; after callbacks, run after the event; and around callbacks,
|
27
28
|
# blocks that surround the event, triggering it when they yield. Callback code
|
@@ -63,16 +64,11 @@ module ActiveSupport
|
|
63
64
|
|
64
65
|
included do
|
65
66
|
extend ActiveSupport::DescendantsTracker
|
67
|
+
class_attribute :__callbacks, instance_writer: false, default: {}
|
66
68
|
end
|
67
69
|
|
68
70
|
CALLBACK_FILTER_TYPES = [:before, :after, :around]
|
69
71
|
|
70
|
-
# If true, Active Record and Active Model callbacks returning +false+ will
|
71
|
-
# halt the entire callback chain and display a deprecation message.
|
72
|
-
# If false, callback chains will only be halted by calling +throw :abort+.
|
73
|
-
# Defaults to +true+.
|
74
|
-
mattr_accessor(:halt_and_display_warning_on_return_false, instance_writer: false) { true }
|
75
|
-
|
76
72
|
# Runs the callbacks for the given event.
|
77
73
|
#
|
78
74
|
# Calls the before and around callbacks in the order they were set, yields
|
@@ -86,706 +82,781 @@ module ActiveSupport
|
|
86
82
|
# run_callbacks :save do
|
87
83
|
# save
|
88
84
|
# end
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
85
|
+
#
|
86
|
+
#--
|
87
|
+
#
|
88
|
+
# As this method is used in many places, and often wraps large portions of
|
89
|
+
# user code, it has an additional design goal of minimizing its impact on
|
90
|
+
# the visible call stack. An exception from inside a :before or :after
|
91
|
+
# callback can be as noisy as it likes -- but when control has passed
|
92
|
+
# smoothly through and into the supplied block, we want as little evidence
|
93
|
+
# as possible that we were here.
|
94
|
+
def run_callbacks(kind)
|
95
|
+
callbacks = __callbacks[kind.to_sym]
|
94
96
|
|
95
|
-
def __run_callbacks__(callbacks, &block)
|
96
97
|
if callbacks.empty?
|
97
98
|
yield if block_given?
|
98
99
|
else
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
100
|
+
env = Filters::Environment.new(self, false, nil)
|
101
|
+
next_sequence = callbacks.compile
|
102
|
+
|
103
|
+
# Common case: no 'around' callbacks defined
|
104
|
+
if next_sequence.final?
|
105
|
+
next_sequence.invoke_before(env)
|
106
|
+
env.value = !env.halted && (!block_given? || yield)
|
107
|
+
next_sequence.invoke_after(env)
|
108
|
+
env.value
|
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
|
110
136
|
|
111
|
-
|
112
|
-
class Value
|
113
|
-
def initialize(&block)
|
114
|
-
@block = block
|
137
|
+
invoke_sequence.call
|
115
138
|
end
|
116
|
-
def call(target, value); @block.call(value); end
|
117
139
|
end
|
118
140
|
end
|
119
141
|
|
120
|
-
|
121
|
-
|
142
|
+
private
|
143
|
+
# A hook invoked every time a before callback is halted.
|
144
|
+
# This can be overridden in ActiveSupport::Callbacks implementors in order
|
145
|
+
# to provide better debugging/logging.
|
146
|
+
def halted_callback_hook(filter, name)
|
147
|
+
end
|
122
148
|
|
123
|
-
|
124
|
-
|
125
|
-
block
|
126
|
-
|
127
|
-
|
149
|
+
module Conditionals # :nodoc:
|
150
|
+
class Value
|
151
|
+
def initialize(&block)
|
152
|
+
@block = block
|
153
|
+
end
|
154
|
+
def call(target, value); @block.call(value); end
|
128
155
|
end
|
129
156
|
end
|
130
|
-
ENDING = End.new
|
131
157
|
|
132
|
-
|
133
|
-
|
134
|
-
halted_lambda = chain_config[:terminator]
|
158
|
+
module Filters
|
159
|
+
Environment = Struct.new(:target, :halted, :value)
|
135
160
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
halting(callback_sequence, user_callback, halted_lambda, filter)
|
140
|
-
end
|
141
|
-
end
|
161
|
+
class Before
|
162
|
+
def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter, name)
|
163
|
+
halted_lambda = chain_config[:terminator]
|
142
164
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
165
|
+
if user_conditions.any?
|
166
|
+
halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
|
167
|
+
else
|
168
|
+
halting(callback_sequence, user_callback, halted_lambda, filter, name)
|
169
|
+
end
|
170
|
+
end
|
148
171
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
172
|
+
def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
|
173
|
+
callback_sequence.before do |env|
|
174
|
+
target = env.target
|
175
|
+
value = env.value
|
176
|
+
halted = env.halted
|
177
|
+
|
178
|
+
if !halted && user_conditions.all? { |c| c.call(target, value) }
|
179
|
+
result_lambda = -> { user_callback.call target, value }
|
180
|
+
env.halted = halted_lambda.call(target, result_lambda)
|
181
|
+
if env.halted
|
182
|
+
target.send :halted_callback_hook, filter, name
|
183
|
+
end
|
154
184
|
end
|
185
|
+
|
186
|
+
env
|
155
187
|
end
|
188
|
+
end
|
189
|
+
private_class_method :halting_and_conditional
|
190
|
+
|
191
|
+
def self.halting(callback_sequence, user_callback, halted_lambda, filter, name)
|
192
|
+
callback_sequence.before do |env|
|
193
|
+
target = env.target
|
194
|
+
value = env.value
|
195
|
+
halted = env.halted
|
196
|
+
|
197
|
+
unless halted
|
198
|
+
result_lambda = -> { user_callback.call target, value }
|
199
|
+
env.halted = halted_lambda.call(target, result_lambda)
|
200
|
+
if env.halted
|
201
|
+
target.send :halted_callback_hook, filter, name
|
202
|
+
end
|
203
|
+
end
|
156
204
|
|
157
|
-
|
205
|
+
env
|
206
|
+
end
|
158
207
|
end
|
208
|
+
private_class_method :halting
|
159
209
|
end
|
160
|
-
private_class_method :halting_and_conditional
|
161
210
|
|
162
|
-
|
163
|
-
callback_sequence
|
164
|
-
|
165
|
-
|
166
|
-
|
211
|
+
class After
|
212
|
+
def self.build(callback_sequence, user_callback, user_conditions, chain_config)
|
213
|
+
if chain_config[:skip_after_callbacks_if_terminated]
|
214
|
+
if user_conditions.any?
|
215
|
+
halting_and_conditional(callback_sequence, user_callback, user_conditions)
|
216
|
+
else
|
217
|
+
halting(callback_sequence, user_callback)
|
218
|
+
end
|
219
|
+
else
|
220
|
+
if user_conditions.any?
|
221
|
+
conditional callback_sequence, user_callback, user_conditions
|
222
|
+
else
|
223
|
+
simple callback_sequence, user_callback
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
167
227
|
|
168
|
-
|
169
|
-
|
170
|
-
|
228
|
+
def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
|
229
|
+
callback_sequence.after do |env|
|
230
|
+
target = env.target
|
231
|
+
value = env.value
|
232
|
+
halted = env.halted
|
171
233
|
|
172
|
-
if
|
173
|
-
|
234
|
+
if !halted && user_conditions.all? { |c| c.call(target, value) }
|
235
|
+
user_callback.call target, value
|
174
236
|
end
|
175
|
-
end
|
176
237
|
|
177
|
-
|
238
|
+
env
|
239
|
+
end
|
178
240
|
end
|
179
|
-
|
180
|
-
private_class_method :halting
|
181
|
-
end
|
241
|
+
private_class_method :halting_and_conditional
|
182
242
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
end
|
191
|
-
else
|
192
|
-
if user_conditions.any?
|
193
|
-
conditional callback_sequence, user_callback, user_conditions
|
194
|
-
else
|
195
|
-
simple callback_sequence, user_callback
|
243
|
+
def self.halting(callback_sequence, user_callback)
|
244
|
+
callback_sequence.after do |env|
|
245
|
+
unless env.halted
|
246
|
+
user_callback.call env.target, env.value
|
247
|
+
end
|
248
|
+
|
249
|
+
env
|
196
250
|
end
|
197
251
|
end
|
198
|
-
|
252
|
+
private_class_method :halting
|
199
253
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
halted = env.halted
|
254
|
+
def self.conditional(callback_sequence, user_callback, user_conditions)
|
255
|
+
callback_sequence.after do |env|
|
256
|
+
target = env.target
|
257
|
+
value = env.value
|
205
258
|
|
206
|
-
|
207
|
-
|
208
|
-
|
259
|
+
if user_conditions.all? { |c| c.call(target, value) }
|
260
|
+
user_callback.call target, value
|
261
|
+
end
|
209
262
|
|
210
|
-
|
263
|
+
env
|
264
|
+
end
|
211
265
|
end
|
212
|
-
|
213
|
-
private_class_method :halting_and_conditional
|
266
|
+
private_class_method :conditional
|
214
267
|
|
215
|
-
|
216
|
-
|
217
|
-
unless env.halted
|
268
|
+
def self.simple(callback_sequence, user_callback)
|
269
|
+
callback_sequence.after do |env|
|
218
270
|
user_callback.call env.target, env.value
|
271
|
+
|
272
|
+
env
|
219
273
|
end
|
274
|
+
end
|
275
|
+
private_class_method :simple
|
276
|
+
end
|
277
|
+
end
|
220
278
|
|
221
|
-
|
279
|
+
class Callback #:nodoc:#
|
280
|
+
def self.build(chain, filter, kind, options)
|
281
|
+
if filter.is_a?(String)
|
282
|
+
raise ArgumentError, <<-MSG.squish
|
283
|
+
Passing string to define a callback is not supported. See the `.set_callback`
|
284
|
+
documentation to see supported values.
|
285
|
+
MSG
|
222
286
|
end
|
287
|
+
|
288
|
+
new chain.name, filter, kind, options, chain.config
|
223
289
|
end
|
224
|
-
private_class_method :halting
|
225
290
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
291
|
+
attr_accessor :kind, :name
|
292
|
+
attr_reader :chain_config
|
293
|
+
|
294
|
+
def initialize(name, filter, kind, options, chain_config)
|
295
|
+
@chain_config = chain_config
|
296
|
+
@name = name
|
297
|
+
@kind = kind
|
298
|
+
@filter = filter
|
299
|
+
@key = compute_identifier filter
|
300
|
+
@if = check_conditionals(options[:if])
|
301
|
+
@unless = check_conditionals(options[:unless])
|
302
|
+
end
|
230
303
|
|
231
|
-
|
232
|
-
|
233
|
-
end
|
304
|
+
def filter; @key; end
|
305
|
+
def raw_filter; @filter; end
|
234
306
|
|
235
|
-
|
236
|
-
|
307
|
+
def merge_conditional_options(chain, if_option:, unless_option:)
|
308
|
+
options = {
|
309
|
+
if: @if.dup,
|
310
|
+
unless: @unless.dup
|
311
|
+
}
|
312
|
+
|
313
|
+
options[:if].concat Array(unless_option)
|
314
|
+
options[:unless].concat Array(if_option)
|
315
|
+
|
316
|
+
self.class.build chain, @filter, @kind, options
|
237
317
|
end
|
238
|
-
private_class_method :conditional
|
239
318
|
|
240
|
-
def
|
241
|
-
|
242
|
-
|
319
|
+
def matches?(_kind, _filter)
|
320
|
+
@kind == _kind && filter == _filter
|
321
|
+
end
|
243
322
|
|
244
|
-
|
323
|
+
def duplicates?(other)
|
324
|
+
case @filter
|
325
|
+
when Symbol
|
326
|
+
matches?(other.kind, other.filter)
|
327
|
+
else
|
328
|
+
false
|
245
329
|
end
|
246
330
|
end
|
247
|
-
private_class_method :simple
|
248
|
-
end
|
249
331
|
|
250
|
-
|
251
|
-
def
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
332
|
+
# Wraps code with filter
|
333
|
+
def apply(callback_sequence)
|
334
|
+
user_conditions = conditions_lambdas
|
335
|
+
user_callback = CallTemplate.build(@filter, self)
|
336
|
+
|
337
|
+
case kind
|
338
|
+
when :before
|
339
|
+
Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter, name)
|
340
|
+
when :after
|
341
|
+
Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config)
|
342
|
+
when :around
|
343
|
+
callback_sequence.around(user_callback, user_conditions)
|
256
344
|
end
|
257
345
|
end
|
258
346
|
|
259
|
-
def
|
260
|
-
|
261
|
-
|
262
|
-
value = env.value
|
263
|
-
halted = env.halted
|
347
|
+
def current_scopes
|
348
|
+
Array(chain_config[:scope]).map { |s| public_send(s) }
|
349
|
+
end
|
264
350
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
351
|
+
private
|
352
|
+
EMPTY_ARRAY = [].freeze
|
353
|
+
private_constant :EMPTY_ARRAY
|
354
|
+
|
355
|
+
def check_conditionals(conditionals)
|
356
|
+
return EMPTY_ARRAY if conditionals.blank?
|
357
|
+
|
358
|
+
conditionals = Array(conditionals)
|
359
|
+
if conditionals.any? { |c| c.is_a?(String) }
|
360
|
+
raise ArgumentError, <<-MSG.squish
|
361
|
+
Passing string to be evaluated in :if and :unless conditional
|
362
|
+
options is not supported. Pass a symbol for an instance method,
|
363
|
+
or a lambda, proc or block, instead.
|
364
|
+
MSG
|
272
365
|
end
|
273
|
-
end
|
274
|
-
end
|
275
|
-
private_class_method :halting_and_conditional
|
276
366
|
|
277
|
-
|
278
|
-
|
279
|
-
target = env.target
|
280
|
-
value = env.value
|
367
|
+
conditionals.freeze
|
368
|
+
end
|
281
369
|
|
282
|
-
|
283
|
-
|
370
|
+
def compute_identifier(filter)
|
371
|
+
case filter
|
372
|
+
when ::Proc
|
373
|
+
filter.object_id
|
284
374
|
else
|
285
|
-
|
286
|
-
run.call.value
|
287
|
-
}
|
288
|
-
env
|
375
|
+
filter
|
289
376
|
end
|
290
377
|
end
|
291
|
-
|
292
|
-
|
378
|
+
|
379
|
+
def conditions_lambdas
|
380
|
+
@if.map { |c| CallTemplate.build(c, self).make_lambda } +
|
381
|
+
@unless.map { |c| CallTemplate.build(c, self).inverted_lambda }
|
382
|
+
end
|
293
383
|
end
|
294
|
-
end
|
295
384
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
385
|
+
# A future invocation of user-supplied code (either as a callback,
|
386
|
+
# or a condition filter).
|
387
|
+
class CallTemplate # :nodoc:
|
388
|
+
def initialize(target, method, arguments, block)
|
389
|
+
@override_target = target
|
390
|
+
@method_name = method
|
391
|
+
@arguments = arguments
|
392
|
+
@override_block = block
|
303
393
|
end
|
304
394
|
|
305
|
-
|
306
|
-
|
395
|
+
# Return the parts needed to make this call, with the given
|
396
|
+
# input values.
|
397
|
+
#
|
398
|
+
# Returns an array of the form:
|
399
|
+
#
|
400
|
+
# [target, block, method, *arguments]
|
401
|
+
#
|
402
|
+
# This array can be used as such:
|
403
|
+
#
|
404
|
+
# target.send(method, *arguments, &block)
|
405
|
+
#
|
406
|
+
# The actual invocation is left up to the caller to minimize
|
407
|
+
# call stack pollution.
|
408
|
+
def expand(target, value, block)
|
409
|
+
expanded = [@override_target || target, @override_block || block, @method_name]
|
410
|
+
|
411
|
+
@arguments.each do |arg|
|
412
|
+
case arg
|
413
|
+
when :value then expanded << value
|
414
|
+
when :target then expanded << target
|
415
|
+
when :block then expanded << (block || raise(ArgumentError))
|
416
|
+
end
|
417
|
+
end
|
307
418
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
def initialize(name, filter, kind, options, chain_config)
|
312
|
-
@chain_config = chain_config
|
313
|
-
@name = name
|
314
|
-
@kind = kind
|
315
|
-
@filter = filter
|
316
|
-
@key = compute_identifier filter
|
317
|
-
@if = Array(options[:if])
|
318
|
-
@unless = Array(options[:unless])
|
319
|
-
end
|
419
|
+
expanded
|
420
|
+
end
|
320
421
|
|
321
|
-
|
322
|
-
|
422
|
+
# Return a lambda that will make this call when given the input
|
423
|
+
# values.
|
424
|
+
def make_lambda
|
425
|
+
lambda do |target, value, &block|
|
426
|
+
target, block, method, *arguments = expand(target, value, block)
|
427
|
+
target.send(method, *arguments, &block)
|
428
|
+
end
|
429
|
+
end
|
323
430
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
431
|
+
# Return a lambda that will make this call when given the input
|
432
|
+
# values, but then return the boolean inverse of that result.
|
433
|
+
def inverted_lambda
|
434
|
+
lambda do |target, value, &block|
|
435
|
+
target, block, method, *arguments = expand(target, value, block)
|
436
|
+
! target.send(method, *arguments, &block)
|
437
|
+
end
|
438
|
+
end
|
329
439
|
|
330
|
-
|
331
|
-
|
440
|
+
# Filters support:
|
441
|
+
#
|
442
|
+
# Symbols:: A method to call.
|
443
|
+
# Procs:: A proc to call with the object.
|
444
|
+
# Objects:: An object with a <tt>before_foo</tt> method on it to call.
|
445
|
+
#
|
446
|
+
# All of these objects are converted into a CallTemplate and handled
|
447
|
+
# the same after this point.
|
448
|
+
def self.build(filter, callback)
|
449
|
+
case filter
|
450
|
+
when Symbol
|
451
|
+
new(nil, filter, [], nil)
|
452
|
+
when Conditionals::Value
|
453
|
+
new(filter, :call, [:target, :value], nil)
|
454
|
+
when ::Proc
|
455
|
+
if filter.arity > 1
|
456
|
+
new(nil, :instance_exec, [:target, :block], filter)
|
457
|
+
elsif filter.arity > 0
|
458
|
+
new(nil, :instance_exec, [:target], filter)
|
459
|
+
else
|
460
|
+
new(nil, :instance_exec, [], filter)
|
461
|
+
end
|
462
|
+
else
|
463
|
+
method_to_call = callback.current_scopes.join("_")
|
332
464
|
|
333
|
-
|
465
|
+
new(filter, method_to_call, [:target], nil)
|
466
|
+
end
|
467
|
+
end
|
334
468
|
end
|
335
469
|
|
336
|
-
|
337
|
-
|
338
|
-
|
470
|
+
# Execute before and after filters in a sequence instead of
|
471
|
+
# chaining them with nested lambda calls, see:
|
472
|
+
# https://github.com/rails/rails/issues/18011
|
473
|
+
class CallbackSequence # :nodoc:
|
474
|
+
def initialize(nested = nil, call_template = nil, user_conditions = nil)
|
475
|
+
@nested = nested
|
476
|
+
@call_template = call_template
|
477
|
+
@user_conditions = user_conditions
|
339
478
|
|
340
|
-
|
341
|
-
|
342
|
-
when Symbol, String
|
343
|
-
matches?(other.kind, other.filter)
|
344
|
-
else
|
345
|
-
false
|
479
|
+
@before = []
|
480
|
+
@after = []
|
346
481
|
end
|
347
|
-
end
|
348
482
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
483
|
+
def before(&before)
|
484
|
+
@before.unshift(before)
|
485
|
+
self
|
486
|
+
end
|
353
487
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
when :after
|
358
|
-
Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
|
359
|
-
when :around
|
360
|
-
Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
|
488
|
+
def after(&after)
|
489
|
+
@after.push(after)
|
490
|
+
self
|
361
491
|
end
|
362
|
-
end
|
363
492
|
|
364
|
-
|
493
|
+
def around(call_template, user_conditions)
|
494
|
+
CallbackSequence.new(self, call_template, user_conditions)
|
495
|
+
end
|
365
496
|
|
366
|
-
|
367
|
-
|
368
|
-
|
497
|
+
def skip?(arg)
|
498
|
+
arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) }
|
499
|
+
end
|
369
500
|
|
370
|
-
|
371
|
-
#
|
372
|
-
# Symbols:: A method to call.
|
373
|
-
# Strings:: Some content to evaluate.
|
374
|
-
# Procs:: A proc to call with the object.
|
375
|
-
# Objects:: An object with a <tt>before_foo</tt> method on it to call.
|
376
|
-
#
|
377
|
-
# All of these objects are converted into a lambda and handled
|
378
|
-
# the same after this point.
|
379
|
-
def make_lambda(filter)
|
380
|
-
case filter
|
381
|
-
when Symbol
|
382
|
-
lambda { |target, _, &blk| target.send filter, &blk }
|
383
|
-
when String
|
384
|
-
l = eval "lambda { |value| #{filter} }"
|
385
|
-
lambda { |target, value| target.instance_exec(value, &l) }
|
386
|
-
when Conditionals::Value then filter
|
387
|
-
when ::Proc
|
388
|
-
if filter.arity > 1
|
389
|
-
return lambda { |target, _, &block|
|
390
|
-
raise ArgumentError unless block
|
391
|
-
target.instance_exec(target, block, &filter)
|
392
|
-
}
|
393
|
-
end
|
394
|
-
|
395
|
-
if filter.arity <= 0
|
396
|
-
lambda { |target, _| target.instance_exec(&filter) }
|
397
|
-
else
|
398
|
-
lambda { |target, _| target.instance_exec(target, &filter) }
|
399
|
-
end
|
400
|
-
else
|
401
|
-
scopes = Array(chain_config[:scope])
|
402
|
-
method_to_call = scopes.map{ |s| public_send(s) }.join("_")
|
501
|
+
attr_reader :nested
|
403
502
|
|
404
|
-
|
405
|
-
|
406
|
-
}
|
503
|
+
def final?
|
504
|
+
!@call_template
|
407
505
|
end
|
408
|
-
end
|
409
506
|
|
410
|
-
|
411
|
-
|
412
|
-
when String, ::Proc
|
413
|
-
filter.object_id
|
414
|
-
else
|
415
|
-
filter
|
507
|
+
def expand_call_template(arg, block)
|
508
|
+
@call_template.expand(arg.target, arg.value, block)
|
416
509
|
end
|
417
|
-
end
|
418
510
|
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
end
|
423
|
-
end
|
511
|
+
def invoke_before(arg)
|
512
|
+
@before.each { |b| b.call(arg) }
|
513
|
+
end
|
424
514
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
class CallbackSequence
|
429
|
-
def initialize(&call)
|
430
|
-
@call = call
|
431
|
-
@before = []
|
432
|
-
@after = []
|
515
|
+
def invoke_after(arg)
|
516
|
+
@after.each { |a| a.call(arg) }
|
517
|
+
end
|
433
518
|
end
|
434
519
|
|
435
|
-
|
436
|
-
|
437
|
-
self
|
438
|
-
end
|
520
|
+
class CallbackChain #:nodoc:#
|
521
|
+
include Enumerable
|
439
522
|
|
440
|
-
|
441
|
-
@after.push(after)
|
442
|
-
self
|
443
|
-
end
|
523
|
+
attr_reader :name, :config
|
444
524
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
525
|
+
def initialize(name, config)
|
526
|
+
@name = name
|
527
|
+
@config = {
|
528
|
+
scope: [:kind],
|
529
|
+
terminator: default_terminator
|
530
|
+
}.merge!(config)
|
531
|
+
@chain = []
|
532
|
+
@callbacks = nil
|
533
|
+
@mutex = Mutex.new
|
450
534
|
end
|
451
|
-
end
|
452
535
|
|
453
|
-
|
454
|
-
@
|
455
|
-
|
456
|
-
@after.each { |a| a.call(arg) }
|
457
|
-
value
|
458
|
-
end
|
459
|
-
end
|
460
|
-
|
461
|
-
# An Array with a compile method.
|
462
|
-
class CallbackChain #:nodoc:#
|
463
|
-
include Enumerable
|
464
|
-
|
465
|
-
attr_reader :name, :config
|
466
|
-
|
467
|
-
def initialize(name, config)
|
468
|
-
@name = name
|
469
|
-
@config = {
|
470
|
-
scope: [:kind],
|
471
|
-
terminator: default_terminator
|
472
|
-
}.merge!(config)
|
473
|
-
@chain = []
|
474
|
-
@callbacks = nil
|
475
|
-
@mutex = Mutex.new
|
476
|
-
end
|
477
|
-
|
478
|
-
def each(&block); @chain.each(&block); end
|
479
|
-
def index(o); @chain.index(o); end
|
480
|
-
def empty?; @chain.empty?; end
|
536
|
+
def each(&block); @chain.each(&block); end
|
537
|
+
def index(o); @chain.index(o); end
|
538
|
+
def empty?; @chain.empty?; end
|
481
539
|
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
540
|
+
def insert(index, o)
|
541
|
+
@callbacks = nil
|
542
|
+
@chain.insert(index, o)
|
543
|
+
end
|
486
544
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
545
|
+
def delete(o)
|
546
|
+
@callbacks = nil
|
547
|
+
@chain.delete(o)
|
548
|
+
end
|
491
549
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
550
|
+
def clear
|
551
|
+
@callbacks = nil
|
552
|
+
@chain.clear
|
553
|
+
self
|
554
|
+
end
|
497
555
|
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
556
|
+
def initialize_copy(other)
|
557
|
+
@callbacks = nil
|
558
|
+
@chain = other.chain.dup
|
559
|
+
@mutex = Mutex.new
|
560
|
+
end
|
503
561
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
562
|
+
def compile
|
563
|
+
@callbacks || @mutex.synchronize do
|
564
|
+
final_sequence = CallbackSequence.new
|
565
|
+
@callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
|
566
|
+
callback.apply callback_sequence
|
567
|
+
end
|
509
568
|
end
|
510
569
|
end
|
511
|
-
end
|
512
570
|
|
513
|
-
|
514
|
-
|
515
|
-
|
571
|
+
def append(*callbacks)
|
572
|
+
callbacks.each { |c| append_one(c) }
|
573
|
+
end
|
516
574
|
|
517
|
-
|
518
|
-
|
519
|
-
|
575
|
+
def prepend(*callbacks)
|
576
|
+
callbacks.each { |c| prepend_one(c) }
|
577
|
+
end
|
520
578
|
|
521
|
-
|
522
|
-
|
579
|
+
protected
|
580
|
+
attr_reader :chain
|
523
581
|
|
524
|
-
|
582
|
+
private
|
583
|
+
def append_one(callback)
|
584
|
+
@callbacks = nil
|
585
|
+
remove_duplicates(callback)
|
586
|
+
@chain.push(callback)
|
587
|
+
end
|
525
588
|
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
589
|
+
def prepend_one(callback)
|
590
|
+
@callbacks = nil
|
591
|
+
remove_duplicates(callback)
|
592
|
+
@chain.unshift(callback)
|
593
|
+
end
|
531
594
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
end
|
595
|
+
def remove_duplicates(callback)
|
596
|
+
@callbacks = nil
|
597
|
+
@chain.delete_if { |c| callback.duplicates?(c) }
|
598
|
+
end
|
537
599
|
|
538
|
-
|
539
|
-
|
540
|
-
|
600
|
+
def default_terminator
|
601
|
+
Proc.new do |target, result_lambda|
|
602
|
+
terminate = true
|
603
|
+
catch(:abort) do
|
604
|
+
result_lambda.call
|
605
|
+
terminate = false
|
606
|
+
end
|
607
|
+
terminate
|
608
|
+
end
|
609
|
+
end
|
541
610
|
end
|
542
611
|
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
612
|
+
module ClassMethods
|
613
|
+
def normalize_callback_params(filters, block) # :nodoc:
|
614
|
+
type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
|
615
|
+
options = filters.extract_options!
|
616
|
+
filters.unshift(block) if block
|
617
|
+
[type, filters, options.dup]
|
618
|
+
end
|
619
|
+
|
620
|
+
# This is used internally to append, prepend and skip callbacks to the
|
621
|
+
# CallbackChain.
|
622
|
+
def __update_callbacks(name) #:nodoc:
|
623
|
+
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
|
624
|
+
chain = target.get_callbacks name
|
625
|
+
yield target, chain.dup
|
549
626
|
end
|
550
|
-
terminate
|
551
627
|
end
|
552
|
-
end
|
553
|
-
end
|
554
628
|
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
629
|
+
# Install a callback for the given event.
|
630
|
+
#
|
631
|
+
# set_callback :save, :before, :before_method
|
632
|
+
# set_callback :save, :after, :after_method, if: :condition
|
633
|
+
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
|
634
|
+
#
|
635
|
+
# The second argument indicates whether the callback is to be run +:before+,
|
636
|
+
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
|
637
|
+
# means the first example above can also be written as:
|
638
|
+
#
|
639
|
+
# set_callback :save, :before_method
|
640
|
+
#
|
641
|
+
# The callback can be specified as a symbol naming an instance method; as a
|
642
|
+
# proc, lambda, or block; or as an object that responds to a certain method
|
643
|
+
# determined by the <tt>:scope</tt> argument to +define_callbacks+.
|
644
|
+
#
|
645
|
+
# If a proc, lambda, or block is given, its body is evaluated in the context
|
646
|
+
# of the current object. It can also optionally accept the current object as
|
647
|
+
# an argument.
|
648
|
+
#
|
649
|
+
# Before and around callbacks are called in the order that they are set;
|
650
|
+
# after callbacks are called in the reverse order.
|
651
|
+
#
|
652
|
+
# Around callbacks can access the return value from the event, if it
|
653
|
+
# wasn't halted, from the +yield+ call.
|
654
|
+
#
|
655
|
+
# ===== Options
|
656
|
+
#
|
657
|
+
# * <tt>:if</tt> - A symbol or an array of symbols, each naming an instance
|
658
|
+
# method or a proc; the callback will be called only when they all return
|
659
|
+
# a true value.
|
660
|
+
#
|
661
|
+
# If a proc is given, its body is evaluated in the context of the
|
662
|
+
# current object. It can also optionally accept the current object as
|
663
|
+
# an argument.
|
664
|
+
# * <tt>:unless</tt> - A symbol or an array of symbols, each naming an
|
665
|
+
# instance method or a proc; the callback will be called only when they
|
666
|
+
# all return a false value.
|
667
|
+
#
|
668
|
+
# If a proc is given, its body is evaluated in the context of the
|
669
|
+
# current object. It can also optionally accept the current object as
|
670
|
+
# an argument.
|
671
|
+
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
|
672
|
+
# existing chain rather than appended.
|
673
|
+
def set_callback(name, *filter_list, &block)
|
674
|
+
type, filters, options = normalize_callback_params(filter_list, block)
|
675
|
+
|
676
|
+
self_chain = get_callbacks name
|
677
|
+
mapped = filters.map do |filter|
|
678
|
+
Callback.build(self_chain, filter, type, options)
|
679
|
+
end
|
562
680
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
chain = target.get_callbacks name
|
568
|
-
yield target, chain.dup
|
681
|
+
__update_callbacks(name) do |target, chain|
|
682
|
+
options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
|
683
|
+
target.set_callbacks name, chain
|
684
|
+
end
|
569
685
|
end
|
570
|
-
end
|
571
686
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
# after callbacks are called in the reverse order.
|
595
|
-
#
|
596
|
-
# Around callbacks can access the return value from the event, if it
|
597
|
-
# wasn't halted, from the +yield+ call.
|
598
|
-
#
|
599
|
-
# ===== Options
|
600
|
-
#
|
601
|
-
# * <tt>:if</tt> - A symbol, a string or an array of symbols and strings,
|
602
|
-
# each naming an instance method or a proc; the callback will be called
|
603
|
-
# only when they all return a true value.
|
604
|
-
# * <tt>:unless</tt> - A symbol, a string or an array of symbols and
|
605
|
-
# strings, each naming an instance method or a proc; the callback will
|
606
|
-
# be called only when they all return a false value.
|
607
|
-
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
|
608
|
-
# existing chain rather than appended.
|
609
|
-
def set_callback(name, *filter_list, &block)
|
610
|
-
type, filters, options = normalize_callback_params(filter_list, block)
|
611
|
-
self_chain = get_callbacks name
|
612
|
-
mapped = filters.map do |filter|
|
613
|
-
Callback.build(self_chain, filter, type, options)
|
614
|
-
end
|
615
|
-
|
616
|
-
__update_callbacks(name) do |target, chain|
|
617
|
-
options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
|
618
|
-
target.set_callbacks name, chain
|
619
|
-
end
|
620
|
-
end
|
687
|
+
# Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
|
688
|
+
# <tt>:unless</tt> options may be passed in order to control when the
|
689
|
+
# callback is skipped.
|
690
|
+
#
|
691
|
+
# class Writer < Person
|
692
|
+
# skip_callback :validate, :before, :check_membership, if: -> { age > 18 }
|
693
|
+
# end
|
694
|
+
#
|
695
|
+
# An <tt>ArgumentError</tt> will be raised if the callback has not
|
696
|
+
# already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
|
697
|
+
def skip_callback(name, *filter_list, &block)
|
698
|
+
type, filters, options = normalize_callback_params(filter_list, block)
|
699
|
+
|
700
|
+
options[:raise] = true unless options.key?(:raise)
|
701
|
+
|
702
|
+
__update_callbacks(name) do |target, chain|
|
703
|
+
filters.each do |filter|
|
704
|
+
callback = chain.find { |c| c.matches?(type, filter) }
|
705
|
+
|
706
|
+
if !callback && options[:raise]
|
707
|
+
raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined"
|
708
|
+
end
|
621
709
|
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
# class Writer < Person
|
627
|
-
# skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
|
628
|
-
# end
|
629
|
-
#
|
630
|
-
# An <tt>ArgumentError</tt> will be raised if the callback has not
|
631
|
-
# already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
|
632
|
-
def skip_callback(name, *filter_list, &block)
|
633
|
-
type, filters, options = normalize_callback_params(filter_list, block)
|
634
|
-
options[:raise] = true unless options.key?(:raise)
|
635
|
-
|
636
|
-
__update_callbacks(name) do |target, chain|
|
637
|
-
filters.each do |filter|
|
638
|
-
callback = chain.find {|c| c.matches?(type, filter) }
|
639
|
-
|
640
|
-
if !callback && options[:raise]
|
641
|
-
raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined"
|
642
|
-
end
|
710
|
+
if callback && (options.key?(:if) || options.key?(:unless))
|
711
|
+
new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
|
712
|
+
chain.insert(chain.index(callback), new_callback)
|
713
|
+
end
|
643
714
|
|
644
|
-
|
645
|
-
new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
|
646
|
-
chain.insert(chain.index(callback), new_callback)
|
715
|
+
chain.delete(callback)
|
647
716
|
end
|
648
|
-
|
649
|
-
chain.delete(callback)
|
717
|
+
target.set_callbacks name, chain
|
650
718
|
end
|
651
|
-
target.set_callbacks name, chain
|
652
719
|
end
|
653
|
-
end
|
654
720
|
|
655
|
-
|
656
|
-
|
657
|
-
|
721
|
+
# Remove all set callbacks for the given event.
|
722
|
+
def reset_callbacks(name)
|
723
|
+
callbacks = get_callbacks name
|
658
724
|
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
725
|
+
ActiveSupport::DescendantsTracker.descendants(self).each do |target|
|
726
|
+
chain = target.get_callbacks(name).dup
|
727
|
+
callbacks.each { |c| chain.delete(c) }
|
728
|
+
target.set_callbacks name, chain
|
729
|
+
end
|
664
730
|
|
665
|
-
|
666
|
-
|
731
|
+
set_callbacks(name, callbacks.dup.clear)
|
732
|
+
end
|
667
733
|
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
names
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
734
|
+
# Define sets of events in the object life cycle that support callbacks.
|
735
|
+
#
|
736
|
+
# define_callbacks :validate
|
737
|
+
# define_callbacks :initialize, :save, :destroy
|
738
|
+
#
|
739
|
+
# ===== Options
|
740
|
+
#
|
741
|
+
# * <tt>:terminator</tt> - Determines when a before filter will halt the
|
742
|
+
# callback chain, preventing following before and around callbacks from
|
743
|
+
# being called and the event from being triggered.
|
744
|
+
# This should be a lambda to be executed.
|
745
|
+
# The current object and the result lambda of the callback will be provided
|
746
|
+
# to the terminator lambda.
|
747
|
+
#
|
748
|
+
# define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
|
749
|
+
#
|
750
|
+
# In this example, if any before validate callbacks returns +false+,
|
751
|
+
# any successive before and around callback is not executed.
|
752
|
+
#
|
753
|
+
# The default terminator halts the chain when a callback throws +:abort+.
|
754
|
+
#
|
755
|
+
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
|
756
|
+
# callbacks should be terminated by the <tt>:terminator</tt> option. By
|
757
|
+
# default after callbacks are executed no matter if callback chain was
|
758
|
+
# terminated or not. This option has no effect if <tt>:terminator</tt>
|
759
|
+
# option is set to +nil+.
|
760
|
+
#
|
761
|
+
# * <tt>:scope</tt> - Indicates which methods should be executed when an
|
762
|
+
# object is used as a callback.
|
763
|
+
#
|
764
|
+
# class Audit
|
765
|
+
# def before(caller)
|
766
|
+
# puts 'Audit: before is called'
|
767
|
+
# end
|
768
|
+
#
|
769
|
+
# def before_save(caller)
|
770
|
+
# puts 'Audit: before_save is called'
|
771
|
+
# end
|
772
|
+
# end
|
773
|
+
#
|
774
|
+
# class Account
|
775
|
+
# include ActiveSupport::Callbacks
|
776
|
+
#
|
777
|
+
# define_callbacks :save
|
778
|
+
# set_callback :save, :before, Audit.new
|
779
|
+
#
|
780
|
+
# def save
|
781
|
+
# run_callbacks :save do
|
782
|
+
# puts 'save in main'
|
783
|
+
# end
|
784
|
+
# end
|
785
|
+
# end
|
786
|
+
#
|
787
|
+
# In the above case whenever you save an account the method
|
788
|
+
# <tt>Audit#before</tt> will be called. On the other hand
|
789
|
+
#
|
790
|
+
# define_callbacks :save, scope: [:kind, :name]
|
791
|
+
#
|
792
|
+
# would trigger <tt>Audit#before_save</tt> instead. That's constructed
|
793
|
+
# by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
|
794
|
+
# case "kind" is "before" and "name" is "save". In this context +:kind+
|
795
|
+
# and +:name+ have special meanings: +:kind+ refers to the kind of
|
796
|
+
# callback (before/after/around) and +:name+ refers to the method on
|
797
|
+
# which callbacks are being defined.
|
798
|
+
#
|
799
|
+
# A declaration like
|
800
|
+
#
|
801
|
+
# define_callbacks :save, scope: [:name]
|
802
|
+
#
|
803
|
+
# would call <tt>Audit#save</tt>.
|
804
|
+
#
|
805
|
+
# ===== Notes
|
806
|
+
#
|
807
|
+
# +names+ passed to +define_callbacks+ must not end with
|
808
|
+
# <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
|
809
|
+
#
|
810
|
+
# Calling +define_callbacks+ multiple times with the same +names+ will
|
811
|
+
# overwrite previous callbacks registered with +set_callback+.
|
812
|
+
def define_callbacks(*names)
|
813
|
+
options = names.extract_options!
|
814
|
+
|
815
|
+
names.each do |name|
|
816
|
+
name = name.to_sym
|
817
|
+
|
818
|
+
([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target|
|
819
|
+
target.set_callbacks name, CallbackChain.new(name, options)
|
751
820
|
end
|
752
|
-
RUBY
|
753
|
-
end
|
754
|
-
end
|
755
821
|
|
756
|
-
|
822
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
823
|
+
def _run_#{name}_callbacks(&block)
|
824
|
+
run_callbacks #{name.inspect}, &block
|
825
|
+
end
|
757
826
|
|
758
|
-
|
759
|
-
|
760
|
-
|
827
|
+
def self._#{name}_callbacks
|
828
|
+
get_callbacks(#{name.inspect})
|
829
|
+
end
|
761
830
|
|
762
|
-
|
763
|
-
|
764
|
-
|
831
|
+
def self._#{name}_callbacks=(value)
|
832
|
+
set_callbacks(#{name.inspect}, value)
|
833
|
+
end
|
765
834
|
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
result = result_lambda.call if result_lambda.is_a?(Proc)
|
771
|
-
if Callbacks.halt_and_display_warning_on_return_false && result == false
|
772
|
-
display_deprecation_warning_for_false_terminator
|
773
|
-
else
|
774
|
-
terminate = false
|
775
|
-
end
|
835
|
+
def _#{name}_callbacks
|
836
|
+
__callbacks[#{name.inspect}]
|
837
|
+
end
|
838
|
+
RUBY
|
776
839
|
end
|
777
|
-
terminate
|
778
840
|
end
|
779
|
-
end
|
780
841
|
|
781
|
-
|
842
|
+
protected
|
843
|
+
def get_callbacks(name) # :nodoc:
|
844
|
+
__callbacks[name.to_sym]
|
845
|
+
end
|
782
846
|
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
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
|
859
|
+
end
|
788
860
|
end
|
789
|
-
end
|
790
861
|
end
|
791
862
|
end
|