activesupport 4.2.0 → 5.2.0

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

Potentially problematic release.


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

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