activesupport 4.2.11.1 → 6.0.3.1

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.

Potentially problematic release.


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

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