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