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.

Files changed (268) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +343 -590
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -4
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/all.rb +5 -3
  7. data/lib/active_support/array_inquirer.rb +11 -5
  8. data/lib/active_support/backtrace_cleaner.rb +33 -5
  9. data/lib/active_support/benchmarkable.rb +5 -3
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache/file_store.rb +45 -53
  12. data/lib/active_support/cache/mem_cache_store.rb +81 -79
  13. data/lib/active_support/cache/memory_store.rb +69 -41
  14. data/lib/active_support/cache/null_store.rb +11 -4
  15. data/lib/active_support/cache/redis_cache_store.rb +493 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +74 -37
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
  18. data/lib/active_support/cache.rb +332 -161
  19. data/lib/active_support/callbacks.rb +657 -586
  20. data/lib/active_support/concern.rb +79 -6
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +35 -0
  22. data/lib/active_support/concurrency/share_lock.rb +59 -19
  23. data/lib/active_support/configurable.rb +15 -17
  24. data/lib/active_support/configuration_file.rb +46 -0
  25. data/lib/active_support/core_ext/array/access.rb +21 -7
  26. data/lib/active_support/core_ext/array/conversions.rb +20 -18
  27. data/lib/active_support/core_ext/array/extract.rb +21 -0
  28. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +3 -1
  30. data/lib/active_support/core_ext/array/inquiry.rb +3 -1
  31. data/lib/active_support/core_ext/array/wrap.rb +2 -0
  32. data/lib/active_support/core_ext/array.rb +9 -7
  33. data/lib/active_support/core_ext/benchmark.rb +5 -3
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +6 -6
  35. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  36. data/lib/active_support/core_ext/class/attribute.rb +52 -49
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
  38. data/lib/active_support/core_ext/class/subclasses.rb +18 -26
  39. data/lib/active_support/core_ext/class.rb +4 -2
  40. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  41. data/lib/active_support/core_ext/date/blank.rb +3 -1
  42. data/lib/active_support/core_ext/date/calculations.rb +16 -13
  43. data/lib/active_support/core_ext/date/conversions.rb +23 -21
  44. data/lib/active_support/core_ext/date/zones.rb +4 -2
  45. data/lib/active_support/core_ext/date.rb +7 -5
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +82 -53
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -5
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +9 -9
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  50. data/lib/active_support/core_ext/date_time/blank.rb +3 -1
  51. data/lib/active_support/core_ext/date_time/calculations.rb +23 -11
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +15 -2
  53. data/lib/active_support/core_ext/date_time/conversions.rb +14 -13
  54. data/lib/active_support/core_ext/date_time.rb +7 -5
  55. data/lib/active_support/core_ext/digest/uuid.rb +7 -5
  56. data/lib/active_support/core_ext/digest.rb +3 -0
  57. data/lib/active_support/core_ext/enumerable.rb +165 -29
  58. data/lib/active_support/core_ext/file/atomic.rb +7 -5
  59. data/lib/active_support/core_ext/file.rb +3 -1
  60. data/lib/active_support/core_ext/hash/conversions.rb +40 -39
  61. data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
  62. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  63. data/lib/active_support/core_ext/hash/except.rb +4 -2
  64. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -2
  65. data/lib/active_support/core_ext/hash/keys.rb +9 -36
  66. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  67. data/lib/active_support/core_ext/hash/slice.rb +8 -29
  68. data/lib/active_support/core_ext/hash.rb +10 -9
  69. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  70. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  71. data/lib/active_support/core_ext/integer/time.rb +11 -18
  72. data/lib/active_support/core_ext/integer.rb +5 -3
  73. data/lib/active_support/core_ext/kernel/concern.rb +3 -1
  74. data/lib/active_support/core_ext/kernel/reporting.rb +3 -1
  75. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  76. data/lib/active_support/core_ext/kernel.rb +5 -4
  77. data/lib/active_support/core_ext/load_error.rb +2 -23
  78. data/lib/active_support/core_ext/marshal.rb +6 -2
  79. data/lib/active_support/core_ext/module/aliasing.rb +5 -48
  80. data/lib/active_support/core_ext/module/anonymous.rb +2 -0
  81. data/lib/active_support/core_ext/module/attr_internal.rb +7 -5
  82. data/lib/active_support/core_ext/module/attribute_accessors.rb +53 -59
  83. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +31 -24
  84. data/lib/active_support/core_ext/module/concerning.rb +16 -11
  85. data/lib/active_support/core_ext/module/delegation.rb +159 -44
  86. data/lib/active_support/core_ext/module/deprecation.rb +2 -0
  87. data/lib/active_support/core_ext/module/introspection.rb +23 -26
  88. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  89. data/lib/active_support/core_ext/module/remove_method.rb +5 -23
  90. data/lib/active_support/core_ext/module.rb +13 -12
  91. data/lib/active_support/core_ext/name_error.rb +36 -2
  92. data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
  93. data/lib/active_support/core_ext/numeric/conversions.rb +129 -134
  94. data/lib/active_support/core_ext/numeric/time.rb +18 -26
  95. data/lib/active_support/core_ext/numeric.rb +5 -4
  96. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  97. data/lib/active_support/core_ext/object/blank.rb +14 -2
  98. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  99. data/lib/active_support/core_ext/object/deep_dup.rb +4 -2
  100. data/lib/active_support/core_ext/object/duplicable.rb +13 -62
  101. data/lib/active_support/core_ext/object/inclusion.rb +3 -1
  102. data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
  103. data/lib/active_support/core_ext/object/json.rb +42 -15
  104. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  105. data/lib/active_support/core_ext/object/to_query.rb +10 -5
  106. data/lib/active_support/core_ext/object/try.rb +20 -8
  107. data/lib/active_support/core_ext/object/with_options.rb +15 -2
  108. data/lib/active_support/core_ext/object.rb +14 -12
  109. data/lib/active_support/core_ext/range/compare_range.rb +82 -0
  110. data/lib/active_support/core_ext/range/conversions.rb +35 -25
  111. data/lib/active_support/core_ext/range/each.rb +5 -2
  112. data/lib/active_support/core_ext/range/include_time_with_zone.rb +28 -0
  113. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  114. data/lib/active_support/core_ext/range.rb +7 -4
  115. data/lib/active_support/core_ext/regexp.rb +10 -1
  116. data/lib/active_support/core_ext/securerandom.rb +28 -6
  117. data/lib/active_support/core_ext/string/access.rb +9 -18
  118. data/lib/active_support/core_ext/string/behavior.rb +2 -0
  119. data/lib/active_support/core_ext/string/conversions.rb +5 -2
  120. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  121. data/lib/active_support/core_ext/string/filters.rb +47 -4
  122. data/lib/active_support/core_ext/string/indent.rb +6 -4
  123. data/lib/active_support/core_ext/string/inflections.rb +78 -29
  124. data/lib/active_support/core_ext/string/inquiry.rb +4 -1
  125. data/lib/active_support/core_ext/string/multibyte.rb +10 -5
  126. data/lib/active_support/core_ext/string/output_safety.rb +86 -31
  127. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
  128. data/lib/active_support/core_ext/string/strip.rb +5 -1
  129. data/lib/active_support/core_ext/string/zones.rb +4 -2
  130. data/lib/active_support/core_ext/string.rb +15 -13
  131. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  132. data/lib/active_support/core_ext/symbol.rb +3 -0
  133. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  134. data/lib/active_support/core_ext/time/calculations.rb +117 -45
  135. data/lib/active_support/core_ext/time/compatibility.rb +13 -2
  136. data/lib/active_support/core_ext/time/conversions.rb +18 -12
  137. data/lib/active_support/core_ext/time/zones.rb +9 -7
  138. data/lib/active_support/core_ext/time.rb +7 -5
  139. data/lib/active_support/core_ext/uri.rb +12 -7
  140. data/lib/active_support/core_ext.rb +3 -2
  141. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  142. data/lib/active_support/current_attributes.rb +208 -0
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +7 -1
  145. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  146. data/lib/active_support/dependencies.rb +172 -98
  147. data/lib/active_support/deprecation/behaviors.rb +45 -13
  148. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  149. data/lib/active_support/deprecation/disallowed.rb +56 -0
  150. data/lib/active_support/deprecation/instance_delegator.rb +16 -2
  151. data/lib/active_support/deprecation/method_wrappers.rb +32 -17
  152. data/lib/active_support/deprecation/proxy_wrappers.rb +35 -7
  153. data/lib/active_support/deprecation/reporting.rb +61 -16
  154. data/lib/active_support/deprecation.rb +17 -9
  155. data/lib/active_support/descendants_tracker.rb +61 -9
  156. data/lib/active_support/digest.rb +20 -0
  157. data/lib/active_support/duration/iso8601_parser.rb +67 -66
  158. data/lib/active_support/duration/iso8601_serializer.rb +25 -17
  159. data/lib/active_support/duration.rb +349 -46
  160. data/lib/active_support/encrypted_configuration.rb +45 -0
  161. data/lib/active_support/encrypted_file.rb +117 -0
  162. data/lib/active_support/environment_inquirer.rb +20 -0
  163. data/lib/active_support/evented_file_update_checker.rb +88 -112
  164. data/lib/active_support/execution_wrapper.rb +25 -13
  165. data/lib/active_support/executor.rb +3 -1
  166. data/lib/active_support/file_update_checker.rb +56 -51
  167. data/lib/active_support/fork_tracker.rb +62 -0
  168. data/lib/active_support/gem_version.rb +4 -2
  169. data/lib/active_support/gzip.rb +7 -5
  170. data/lib/active_support/hash_with_indifferent_access.rb +153 -49
  171. data/lib/active_support/i18n.rb +9 -6
  172. data/lib/active_support/i18n_railtie.rb +30 -20
  173. data/lib/active_support/inflections.rb +13 -11
  174. data/lib/active_support/inflector/inflections.rb +28 -15
  175. data/lib/active_support/inflector/methods.rb +120 -109
  176. data/lib/active_support/inflector/transliterate.rb +60 -25
  177. data/lib/active_support/inflector.rb +7 -5
  178. data/lib/active_support/json/decoding.rb +30 -29
  179. data/lib/active_support/json/encoding.rb +22 -11
  180. data/lib/active_support/json.rb +4 -2
  181. data/lib/active_support/key_generator.rb +6 -36
  182. data/lib/active_support/lazy_load_hooks.rb +53 -20
  183. data/lib/active_support/locale/en.rb +33 -0
  184. data/lib/active_support/locale/en.yml +7 -3
  185. data/lib/active_support/log_subscriber/test_helper.rb +11 -9
  186. data/lib/active_support/log_subscriber.rb +51 -18
  187. data/lib/active_support/logger.rb +9 -22
  188. data/lib/active_support/logger_silence.rb +14 -21
  189. data/lib/active_support/logger_thread_safe_level.rb +55 -8
  190. data/lib/active_support/message_encryptor.rb +170 -53
  191. data/lib/active_support/message_verifier.rb +91 -20
  192. data/lib/active_support/messages/metadata.rb +80 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  194. data/lib/active_support/messages/rotator.rb +57 -0
  195. data/lib/active_support/multibyte/chars.rb +24 -78
  196. data/lib/active_support/multibyte/unicode.rb +21 -352
  197. data/lib/active_support/multibyte.rb +4 -2
  198. data/lib/active_support/notifications/fanout.rb +121 -19
  199. data/lib/active_support/notifications/instrumenter.rb +78 -14
  200. data/lib/active_support/notifications.rb +80 -12
  201. data/lib/active_support/number_helper/number_converter.rb +17 -16
  202. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -9
  203. data/lib/active_support/number_helper/number_to_delimited_converter.rb +5 -3
  204. data/lib/active_support/number_helper/number_to_human_converter.rb +13 -12
  205. data/lib/active_support/number_helper/number_to_human_size_converter.rb +11 -13
  206. data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
  207. data/lib/active_support/number_helper/number_to_phone_converter.rb +5 -4
  208. data/lib/active_support/number_helper/number_to_rounded_converter.rb +18 -55
  209. data/lib/active_support/number_helper/rounding_helper.rb +50 -0
  210. data/lib/active_support/number_helper.rb +45 -16
  211. data/lib/active_support/option_merger.rb +25 -4
  212. data/lib/active_support/ordered_hash.rb +6 -4
  213. data/lib/active_support/ordered_options.rb +23 -9
  214. data/lib/active_support/parameter_filter.rb +133 -0
  215. data/lib/active_support/per_thread_registry.rb +7 -5
  216. data/lib/active_support/proxy_object.rb +2 -0
  217. data/lib/active_support/rails.rb +8 -9
  218. data/lib/active_support/railtie.rb +62 -11
  219. data/lib/active_support/reloader.rb +12 -11
  220. data/lib/active_support/rescuable.rb +20 -11
  221. data/lib/active_support/secure_compare_rotator.rb +51 -0
  222. data/lib/active_support/security_utils.rb +26 -15
  223. data/lib/active_support/string_inquirer.rb +12 -3
  224. data/lib/active_support/subscriber.rb +77 -23
  225. data/lib/active_support/tagged_logging.rb +52 -17
  226. data/lib/active_support/test_case.rb +106 -29
  227. data/lib/active_support/testing/assertions.rb +144 -8
  228. data/lib/active_support/testing/autorun.rb +5 -10
  229. data/lib/active_support/testing/constant_lookup.rb +2 -1
  230. data/lib/active_support/testing/declarative.rb +3 -1
  231. data/lib/active_support/testing/deprecation.rb +4 -2
  232. data/lib/active_support/testing/file_fixtures.rb +4 -0
  233. data/lib/active_support/testing/isolation.rb +19 -24
  234. data/lib/active_support/testing/method_call_assertions.rb +31 -2
  235. data/lib/active_support/testing/parallelization/server.rb +78 -0
  236. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  237. data/lib/active_support/testing/parallelization.rb +51 -0
  238. data/lib/active_support/testing/setup_and_teardown.rb +13 -8
  239. data/lib/active_support/testing/stream.rb +30 -29
  240. data/lib/active_support/testing/tagged_logging.rb +3 -1
  241. data/lib/active_support/testing/time_helpers.rb +125 -24
  242. data/lib/active_support/time.rb +14 -12
  243. data/lib/active_support/time_with_zone.rb +142 -55
  244. data/lib/active_support/values/time_zone.rb +160 -53
  245. data/lib/active_support/version.rb +3 -1
  246. data/lib/active_support/xml_mini/jdom.rb +115 -114
  247. data/lib/active_support/xml_mini/libxml.rb +15 -14
  248. data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
  249. data/lib/active_support/xml_mini/nokogiri.rb +13 -13
  250. data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
  251. data/lib/active_support/xml_mini/rexml.rb +18 -9
  252. data/lib/active_support/xml_mini.rb +44 -42
  253. data/lib/active_support.rb +19 -10
  254. metadata +79 -37
  255. data/lib/active_support/concurrency/latch.rb +0 -19
  256. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
  257. data/lib/active_support/core_ext/hash/compact.rb +0 -20
  258. data/lib/active_support/core_ext/hash/transform_values.rb +0 -29
  259. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  260. data/lib/active_support/core_ext/kernel/debugger.rb +0 -3
  261. data/lib/active_support/core_ext/module/method_transplanting.rb +0 -3
  262. data/lib/active_support/core_ext/module/qualified_const.rb +0 -70
  263. data/lib/active_support/core_ext/module/reachable.rb +0 -8
  264. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -26
  265. data/lib/active_support/core_ext/range/include_range.rb +0 -23
  266. data/lib/active_support/core_ext/struct.rb +0 -3
  267. data/lib/active_support/core_ext/time/marshal.rb +0 -3
  268. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,13 +1,11 @@
1
- require 'active_support/concern'
2
- require 'active_support/descendants_tracker'
3
- require 'active_support/core_ext/array/extract_options'
4
- require 'active_support/core_ext/class/attribute'
5
- require 'active_support/core_ext/kernel/reporting'
6
- require 'active_support/core_ext/kernel/singleton_class'
7
- require 'active_support/core_ext/module/attribute_accessors'
8
- require 'active_support/core_ext/string/filters'
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
- def run_callbacks(kind, &block)
90
- send "_run_#{kind}_callbacks", &block
91
- end
92
-
93
- private
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
- runner = callbacks.compile
100
- e = Filters::Environment.new(self, false, nil, block)
101
- runner.call(e).value
102
- end
103
- end
104
-
105
- # A hook invoked every time a before callback is halted.
106
- # This can be overridden in AS::Callback implementors in order
107
- # to provide better debugging/logging.
108
- def halted_callback_hook(filter)
109
- end
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
- module Conditionals # :nodoc:
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
- module Filters
121
- Environment = Struct.new(:target, :halted, :value, :run_block)
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
- class End
124
- def call(env)
125
- block = env.run_block
126
- env.value = !env.halted && (!block || block.call)
127
- env
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
- class Before
133
- def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
134
- halted_lambda = chain_config[:terminator]
158
+ module Filters
159
+ Environment = Struct.new(:target, :halted, :value)
135
160
 
136
- if user_conditions.any?
137
- halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
138
- else
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
- def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
144
- callback_sequence.before do |env|
145
- target = env.target
146
- value = env.value
147
- halted = env.halted
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
- if !halted && user_conditions.all? { |c| c.call(target, value) }
150
- result_lambda = -> { user_callback.call target, value }
151
- env.halted = halted_lambda.call(target, result_lambda)
152
- if env.halted
153
- target.send :halted_callback_hook, filter
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
- env
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
- def self.halting(callback_sequence, user_callback, halted_lambda, filter)
163
- callback_sequence.before do |env|
164
- target = env.target
165
- value = env.value
166
- halted = env.halted
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
- unless halted
169
- result_lambda = -> { user_callback.call target, value }
170
- env.halted = halted_lambda.call(target, result_lambda)
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 env.halted
173
- target.send :halted_callback_hook, filter
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
- env
238
+ env
239
+ end
178
240
  end
179
- end
180
- private_class_method :halting
181
- end
241
+ private_class_method :halting_and_conditional
182
242
 
183
- class After
184
- def self.build(callback_sequence, user_callback, user_conditions, chain_config)
185
- if chain_config[:skip_after_callbacks_if_terminated]
186
- if user_conditions.any?
187
- halting_and_conditional(callback_sequence, user_callback, user_conditions)
188
- else
189
- halting(callback_sequence, user_callback)
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
- end
252
+ private_class_method :halting
199
253
 
200
- def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
201
- callback_sequence.after do |env|
202
- target = env.target
203
- value = env.value
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
- if !halted && user_conditions.all? { |c| c.call(target, value) }
207
- user_callback.call target, value
208
- end
259
+ if user_conditions.all? { |c| c.call(target, value) }
260
+ user_callback.call target, value
261
+ end
209
262
 
210
- env
263
+ env
264
+ end
211
265
  end
212
- end
213
- private_class_method :halting_and_conditional
266
+ private_class_method :conditional
214
267
 
215
- def self.halting(callback_sequence, user_callback)
216
- callback_sequence.after do |env|
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
- env
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
- def self.conditional(callback_sequence, user_callback, user_conditions)
227
- callback_sequence.after do |env|
228
- target = env.target
229
- value = env.value
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
- if user_conditions.all? { |c| c.call(target, value) }
232
- user_callback.call target, value
233
- end
304
+ def filter; @key; end
305
+ def raw_filter; @filter; end
234
306
 
235
- env
236
- end
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 self.simple(callback_sequence, user_callback)
241
- callback_sequence.after do |env|
242
- user_callback.call env.target, env.value
319
+ def matches?(_kind, _filter)
320
+ @kind == _kind && filter == _filter
321
+ end
243
322
 
244
- env
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
- class Around
251
- def self.build(callback_sequence, user_callback, user_conditions, chain_config)
252
- if user_conditions.any?
253
- halting_and_conditional(callback_sequence, user_callback, user_conditions)
254
- else
255
- halting(callback_sequence, user_callback)
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 self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
260
- callback_sequence.around do |env, &run|
261
- target = env.target
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
- if !halted && user_conditions.all? { |c| c.call(target, value) }
266
- user_callback.call(target, value) {
267
- run.call.value
268
- }
269
- env
270
- else
271
- run.call
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
- def self.halting(callback_sequence, user_callback)
278
- callback_sequence.around do |env, &run|
279
- target = env.target
280
- value = env.value
367
+ conditionals.freeze
368
+ end
281
369
 
282
- if env.halted
283
- run.call
370
+ def compute_identifier(filter)
371
+ case filter
372
+ when ::Proc
373
+ filter.object_id
284
374
  else
285
- user_callback.call(target, value) {
286
- run.call.value
287
- }
288
- env
375
+ filter
289
376
  end
290
377
  end
291
- end
292
- private_class_method :halting
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
- class Callback #:nodoc:#
297
- def self.build(chain, filter, kind, options)
298
- if filter.is_a?(String)
299
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
300
- Passing string to define callback is deprecated and will be removed
301
- in Rails 5.1 without replacement.
302
- MSG
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
- new chain.name, filter, kind, options, chain.config
306
- end
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
- attr_accessor :kind, :name
309
- attr_reader :chain_config
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
- def filter; @key; end
322
- def raw_filter; @filter; end
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
- def merge_conditional_options(chain, if_option:, unless_option:)
325
- options = {
326
- :if => @if.dup,
327
- :unless => @unless.dup
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
- options[:if].concat Array(unless_option)
331
- options[:unless].concat Array(if_option)
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
- self.class.build chain, @filter, @kind, options
465
+ new(filter, method_to_call, [:target], nil)
466
+ end
467
+ end
334
468
  end
335
469
 
336
- def matches?(_kind, _filter)
337
- @kind == _kind && filter == _filter
338
- end
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
- def duplicates?(other)
341
- case @filter
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
- # Wraps code with filter
350
- def apply(callback_sequence)
351
- user_conditions = conditions_lambdas
352
- user_callback = make_lambda @filter
483
+ def before(&before)
484
+ @before.unshift(before)
485
+ self
486
+ end
353
487
 
354
- case kind
355
- when :before
356
- Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
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
- private
493
+ def around(call_template, user_conditions)
494
+ CallbackSequence.new(self, call_template, user_conditions)
495
+ end
365
496
 
366
- def invert_lambda(l)
367
- lambda { |*args, &blk| !l.call(*args, &blk) }
368
- end
497
+ def skip?(arg)
498
+ arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) }
499
+ end
369
500
 
370
- # Filters support:
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
- lambda { |target, _, &blk|
405
- filter.public_send method_to_call, target, &blk
406
- }
503
+ def final?
504
+ !@call_template
407
505
  end
408
- end
409
506
 
410
- def compute_identifier(filter)
411
- case filter
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
- def conditions_lambdas
420
- @if.map { |c| make_lambda c } +
421
- @unless.map { |c| invert_lambda make_lambda c }
422
- end
423
- end
511
+ def invoke_before(arg)
512
+ @before.each { |b| b.call(arg) }
513
+ end
424
514
 
425
- # Execute before and after filters in a sequence instead of
426
- # chaining them with nested lambda calls, see:
427
- # https://github.com/rails/rails/issues/18011
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
- def before(&before)
436
- @before.unshift(before)
437
- self
438
- end
520
+ class CallbackChain #:nodoc:#
521
+ include Enumerable
439
522
 
440
- def after(&after)
441
- @after.push(after)
442
- self
443
- end
523
+ attr_reader :name, :config
444
524
 
445
- def around(&around)
446
- CallbackSequence.new do |arg|
447
- around.call(arg) {
448
- self.call(arg)
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
- def call(arg)
454
- @before.each { |b| b.call(arg) }
455
- value = @call.call(arg)
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
- def insert(index, o)
483
- @callbacks = nil
484
- @chain.insert(index, o)
485
- end
540
+ def insert(index, o)
541
+ @callbacks = nil
542
+ @chain.insert(index, o)
543
+ end
486
544
 
487
- def delete(o)
488
- @callbacks = nil
489
- @chain.delete(o)
490
- end
545
+ def delete(o)
546
+ @callbacks = nil
547
+ @chain.delete(o)
548
+ end
491
549
 
492
- def clear
493
- @callbacks = nil
494
- @chain.clear
495
- self
496
- end
550
+ def clear
551
+ @callbacks = nil
552
+ @chain.clear
553
+ self
554
+ end
497
555
 
498
- def initialize_copy(other)
499
- @callbacks = nil
500
- @chain = other.chain.dup
501
- @mutex = Mutex.new
502
- end
556
+ def initialize_copy(other)
557
+ @callbacks = nil
558
+ @chain = other.chain.dup
559
+ @mutex = Mutex.new
560
+ end
503
561
 
504
- def compile
505
- @callbacks || @mutex.synchronize do
506
- final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
507
- @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
508
- callback.apply callback_sequence
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
- def append(*callbacks)
514
- callbacks.each { |c| append_one(c) }
515
- end
571
+ def append(*callbacks)
572
+ callbacks.each { |c| append_one(c) }
573
+ end
516
574
 
517
- def prepend(*callbacks)
518
- callbacks.each { |c| prepend_one(c) }
519
- end
575
+ def prepend(*callbacks)
576
+ callbacks.each { |c| prepend_one(c) }
577
+ end
520
578
 
521
- protected
522
- def chain; @chain; end
579
+ protected
580
+ attr_reader :chain
523
581
 
524
- private
582
+ private
583
+ def append_one(callback)
584
+ @callbacks = nil
585
+ remove_duplicates(callback)
586
+ @chain.push(callback)
587
+ end
525
588
 
526
- def append_one(callback)
527
- @callbacks = nil
528
- remove_duplicates(callback)
529
- @chain.push(callback)
530
- end
589
+ def prepend_one(callback)
590
+ @callbacks = nil
591
+ remove_duplicates(callback)
592
+ @chain.unshift(callback)
593
+ end
531
594
 
532
- def prepend_one(callback)
533
- @callbacks = nil
534
- remove_duplicates(callback)
535
- @chain.unshift(callback)
536
- end
595
+ def remove_duplicates(callback)
596
+ @callbacks = nil
597
+ @chain.delete_if { |c| callback.duplicates?(c) }
598
+ end
537
599
 
538
- def remove_duplicates(callback)
539
- @callbacks = nil
540
- @chain.delete_if { |c| callback.duplicates?(c) }
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
- def default_terminator
544
- Proc.new do |target, result_lambda|
545
- terminate = true
546
- catch(:abort) do
547
- result_lambda.call if result_lambda.is_a?(Proc)
548
- terminate = false
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
- module ClassMethods
556
- def normalize_callback_params(filters, block) # :nodoc:
557
- type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
558
- options = filters.extract_options!
559
- filters.unshift(block) if block
560
- [type, filters, options.dup]
561
- end
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
- # This is used internally to append, prepend and skip callbacks to the
564
- # CallbackChain.
565
- def __update_callbacks(name) #:nodoc:
566
- ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
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
- # Install a callback for the given event.
573
- #
574
- # set_callback :save, :before, :before_method
575
- # set_callback :save, :after, :after_method, if: :condition
576
- # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
577
- #
578
- # The second argument indicates whether the callback is to be run +:before+,
579
- # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
580
- # means the first example above can also be written as:
581
- #
582
- # set_callback :save, :before_method
583
- #
584
- # The callback can be specified as a symbol naming an instance method; as a
585
- # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
586
- # object that responds to a certain method determined by the <tt>:scope</tt>
587
- # argument to +define_callbacks+.
588
- #
589
- # If a proc, lambda, or block is given, its body is evaluated in the context
590
- # of the current object. It can also optionally accept the current object as
591
- # an argument.
592
- #
593
- # Before and around callbacks are called in the order that they are set;
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
- # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
623
- # <tt>:unless</tt> options may be passed in order to control when the
624
- # callback is skipped.
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
- if callback && (options.key?(:if) || options.key?(:unless))
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
- # Remove all set callbacks for the given event.
656
- def reset_callbacks(name)
657
- callbacks = get_callbacks name
721
+ # Remove all set callbacks for the given event.
722
+ def reset_callbacks(name)
723
+ callbacks = get_callbacks name
658
724
 
659
- ActiveSupport::DescendantsTracker.descendants(self).each do |target|
660
- chain = target.get_callbacks(name).dup
661
- callbacks.each { |c| chain.delete(c) }
662
- target.set_callbacks name, chain
663
- end
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
- self.set_callbacks name, callbacks.dup.clear
666
- end
731
+ set_callbacks(name, callbacks.dup.clear)
732
+ end
667
733
 
668
- # Define sets of events in the object life cycle that support callbacks.
669
- #
670
- # define_callbacks :validate
671
- # define_callbacks :initialize, :save, :destroy
672
- #
673
- # ===== Options
674
- #
675
- # * <tt>:terminator</tt> - Determines when a before filter will halt the
676
- # callback chain, preventing following before and around callbacks from
677
- # being called and the event from being triggered.
678
- # This should be a lambda to be executed.
679
- # The current object and the result lambda of the callback will be provided
680
- # to the terminator lambda.
681
- #
682
- # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
683
- #
684
- # In this example, if any before validate callbacks returns +false+,
685
- # any successive before and around callback is not executed.
686
- #
687
- # The default terminator halts the chain when a callback throws +:abort+.
688
- #
689
- # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
690
- # callbacks should be terminated by the <tt>:terminator</tt> option. By
691
- # default after callbacks are executed no matter if callback chain was
692
- # terminated or not. This option makes sense only when <tt>:terminator</tt>
693
- # option is specified.
694
- #
695
- # * <tt>:scope</tt> - Indicates which methods should be executed when an
696
- # object is used as a callback.
697
- #
698
- # class Audit
699
- # def before(caller)
700
- # puts 'Audit: before is called'
701
- # end
702
- #
703
- # def before_save(caller)
704
- # puts 'Audit: before_save is called'
705
- # end
706
- # end
707
- #
708
- # class Account
709
- # include ActiveSupport::Callbacks
710
- #
711
- # define_callbacks :save
712
- # set_callback :save, :before, Audit.new
713
- #
714
- # def save
715
- # run_callbacks :save do
716
- # puts 'save in main'
717
- # end
718
- # end
719
- # end
720
- #
721
- # In the above case whenever you save an account the method
722
- # <tt>Audit#before</tt> will be called. On the other hand
723
- #
724
- # define_callbacks :save, scope: [:kind, :name]
725
- #
726
- # would trigger <tt>Audit#before_save</tt> instead. That's constructed
727
- # by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
728
- # case "kind" is "before" and "name" is "save". In this context +:kind+
729
- # and +:name+ have special meanings: +:kind+ refers to the kind of
730
- # callback (before/after/around) and +:name+ refers to the method on
731
- # which callbacks are being defined.
732
- #
733
- # A declaration like
734
- #
735
- # define_callbacks :save, scope: [:name]
736
- #
737
- # would call <tt>Audit#save</tt>.
738
- #
739
- # NOTE: +method_name+ passed to `define_model_callbacks` must not end with
740
- # `!`, `?` or `=`.
741
- def define_callbacks(*names)
742
- options = names.extract_options!
743
-
744
- names.each do |name|
745
- class_attribute "_#{name}_callbacks", instance_writer: false
746
- set_callbacks name, CallbackChain.new(name, options)
747
-
748
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
749
- def _run_#{name}_callbacks(&block)
750
- __run_callbacks__(_#{name}_callbacks, &block)
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
- protected
822
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
823
+ def _run_#{name}_callbacks(&block)
824
+ run_callbacks #{name.inspect}, &block
825
+ end
757
826
 
758
- def get_callbacks(name) # :nodoc:
759
- send "_#{name}_callbacks"
760
- end
827
+ def self._#{name}_callbacks
828
+ get_callbacks(#{name.inspect})
829
+ end
761
830
 
762
- def set_callbacks(name, callbacks) # :nodoc:
763
- send "_#{name}_callbacks=", callbacks
764
- end
831
+ def self._#{name}_callbacks=(value)
832
+ set_callbacks(#{name.inspect}, value)
833
+ end
765
834
 
766
- def deprecated_false_terminator # :nodoc:
767
- Proc.new do |target, result_lambda|
768
- terminate = true
769
- catch(:abort) do
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
- private
842
+ protected
843
+ def get_callbacks(name) # :nodoc:
844
+ __callbacks[name.to_sym]
845
+ end
782
846
 
783
- def display_deprecation_warning_for_false_terminator
784
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
785
- Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1.
786
- To explicitly halt the callback chain, please use `throw :abort` instead.
787
- MSG
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