core_ext 0.0.1

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