activesupport 4.0.13 → 4.2.11.3

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 (166) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +406 -418
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +7 -2
  5. data/lib/active_support/backtrace_cleaner.rb +8 -8
  6. data/lib/active_support/benchmarkable.rb +0 -10
  7. data/lib/active_support/cache/file_store.rb +32 -22
  8. data/lib/active_support/cache/mem_cache_store.rb +5 -7
  9. data/lib/active_support/cache/memory_store.rb +1 -0
  10. data/lib/active_support/cache/strategy/local_cache.rb +11 -30
  11. data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
  12. data/lib/active_support/cache.rb +75 -41
  13. data/lib/active_support/callbacks.rb +482 -261
  14. data/lib/active_support/concern.rb +23 -7
  15. data/lib/active_support/configurable.rb +1 -1
  16. data/lib/active_support/core_ext/array/access.rb +11 -1
  17. data/lib/active_support/core_ext/array/conversions.rb +2 -17
  18. data/lib/active_support/core_ext/array/grouping.rb +29 -12
  19. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -2
  20. data/lib/active_support/core_ext/array.rb +0 -1
  21. data/lib/active_support/core_ext/big_decimal/conversions.rb +0 -15
  22. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +16 -0
  23. data/lib/active_support/core_ext/class/attribute.rb +1 -2
  24. data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -170
  25. data/lib/active_support/core_ext/class/delegating_attributes.rb +13 -8
  26. data/lib/active_support/core_ext/class/subclasses.rb +0 -2
  27. data/lib/active_support/core_ext/class.rb +0 -1
  28. data/lib/active_support/core_ext/date/calculations.rb +10 -0
  29. data/lib/active_support/core_ext/date/conversions.rb +9 -1
  30. data/lib/active_support/core_ext/date/zones.rb +2 -33
  31. data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -11
  32. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  33. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  34. data/lib/active_support/core_ext/date_time/calculations.rb +45 -22
  35. data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
  36. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  37. data/lib/active_support/core_ext/date_time/zones.rb +3 -21
  38. data/lib/active_support/core_ext/date_time.rb +1 -0
  39. data/lib/active_support/core_ext/digest/uuid.rb +51 -0
  40. data/lib/active_support/core_ext/enumerable.rb +17 -1
  41. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  42. data/lib/active_support/core_ext/hash/compact.rb +24 -0
  43. data/lib/active_support/core_ext/hash/conversions.rb +9 -8
  44. data/lib/active_support/core_ext/hash/except.rb +8 -2
  45. data/lib/active_support/core_ext/hash/indifferent_access.rb +1 -0
  46. data/lib/active_support/core_ext/hash/keys.rb +25 -19
  47. data/lib/active_support/core_ext/hash/slice.rb +8 -2
  48. data/lib/active_support/core_ext/hash/transform_values.rb +23 -0
  49. data/lib/active_support/core_ext/hash.rb +2 -1
  50. data/lib/active_support/core_ext/integer/time.rb +0 -15
  51. data/lib/active_support/core_ext/kernel/concern.rb +10 -0
  52. data/lib/active_support/core_ext/kernel/debugger.rb +1 -1
  53. data/lib/active_support/core_ext/kernel/reporting.rb +13 -2
  54. data/lib/active_support/core_ext/kernel.rb +3 -2
  55. data/lib/active_support/core_ext/load_error.rb +4 -1
  56. data/lib/active_support/core_ext/marshal.rb +8 -5
  57. data/lib/active_support/core_ext/module/aliasing.rb +2 -2
  58. data/lib/active_support/core_ext/module/attr_internal.rb +2 -1
  59. data/lib/active_support/core_ext/module/attribute_accessors.rb +160 -14
  60. data/lib/active_support/core_ext/module/concerning.rb +135 -0
  61. data/lib/active_support/core_ext/module/delegation.rb +53 -25
  62. data/lib/active_support/core_ext/module/deprecation.rb +0 -2
  63. data/lib/active_support/core_ext/module/introspection.rb +0 -16
  64. data/lib/active_support/core_ext/module/method_transplanting.rb +13 -0
  65. data/lib/active_support/core_ext/module.rb +1 -0
  66. data/lib/active_support/core_ext/numeric/conversions.rb +11 -3
  67. data/lib/active_support/core_ext/numeric/time.rb +4 -29
  68. data/lib/active_support/core_ext/object/blank.rb +44 -18
  69. data/lib/active_support/core_ext/object/deep_dup.rb +6 -6
  70. data/lib/active_support/core_ext/object/duplicable.rb +72 -33
  71. data/lib/active_support/core_ext/object/inclusion.rb +16 -15
  72. data/lib/active_support/core_ext/object/itself.rb +15 -0
  73. data/lib/active_support/core_ext/object/json.rb +197 -0
  74. data/lib/active_support/core_ext/object/to_query.rb +14 -6
  75. data/lib/active_support/core_ext/object/try.rb +36 -14
  76. data/lib/active_support/core_ext/object/with_options.rb +30 -3
  77. data/lib/active_support/core_ext/object.rb +2 -1
  78. data/lib/active_support/core_ext/string/access.rb +35 -35
  79. data/lib/active_support/core_ext/string/conversions.rb +10 -9
  80. data/lib/active_support/core_ext/string/exclude.rb +3 -3
  81. data/lib/active_support/core_ext/string/filters.rb +51 -3
  82. data/lib/active_support/core_ext/string/inflections.rb +15 -10
  83. data/lib/active_support/core_ext/string/output_safety.rb +97 -33
  84. data/lib/active_support/core_ext/string/zones.rb +1 -0
  85. data/lib/active_support/core_ext/thread.rb +12 -5
  86. data/lib/active_support/core_ext/time/calculations.rb +47 -68
  87. data/lib/active_support/core_ext/time/compatibility.rb +14 -0
  88. data/lib/active_support/core_ext/time/conversions.rb +4 -2
  89. data/lib/active_support/core_ext/time/zones.rb +2 -20
  90. data/lib/active_support/core_ext/time.rb +1 -0
  91. data/lib/active_support/core_ext.rb +0 -1
  92. data/lib/active_support/dependencies/autoload.rb +1 -1
  93. data/lib/active_support/dependencies.rb +64 -25
  94. data/lib/active_support/deprecation/behaviors.rb +4 -4
  95. data/lib/active_support/deprecation.rb +4 -4
  96. data/lib/active_support/duration.rb +55 -11
  97. data/lib/active_support/file_update_checker.rb +1 -1
  98. data/lib/active_support/gem_version.rb +15 -0
  99. data/lib/active_support/hash_with_indifferent_access.rb +39 -11
  100. data/lib/active_support/i18n.rb +4 -4
  101. data/lib/active_support/i18n_railtie.rb +1 -7
  102. data/lib/active_support/inflections.rb +6 -1
  103. data/lib/active_support/inflector/inflections.rb +19 -19
  104. data/lib/active_support/inflector/methods.rb +66 -25
  105. data/lib/active_support/json/decoding.rb +15 -22
  106. data/lib/active_support/json/encoding.rb +125 -286
  107. data/lib/active_support/key_generator.rb +8 -10
  108. data/lib/active_support/lazy_load_hooks.rb +1 -1
  109. data/lib/active_support/log_subscriber/test_helper.rb +1 -1
  110. data/lib/active_support/logger.rb +51 -1
  111. data/lib/active_support/logger_silence.rb +7 -4
  112. data/lib/active_support/logger_thread_safe_level.rb +32 -0
  113. data/lib/active_support/message_encryptor.rb +14 -6
  114. data/lib/active_support/message_verifier.rb +16 -12
  115. data/lib/active_support/multibyte/chars.rb +2 -3
  116. data/lib/active_support/multibyte/unicode.rb +46 -58
  117. data/lib/active_support/notifications/fanout.rb +12 -7
  118. data/lib/active_support/notifications/instrumenter.rb +2 -1
  119. data/lib/active_support/notifications.rb +11 -6
  120. data/lib/active_support/number_helper/number_converter.rb +182 -0
  121. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  122. data/lib/active_support/number_helper/number_to_delimited_converter.rb +23 -0
  123. data/lib/active_support/number_helper/number_to_human_converter.rb +66 -0
  124. data/lib/active_support/number_helper/number_to_human_size_converter.rb +58 -0
  125. data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
  126. data/lib/active_support/number_helper/number_to_phone_converter.rb +49 -0
  127. data/lib/active_support/number_helper/number_to_rounded_converter.rb +87 -0
  128. data/lib/active_support/number_helper.rb +32 -324
  129. data/lib/active_support/ordered_options.rb +8 -0
  130. data/lib/active_support/per_thread_registry.rb +13 -10
  131. data/lib/active_support/security_utils.rb +27 -0
  132. data/lib/active_support/subscriber.rb +35 -3
  133. data/lib/active_support/test_case.rb +52 -19
  134. data/lib/active_support/testing/assertions.rb +1 -31
  135. data/lib/active_support/testing/autorun.rb +2 -2
  136. data/lib/active_support/testing/constant_lookup.rb +1 -5
  137. data/lib/active_support/testing/declarative.rb +7 -21
  138. data/lib/active_support/testing/isolation.rb +29 -69
  139. data/lib/active_support/testing/setup_and_teardown.rb +17 -2
  140. data/lib/active_support/testing/tagged_logging.rb +2 -2
  141. data/lib/active_support/testing/time_helpers.rb +134 -0
  142. data/lib/active_support/time.rb +0 -2
  143. data/lib/active_support/time_with_zone.rb +60 -40
  144. data/lib/active_support/values/time_zone.rb +101 -101
  145. data/lib/active_support/values/unicode_tables.dat +0 -0
  146. data/lib/active_support/version.rb +4 -7
  147. data/lib/active_support/xml_mini/jdom.rb +6 -5
  148. data/lib/active_support/xml_mini/libxml.rb +1 -3
  149. data/lib/active_support/xml_mini/libxmlsax.rb +1 -4
  150. data/lib/active_support/xml_mini/nokogiri.rb +1 -3
  151. data/lib/active_support/xml_mini/nokogirisax.rb +1 -3
  152. data/lib/active_support/xml_mini/rexml.rb +7 -8
  153. data/lib/active_support/xml_mini.rb +33 -15
  154. data/lib/active_support.rb +27 -2
  155. metadata +43 -43
  156. data/lib/active_support/basic_object.rb +0 -11
  157. data/lib/active_support/buffered_logger.rb +0 -21
  158. data/lib/active_support/core_ext/array/uniq_by.rb +0 -19
  159. data/lib/active_support/core_ext/hash/diff.rb +0 -14
  160. data/lib/active_support/core_ext/logger.rb +0 -67
  161. data/lib/active_support/core_ext/object/to_json.rb +0 -27
  162. data/lib/active_support/core_ext/proc.rb +0 -17
  163. data/lib/active_support/core_ext/string/encoding.rb +0 -8
  164. data/lib/active_support/file_watcher.rb +0 -36
  165. data/lib/active_support/json/variable.rb +0 -18
  166. data/lib/active_support/testing/pending.rb +0 -14
@@ -1,9 +1,10 @@
1
- require 'thread_safe'
2
1
  require 'active_support/concern'
3
2
  require 'active_support/descendants_tracker'
3
+ require 'active_support/core_ext/array/extract_options'
4
4
  require 'active_support/core_ext/class/attribute'
5
5
  require 'active_support/core_ext/kernel/reporting'
6
6
  require 'active_support/core_ext/kernel/singleton_class'
7
+ require 'thread'
7
8
 
8
9
  module ActiveSupport
9
10
  # Callbacks are code hooks that are run at key points in an object's life cycle.
@@ -70,266 +71,492 @@ module ActiveSupport
70
71
  # order.
71
72
  #
72
73
  # If the callback chain was halted, returns +false+. Otherwise returns the
73
- # result of the block, or +true+ if no block is given.
74
+ # result of the block, +nil+ if no callbacks have been set, or +true+
75
+ # if callbacks have been set but no block is given.
74
76
  #
75
77
  # run_callbacks :save do
76
78
  # save
77
79
  # end
78
80
  def run_callbacks(kind, &block)
79
- runner_name = self.class.__define_callbacks(kind, self)
80
- send(runner_name, &block)
81
+ send "_run_#{kind}_callbacks", &block
81
82
  end
82
83
 
83
84
  private
84
85
 
85
- # A hook invoked everytime a before callback is halted.
86
+ def __run_callbacks__(callbacks, &block)
87
+ if callbacks.empty?
88
+ yield if block_given?
89
+ 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.
86
97
  # This can be overridden in AS::Callback implementors in order
87
98
  # to provide better debugging/logging.
88
99
  def halted_callback_hook(filter)
89
100
  end
90
101
 
91
- class Callback #:nodoc:#
92
- @@_callback_sequence = 0
93
-
94
- attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
102
+ module Conditionals # :nodoc:
103
+ class Value
104
+ def initialize(&block)
105
+ @block = block
106
+ end
107
+ def call(target, value); @block.call(value); end
108
+ end
109
+ end
95
110
 
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)
111
+ module Filters
112
+ Environment = Struct.new(:target, :halted, :value, :run_block)
100
113
 
101
- @raw_filter, @options = filter, options
102
- @filter = _compile_filter(filter)
103
- recompile_options!
114
+ class End
115
+ def call(env)
116
+ block = env.run_block
117
+ env.value = !env.halted && (!block || block.call)
118
+ env
119
+ end
104
120
  end
121
+ ENDING = End.new
105
122
 
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."
123
+ class Before
124
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
125
+ halted_lambda = chain_config[:terminator]
126
+
127
+ if chain_config.key?(:terminator) && user_conditions.any?
128
+ halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
129
+ elsif chain_config.key? :terminator
130
+ halting(callback_sequence, user_callback, halted_lambda, filter)
131
+ elsif user_conditions.any?
132
+ conditional(callback_sequence, user_callback, user_conditions)
133
+ else
134
+ simple callback_sequence, user_callback
135
+ end
109
136
  end
110
- end
111
137
 
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
138
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
139
+ callback_sequence.before do |env|
140
+ target = env.target
141
+ value = env.value
142
+ halted = env.halted
143
+
144
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
145
+ result = user_callback.call target, value
146
+ env.halted = halted_lambda.call(target, result)
147
+ if env.halted
148
+ target.send :halted_callback_hook, filter
149
+ end
150
+ end
121
151
 
122
- def normalize_options!(options)
123
- options[:if] = Array(options[:if])
124
- options[:unless] = Array(options[:unless])
125
- end
152
+ env
153
+ end
154
+ end
155
+ private_class_method :halting_and_conditional
156
+
157
+ def self.halting(callback_sequence, user_callback, halted_lambda, filter)
158
+ callback_sequence.before do |env|
159
+ target = env.target
160
+ value = env.value
161
+ halted = env.halted
162
+
163
+ unless halted
164
+ result = user_callback.call target, value
165
+ env.halted = halted_lambda.call(target, result)
166
+ if env.halted
167
+ target.send :halted_callback_hook, filter
168
+ end
169
+ end
126
170
 
127
- def name
128
- chain.name
129
- end
171
+ env
172
+ end
173
+ end
174
+ private_class_method :halting
175
+
176
+ def self.conditional(callback_sequence, user_callback, user_conditions)
177
+ callback_sequence.before do |env|
178
+ target = env.target
179
+ value = env.value
130
180
 
131
- def next_id
132
- @@_callback_sequence += 1
181
+ if user_conditions.all? { |c| c.call(target, value) }
182
+ user_callback.call target, value
183
+ end
184
+
185
+ env
186
+ end
187
+ end
188
+ private_class_method :conditional
189
+
190
+ def self.simple(callback_sequence, user_callback)
191
+ callback_sequence.before do |env|
192
+ user_callback.call env.target, env.value
193
+
194
+ env
195
+ end
196
+ end
197
+ private_class_method :simple
198
+ end
199
+
200
+ class After
201
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
202
+ if chain_config[:skip_after_callbacks_if_terminated]
203
+ if chain_config.key?(:terminator) && user_conditions.any?
204
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
205
+ elsif chain_config.key?(:terminator)
206
+ halting(callback_sequence, user_callback)
207
+ elsif user_conditions.any?
208
+ conditional callback_sequence, user_callback, user_conditions
209
+ else
210
+ simple callback_sequence, user_callback
211
+ end
212
+ else
213
+ if user_conditions.any?
214
+ conditional callback_sequence, user_callback, user_conditions
215
+ else
216
+ simple callback_sequence, user_callback
217
+ end
218
+ end
219
+ end
220
+
221
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
222
+ callback_sequence.after do |env|
223
+ target = env.target
224
+ value = env.value
225
+ halted = env.halted
226
+
227
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
228
+ user_callback.call target, value
229
+ end
230
+
231
+ env
232
+ end
233
+ end
234
+ private_class_method :halting_and_conditional
235
+
236
+ def self.halting(callback_sequence, user_callback)
237
+ callback_sequence.after do |env|
238
+ unless env.halted
239
+ user_callback.call env.target, env.value
240
+ end
241
+
242
+ env
243
+ end
244
+ end
245
+ private_class_method :halting
246
+
247
+ def self.conditional(callback_sequence, user_callback, user_conditions)
248
+ callback_sequence.after do |env|
249
+ target = env.target
250
+ value = env.value
251
+
252
+ if user_conditions.all? { |c| c.call(target, value) }
253
+ user_callback.call target, value
254
+ end
255
+
256
+ env
257
+ end
258
+ end
259
+ private_class_method :conditional
260
+
261
+ def self.simple(callback_sequence, user_callback)
262
+ callback_sequence.after do |env|
263
+ user_callback.call env.target, env.value
264
+
265
+ env
266
+ end
267
+ end
268
+ private_class_method :simple
269
+ end
270
+
271
+ class Around
272
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
273
+ if chain_config.key?(:terminator) && user_conditions.any?
274
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
275
+ elsif chain_config.key? :terminator
276
+ halting(callback_sequence, user_callback)
277
+ elsif user_conditions.any?
278
+ conditional(callback_sequence, user_callback, user_conditions)
279
+ else
280
+ simple(callback_sequence, user_callback)
281
+ end
282
+ end
283
+
284
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
285
+ callback_sequence.around do |env, &run|
286
+ target = env.target
287
+ value = env.value
288
+ halted = env.halted
289
+
290
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
291
+ user_callback.call(target, value) {
292
+ env = run.call env
293
+ env.value
294
+ }
295
+
296
+ env
297
+ else
298
+ run.call env
299
+ end
300
+ end
301
+ end
302
+ private_class_method :halting_and_conditional
303
+
304
+ def self.halting(callback_sequence, user_callback)
305
+ callback_sequence.around do |env, &run|
306
+ target = env.target
307
+ value = env.value
308
+
309
+ if env.halted
310
+ run.call env
311
+ else
312
+ user_callback.call(target, value) {
313
+ env = run.call env
314
+ env.value
315
+ }
316
+ env
317
+ end
318
+ end
319
+ end
320
+ private_class_method :halting
321
+
322
+ def self.conditional(callback_sequence, user_callback, user_conditions)
323
+ callback_sequence.around do |env, &run|
324
+ target = env.target
325
+ value = env.value
326
+
327
+ if user_conditions.all? { |c| c.call(target, value) }
328
+ user_callback.call(target, value) {
329
+ env = run.call env
330
+ env.value
331
+ }
332
+ env
333
+ else
334
+ run.call env
335
+ end
336
+ end
337
+ end
338
+ private_class_method :conditional
339
+
340
+ def self.simple(callback_sequence, user_callback)
341
+ callback_sequence.around do |env, &run|
342
+ user_callback.call(env.target, env.value) {
343
+ env = run.call env
344
+ env.value
345
+ }
346
+ env
347
+ end
348
+ end
349
+ private_class_method :simple
133
350
  end
351
+ end
134
352
 
135
- def matches?(_kind, _filter)
136
- @kind == _kind && @filter == _filter
353
+ class Callback #:nodoc:#
354
+ def self.build(chain, filter, kind, options)
355
+ new chain.name, filter, kind, options, chain.config
137
356
  end
138
357
 
139
- def duplicates?(other)
140
- matches?(other.kind, other.filter)
358
+ attr_accessor :kind, :name
359
+ attr_reader :chain_config
360
+
361
+ def initialize(name, filter, kind, options, chain_config)
362
+ @chain_config = chain_config
363
+ @name = name
364
+ @kind = kind
365
+ @filter = filter
366
+ @key = compute_identifier filter
367
+ @if = Array(options[:if])
368
+ @unless = Array(options[:unless])
141
369
  end
142
370
 
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)
371
+ def filter; @key; end
372
+ def raw_filter; @filter; end
373
+
374
+ def merge(chain, new_options)
375
+ options = {
376
+ :if => @if.dup,
377
+ :unless => @unless.dup
378
+ }
379
+
380
+ options[:if].concat Array(new_options.fetch(:unless, []))
381
+ options[:unless].concat Array(new_options.fetch(:if, []))
382
+
383
+ self.class.build chain, @filter, @kind, options
146
384
  end
147
385
 
148
- def recompile!(_options)
149
- deprecate_per_key_option(_options)
150
- _update_filter(self.options, _options)
386
+ def matches?(_kind, _filter)
387
+ @kind == _kind && filter == _filter
388
+ end
151
389
 
152
- recompile_options!
390
+ def duplicates?(other)
391
+ case @filter
392
+ when Symbol, String
393
+ matches?(other.kind, other.filter)
394
+ else
395
+ false
396
+ end
153
397
  end
154
398
 
155
399
  # Wraps code with filter
156
- def apply(code)
157
- case @kind
400
+ def apply(callback_sequence)
401
+ user_conditions = conditions_lambdas
402
+ user_callback = make_lambda @filter
403
+
404
+ case kind
158
405
  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})
168
- end
169
- end
170
- #{code}
171
- RUBY_EVAL
406
+ Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
172
407
  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
408
+ Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
179
409
  when :around
180
- name = define_conditional_callback
181
- <<-RUBY_EVAL
182
- #{name}(halted) do
183
- #{code}
184
- value
185
- end
186
- RUBY_EVAL
410
+ Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
187
411
  end
188
412
  end
189
413
 
190
414
  private
191
415
 
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
221
-
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"]
227
-
228
- unless options[:if].empty?
229
- conditions << Array(_compile_filter(options[:if]))
230
- end
231
-
232
- unless options[:unless].empty?
233
- conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
234
- end
235
-
236
- @compiled_options = conditions.flatten.join(" && ")
416
+ def invert_lambda(l)
417
+ lambda { |*args, &blk| !l.call(*args, &blk) }
237
418
  end
238
419
 
239
420
  # Filters support:
240
421
  #
241
- # Arrays:: Used in conditions. This is used to specify
242
- # multiple conditions. Used internally to
243
- # merge conditions from skip_* filters.
244
422
  # Symbols:: A method to call.
245
423
  # Strings:: Some content to evaluate.
246
424
  # Procs:: A proc to call with the object.
247
425
  # Objects:: An object with a <tt>before_foo</tt> method on it to call.
248
426
  #
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)
427
+ # All of these objects are converted into a lambda and handled
428
+ # the same after this point.
429
+ def make_lambda(filter)
260
430
  case filter
261
- when Array
262
- filter.map {|f| _compile_filter(f)}
263
431
  when Symbol
264
- filter
432
+ lambda { |target, _, &blk| target.send filter, &blk }
265
433
  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
434
+ l = eval "lambda { |value| #{filter} }"
435
+ lambda { |target, value| target.instance_exec(value, &l) }
436
+ when Conditionals::Value then filter
437
+ when ::Proc
438
+ if filter.arity > 1
439
+ return lambda { |target, _, &block|
440
+ raise ArgumentError unless block
441
+ target.instance_exec(target, block, &filter)
442
+ }
443
+ end
271
444
 
272
- method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
445
+ if filter.arity <= 0
446
+ lambda { |target, _| target.instance_exec(&filter) }
447
+ else
448
+ lambda { |target, _| target.instance_exec(target, &filter) }
449
+ end
273
450
  else
274
- method_name = "_callback_#{@kind}_#{next_id}"
275
- @klass.send(:define_method, "#{method_name}_object") { filter }
451
+ scopes = Array(chain_config[:scope])
452
+ method_to_call = scopes.map{ |s| public_send(s) }.join("_")
276
453
 
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("_")
280
-
281
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
282
- def #{method_name}(&blk)
283
- #{method_name}_object.send(:#{method_to_call}, self, &blk)
284
- end
285
- RUBY_EVAL
454
+ lambda { |target, _, &blk|
455
+ filter.public_send method_to_call, target, &blk
456
+ }
457
+ end
458
+ end
286
459
 
287
- method_name
460
+ def compute_identifier(filter)
461
+ case filter
462
+ when String, ::Proc
463
+ filter.object_id
464
+ else
465
+ filter
288
466
  end
289
467
  end
290
468
 
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)
306
- end
469
+ def conditions_lambdas
470
+ @if.map { |c| make_lambda c } +
471
+ @unless.map { |c| invert_lambda make_lambda c }
472
+ end
473
+ end
474
+
475
+ # Execute before and after filters in a sequence instead of
476
+ # chaining them with nested lambda calls, see:
477
+ # https://github.com/rails/rails/issues/18011
478
+ class CallbackSequence
479
+ def initialize(&call)
480
+ @call = call
481
+ @before = []
482
+ @after = []
483
+ end
484
+
485
+ def before(&before)
486
+ @before.unshift(before)
487
+ self
488
+ end
489
+
490
+ def after(&after)
491
+ @after.push(after)
492
+ self
493
+ end
494
+
495
+ def around(&around)
496
+ CallbackSequence.new do |*args|
497
+ around.call(*args) {
498
+ self.call(*args)
499
+ }
307
500
  end
308
501
  end
502
+
503
+ def call(*args)
504
+ @before.each { |b| b.call(*args) }
505
+ value = @call.call(*args)
506
+ @after.each { |a| a.call(*args) }
507
+ value
508
+ end
309
509
  end
310
510
 
311
511
  # An Array with a compile method.
312
- class CallbackChain < Array #:nodoc:#
512
+ class CallbackChain #:nodoc:#
513
+ include Enumerable
514
+
313
515
  attr_reader :name, :config
314
516
 
315
517
  def initialize(name, config)
316
518
  @name = name
317
519
  @config = {
318
- :terminator => "false",
319
520
  :scope => [ :kind ]
320
521
  }.merge!(config)
522
+ @chain = []
523
+ @callbacks = nil
524
+ @mutex = Mutex.new
525
+ end
526
+
527
+ def each(&block); @chain.each(&block); end
528
+ def index(o); @chain.index(o); end
529
+ def empty?; @chain.empty?; end
530
+
531
+ def insert(index, o)
532
+ @callbacks = nil
533
+ @chain.insert(index, o)
534
+ end
535
+
536
+ def delete(o)
537
+ @callbacks = nil
538
+ @chain.delete(o)
539
+ end
540
+
541
+ def clear
542
+ @callbacks = nil
543
+ @chain.clear
544
+ self
545
+ end
546
+
547
+ def initialize_copy(other)
548
+ @callbacks = nil
549
+ @chain = other.chain.dup
550
+ @mutex = Mutex.new
321
551
  end
322
552
 
323
553
  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)
554
+ @callbacks || @mutex.synchronize do
555
+ final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
556
+ @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
557
+ callback.apply callback_sequence
558
+ end
328
559
  end
329
- method << callbacks
330
-
331
- method << "value"
332
- method.join("\n")
333
560
  end
334
561
 
335
562
  def append(*callbacks)
@@ -340,69 +567,43 @@ module ActiveSupport
340
567
  callbacks.each { |c| prepend_one(c) }
341
568
  end
342
569
 
570
+ protected
571
+ def chain; @chain; end
572
+
343
573
  private
344
574
 
345
575
  def append_one(callback)
576
+ @callbacks = nil
346
577
  remove_duplicates(callback)
347
- push(callback)
578
+ @chain.push(callback)
348
579
  end
349
580
 
350
581
  def prepend_one(callback)
582
+ @callbacks = nil
351
583
  remove_duplicates(callback)
352
- unshift(callback)
584
+ @chain.unshift(callback)
353
585
  end
354
586
 
355
587
  def remove_duplicates(callback)
356
- delete_if { |c| callback.duplicates?(c) }
588
+ @callbacks = nil
589
+ @chain.delete_if { |c| callback.duplicates?(c) }
357
590
  end
358
-
359
591
  end
360
592
 
361
593
  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
376
- end
377
-
378
- def __reset_runner(symbol)
379
- name = __callback_runner_name(symbol)
380
- undef_method(name) if method_defined?(name)
381
- end
382
-
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
386
-
387
- def __generate_callback_runner_name(kind)
388
- "_run__#{self.name.hash.abs}__#{kind}__callbacks"
389
- end
390
-
391
- def __callback_runner_name(kind)
392
- __callback_runner_name_cache[kind]
594
+ def normalize_callback_params(filters, block) # :nodoc:
595
+ type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
596
+ options = filters.extract_options!
597
+ filters.unshift(block) if block
598
+ [type, filters, options.dup]
393
599
  end
394
600
 
395
601
  # This is used internally to append, prepend and skip callbacks to the
396
602
  # 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
401
-
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)
603
+ def __update_callbacks(name) #:nodoc:
604
+ ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
605
+ chain = target.get_callbacks name
606
+ yield target, chain.dup
406
607
  end
407
608
  end
408
609
 
@@ -410,7 +611,7 @@ module ActiveSupport
410
611
  #
411
612
  # set_callback :save, :before, :before_meth
412
613
  # set_callback :save, :after, :after_meth, if: :condition
413
- # set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff }
614
+ # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
414
615
  #
415
616
  # The second arguments indicates whether the callback is to be run +:before+,
416
617
  # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
@@ -421,7 +622,7 @@ module ActiveSupport
421
622
  # The callback can be specified as a symbol naming an instance method; as a
422
623
  # proc, lambda, or block; as a string to be instance evaluated; or as an
423
624
  # object that responds to a certain method determined by the <tt>:scope</tt>
424
- # argument to +define_callback+.
625
+ # argument to +define_callbacks+.
425
626
  #
426
627
  # If a proc, lambda, or block is given, its body is evaluated in the context
427
628
  # of the current object. It can also optionally accept the current object as
@@ -435,23 +636,24 @@ module ActiveSupport
435
636
  #
436
637
  # ===== Options
437
638
  #
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.
639
+ # * <tt>:if</tt> - A symbol, a string or an array of symbols and strings,
640
+ # each naming an instance method or a proc; the callback will be called
641
+ # only when they all return a true value.
642
+ # * <tt>:unless</tt> - A symbol, a string or an array of symbols and
643
+ # strings, each naming an instance method or a proc; the callback will
644
+ # be called only when they all return a false value.
442
645
  # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
443
646
  # existing chain rather than appended.
444
647
  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
648
+ type, filters, options = normalize_callback_params(filter_list, block)
649
+ self_chain = get_callbacks name
650
+ mapped = filters.map do |filter|
651
+ Callback.build(self_chain, filter, type, options)
652
+ end
451
653
 
654
+ __update_callbacks(name) do |target, chain|
452
655
  options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
453
-
454
- target.send("_#{name}_callbacks=", chain)
656
+ target.set_callbacks name, chain
455
657
  end
456
658
  end
457
659
 
@@ -463,36 +665,34 @@ module ActiveSupport
463
665
  # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
464
666
  # end
465
667
  def skip_callback(name, *filter_list, &block)
466
- __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
668
+ type, filters, options = normalize_callback_params(filter_list, block)
669
+
670
+ __update_callbacks(name) do |target, chain|
467
671
  filters.each do |filter|
468
672
  filter = chain.find {|c| c.matches?(type, filter) }
469
673
 
470
674
  if filter && options.any?
471
- new_filter = filter.clone(chain, self)
675
+ new_filter = filter.merge(chain, options)
472
676
  chain.insert(chain.index(filter), new_filter)
473
- new_filter.recompile!(options)
474
677
  end
475
678
 
476
679
  chain.delete(filter)
477
680
  end
478
- target.send("_#{name}_callbacks=", chain)
681
+ target.set_callbacks name, chain
479
682
  end
480
683
  end
481
684
 
482
685
  # Remove all set callbacks for the given event.
483
- def reset_callbacks(symbol)
484
- callbacks = send("_#{symbol}_callbacks")
686
+ def reset_callbacks(name)
687
+ callbacks = get_callbacks name
485
688
 
486
689
  ActiveSupport::DescendantsTracker.descendants(self).each do |target|
487
- chain = target.send("_#{symbol}_callbacks").dup
690
+ chain = target.get_callbacks(name).dup
488
691
  callbacks.each { |c| chain.delete(c) }
489
- target.send("_#{symbol}_callbacks=", chain)
490
- target.__reset_runner(symbol)
692
+ target.set_callbacks name, chain
491
693
  end
492
694
 
493
- self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
494
-
495
- __reset_runner(symbol)
695
+ self.set_callbacks name, callbacks.dup.clear
496
696
  end
497
697
 
498
698
  # Define sets of events in the object life cycle that support callbacks.
@@ -504,10 +704,11 @@ module ActiveSupport
504
704
  #
505
705
  # * <tt>:terminator</tt> - Determines when a before filter will halt the
506
706
  # 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.
707
+ # the event from being triggered. This should be a lambda to be executed.
708
+ # The current object and the return result of the callback will be called
709
+ # with the lambda.
509
710
  #
510
- # define_callbacks :validate, terminator: 'result == false'
711
+ # define_callbacks :validate, terminator: ->(target, result) { result == false }
511
712
  #
512
713
  # In this example, if any before validate callbacks returns +false+,
513
714
  # other callbacks are not executed. Defaults to +false+, meaning no value
@@ -562,13 +763,33 @@ module ActiveSupport
562
763
  # define_callbacks :save, scope: [:name]
563
764
  #
564
765
  # 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))
766
+ #
767
+ # NOTE: +method_name+ passed to `define_model_callbacks` must not end with
768
+ # `!`, `?` or `=`.
769
+ def define_callbacks(*names)
770
+ options = names.extract_options!
771
+
772
+ names.each do |name|
773
+ class_attribute "_#{name}_callbacks", instance_writer: false
774
+ set_callbacks name, CallbackChain.new(name, options)
775
+
776
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
777
+ def _run_#{name}_callbacks(&block)
778
+ __run_callbacks__(_#{name}_callbacks, &block)
779
+ end
780
+ RUBY
570
781
  end
571
782
  end
783
+
784
+ protected
785
+
786
+ def get_callbacks(name)
787
+ send "_#{name}_callbacks"
788
+ end
789
+
790
+ def set_callbacks(name, callbacks)
791
+ send "_#{name}_callbacks=", callbacks
792
+ end
572
793
  end
573
794
  end
574
795
  end