activesupport 1.2.4 → 8.1.2

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