activesupport 4.2.11.3 → 5.2.8.1

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