activesupport 3.1.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (276) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +798 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +13 -7
  5. data/lib/active_support/array_inquirer.rb +44 -0
  6. data/lib/active_support/backtrace_cleaner.rb +38 -34
  7. data/lib/active_support/benchmarkable.rb +17 -28
  8. data/lib/active_support/cache/file_store.rb +85 -70
  9. data/lib/active_support/cache/mem_cache_store.rb +75 -66
  10. data/lib/active_support/cache/memory_store.rb +31 -23
  11. data/lib/active_support/cache/null_store.rb +41 -0
  12. data/lib/active_support/cache/strategy/local_cache.rb +73 -70
  13. data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
  14. data/lib/active_support/cache.rb +360 -294
  15. data/lib/active_support/callbacks.rb +563 -393
  16. data/lib/active_support/concern.rb +42 -34
  17. data/lib/active_support/concurrency/latch.rb +19 -0
  18. data/lib/active_support/concurrency/share_lock.rb +186 -0
  19. data/lib/active_support/configurable.rb +70 -12
  20. data/lib/active_support/core_ext/array/access.rb +53 -9
  21. data/lib/active_support/core_ext/array/conversions.rb +109 -62
  22. data/lib/active_support/core_ext/array/extract_options.rb +2 -2
  23. data/lib/active_support/core_ext/array/grouping.rb +39 -32
  24. data/lib/active_support/core_ext/array/inquiry.rb +17 -0
  25. data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -0
  26. data/lib/active_support/core_ext/array/wrap.rb +16 -18
  27. data/lib/active_support/core_ext/array.rb +2 -2
  28. data/lib/active_support/core_ext/benchmark.rb +7 -0
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +8 -36
  30. data/lib/active_support/core_ext/class/attribute.rb +47 -34
  31. data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -79
  32. data/lib/active_support/core_ext/class/subclasses.rb +12 -7
  33. data/lib/active_support/core_ext/class.rb +0 -3
  34. data/lib/active_support/core_ext/date/blank.rb +12 -0
  35. data/lib/active_support/core_ext/date/calculations.rb +57 -167
  36. data/lib/active_support/core_ext/date/conversions.rb +31 -42
  37. data/lib/active_support/core_ext/date/zones.rb +2 -10
  38. data/lib/active_support/core_ext/date.rb +5 -0
  39. data/lib/active_support/core_ext/date_and_time/calculations.rb +335 -0
  40. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -0
  41. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  42. data/lib/active_support/core_ext/date_time/acts_like.rb +1 -0
  43. data/lib/active_support/core_ext/date_time/blank.rb +12 -0
  44. data/lib/active_support/core_ext/date_time/calculations.rb +132 -65
  45. data/lib/active_support/core_ext/date_time/compatibility.rb +5 -0
  46. data/lib/active_support/core_ext/date_time/conversions.rb +36 -34
  47. data/lib/active_support/core_ext/date_time.rb +5 -0
  48. data/lib/active_support/core_ext/digest/uuid.rb +51 -0
  49. data/lib/active_support/core_ext/enumerable.rb +81 -74
  50. data/lib/active_support/core_ext/file/atomic.rb +53 -26
  51. data/lib/active_support/core_ext/file.rb +0 -1
  52. data/lib/active_support/core_ext/hash/compact.rb +20 -0
  53. data/lib/active_support/core_ext/hash/conversions.rb +175 -70
  54. data/lib/active_support/core_ext/hash/deep_merge.rb +30 -8
  55. data/lib/active_support/core_ext/hash/except.rb +11 -12
  56. data/lib/active_support/core_ext/hash/indifferent_access.rb +7 -8
  57. data/lib/active_support/core_ext/hash/keys.rb +147 -24
  58. data/lib/active_support/core_ext/hash/reverse_merge.rb +2 -3
  59. data/lib/active_support/core_ext/hash/slice.rb +22 -14
  60. data/lib/active_support/core_ext/hash/transform_values.rb +29 -0
  61. data/lib/active_support/core_ext/hash.rb +2 -2
  62. data/lib/active_support/core_ext/integer/inflections.rb +13 -1
  63. data/lib/active_support/core_ext/integer/multiple.rb +4 -0
  64. data/lib/active_support/core_ext/integer/time.rb +12 -22
  65. data/lib/active_support/core_ext/kernel/agnostics.rb +2 -2
  66. data/lib/active_support/core_ext/kernel/concern.rb +12 -0
  67. data/lib/active_support/core_ext/kernel/debugger.rb +2 -15
  68. data/lib/active_support/core_ext/kernel/reporting.rb +12 -62
  69. data/lib/active_support/core_ext/kernel/singleton_class.rb +0 -7
  70. data/lib/active_support/core_ext/kernel.rb +2 -3
  71. data/lib/active_support/core_ext/load_error.rb +14 -7
  72. data/lib/active_support/core_ext/marshal.rb +22 -0
  73. data/lib/active_support/core_ext/module/aliasing.rb +16 -12
  74. data/lib/active_support/core_ext/module/anonymous.rb +12 -8
  75. data/lib/active_support/core_ext/module/attr_internal.rb +2 -5
  76. data/lib/active_support/core_ext/module/attribute_accessors.rb +165 -13
  77. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
  78. data/lib/active_support/core_ext/module/concerning.rb +135 -0
  79. data/lib/active_support/core_ext/module/delegation.rb +141 -68
  80. data/lib/active_support/core_ext/module/deprecation.rb +17 -3
  81. data/lib/active_support/core_ext/module/introspection.rb +9 -31
  82. data/lib/active_support/core_ext/module/method_transplanting.rb +3 -0
  83. data/lib/active_support/core_ext/module/qualified_const.rb +70 -0
  84. data/lib/active_support/core_ext/module/reachable.rb +1 -3
  85. data/lib/active_support/core_ext/module/remove_method.rb +24 -5
  86. data/lib/active_support/core_ext/module.rb +3 -3
  87. data/lib/active_support/core_ext/name_error.rb +15 -2
  88. data/lib/active_support/core_ext/numeric/bytes.rb +20 -0
  89. data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
  90. data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
  91. data/lib/active_support/core_ext/numeric/time.rb +31 -36
  92. data/lib/active_support/core_ext/numeric.rb +2 -0
  93. data/lib/active_support/core_ext/object/acts_like.rb +4 -4
  94. data/lib/active_support/core_ext/object/blank.rb +52 -18
  95. data/lib/active_support/core_ext/object/deep_dup.rb +53 -0
  96. data/lib/active_support/core_ext/object/duplicable.rb +12 -20
  97. data/lib/active_support/core_ext/object/inclusion.rb +13 -1
  98. data/lib/active_support/core_ext/object/instance_variables.rb +7 -12
  99. data/lib/active_support/core_ext/object/json.rb +205 -0
  100. data/lib/active_support/core_ext/object/to_param.rb +1 -55
  101. data/lib/active_support/core_ext/object/to_query.rb +66 -9
  102. data/lib/active_support/core_ext/object/try.rb +124 -33
  103. data/lib/active_support/core_ext/object/with_options.rb +37 -11
  104. data/lib/active_support/core_ext/object.rb +2 -1
  105. data/lib/active_support/core_ext/range/conversions.rb +17 -7
  106. data/lib/active_support/core_ext/range/each.rb +21 -0
  107. data/lib/active_support/core_ext/range/include_range.rb +20 -18
  108. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  109. data/lib/active_support/core_ext/range.rb +1 -2
  110. data/lib/active_support/core_ext/securerandom.rb +23 -0
  111. data/lib/active_support/core_ext/string/access.rb +95 -90
  112. data/lib/active_support/core_ext/string/behavior.rb +1 -1
  113. data/lib/active_support/core_ext/string/conversions.rb +41 -38
  114. data/lib/active_support/core_ext/string/exclude.rb +6 -1
  115. data/lib/active_support/core_ext/string/filters.rb +70 -17
  116. data/lib/active_support/core_ext/string/indent.rb +43 -0
  117. data/lib/active_support/core_ext/string/inflections.rb +139 -59
  118. data/lib/active_support/core_ext/string/inquiry.rb +2 -2
  119. data/lib/active_support/core_ext/string/multibyte.rb +46 -65
  120. data/lib/active_support/core_ext/string/output_safety.rb +153 -56
  121. data/lib/active_support/core_ext/string/strip.rb +3 -6
  122. data/lib/active_support/core_ext/string/zones.rb +14 -0
  123. data/lib/active_support/core_ext/string.rb +2 -3
  124. data/lib/active_support/core_ext/struct.rb +3 -0
  125. data/lib/active_support/core_ext/time/calculations.rb +173 -173
  126. data/lib/active_support/core_ext/time/compatibility.rb +5 -0
  127. data/lib/active_support/core_ext/time/conversions.rb +33 -29
  128. data/lib/active_support/core_ext/time/marshal.rb +2 -56
  129. data/lib/active_support/core_ext/time/zones.rb +57 -32
  130. data/lib/active_support/core_ext/time.rb +5 -0
  131. data/lib/active_support/core_ext/uri.rb +13 -19
  132. data/lib/active_support/core_ext.rb +3 -2
  133. data/lib/active_support/dependencies/autoload.rb +47 -20
  134. data/lib/active_support/dependencies/interlock.rb +51 -0
  135. data/lib/active_support/dependencies.rb +315 -265
  136. data/lib/active_support/deprecation/behaviors.rb +71 -30
  137. data/lib/active_support/deprecation/instance_delegator.rb +24 -0
  138. data/lib/active_support/deprecation/method_wrappers.rb +59 -18
  139. data/lib/active_support/deprecation/proxy_wrappers.rb +82 -14
  140. data/lib/active_support/deprecation/reporting.rb +61 -14
  141. data/lib/active_support/deprecation.rb +38 -13
  142. data/lib/active_support/descendants_tracker.rb +34 -19
  143. data/lib/active_support/duration/iso8601_parser.rb +122 -0
  144. data/lib/active_support/duration/iso8601_serializer.rb +51 -0
  145. data/lib/active_support/duration.rb +85 -14
  146. data/lib/active_support/evented_file_update_checker.rb +194 -0
  147. data/lib/active_support/execution_wrapper.rb +117 -0
  148. data/lib/active_support/executor.rb +6 -0
  149. data/lib/active_support/file_update_checker.rb +138 -17
  150. data/lib/active_support/gem_version.rb +15 -0
  151. data/lib/active_support/gzip.rb +11 -5
  152. data/lib/active_support/hash_with_indifferent_access.rb +199 -49
  153. data/lib/active_support/i18n.rb +6 -2
  154. data/lib/active_support/i18n_railtie.rb +40 -21
  155. data/lib/active_support/inflections.rb +22 -13
  156. data/lib/active_support/inflector/inflections.rb +175 -144
  157. data/lib/active_support/inflector/methods.rb +328 -91
  158. data/lib/active_support/inflector/transliterate.rb +51 -37
  159. data/lib/active_support/json/decoding.rb +31 -22
  160. data/lib/active_support/json/encoding.rb +88 -248
  161. data/lib/active_support/key_generator.rb +71 -0
  162. data/lib/active_support/lazy_load_hooks.rb +27 -25
  163. data/lib/active_support/locale/en.yml +102 -3
  164. data/lib/active_support/log_subscriber/test_helper.rb +24 -21
  165. data/lib/active_support/log_subscriber.rb +36 -49
  166. data/lib/active_support/logger.rb +106 -0
  167. data/lib/active_support/logger_silence.rb +28 -0
  168. data/lib/active_support/logger_thread_safe_level.rb +31 -0
  169. data/lib/active_support/message_encryptor.rb +72 -36
  170. data/lib/active_support/message_verifier.rb +96 -24
  171. data/lib/active_support/multibyte/chars.rb +88 -333
  172. data/lib/active_support/multibyte/unicode.rb +156 -136
  173. data/lib/active_support/multibyte.rb +5 -28
  174. data/lib/active_support/notifications/fanout.rb +115 -19
  175. data/lib/active_support/notifications/instrumenter.rb +52 -15
  176. data/lib/active_support/notifications.rb +168 -33
  177. data/lib/active_support/number_helper/number_converter.rb +182 -0
  178. data/lib/active_support/number_helper/number_to_currency_converter.rb +44 -0
  179. data/lib/active_support/number_helper/number_to_delimited_converter.rb +28 -0
  180. data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
  181. data/lib/active_support/number_helper/number_to_human_size_converter.rb +62 -0
  182. data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
  183. data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
  184. data/lib/active_support/number_helper/number_to_rounded_converter.rb +92 -0
  185. data/lib/active_support/number_helper.rb +368 -0
  186. data/lib/active_support/option_merger.rb +1 -1
  187. data/lib/active_support/ordered_hash.rb +18 -183
  188. data/lib/active_support/ordered_options.rb +44 -24
  189. data/lib/active_support/per_thread_registry.rb +58 -0
  190. data/lib/active_support/proxy_object.rb +13 -0
  191. data/lib/active_support/rails.rb +27 -0
  192. data/lib/active_support/railtie.rb +25 -34
  193. data/lib/active_support/reloader.rb +129 -0
  194. data/lib/active_support/rescuable.rb +98 -48
  195. data/lib/active_support/security_utils.rb +27 -0
  196. data/lib/active_support/string_inquirer.rb +14 -9
  197. data/lib/active_support/subscriber.rb +120 -0
  198. data/lib/active_support/tagged_logging.rb +78 -0
  199. data/lib/active_support/test_case.rb +69 -17
  200. data/lib/active_support/testing/assertions.rb +43 -41
  201. data/lib/active_support/testing/autorun.rb +12 -0
  202. data/lib/active_support/testing/constant_lookup.rb +50 -0
  203. data/lib/active_support/testing/declarative.rb +7 -21
  204. data/lib/active_support/testing/deprecation.rb +14 -33
  205. data/lib/active_support/testing/file_fixtures.rb +34 -0
  206. data/lib/active_support/testing/isolation.rb +53 -95
  207. data/lib/active_support/testing/method_call_assertions.rb +41 -0
  208. data/lib/active_support/testing/setup_and_teardown.rb +21 -82
  209. data/lib/active_support/testing/stream.rb +42 -0
  210. data/lib/active_support/testing/tagged_logging.rb +25 -0
  211. data/lib/active_support/testing/time_helpers.rb +134 -0
  212. data/lib/active_support/time.rb +6 -23
  213. data/lib/active_support/time_with_zone.rb +239 -92
  214. data/lib/active_support/values/time_zone.rb +236 -160
  215. data/lib/active_support/values/unicode_tables.dat +0 -0
  216. data/lib/active_support/version.rb +5 -7
  217. data/lib/active_support/xml_mini/jdom.rb +19 -13
  218. data/lib/active_support/xml_mini/libxml.rb +3 -4
  219. data/lib/active_support/xml_mini/libxmlsax.rb +2 -3
  220. data/lib/active_support/xml_mini/nokogiri.rb +3 -4
  221. data/lib/active_support/xml_mini/nokogirisax.rb +2 -3
  222. data/lib/active_support/xml_mini/rexml.rb +8 -10
  223. data/lib/active_support/xml_mini.rb +66 -34
  224. data/lib/active_support.rb +40 -23
  225. metadata +185 -134
  226. data/CHANGELOG +0 -1534
  227. data/lib/active_support/base64.rb +0 -42
  228. data/lib/active_support/basic_object.rb +0 -21
  229. data/lib/active_support/buffered_logger.rb +0 -137
  230. data/lib/active_support/cache/compressed_mem_cache_store.rb +0 -13
  231. data/lib/active_support/cache/synchronized_memory_store.rb +0 -11
  232. data/lib/active_support/core_ext/array/random_access.rb +0 -30
  233. data/lib/active_support/core_ext/array/uniq_by.rb +0 -16
  234. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -44
  235. data/lib/active_support/core_ext/class/inheritable_attributes.rb +0 -178
  236. data/lib/active_support/core_ext/date/freeze.rb +0 -31
  237. data/lib/active_support/core_ext/date_time/zones.rb +0 -21
  238. data/lib/active_support/core_ext/exception.rb +0 -3
  239. data/lib/active_support/core_ext/file/path.rb +0 -5
  240. data/lib/active_support/core_ext/float/rounding.rb +0 -19
  241. data/lib/active_support/core_ext/float.rb +0 -1
  242. data/lib/active_support/core_ext/hash/deep_dup.rb +0 -11
  243. data/lib/active_support/core_ext/hash/diff.rb +0 -13
  244. data/lib/active_support/core_ext/kernel/requires.rb +0 -28
  245. data/lib/active_support/core_ext/logger.rb +0 -81
  246. data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +0 -31
  247. data/lib/active_support/core_ext/module/method_names.rb +0 -14
  248. data/lib/active_support/core_ext/module/synchronization.rb +0 -43
  249. data/lib/active_support/core_ext/object/to_json.rb +0 -19
  250. data/lib/active_support/core_ext/proc.rb +0 -14
  251. data/lib/active_support/core_ext/process/daemon.rb +0 -23
  252. data/lib/active_support/core_ext/process.rb +0 -1
  253. data/lib/active_support/core_ext/range/blockless_step.rb +0 -29
  254. data/lib/active_support/core_ext/range/cover.rb +0 -3
  255. data/lib/active_support/core_ext/rexml.rb +0 -46
  256. data/lib/active_support/core_ext/string/encoding.rb +0 -11
  257. data/lib/active_support/core_ext/string/interpolation.rb +0 -2
  258. data/lib/active_support/core_ext/string/xchar.rb +0 -18
  259. data/lib/active_support/core_ext/time/publicize_conversion_methods.rb +0 -10
  260. data/lib/active_support/file_watcher.rb +0 -36
  261. data/lib/active_support/json/variable.rb +0 -9
  262. data/lib/active_support/memoizable.rb +0 -105
  263. data/lib/active_support/multibyte/exceptions.rb +0 -8
  264. data/lib/active_support/multibyte/utils.rb +0 -60
  265. data/lib/active_support/ruby/shim.rb +0 -22
  266. data/lib/active_support/secure_random.rb +0 -6
  267. data/lib/active_support/testing/mochaing.rb +0 -7
  268. data/lib/active_support/testing/pending.rb +0 -52
  269. data/lib/active_support/testing/performance/jruby.rb +0 -115
  270. data/lib/active_support/testing/performance/rubinius.rb +0 -113
  271. data/lib/active_support/testing/performance/ruby/mri.rb +0 -57
  272. data/lib/active_support/testing/performance/ruby/yarv.rb +0 -57
  273. data/lib/active_support/testing/performance/ruby.rb +0 -152
  274. data/lib/active_support/testing/performance.rb +0 -317
  275. data/lib/active_support/time/autoload.rb +0 -5
  276. data/lib/active_support/whiny_nil.rb +0 -60
@@ -1,30 +1,33 @@
1
1
  require 'active_support/concern'
2
2
  require 'active_support/descendants_tracker'
3
- require 'active_support/core_ext/array/wrap'
3
+ require 'active_support/core_ext/array/extract_options'
4
4
  require 'active_support/core_ext/class/attribute'
5
5
  require 'active_support/core_ext/kernel/reporting'
6
6
  require 'active_support/core_ext/kernel/singleton_class'
7
- require 'active_support/core_ext/object/inclusion'
7
+ require 'active_support/core_ext/module/attribute_accessors'
8
+ require 'active_support/core_ext/string/filters'
9
+ require 'active_support/deprecation'
10
+ require 'thread'
8
11
 
9
12
  module ActiveSupport
10
- # \Callbacks are code hooks that are run at key points in an object's lifecycle.
11
- # The typical use case is to have a base class define a set of callbacks relevant
12
- # to the other functionality it supplies, so that subclasses can install callbacks
13
- # that enhance or modify the base functionality without needing to override
14
- # or redefine methods of the base class.
13
+ # Callbacks are code hooks that are run at key points in an object's life cycle.
14
+ # The typical use case is to have a base class define a set of callbacks
15
+ # relevant to the other functionality it supplies, so that subclasses can
16
+ # install callbacks that enhance or modify the base functionality without
17
+ # needing to override or redefine methods of the base class.
15
18
  #
16
- # Mixing in this module allows you to define the events in the object's lifecycle
17
- # that will support callbacks (via +ClassMethods.define_callbacks+), set the instance
18
- # methods, procs, or callback objects to be called (via +ClassMethods.set_callback+),
19
- # and run the installed callbacks at the appropriate times (via +run_callbacks+).
19
+ # Mixing in this module allows you to define the events in the object's
20
+ # life cycle that will support callbacks (via +ClassMethods.define_callbacks+),
21
+ # set the instance methods, procs, or callback objects to be called (via
22
+ # +ClassMethods.set_callback+), and run the installed callbacks at the
23
+ # appropriate times (via +run_callbacks+).
20
24
  #
21
- # Three kinds of callbacks are supported: before callbacks, run before a certain event;
22
- # after callbacks, run after the event; and around callbacks, blocks that surround the
23
- # event, triggering it when they yield. Callback code can be contained in instance
24
- # methods, procs or lambdas, or callback objects that respond to certain predetermined
25
- # methods. See +ClassMethods.set_callback+ for details.
26
- #
27
- # ==== Example
25
+ # Three kinds of callbacks are supported: before callbacks, run before a
26
+ # certain event; after callbacks, run after the event; and around callbacks,
27
+ # blocks that surround the event, triggering it when they yield. Callback code
28
+ # can be contained in instance methods, procs or lambdas, or callback objects
29
+ # that respond to certain predetermined methods. See +ClassMethods.set_callback+
30
+ # for details.
28
31
  #
29
32
  # class Record
30
33
  # include ActiveSupport::Callbacks
@@ -55,7 +58,6 @@ module ActiveSupport
55
58
  # saving...
56
59
  # - save
57
60
  # saved
58
- #
59
61
  module Callbacks
60
62
  extend Concern
61
63
 
@@ -63,510 +65,635 @@ module ActiveSupport
63
65
  extend ActiveSupport::DescendantsTracker
64
66
  end
65
67
 
68
+ CALLBACK_FILTER_TYPES = [:before, :after, :around]
69
+
70
+ # If true, Active Record and Active Model callbacks returning +false+ will
71
+ # halt the entire callback chain and display a deprecation message.
72
+ # If false, callback chains will only be halted by calling +throw :abort+.
73
+ # Defaults to +true+.
74
+ mattr_accessor(:halt_and_display_warning_on_return_false, instance_writer: false) { true }
75
+
66
76
  # Runs the callbacks for the given event.
67
77
  #
68
78
  # Calls the before and around callbacks in the order they were set, yields
69
- # the block (if given one), and then runs the after callbacks in reverse order.
70
- # Optionally accepts a key, which will be used to compile an optimized callback
71
- # method for each key. See +ClassMethods.define_callbacks+ for more information.
79
+ # the block (if given one), and then runs the after callbacks in reverse
80
+ # order.
72
81
  #
73
- # If the callback chain was halted, returns +false+. Otherwise returns the result
74
- # of the block, or +true+ if no block is given.
82
+ # If the callback chain was halted, returns +false+. Otherwise returns the
83
+ # result of the block, +nil+ if no callbacks have been set, or +true+
84
+ # if callbacks have been set but no block is given.
75
85
  #
76
86
  # run_callbacks :save do
77
87
  # save
78
88
  # end
79
- #
80
- def run_callbacks(kind, *args, &block)
81
- send("_run_#{kind}_callbacks", *args, &block)
89
+ def run_callbacks(kind, &block)
90
+ send "_run_#{kind}_callbacks", &block
82
91
  end
83
92
 
84
- class Callback #:nodoc:#
85
- @@_callback_sequence = 0
86
-
87
- attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
88
-
89
- def initialize(chain, filter, kind, options, klass)
90
- @chain, @kind, @klass = chain, kind, klass
91
- normalize_options!(options)
92
-
93
- @per_key = options.delete(:per_key)
94
- @raw_filter, @options = filter, options
95
- @filter = _compile_filter(filter)
96
- @compiled_options = _compile_options(options)
97
- @callback_id = next_id
93
+ private
98
94
 
99
- _compile_per_key_options
95
+ def __run_callbacks__(callbacks, &block)
96
+ if callbacks.empty?
97
+ yield if block_given?
98
+ else
99
+ runner = callbacks.compile
100
+ e = Filters::Environment.new(self, false, nil, block)
101
+ runner.call(e).value
100
102
  end
103
+ end
104
+
105
+ # A hook invoked every time a before callback is halted.
106
+ # This can be overridden in AS::Callback implementors in order
107
+ # to provide better debugging/logging.
108
+ def halted_callback_hook(filter)
109
+ end
101
110
 
102
- def clone(chain, klass)
103
- obj = super()
104
- obj.chain = chain
105
- obj.klass = klass
106
- obj.per_key = @per_key.dup
107
- obj.options = @options.dup
108
- obj.per_key[:if] = @per_key[:if].dup
109
- obj.per_key[:unless] = @per_key[:unless].dup
110
- obj.options[:if] = @options[:if].dup
111
- obj.options[:unless] = @options[:unless].dup
112
- obj
111
+ module Conditionals # :nodoc:
112
+ class Value
113
+ def initialize(&block)
114
+ @block = block
115
+ end
116
+ def call(target, value); @block.call(value); end
113
117
  end
118
+ end
114
119
 
115
- def normalize_options!(options)
116
- options[:if] = Array.wrap(options[:if])
117
- options[:unless] = Array.wrap(options[:unless])
120
+ module Filters
121
+ Environment = Struct.new(:target, :halted, :value, :run_block)
118
122
 
119
- options[:per_key] ||= {}
120
- options[:per_key][:if] = Array.wrap(options[:per_key][:if])
121
- options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
123
+ class End
124
+ def call(env)
125
+ block = env.run_block
126
+ env.value = !env.halted && (!block || block.call)
127
+ env
128
+ end
122
129
  end
130
+ ENDING = End.new
123
131
 
124
- def name
125
- chain.name
126
- end
132
+ class Before
133
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
134
+ halted_lambda = chain_config[:terminator]
127
135
 
128
- def next_id
129
- @@_callback_sequence += 1
130
- end
136
+ if user_conditions.any?
137
+ halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
138
+ else
139
+ halting(callback_sequence, user_callback, halted_lambda, filter)
140
+ end
141
+ end
131
142
 
132
- def matches?(_kind, _filter)
133
- @kind == _kind && @filter == _filter
134
- end
143
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
144
+ callback_sequence.before do |env|
145
+ target = env.target
146
+ value = env.value
147
+ halted = env.halted
148
+
149
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
150
+ result_lambda = -> { user_callback.call target, value }
151
+ env.halted = halted_lambda.call(target, result_lambda)
152
+ if env.halted
153
+ target.send :halted_callback_hook, filter
154
+ end
155
+ end
135
156
 
136
- def _update_filter(filter_options, new_options)
137
- filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
138
- filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
139
- end
157
+ env
158
+ end
159
+ end
160
+ private_class_method :halting_and_conditional
140
161
 
141
- def recompile!(_options, _per_key)
142
- _update_filter(self.options, _options)
143
- _update_filter(self.per_key, _per_key)
162
+ def self.halting(callback_sequence, user_callback, halted_lambda, filter)
163
+ callback_sequence.before do |env|
164
+ target = env.target
165
+ value = env.value
166
+ halted = env.halted
144
167
 
145
- @callback_id = next_id
146
- @filter = _compile_filter(@raw_filter)
147
- @compiled_options = _compile_options(@options)
148
- _compile_per_key_options
149
- end
168
+ unless halted
169
+ result_lambda = -> { user_callback.call target, value }
170
+ env.halted = halted_lambda.call(target, result_lambda)
150
171
 
151
- def _compile_per_key_options
152
- key_options = _compile_options(@per_key)
172
+ if env.halted
173
+ target.send :halted_callback_hook, filter
174
+ end
175
+ end
153
176
 
154
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
155
- def _one_time_conditions_valid_#{@callback_id}?
156
- true #{key_options[0]}
177
+ env
157
178
  end
158
- RUBY_EVAL
179
+ end
180
+ private_class_method :halting
159
181
  end
160
182
 
161
- # This will supply contents for before and around filters, and no
162
- # contents for after filters (for the forward pass).
163
- def start(key=nil, object=nil)
164
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
183
+ class After
184
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
185
+ if chain_config[:skip_after_callbacks_if_terminated]
186
+ if user_conditions.any?
187
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
188
+ else
189
+ halting(callback_sequence, user_callback)
190
+ end
191
+ else
192
+ if user_conditions.any?
193
+ conditional callback_sequence, user_callback, user_conditions
194
+ else
195
+ simple callback_sequence, user_callback
196
+ end
197
+ end
198
+ end
165
199
 
166
- # options[0] is the compiled form of supplied conditions
167
- # options[1] is the "end" for the conditional
168
- #
169
- case @kind
170
- when :before
171
- # if condition # before_save :filter_name, :if => :condition
172
- # filter_name
173
- # end
174
- filter = <<-RUBY_EVAL
175
- unless halted
176
- # This double assignment is to prevent warnings in 1.9.3. I would
177
- # remove the `result` variable, but apparently some other
178
- # generated code is depending on this variable being set sometimes
179
- # and sometimes not.
180
- result = result = #{@filter}
181
- halted = (#{chain.config[:terminator]})
200
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
201
+ callback_sequence.after do |env|
202
+ target = env.target
203
+ value = env.value
204
+ halted = env.halted
205
+
206
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
207
+ user_callback.call target, value
182
208
  end
183
- RUBY_EVAL
184
209
 
185
- [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
186
- when :around
187
- # Compile around filters with conditions into proxy methods
188
- # that contain the conditions.
189
- #
190
- # For `around_save :filter_name, :if => :condition':
191
- #
192
- # def _conditional_callback_save_17
193
- # if condition
194
- # filter_name do
195
- # yield self
196
- # end
197
- # else
198
- # yield self
199
- # end
200
- # end
201
- #
202
- name = "_conditional_callback_#{@kind}_#{next_id}"
203
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
204
- def #{name}(halted)
205
- #{@compiled_options[0] || "if true"} && !halted
206
- #{@filter} do
207
- yield self
208
- end
209
- else
210
- yield self
211
- end
210
+ env
211
+ end
212
+ end
213
+ private_class_method :halting_and_conditional
214
+
215
+ def self.halting(callback_sequence, user_callback)
216
+ callback_sequence.after do |env|
217
+ unless env.halted
218
+ user_callback.call env.target, env.value
219
+ end
220
+
221
+ env
222
+ end
223
+ end
224
+ private_class_method :halting
225
+
226
+ def self.conditional(callback_sequence, user_callback, user_conditions)
227
+ callback_sequence.after do |env|
228
+ target = env.target
229
+ value = env.value
230
+
231
+ if user_conditions.all? { |c| c.call(target, value) }
232
+ user_callback.call target, value
212
233
  end
213
- RUBY_EVAL
214
- "#{name}(halted) do"
234
+
235
+ env
236
+ end
237
+ end
238
+ private_class_method :conditional
239
+
240
+ def self.simple(callback_sequence, user_callback)
241
+ callback_sequence.after do |env|
242
+ user_callback.call env.target, env.value
243
+
244
+ env
245
+ end
215
246
  end
247
+ private_class_method :simple
216
248
  end
217
249
 
218
- # This will supply contents for around and after filters, but not
219
- # before filters (for the backward pass).
220
- def end(key=nil, object=nil)
221
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
250
+ class Around
251
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
252
+ if user_conditions.any?
253
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
254
+ else
255
+ halting(callback_sequence, user_callback)
256
+ end
257
+ end
222
258
 
223
- case @kind
224
- when :after
225
- # if condition # after_save :filter_name, :if => :condition
226
- # filter_name
227
- # end
228
- [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
229
- when :around
230
- <<-RUBY_EVAL
231
- value
259
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
260
+ callback_sequence.around do |env, &run|
261
+ target = env.target
262
+ value = env.value
263
+ halted = env.halted
264
+
265
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
266
+ user_callback.call(target, value) {
267
+ run.call.value
268
+ }
269
+ env
270
+ else
271
+ run.call
272
+ end
273
+ end
274
+ end
275
+ private_class_method :halting_and_conditional
276
+
277
+ def self.halting(callback_sequence, user_callback)
278
+ callback_sequence.around do |env, &run|
279
+ target = env.target
280
+ value = env.value
281
+
282
+ if env.halted
283
+ run.call
284
+ else
285
+ user_callback.call(target, value) {
286
+ run.call.value
287
+ }
288
+ env
289
+ end
232
290
  end
233
- RUBY_EVAL
234
291
  end
292
+ private_class_method :halting
235
293
  end
294
+ end
236
295
 
237
- private
296
+ class Callback #:nodoc:#
297
+ def self.build(chain, filter, kind, options)
298
+ if filter.is_a?(String)
299
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
300
+ Passing string to define callback is deprecated and will be removed
301
+ in Rails 5.1 without replacement.
302
+ MSG
303
+ end
304
+
305
+ new chain.name, filter, kind, options, chain.config
306
+ end
238
307
 
239
- # Options support the same options as filters themselves (and support
240
- # symbols, string, procs, and objects), so compile a conditional
241
- # expression based on the options
242
- def _compile_options(options)
243
- return [] if options[:if].empty? && options[:unless].empty?
308
+ attr_accessor :kind, :name
309
+ attr_reader :chain_config
310
+
311
+ def initialize(name, filter, kind, options, chain_config)
312
+ @chain_config = chain_config
313
+ @name = name
314
+ @kind = kind
315
+ @filter = filter
316
+ @key = compute_identifier filter
317
+ @if = Array(options[:if])
318
+ @unless = Array(options[:unless])
319
+ end
320
+
321
+ def filter; @key; end
322
+ def raw_filter; @filter; end
323
+
324
+ def merge_conditional_options(chain, if_option:, unless_option:)
325
+ options = {
326
+ :if => @if.dup,
327
+ :unless => @unless.dup
328
+ }
329
+
330
+ options[:if].concat Array(unless_option)
331
+ options[:unless].concat Array(if_option)
332
+
333
+ self.class.build chain, @filter, @kind, options
334
+ end
244
335
 
245
- conditions = []
336
+ def matches?(_kind, _filter)
337
+ @kind == _kind && filter == _filter
338
+ end
246
339
 
247
- unless options[:if].empty?
248
- conditions << Array.wrap(_compile_filter(options[:if]))
340
+ def duplicates?(other)
341
+ case @filter
342
+ when Symbol, String
343
+ matches?(other.kind, other.filter)
344
+ else
345
+ false
249
346
  end
347
+ end
348
+
349
+ # Wraps code with filter
350
+ def apply(callback_sequence)
351
+ user_conditions = conditions_lambdas
352
+ user_callback = make_lambda @filter
250
353
 
251
- unless options[:unless].empty?
252
- conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
354
+ case kind
355
+ when :before
356
+ Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
357
+ when :after
358
+ Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
359
+ when :around
360
+ Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
253
361
  end
362
+ end
254
363
 
255
- ["if #{conditions.flatten.join(" && ")}", "end"]
364
+ private
365
+
366
+ def invert_lambda(l)
367
+ lambda { |*args, &blk| !l.call(*args, &blk) }
256
368
  end
257
369
 
258
370
  # Filters support:
259
371
  #
260
- # Arrays:: Used in conditions. This is used to specify
261
- # multiple conditions. Used internally to
262
- # merge conditions from skip_* filters
263
- # Symbols:: A method to call
264
- # Strings:: Some content to evaluate
265
- # Procs:: A proc to call with the object
266
- # Objects:: An object with a before_foo method on it to call
267
- #
268
- # All of these objects are compiled into methods and handled
269
- # the same after this point:
270
- #
271
- # Arrays:: Merged together into a single filter
272
- # Symbols:: Already methods
273
- # Strings:: class_eval'ed into methods
274
- # Procs:: define_method'ed into methods
275
- # Objects::
276
- # a method is created that calls the before_foo method
277
- # on the object.
278
- #
279
- def _compile_filter(filter)
280
- method_name = "_callback_#{@kind}_#{next_id}"
372
+ # Symbols:: A method to call.
373
+ # Strings:: Some content to evaluate.
374
+ # Procs:: A proc to call with the object.
375
+ # Objects:: An object with a <tt>before_foo</tt> method on it to call.
376
+ #
377
+ # All of these objects are converted into a lambda and handled
378
+ # the same after this point.
379
+ def make_lambda(filter)
281
380
  case filter
282
- when Array
283
- filter.map {|f| _compile_filter(f)}
284
381
  when Symbol
285
- filter
382
+ lambda { |target, _, &blk| target.send filter, &blk }
286
383
  when String
287
- "(#{filter})"
288
- when Proc
289
- @klass.send(:define_method, method_name, &filter)
290
- return method_name if filter.arity <= 0
384
+ l = eval "lambda { |value| #{filter} }"
385
+ lambda { |target, value| target.instance_exec(value, &l) }
386
+ when Conditionals::Value then filter
387
+ when ::Proc
388
+ if filter.arity > 1
389
+ return lambda { |target, _, &block|
390
+ raise ArgumentError unless block
391
+ target.instance_exec(target, block, &filter)
392
+ }
393
+ end
291
394
 
292
- method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
395
+ if filter.arity <= 0
396
+ lambda { |target, _| target.instance_exec(&filter) }
397
+ else
398
+ lambda { |target, _| target.instance_exec(target, &filter) }
399
+ end
293
400
  else
294
- @klass.send(:define_method, "#{method_name}_object") { filter }
401
+ scopes = Array(chain_config[:scope])
402
+ method_to_call = scopes.map{ |s| public_send(s) }.join("_")
295
403
 
296
- _normalize_legacy_filter(kind, filter)
297
- scopes = Array.wrap(chain.config[:scope])
298
- method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
299
-
300
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
301
- def #{method_name}(&blk)
302
- #{method_name}_object.send(:#{method_to_call}, self, &blk)
303
- end
304
- RUBY_EVAL
404
+ lambda { |target, _, &blk|
405
+ filter.public_send method_to_call, target, &blk
406
+ }
407
+ end
408
+ end
305
409
 
306
- method_name
410
+ def compute_identifier(filter)
411
+ case filter
412
+ when String, ::Proc
413
+ filter.object_id
414
+ else
415
+ filter
307
416
  end
308
417
  end
309
418
 
310
- def _normalize_legacy_filter(kind, filter)
311
- if !filter.respond_to?(kind) && filter.respond_to?(:filter)
312
- filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
313
- def #{kind}(context, &block) filter(context, &block) end
314
- RUBY_EVAL
315
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
316
- def filter.around(context)
317
- should_continue = before(context)
318
- yield if should_continue
319
- after(context)
320
- end
419
+ def conditions_lambdas
420
+ @if.map { |c| make_lambda c } +
421
+ @unless.map { |c| invert_lambda make_lambda c }
422
+ end
423
+ end
424
+
425
+ # Execute before and after filters in a sequence instead of
426
+ # chaining them with nested lambda calls, see:
427
+ # https://github.com/rails/rails/issues/18011
428
+ class CallbackSequence
429
+ def initialize(&call)
430
+ @call = call
431
+ @before = []
432
+ @after = []
433
+ end
434
+
435
+ def before(&before)
436
+ @before.unshift(before)
437
+ self
438
+ end
439
+
440
+ def after(&after)
441
+ @after.push(after)
442
+ self
443
+ end
444
+
445
+ def around(&around)
446
+ CallbackSequence.new do |arg|
447
+ around.call(arg) {
448
+ self.call(arg)
449
+ }
321
450
  end
322
451
  end
452
+
453
+ def call(arg)
454
+ @before.each { |b| b.call(arg) }
455
+ value = @call.call(arg)
456
+ @after.each { |a| a.call(arg) }
457
+ value
458
+ end
323
459
  end
324
460
 
325
- # An Array with a compile method
326
- class CallbackChain < Array #:nodoc:#
461
+ # An Array with a compile method.
462
+ class CallbackChain #:nodoc:#
463
+ include Enumerable
464
+
327
465
  attr_reader :name, :config
328
466
 
329
467
  def initialize(name, config)
330
468
  @name = name
331
469
  @config = {
332
- :terminator => "false",
333
- :rescuable => false,
334
- :scope => [ :kind ]
335
- }.merge(config)
470
+ scope: [:kind],
471
+ terminator: default_terminator
472
+ }.merge!(config)
473
+ @chain = []
474
+ @callbacks = nil
475
+ @mutex = Mutex.new
336
476
  end
337
477
 
338
- def compile(key=nil, object=nil)
339
- method = []
340
- method << "value = nil"
341
- method << "halted = false"
478
+ def each(&block); @chain.each(&block); end
479
+ def index(o); @chain.index(o); end
480
+ def empty?; @chain.empty?; end
342
481
 
343
- each do |callback|
344
- method << callback.start(key, object)
345
- end
482
+ def insert(index, o)
483
+ @callbacks = nil
484
+ @chain.insert(index, o)
485
+ end
346
486
 
347
- if config[:rescuable]
348
- method << "rescued_error = nil"
349
- method << "begin"
350
- end
487
+ def delete(o)
488
+ @callbacks = nil
489
+ @chain.delete(o)
490
+ end
491
+
492
+ def clear
493
+ @callbacks = nil
494
+ @chain.clear
495
+ self
496
+ end
351
497
 
352
- method << "value = yield if block_given? && !halted"
498
+ def initialize_copy(other)
499
+ @callbacks = nil
500
+ @chain = other.chain.dup
501
+ @mutex = Mutex.new
502
+ end
353
503
 
354
- if config[:rescuable]
355
- method << "rescue Exception => e"
356
- method << "rescued_error = e"
357
- method << "end"
504
+ def compile
505
+ @callbacks || @mutex.synchronize do
506
+ final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
507
+ @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
508
+ callback.apply callback_sequence
509
+ end
358
510
  end
511
+ end
359
512
 
360
- reverse_each do |callback|
361
- method << callback.end(key, object)
362
- end
513
+ def append(*callbacks)
514
+ callbacks.each { |c| append_one(c) }
515
+ end
363
516
 
364
- method << "raise rescued_error if rescued_error" if config[:rescuable]
365
- method << "halted ? false : (block_given? ? value : true)"
366
- method.compact.join("\n")
517
+ def prepend(*callbacks)
518
+ callbacks.each { |c| prepend_one(c) }
367
519
  end
368
- end
369
520
 
370
- module ClassMethods
371
- # Generate the internal runner method called by +run_callbacks+.
372
- def __define_runner(symbol) #:nodoc:
373
- body = send("_#{symbol}_callbacks").compile
374
-
375
- silence_warnings do
376
- undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
377
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
378
- def _run_#{symbol}_callbacks(key = nil, &blk)
379
- if key
380
- name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
381
-
382
- unless respond_to?(name)
383
- self.class.__create_keyed_callback(name, :#{symbol}, self, &blk)
384
- end
385
-
386
- send(name, &blk)
387
- else
388
- #{body}
389
- end
390
- end
391
- private :_run_#{symbol}_callbacks
392
- RUBY_EVAL
393
- end
521
+ protected
522
+ def chain; @chain; end
523
+
524
+ private
525
+
526
+ def append_one(callback)
527
+ @callbacks = nil
528
+ remove_duplicates(callback)
529
+ @chain.push(callback)
394
530
  end
395
531
 
396
- # This is called the first time a callback is called with a particular
397
- # key. It creates a new callback method for the key, calculating
398
- # which callbacks can be omitted because of per_key conditions.
399
- #
400
- def __create_keyed_callback(name, kind, object, &blk) #:nodoc:
401
- @_keyed_callbacks ||= {}
402
- @_keyed_callbacks[name] ||= begin
403
- str = send("_#{kind}_callbacks").compile(name, object)
404
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
405
- def #{name}() #{str} end
406
- protected :#{name}
407
- RUBY_EVAL
408
- true
532
+ def prepend_one(callback)
533
+ @callbacks = nil
534
+ remove_duplicates(callback)
535
+ @chain.unshift(callback)
536
+ end
537
+
538
+ def remove_duplicates(callback)
539
+ @callbacks = nil
540
+ @chain.delete_if { |c| callback.duplicates?(c) }
541
+ end
542
+
543
+ def default_terminator
544
+ Proc.new do |target, result_lambda|
545
+ terminate = true
546
+ catch(:abort) do
547
+ result_lambda.call if result_lambda.is_a?(Proc)
548
+ terminate = false
549
+ end
550
+ terminate
409
551
  end
410
552
  end
553
+ end
411
554
 
412
- # This is used internally to append, prepend and skip callbacks to the
413
- # CallbackChain.
414
- #
415
- def __update_callbacks(name, filters = [], block = nil) #:nodoc:
416
- type = filters.first.in?([:before, :after, :around]) ? filters.shift : :before
417
- options = filters.last.is_a?(Hash) ? filters.pop : {}
555
+ module ClassMethods
556
+ def normalize_callback_params(filters, block) # :nodoc:
557
+ type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
558
+ options = filters.extract_options!
418
559
  filters.unshift(block) if block
560
+ [type, filters, options.dup]
561
+ end
419
562
 
420
- ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
421
- chain = target.send("_#{name}_callbacks")
422
- yield target, chain.dup, type, filters, options
423
- target.__define_runner(name)
563
+ # This is used internally to append, prepend and skip callbacks to the
564
+ # CallbackChain.
565
+ def __update_callbacks(name) #:nodoc:
566
+ ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
567
+ chain = target.get_callbacks name
568
+ yield target, chain.dup
424
569
  end
425
570
  end
426
571
 
427
572
  # Install a callback for the given event.
428
573
  #
429
- # set_callback :save, :before, :before_meth
430
- # set_callback :save, :after, :after_meth, :if => :condition
431
- # set_callback :save, :around, lambda { |r| stuff; result = yield; stuff }
574
+ # set_callback :save, :before, :before_method
575
+ # set_callback :save, :after, :after_method, if: :condition
576
+ # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
432
577
  #
433
- # The second arguments indicates whether the callback is to be run +:before+,
578
+ # The second argument indicates whether the callback is to be run +:before+,
434
579
  # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
435
580
  # means the first example above can also be written as:
436
581
  #
437
- # set_callback :save, :before_meth
582
+ # set_callback :save, :before_method
438
583
  #
439
- # The callback can specified as a symbol naming an instance method; as a proc,
440
- # lambda, or block; as a string to be instance evaluated; or as an object that
441
- # responds to a certain method determined by the <tt>:scope</tt> argument to
442
- # +define_callback+.
584
+ # The callback can be specified as a symbol naming an instance method; as a
585
+ # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
586
+ # object that responds to a certain method determined by the <tt>:scope</tt>
587
+ # argument to +define_callbacks+.
443
588
  #
444
589
  # If a proc, lambda, or block is given, its body is evaluated in the context
445
590
  # of the current object. It can also optionally accept the current object as
446
591
  # an argument.
447
592
  #
448
- # Before and around callbacks are called in the order that they are set; after
449
- # callbacks are called in the reverse order.
450
- #
593
+ # Before and around callbacks are called in the order that they are set;
594
+ # after callbacks are called in the reverse order.
595
+ #
451
596
  # Around callbacks can access the return value from the event, if it
452
597
  # wasn't halted, from the +yield+ call.
453
598
  #
454
599
  # ===== Options
455
600
  #
456
- # * <tt>:if</tt> - A symbol naming an instance method or a proc; the callback
457
- # will be called only when it returns a true value.
458
- # * <tt>:unless</tt> - A symbol naming an instance method or a proc; the callback
459
- # will be called only when it returns a false value.
460
- # * <tt>:prepend</tt> - If true, the callback will be prepended to the existing
461
- # chain rather than appended.
462
- # * <tt>:per_key</tt> - A hash with <tt>:if</tt> and <tt>:unless</tt> options;
463
- # see "Per-key conditions" below.
464
- #
465
- # ===== Per-key conditions
466
- #
467
- # When creating or skipping callbacks, you can specify conditions that
468
- # are always the same for a given key. For instance, in Action Pack,
469
- # we convert :only and :except conditions into per-key conditions.
470
- #
471
- # before_filter :authenticate, :except => "index"
472
- #
473
- # becomes
474
- #
475
- # set_callback :process_action, :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
476
- #
477
- # Per-key conditions are evaluated only once per use of a given key.
478
- # In the case of the above example, you would do:
479
- #
480
- # run_callbacks(:process_action, action_name) { ... dispatch stuff ... }
481
- #
482
- # In that case, each action_name would get its own compiled callback
483
- # method that took into consideration the per_key conditions. This
484
- # is a speed improvement for ActionPack.
485
- #
601
+ # * <tt>:if</tt> - A symbol, a string or an array of symbols and strings,
602
+ # each naming an instance method or a proc; the callback will be called
603
+ # only when they all return a true value.
604
+ # * <tt>:unless</tt> - A symbol, a string or an array of symbols and
605
+ # strings, each naming an instance method or a proc; the callback will
606
+ # be called only when they all return a false value.
607
+ # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
608
+ # existing chain rather than appended.
486
609
  def set_callback(name, *filter_list, &block)
487
- mapped = nil
488
-
489
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
490
- mapped ||= filters.map do |filter|
491
- Callback.new(chain, filter, type, options.dup, self)
492
- end
493
-
494
- filters.each do |filter|
495
- chain.delete_if {|c| c.matches?(type, filter) }
496
- end
497
-
498
- options[:prepend] ? chain.unshift(*(mapped.reverse)) : chain.push(*mapped)
610
+ type, filters, options = normalize_callback_params(filter_list, block)
611
+ self_chain = get_callbacks name
612
+ mapped = filters.map do |filter|
613
+ Callback.build(self_chain, filter, type, options)
614
+ end
499
615
 
500
- target.send("_#{name}_callbacks=", chain)
616
+ __update_callbacks(name) do |target, chain|
617
+ options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
618
+ target.set_callbacks name, chain
501
619
  end
502
620
  end
503
621
 
504
- # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or <tt>:unless</tt>
505
- # options may be passed in order to control when the callback is skipped.
622
+ # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
623
+ # <tt>:unless</tt> options may be passed in order to control when the
624
+ # callback is skipped.
506
625
  #
507
626
  # class Writer < Person
508
- # skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 }
627
+ # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
509
628
  # end
510
629
  #
630
+ # An <tt>ArgumentError</tt> will be raised if the callback has not
631
+ # already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
511
632
  def skip_callback(name, *filter_list, &block)
512
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
633
+ type, filters, options = normalize_callback_params(filter_list, block)
634
+ options[:raise] = true unless options.key?(:raise)
635
+
636
+ __update_callbacks(name) do |target, chain|
513
637
  filters.each do |filter|
514
- filter = chain.find {|c| c.matches?(type, filter) }
638
+ callback = chain.find {|c| c.matches?(type, filter) }
515
639
 
516
- if filter && options.any?
517
- new_filter = filter.clone(chain, self)
518
- chain.insert(chain.index(filter), new_filter)
519
- new_filter.recompile!(options, options[:per_key] || {})
640
+ if !callback && options[:raise]
641
+ raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined"
520
642
  end
521
643
 
522
- chain.delete(filter)
644
+ if callback && (options.key?(:if) || options.key?(:unless))
645
+ new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
646
+ chain.insert(chain.index(callback), new_callback)
647
+ end
648
+
649
+ chain.delete(callback)
523
650
  end
524
- target.send("_#{name}_callbacks=", chain)
651
+ target.set_callbacks name, chain
525
652
  end
526
653
  end
527
654
 
528
655
  # Remove all set callbacks for the given event.
529
- #
530
- def reset_callbacks(symbol)
531
- callbacks = send("_#{symbol}_callbacks")
656
+ def reset_callbacks(name)
657
+ callbacks = get_callbacks name
532
658
 
533
659
  ActiveSupport::DescendantsTracker.descendants(self).each do |target|
534
- chain = target.send("_#{symbol}_callbacks").dup
660
+ chain = target.get_callbacks(name).dup
535
661
  callbacks.each { |c| chain.delete(c) }
536
- target.send("_#{symbol}_callbacks=", chain)
537
- target.__define_runner(symbol)
662
+ target.set_callbacks name, chain
538
663
  end
539
664
 
540
- self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
541
-
542
- __define_runner(symbol)
665
+ self.set_callbacks name, callbacks.dup.clear
543
666
  end
544
667
 
545
- # Define sets of events in the object lifecycle that support callbacks.
668
+ # Define sets of events in the object life cycle that support callbacks.
546
669
  #
547
670
  # define_callbacks :validate
548
671
  # define_callbacks :initialize, :save, :destroy
549
672
  #
550
673
  # ===== Options
551
674
  #
552
- # * <tt>:terminator</tt> - Determines when a before filter will halt the callback
553
- # chain, preventing following callbacks from being called and the event from being
554
- # triggered. This is a string to be eval'ed. The result of the callback is available
555
- # in the <tt>result</tt> variable.
675
+ # * <tt>:terminator</tt> - Determines when a before filter will halt the
676
+ # callback chain, preventing following before and around callbacks from
677
+ # being called and the event from being triggered.
678
+ # This should be a lambda to be executed.
679
+ # The current object and the result lambda of the callback will be provided
680
+ # to the terminator lambda.
556
681
  #
557
- # define_callbacks :validate, :terminator => "result == false"
682
+ # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
558
683
  #
559
684
  # In this example, if any before validate callbacks returns +false+,
560
- # other callbacks are not executed. Defaults to "false", meaning no value
561
- # halts the chain.
685
+ # any successive before and around callback is not executed.
686
+ #
687
+ # The default terminator halts the chain when a callback throws +:abort+.
562
688
  #
563
- # * <tt>:rescuable</tt> - By default, after filters are not executed if
564
- # the given block or a before filter raises an error. By setting this option
565
- # to <tt>true</tt> exception raised by given block is stored and after
566
- # executing all the after callbacks the stored exception is raised.
689
+ # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
690
+ # callbacks should be terminated by the <tt>:terminator</tt> option. By
691
+ # default after callbacks are executed no matter if callback chain was
692
+ # terminated or not. This option makes sense only when <tt>:terminator</tt>
693
+ # option is specified.
567
694
  #
568
- # * <tt>:scope</tt> - Indicates which methods should be executed when an object
569
- # is used as a callback.
695
+ # * <tt>:scope</tt> - Indicates which methods should be executed when an
696
+ # object is used as a callback.
570
697
  #
571
698
  # class Audit
572
699
  # def before(caller)
@@ -591,31 +718,74 @@ module ActiveSupport
591
718
  # end
592
719
  # end
593
720
  #
594
- # In the above case whenever you save an account the method <tt>Audit#before</tt> will
595
- # be called. On the other hand
721
+ # In the above case whenever you save an account the method
722
+ # <tt>Audit#before</tt> will be called. On the other hand
596
723
  #
597
- # define_callbacks :save, :scope => [:kind, :name]
724
+ # define_callbacks :save, scope: [:kind, :name]
598
725
  #
599
- # would trigger <tt>Audit#before_save</tt> instead. That's constructed by calling
600
- # <tt>#{kind}_#{name}</tt> on the given instance. In this case "kind" is "before" and
601
- # "name" is "save". In this context +:kind+ and +:name+ have special meanings: +:kind+
602
- # refers to the kind of callback (before/after/around) and +:name+ refers to the
603
- # method on which callbacks are being defined.
726
+ # would trigger <tt>Audit#before_save</tt> instead. That's constructed
727
+ # by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
728
+ # case "kind" is "before" and "name" is "save". In this context +:kind+
729
+ # and +:name+ have special meanings: +:kind+ refers to the kind of
730
+ # callback (before/after/around) and +:name+ refers to the method on
731
+ # which callbacks are being defined.
604
732
  #
605
733
  # A declaration like
606
734
  #
607
- # define_callbacks :save, :scope => [:name]
735
+ # define_callbacks :save, scope: [:name]
608
736
  #
609
737
  # would call <tt>Audit#save</tt>.
610
738
  #
611
- def define_callbacks(*callbacks)
612
- config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
613
- callbacks.each do |callback|
614
- class_attribute "_#{callback}_callbacks"
615
- send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
616
- __define_runner(callback)
739
+ # NOTE: +method_name+ passed to `define_model_callbacks` must not end with
740
+ # `!`, `?` or `=`.
741
+ def define_callbacks(*names)
742
+ options = names.extract_options!
743
+
744
+ names.each do |name|
745
+ class_attribute "_#{name}_callbacks", instance_writer: false
746
+ set_callbacks name, CallbackChain.new(name, options)
747
+
748
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
749
+ def _run_#{name}_callbacks(&block)
750
+ __run_callbacks__(_#{name}_callbacks, &block)
751
+ end
752
+ RUBY
753
+ end
754
+ end
755
+
756
+ protected
757
+
758
+ def get_callbacks(name) # :nodoc:
759
+ send "_#{name}_callbacks"
760
+ end
761
+
762
+ def set_callbacks(name, callbacks) # :nodoc:
763
+ send "_#{name}_callbacks=", callbacks
764
+ end
765
+
766
+ def deprecated_false_terminator # :nodoc:
767
+ Proc.new do |target, result_lambda|
768
+ terminate = true
769
+ catch(:abort) do
770
+ result = result_lambda.call if result_lambda.is_a?(Proc)
771
+ if Callbacks.halt_and_display_warning_on_return_false && result == false
772
+ display_deprecation_warning_for_false_terminator
773
+ else
774
+ terminate = false
775
+ end
776
+ end
777
+ terminate
617
778
  end
618
779
  end
780
+
781
+ private
782
+
783
+ def display_deprecation_warning_for_false_terminator
784
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
785
+ Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1.
786
+ To explicitly halt the callback chain, please use `throw :abort` instead.
787
+ MSG
788
+ end
619
789
  end
620
790
  end
621
791
  end