activesupport 4.0.13 → 5.2.5

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 (264) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +412 -444
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +8 -4
  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 +14 -12
  8. data/lib/active_support/benchmarkable.rb +6 -14
  9. data/lib/active_support/builder.rb +3 -1
  10. data/lib/active_support/cache/file_store.rb +67 -51
  11. data/lib/active_support/cache/mem_cache_store.rb +95 -97
  12. data/lib/active_support/cache/memory_store.rb +28 -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 +70 -56
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  17. data/lib/active_support/cache.rb +331 -206
  18. data/lib/active_support/callbacks.rb +697 -426
  19. data/lib/active_support/concern.rb +32 -10
  20. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -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 +39 -1
  24. data/lib/active_support/core_ext/array/conversions.rb +24 -35
  25. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +23 -13
  27. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  28. data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -5
  29. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  30. data/lib/active_support/core_ext/array.rb +9 -7
  31. data/lib/active_support/core_ext/benchmark.rb +3 -1
  32. data/lib/active_support/core_ext/big_decimal/conversions.rb +9 -26
  33. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  34. data/lib/active_support/core_ext/class/attribute.rb +41 -23
  35. data/lib/active_support/core_ext/class/attribute_accessors.rb +5 -169
  36. data/lib/active_support/core_ext/class/subclasses.rb +20 -8
  37. data/lib/active_support/core_ext/class.rb +4 -4
  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 +21 -9
  41. data/lib/active_support/core_ext/date/conversions.rb +32 -22
  42. data/lib/active_support/core_ext/date/zones.rb +5 -34
  43. data/lib/active_support/core_ext/date.rb +6 -4
  44. data/lib/active_support/core_ext/date_and_time/calculations.rb +199 -57
  45. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  46. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  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 +78 -37
  50. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  51. data/lib/active_support/core_ext/date_time/conversions.rb +19 -13
  52. data/lib/active_support/core_ext/date_time.rb +7 -4
  53. data/lib/active_support/core_ext/digest/uuid.rb +53 -0
  54. data/lib/active_support/core_ext/digest.rb +3 -0
  55. data/lib/active_support/core_ext/enumerable.rb +113 -29
  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 +29 -0
  59. data/lib/active_support/core_ext/hash/conversions.rb +71 -49
  60. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  61. data/lib/active_support/core_ext/hash/except.rb +12 -3
  62. data/lib/active_support/core_ext/hash/indifferent_access.rb +5 -3
  63. data/lib/active_support/core_ext/hash/keys.rb +50 -38
  64. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  65. data/lib/active_support/core_ext/hash/slice.rb +12 -6
  66. data/lib/active_support/core_ext/hash/transform_values.rb +32 -0
  67. data/lib/active_support/core_ext/hash.rb +11 -8
  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 -33
  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 +14 -0
  74. data/lib/active_support/core_ext/kernel/reporting.rb +5 -74
  75. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  76. data/lib/active_support/core_ext/kernel.rb +6 -4
  77. data/lib/active_support/core_ext/load_error.rb +5 -21
  78. data/lib/active_support/core_ext/marshal.rb +13 -10
  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 -8
  82. data/lib/active_support/core_ext/module/attribute_accessors.rb +170 -21
  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 +134 -0
  85. data/lib/active_support/core_ext/module/delegation.rb +135 -45
  86. data/lib/active_support/core_ext/module/deprecation.rb +3 -3
  87. data/lib/active_support/core_ext/module/introspection.rb +9 -25
  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 -10
  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 +79 -74
  95. data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
  96. data/lib/active_support/core_ext/numeric/time.rb +37 -50
  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 +70 -19
  100. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  101. data/lib/active_support/core_ext/object/deep_dup.rb +19 -10
  102. data/lib/active_support/core_ext/object/duplicable.rb +100 -34
  103. data/lib/active_support/core_ext/object/inclusion.rb +18 -15
  104. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  105. data/lib/active_support/core_ext/object/json.rb +227 -0
  106. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  107. data/lib/active_support/core_ext/object/to_query.rb +21 -8
  108. data/lib/active_support/core_ext/object/try.rb +94 -24
  109. data/lib/active_support/core_ext/object/with_options.rb +45 -5
  110. data/lib/active_support/core_ext/object.rb +14 -12
  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 +41 -39
  121. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  122. data/lib/active_support/core_ext/string/conversions.rb +17 -13
  123. data/lib/active_support/core_ext/string/exclude.rb +5 -3
  124. data/lib/active_support/core_ext/string/filters.rb +55 -6
  125. data/lib/active_support/core_ext/string/indent.rb +6 -4
  126. data/lib/active_support/core_ext/string/inflections.rb +66 -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 +114 -54
  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 -1
  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 +123 -110
  136. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  137. data/lib/active_support/core_ext/time/conversions.rb +23 -14
  138. data/lib/active_support/core_ext/time/zones.rb +42 -26
  139. data/lib/active_support/core_ext/time.rb +7 -5
  140. data/lib/active_support/core_ext/uri.rb +6 -8
  141. data/lib/active_support/core_ext.rb +3 -2
  142. data/lib/active_support/current_attributes.rb +195 -0
  143. data/lib/active_support/dependencies/autoload.rb +3 -1
  144. data/lib/active_support/dependencies/interlock.rb +57 -0
  145. data/lib/active_support/dependencies.rb +196 -166
  146. data/lib/active_support/deprecation/behaviors.rb +48 -15
  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 +14 -11
  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 +354 -34
  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 +128 -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 +17 -0
  165. data/lib/active_support/gzip.rb +7 -5
  166. data/lib/active_support/hash_with_indifferent_access.rb +158 -35
  167. data/lib/active_support/i18n.rb +8 -6
  168. data/lib/active_support/i18n_railtie.rb +38 -20
  169. data/lib/active_support/inflections.rb +19 -12
  170. data/lib/active_support/inflector/inflections.rb +79 -30
  171. data/lib/active_support/inflector/methods.rb +197 -129
  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 +21 -25
  175. data/lib/active_support/json/encoding.rb +84 -292
  176. data/lib/active_support/json.rb +4 -2
  177. data/lib/active_support/key_generator.rb +26 -28
  178. data/lib/active_support/lazy_load_hooks.rb +51 -21
  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 +54 -3
  183. data/lib/active_support/logger_silence.rb +12 -7
  184. data/lib/active_support/logger_thread_safe_level.rb +34 -0
  185. data/lib/active_support/message_encryptor.rb +173 -50
  186. data/lib/active_support/message_verifier.rb +159 -22
  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 +38 -26
  191. data/lib/active_support/multibyte/unicode.rb +138 -146
  192. data/lib/active_support/multibyte.rb +4 -2
  193. data/lib/active_support/notifications/fanout.rb +23 -16
  194. data/lib/active_support/notifications/instrumenter.rb +29 -8
  195. data/lib/active_support/notifications.rb +22 -13
  196. data/lib/active_support/number_helper/number_converter.rb +184 -0
  197. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  198. data/lib/active_support/number_helper/number_to_delimited_converter.rb +29 -0
  199. data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
  200. data/lib/active_support/number_helper/number_to_human_size_converter.rb +59 -0
  201. data/lib/active_support/number_helper/number_to_percentage_converter.rb +14 -0
  202. data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
  203. data/lib/active_support/number_helper/number_to_rounded_converter.rb +54 -0
  204. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  205. data/lib/active_support/number_helper.rb +125 -391
  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 +31 -5
  209. data/lib/active_support/per_thread_registry.rb +19 -11
  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 +31 -0
  216. data/lib/active_support/string_inquirer.rb +11 -3
  217. data/lib/active_support/subscriber.rb +54 -17
  218. data/lib/active_support/tagged_logging.rb +14 -11
  219. data/lib/active_support/test_case.rb +42 -37
  220. data/lib/active_support/testing/assertions.rb +126 -39
  221. data/lib/active_support/testing/autorun.rb +5 -3
  222. data/lib/active_support/testing/constant_lookup.rb +3 -6
  223. data/lib/active_support/testing/declarative.rb +10 -22
  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 +55 -86
  227. data/lib/active_support/testing/method_call_assertions.rb +43 -0
  228. data/lib/active_support/testing/setup_and_teardown.rb +30 -10
  229. data/lib/active_support/testing/stream.rb +44 -0
  230. data/lib/active_support/testing/tagged_logging.rb +5 -3
  231. data/lib/active_support/testing/time_helpers.rb +200 -0
  232. data/lib/active_support/time.rb +13 -13
  233. data/lib/active_support/time_with_zone.rb +223 -73
  234. data/lib/active_support/values/time_zone.rb +261 -126
  235. data/lib/active_support/values/unicode_tables.dat +0 -0
  236. data/lib/active_support/version.rb +6 -7
  237. data/lib/active_support/xml_mini/jdom.rb +116 -113
  238. data/lib/active_support/xml_mini/libxml.rb +17 -16
  239. data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
  240. data/lib/active_support/xml_mini/nokogiri.rb +15 -15
  241. data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
  242. data/lib/active_support/xml_mini/rexml.rb +17 -16
  243. data/lib/active_support/xml_mini.rb +69 -51
  244. data/lib/active_support.rb +29 -3
  245. metadata +84 -54
  246. data/lib/active_support/basic_object.rb +0 -11
  247. data/lib/active_support/buffered_logger.rb +0 -21
  248. data/lib/active_support/concurrency/latch.rb +0 -27
  249. data/lib/active_support/core_ext/array/uniq_by.rb +0 -19
  250. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -40
  251. data/lib/active_support/core_ext/date_time/zones.rb +0 -24
  252. data/lib/active_support/core_ext/hash/diff.rb +0 -14
  253. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  254. data/lib/active_support/core_ext/logger.rb +0 -67
  255. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  256. data/lib/active_support/core_ext/object/to_json.rb +0 -27
  257. data/lib/active_support/core_ext/proc.rb +0 -17
  258. data/lib/active_support/core_ext/string/encoding.rb +0 -8
  259. data/lib/active_support/core_ext/struct.rb +0 -6
  260. data/lib/active_support/core_ext/thread.rb +0 -79
  261. data/lib/active_support/core_ext/time/marshal.rb +0 -30
  262. data/lib/active_support/file_watcher.rb +0 -36
  263. data/lib/active_support/json/variable.rb +0 -18
  264. data/lib/active_support/testing/pending.rb +0 -14
@@ -1,9 +1,14 @@
1
- require 'thread_safe'
2
- require 'active_support/concern'
3
- require 'active_support/descendants_tracker'
4
- require 'active_support/core_ext/class/attribute'
5
- require 'active_support/core_ext/kernel/reporting'
6
- require 'active_support/core_ext/kernel/singleton_class'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "active_support/descendants_tracker"
5
+ require "active_support/core_ext/array/extract_options"
6
+ require "active_support/core_ext/class/attribute"
7
+ require "active_support/core_ext/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"
7
12
 
8
13
  module ActiveSupport
9
14
  # Callbacks are code hooks that are run at key points in an object's life cycle.
@@ -59,6 +64,7 @@ module ActiveSupport
59
64
 
60
65
  included do
61
66
  extend ActiveSupport::DescendantsTracker
67
+ class_attribute :__callbacks, instance_writer: false, default: {}
62
68
  end
63
69
 
64
70
  CALLBACK_FILTER_TYPES = [:before, :after, :around]
@@ -70,505 +76,770 @@ module ActiveSupport
70
76
  # order.
71
77
  #
72
78
  # If the callback chain was halted, returns +false+. Otherwise returns the
73
- # result of the block, or +true+ if no block is given.
79
+ # result of the block, +nil+ if no callbacks have been set, or +true+
80
+ # if callbacks have been set but no block is given.
74
81
  #
75
82
  # run_callbacks :save do
76
83
  # save
77
84
  # end
78
- def run_callbacks(kind, &block)
79
- runner_name = self.class.__define_callbacks(kind, self)
80
- send(runner_name, &block)
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]
96
+
97
+ if callbacks.empty?
98
+ yield if block_given?
99
+ else
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
128
+
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
137
+ end
138
+ end
81
139
  end
82
140
 
83
141
  private
84
142
 
85
- # A hook invoked everytime a before callback is halted.
86
- # This can be overridden in AS::Callback implementors in order
87
- # to provide better debugging/logging.
88
- def halted_callback_hook(filter)
89
- end
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)
147
+ end
90
148
 
91
- class Callback #:nodoc:#
92
- @@_callback_sequence = 0
149
+ module Conditionals # :nodoc:
150
+ class Value
151
+ def initialize(&block)
152
+ @block = block
153
+ end
154
+ def call(target, value); @block.call(value); end
155
+ end
156
+ end
93
157
 
94
- attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
158
+ module Filters
159
+ Environment = Struct.new(:target, :halted, :value)
95
160
 
96
- def initialize(chain, filter, kind, options, klass)
97
- @chain, @kind, @klass = chain, kind, klass
98
- deprecate_per_key_option(options)
99
- normalize_options!(options)
161
+ class Before
162
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
163
+ halted_lambda = chain_config[:terminator]
100
164
 
101
- @raw_filter, @options = filter, options
102
- @filter = _compile_filter(filter)
103
- recompile_options!
104
- end
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
170
+ end
105
171
 
106
- def deprecate_per_key_option(options)
107
- if options[:per_key]
108
- raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead."
109
- end
110
- end
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
184
+ end
111
185
 
112
- def clone(chain, klass)
113
- obj = super()
114
- obj.chain = chain
115
- obj.klass = klass
116
- obj.options = @options.dup
117
- obj.options[:if] = @options[:if].dup
118
- obj.options[:unless] = @options[:unless].dup
119
- obj
120
- end
186
+ env
187
+ end
188
+ end
189
+ private_class_method :halting_and_conditional
121
190
 
122
- def normalize_options!(options)
123
- options[:if] = Array(options[:if])
124
- options[:unless] = Array(options[:unless])
125
- 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
126
196
 
127
- def name
128
- chain.name
129
- end
197
+ unless halted
198
+ result_lambda = -> { user_callback.call target, value }
199
+ env.halted = halted_lambda.call(target, result_lambda)
130
200
 
131
- def next_id
132
- @@_callback_sequence += 1
133
- end
201
+ if env.halted
202
+ target.send :halted_callback_hook, filter
203
+ end
204
+ end
134
205
 
135
- def matches?(_kind, _filter)
136
- @kind == _kind && @filter == _filter
137
- end
206
+ env
207
+ end
208
+ end
209
+ private_class_method :halting
210
+ end
138
211
 
139
- def duplicates?(other)
140
- matches?(other.kind, other.filter)
141
- end
212
+ class After
213
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
214
+ if chain_config[:skip_after_callbacks_if_terminated]
215
+ if user_conditions.any?
216
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
217
+ else
218
+ halting(callback_sequence, user_callback)
219
+ end
220
+ else
221
+ if user_conditions.any?
222
+ conditional callback_sequence, user_callback, user_conditions
223
+ else
224
+ simple callback_sequence, user_callback
225
+ end
226
+ end
227
+ end
142
228
 
143
- def _update_filter(filter_options, new_options)
144
- filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
145
- filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
146
- end
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
147
234
 
148
- def recompile!(_options)
149
- deprecate_per_key_option(_options)
150
- _update_filter(self.options, _options)
235
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
236
+ user_callback.call target, value
237
+ end
151
238
 
152
- recompile_options!
153
- end
239
+ env
240
+ end
241
+ end
242
+ private_class_method :halting_and_conditional
154
243
 
155
- # Wraps code with filter
156
- def apply(code)
157
- case @kind
158
- when :before
159
- <<-RUBY_EVAL
160
- if !halted && #{@compiled_options}
161
- # This double assignment is to prevent warnings in 1.9.3 as
162
- # the `result` variable is not always used except if the
163
- # terminator code refers to it.
164
- result = result = #{@filter}
165
- halted = (#{chain.config[:terminator]})
166
- if halted
167
- halted_callback_hook(#{@raw_filter.inspect.inspect})
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
168
248
  end
249
+
250
+ env
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
263
+
264
+ env
265
+ end
266
+ end
267
+ private_class_method :conditional
268
+
269
+ def self.simple(callback_sequence, user_callback)
270
+ callback_sequence.after do |env|
271
+ user_callback.call env.target, env.value
272
+
273
+ env
169
274
  end
170
- #{code}
171
- RUBY_EVAL
172
- when :after
173
- <<-RUBY_EVAL
174
- #{code}
175
- if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options}
176
- #{@filter}
177
- end
178
- RUBY_EVAL
179
- when :around
180
- name = define_conditional_callback
181
- <<-RUBY_EVAL
182
- #{name}(halted) do
183
- #{code}
184
- value
185
- end
186
- RUBY_EVAL
275
+ end
276
+ private_class_method :simple
187
277
  end
188
278
  end
189
279
 
190
- private
191
-
192
- # Compile around filters with conditions into proxy methods
193
- # that contain the conditions.
194
- #
195
- # For `set_callback :save, :around, :filter_name, if: :condition':
196
- #
197
- # def _conditional_callback_save_17
198
- # if condition
199
- # filter_name do
200
- # yield self
201
- # end
202
- # else
203
- # yield self
204
- # end
205
- # end
206
- def define_conditional_callback
207
- name = "_conditional_callback_#{@kind}_#{next_id}"
208
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
209
- def #{name}(halted)
210
- if #{@compiled_options} && !halted
211
- #{@filter} do
212
- yield self
213
- end
214
- else
215
- yield self
216
- end
217
- end
218
- RUBY_EVAL
219
- name
220
- end
280
+ class Callback #:nodoc:#
281
+ def self.build(chain, filter, kind, options)
282
+ if filter.is_a?(String)
283
+ raise ArgumentError, <<-MSG.squish
284
+ Passing string to define a callback is not supported. See the `.set_callback`
285
+ documentation to see supported values.
286
+ MSG
287
+ end
221
288
 
222
- # Options support the same options as filters themselves (and support
223
- # symbols, string, procs, and objects), so compile a conditional
224
- # expression based on the options.
225
- def recompile_options!
226
- conditions = ["true"]
289
+ new chain.name, filter, kind, options, chain.config
290
+ end
227
291
 
228
- unless options[:if].empty?
229
- conditions << Array(_compile_filter(options[:if]))
292
+ attr_accessor :kind, :name
293
+ attr_reader :chain_config
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]))
230
303
  end
231
304
 
232
- unless options[:unless].empty?
233
- conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
305
+ def filter; @key; end
306
+ def raw_filter; @filter; end
307
+
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
234
318
  end
235
319
 
236
- @compiled_options = conditions.flatten.join(" && ")
237
- end
320
+ def matches?(_kind, _filter)
321
+ @kind == _kind && filter == _filter
322
+ end
238
323
 
239
- # Filters support:
240
- #
241
- # Arrays:: Used in conditions. This is used to specify
242
- # multiple conditions. Used internally to
243
- # merge conditions from skip_* filters.
244
- # Symbols:: A method to call.
245
- # Strings:: Some content to evaluate.
246
- # Procs:: A proc to call with the object.
247
- # Objects:: An object with a <tt>before_foo</tt> method on it to call.
248
- #
249
- # All of these objects are compiled into methods and handled
250
- # the same after this point:
251
- #
252
- # Arrays:: Merged together into a single filter.
253
- # Symbols:: Already methods.
254
- # Strings:: class_eval'ed into methods.
255
- # Procs:: define_method'ed into methods.
256
- # Objects::
257
- # a method is created that calls the before_foo method
258
- # on the object.
259
- def _compile_filter(filter)
260
- case filter
261
- when Array
262
- filter.map {|f| _compile_filter(f)}
263
- when Symbol
264
- filter
265
- when String
266
- "(#{filter})"
267
- when Proc
268
- method_name = "_callback_#{@kind}_#{next_id}"
269
- @klass.send(:define_method, method_name, &filter)
270
- return method_name if filter.arity <= 0
271
-
272
- method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
273
- else
274
- method_name = "_callback_#{@kind}_#{next_id}"
275
- @klass.send(:define_method, "#{method_name}_object") { filter }
324
+ def duplicates?(other)
325
+ case @filter
326
+ when Symbol
327
+ matches?(other.kind, other.filter)
328
+ else
329
+ false
330
+ end
331
+ end
332
+
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)
345
+ end
346
+ end
276
347
 
277
- _normalize_legacy_filter(kind, filter)
278
- scopes = Array(chain.config[:scope])
279
- method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
348
+ def current_scopes
349
+ Array(chain_config[:scope]).map { |s| public_send(s) }
350
+ end
280
351
 
281
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
282
- def #{method_name}(&blk)
283
- #{method_name}_object.send(:#{method_to_call}, self, &blk)
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
284
360
  end
285
- RUBY_EVAL
286
361
 
287
- method_name
288
- end
362
+ conditionals
363
+ end
364
+
365
+ def compute_identifier(filter)
366
+ case filter
367
+ when ::Proc
368
+ filter.object_id
369
+ else
370
+ filter
371
+ end
372
+ end
373
+
374
+ def conditions_lambdas
375
+ @if.map { |c| CallTemplate.build(c, self).make_lambda } +
376
+ @unless.map { |c| CallTemplate.build(c, self).inverted_lambda }
377
+ end
289
378
  end
290
379
 
291
- def _normalize_legacy_filter(kind, filter)
292
- if !filter.respond_to?(kind) && filter.respond_to?(:filter)
293
- message = "Filter object with #filter method is deprecated. Define method corresponding " \
294
- "to filter type (#before, #after or #around)."
295
- ActiveSupport::Deprecation.warn message
296
- filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
297
- def #{kind}(context, &block) filter(context, &block) end
298
- RUBY_EVAL
299
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around)
300
- message = "Filter object with #before and #after methods is deprecated. Define #around method instead."
301
- ActiveSupport::Deprecation.warn message
302
- def filter.around(context)
303
- should_continue = before(context)
304
- yield if should_continue
305
- after(context)
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
389
+
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
+ }
411
+
412
+ result.unshift @method_name
413
+ result.unshift @override_block || block
414
+ result.unshift @override_target || target
415
+
416
+ # target, block, method, *arguments = result
417
+ # target.send(method, *arguments, &block)
418
+ result
419
+ end
420
+
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
429
+
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)
306
436
  end
307
437
  end
308
- end
309
- end
310
438
 
311
- # An Array with a compile method.
312
- class CallbackChain < Array #:nodoc:#
313
- attr_reader :name, :config
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("_")
314
463
 
315
- def initialize(name, config)
316
- @name = name
317
- @config = {
318
- :terminator => "false",
319
- :scope => [ :kind ]
320
- }.merge!(config)
464
+ new(filter, method_to_call, [:target], nil)
465
+ end
466
+ end
321
467
  end
322
468
 
323
- def compile
324
- method = ["value = nil", "halted = false"]
325
- callbacks = "value = !halted && (!block_given? || yield)"
326
- reverse_each do |callback|
327
- callbacks = callback.apply(callbacks)
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
477
+
478
+ @before = []
479
+ @after = []
328
480
  end
329
- method << callbacks
330
481
 
331
- method << "value"
332
- method.join("\n")
333
- end
482
+ def before(&before)
483
+ @before.unshift(before)
484
+ self
485
+ end
334
486
 
335
- def append(*callbacks)
336
- callbacks.each { |c| append_one(c) }
337
- end
487
+ def after(&after)
488
+ @after.push(after)
489
+ self
490
+ end
338
491
 
339
- def prepend(*callbacks)
340
- callbacks.each { |c| prepend_one(c) }
341
- end
492
+ def around(call_template, user_conditions)
493
+ CallbackSequence.new(self, call_template, user_conditions)
494
+ end
342
495
 
343
- private
496
+ def skip?(arg)
497
+ arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) }
498
+ end
344
499
 
345
- def append_one(callback)
346
- remove_duplicates(callback)
347
- push(callback)
348
- end
500
+ def nested
501
+ @nested
502
+ end
349
503
 
350
- def prepend_one(callback)
351
- remove_duplicates(callback)
352
- unshift(callback)
353
- end
504
+ def final?
505
+ !@call_template
506
+ end
354
507
 
355
- def remove_duplicates(callback)
356
- delete_if { |c| callback.duplicates?(c) }
357
- end
508
+ def expand_call_template(arg, block)
509
+ @call_template.expand(arg.target, arg.value, block)
510
+ end
358
511
 
359
- end
512
+ def invoke_before(arg)
513
+ @before.each { |b| b.call(arg) }
514
+ end
360
515
 
361
- module ClassMethods
362
-
363
- # This method defines callback chain method for the given kind
364
- # if it was not yet defined.
365
- # This generated method plays caching role.
366
- def __define_callbacks(kind, object) #:nodoc:
367
- name = __callback_runner_name(kind)
368
- unless object.respond_to?(name, true)
369
- str = object.send("_#{kind}_callbacks").compile
370
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
371
- def #{name}() #{str} end
372
- protected :#{name}
373
- RUBY_EVAL
374
- end
375
- name
516
+ def invoke_after(arg)
517
+ @after.each { |a| a.call(arg) }
518
+ end
376
519
  end
377
520
 
378
- def __reset_runner(symbol)
379
- name = __callback_runner_name(symbol)
380
- undef_method(name) if method_defined?(name)
381
- end
521
+ class CallbackChain #:nodoc:#
522
+ include Enumerable
382
523
 
383
- def __callback_runner_name_cache
384
- @__callback_runner_name_cache ||= ThreadSafe::Cache.new {|cache, kind| cache[kind] = __generate_callback_runner_name(kind) }
385
- end
524
+ attr_reader :name, :config
386
525
 
387
- def __generate_callback_runner_name(kind)
388
- "_run__#{self.name.hash.abs}__#{kind}__callbacks"
389
- 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
390
536
 
391
- def __callback_runner_name(kind)
392
- __callback_runner_name_cache[kind]
393
- end
537
+ def each(&block); @chain.each(&block); end
538
+ def index(o); @chain.index(o); end
539
+ def empty?; @chain.empty?; end
394
540
 
395
- # This is used internally to append, prepend and skip callbacks to the
396
- # CallbackChain.
397
- def __update_callbacks(name, filters = [], block = nil) #:nodoc:
398
- type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
399
- options = filters.last.is_a?(Hash) ? filters.pop : {}
400
- filters.unshift(block) if block
541
+ def insert(index, o)
542
+ @callbacks = nil
543
+ @chain.insert(index, o)
544
+ end
401
545
 
402
- ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
403
- chain = target.send("_#{name}_callbacks")
404
- yield target, chain.dup, type, filters, options
405
- target.__reset_runner(name)
546
+ def delete(o)
547
+ @callbacks = nil
548
+ @chain.delete(o)
406
549
  end
407
- end
408
550
 
409
- # Install a callback for the given event.
410
- #
411
- # set_callback :save, :before, :before_meth
412
- # set_callback :save, :after, :after_meth, if: :condition
413
- # set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff }
414
- #
415
- # The second arguments indicates whether the callback is to be run +:before+,
416
- # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
417
- # means the first example above can also be written as:
418
- #
419
- # set_callback :save, :before_meth
420
- #
421
- # The callback can be specified as a symbol naming an instance method; as a
422
- # proc, lambda, or block; as a string to be instance evaluated; or as an
423
- # object that responds to a certain method determined by the <tt>:scope</tt>
424
- # argument to +define_callback+.
425
- #
426
- # If a proc, lambda, or block is given, its body is evaluated in the context
427
- # of the current object. It can also optionally accept the current object as
428
- # an argument.
429
- #
430
- # Before and around callbacks are called in the order that they are set;
431
- # after callbacks are called in the reverse order.
432
- #
433
- # Around callbacks can access the return value from the event, if it
434
- # wasn't halted, from the +yield+ call.
435
- #
436
- # ===== Options
437
- #
438
- # * <tt>:if</tt> - A symbol naming an instance method or a proc; the
439
- # callback will be called only when it returns a +true+ value.
440
- # * <tt>:unless</tt> - A symbol naming an instance method or a proc; the
441
- # callback will be called only when it returns a +false+ value.
442
- # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
443
- # existing chain rather than appended.
444
- def set_callback(name, *filter_list, &block)
445
- mapped = nil
446
-
447
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
448
- mapped ||= filters.map do |filter|
449
- Callback.new(chain, filter, type, options.dup, self)
450
- end
451
-
452
- options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
453
-
454
- target.send("_#{name}_callbacks=", chain)
551
+ def clear
552
+ @callbacks = nil
553
+ @chain.clear
554
+ self
455
555
  end
456
- end
457
556
 
458
- # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
459
- # <tt>:unless</tt> options may be passed in order to control when the
460
- # callback is skipped.
461
- #
462
- # class Writer < Person
463
- # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
464
- # end
465
- def skip_callback(name, *filter_list, &block)
466
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
467
- filters.each do |filter|
468
- filter = chain.find {|c| c.matches?(type, filter) }
469
-
470
- if filter && options.any?
471
- new_filter = filter.clone(chain, self)
472
- chain.insert(chain.index(filter), new_filter)
473
- new_filter.recompile!(options)
474
- end
557
+ def initialize_copy(other)
558
+ @callbacks = nil
559
+ @chain = other.chain.dup
560
+ @mutex = Mutex.new
561
+ end
475
562
 
476
- chain.delete(filter)
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
477
569
  end
478
- target.send("_#{name}_callbacks=", chain)
479
570
  end
480
- end
481
571
 
482
- # Remove all set callbacks for the given event.
483
- def reset_callbacks(symbol)
484
- callbacks = send("_#{symbol}_callbacks")
572
+ def append(*callbacks)
573
+ callbacks.each { |c| append_one(c) }
574
+ end
485
575
 
486
- ActiveSupport::DescendantsTracker.descendants(self).each do |target|
487
- chain = target.send("_#{symbol}_callbacks").dup
488
- callbacks.each { |c| chain.delete(c) }
489
- target.send("_#{symbol}_callbacks=", chain)
490
- target.__reset_runner(symbol)
576
+ def prepend(*callbacks)
577
+ callbacks.each { |c| prepend_one(c) }
491
578
  end
492
579
 
493
- self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
580
+ protected
581
+ def chain; @chain; end
582
+
583
+ private
584
+
585
+ def append_one(callback)
586
+ @callbacks = nil
587
+ remove_duplicates(callback)
588
+ @chain.push(callback)
589
+ end
590
+
591
+ def prepend_one(callback)
592
+ @callbacks = nil
593
+ remove_duplicates(callback)
594
+ @chain.unshift(callback)
595
+ end
596
+
597
+ def remove_duplicates(callback)
598
+ @callbacks = nil
599
+ @chain.delete_if { |c| callback.duplicates?(c) }
600
+ end
494
601
 
495
- __reset_runner(symbol)
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
496
612
  end
497
613
 
498
- # Define sets of events in the object life cycle that support callbacks.
499
- #
500
- # define_callbacks :validate
501
- # define_callbacks :initialize, :save, :destroy
502
- #
503
- # ===== Options
504
- #
505
- # * <tt>:terminator</tt> - Determines when a before filter will halt the
506
- # callback chain, preventing following callbacks from being called and
507
- # the event from being triggered. This is a string to be eval'ed. The
508
- # result of the callback is available in the +result+ variable.
509
- #
510
- # define_callbacks :validate, terminator: 'result == false'
511
- #
512
- # In this example, if any before validate callbacks returns +false+,
513
- # other callbacks are not executed. Defaults to +false+, meaning no value
514
- # halts the chain.
515
- #
516
- # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
517
- # callbacks should be terminated by the <tt>:terminator</tt> option. By
518
- # default after callbacks executed no matter if callback chain was
519
- # terminated or not. Option makes sense only when <tt>:terminator</tt>
520
- # option is specified.
521
- #
522
- # * <tt>:scope</tt> - Indicates which methods should be executed when an
523
- # object is used as a callback.
524
- #
525
- # class Audit
526
- # def before(caller)
527
- # puts 'Audit: before is called'
528
- # end
529
- #
530
- # def before_save(caller)
531
- # puts 'Audit: before_save is called'
532
- # end
533
- # end
534
- #
535
- # class Account
536
- # include ActiveSupport::Callbacks
537
- #
538
- # define_callbacks :save
539
- # set_callback :save, :before, Audit.new
540
- #
541
- # def save
542
- # run_callbacks :save do
543
- # puts 'save in main'
544
- # end
545
- # end
546
- # end
547
- #
548
- # In the above case whenever you save an account the method
549
- # <tt>Audit#before</tt> will be called. On the other hand
550
- #
551
- # define_callbacks :save, scope: [:kind, :name]
552
- #
553
- # would trigger <tt>Audit#before_save</tt> instead. That's constructed
554
- # by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
555
- # case "kind" is "before" and "name" is "save". In this context +:kind+
556
- # and +:name+ have special meanings: +:kind+ refers to the kind of
557
- # callback (before/after/around) and +:name+ refers to the method on
558
- # which callbacks are being defined.
559
- #
560
- # A declaration like
561
- #
562
- # define_callbacks :save, scope: [:name]
563
- #
564
- # would call <tt>Audit#save</tt>.
565
- def define_callbacks(*callbacks)
566
- config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
567
- callbacks.each do |callback|
568
- class_attribute "_#{callback}_callbacks"
569
- send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
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]
620
+ end
621
+
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
629
+ end
630
+
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
674
+
675
+ __update_callbacks(name) do |target, chain|
676
+ options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
677
+ target.set_callbacks name, chain
678
+ end
679
+ end
680
+
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
703
+
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
713
+ end
714
+
715
+ # Remove all set callbacks for the given event.
716
+ def reset_callbacks(name)
717
+ callbacks = get_callbacks name
718
+
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)
570
726
  end
727
+
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
818
+
819
+ def self._#{name}_callbacks
820
+ get_callbacks(#{name.inspect})
821
+ end
822
+
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
571
843
  end
572
- end
573
844
  end
574
845
  end