activesupport 4.0.12 → 7.0.2.4

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