motion_blender-support 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +4 -0
  5. data/HACKS.md +7 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +359 -0
  8. data/Rakefile +14 -0
  9. data/app/app_delegate.rb +5 -0
  10. data/examples/Inflector/.gitignore +16 -0
  11. data/examples/Inflector/Gemfile +5 -0
  12. data/examples/Inflector/Rakefile +13 -0
  13. data/examples/Inflector/app/app_delegate.rb +8 -0
  14. data/examples/Inflector/app/inflections.rb +5 -0
  15. data/examples/Inflector/app/inflector_view_controller.rb +109 -0
  16. data/examples/Inflector/app/words_view_controller.rb +101 -0
  17. data/examples/Inflector/resources/Default-568h@2x.png +0 -0
  18. data/examples/Inflector/spec/main_spec.rb +9 -0
  19. data/lib/motion-support/callbacks.rb +8 -0
  20. data/lib/motion-support/concern.rb +4 -0
  21. data/lib/motion-support/core_ext/array.rb +10 -0
  22. data/lib/motion-support/core_ext/class.rb +5 -0
  23. data/lib/motion-support/core_ext/hash.rb +13 -0
  24. data/lib/motion-support/core_ext/integer.rb +6 -0
  25. data/lib/motion-support/core_ext/module.rb +11 -0
  26. data/lib/motion-support/core_ext/numeric.rb +6 -0
  27. data/lib/motion-support/core_ext/object.rb +12 -0
  28. data/lib/motion-support/core_ext/range.rb +5 -0
  29. data/lib/motion-support/core_ext/string.rb +13 -0
  30. data/lib/motion-support/core_ext/time.rb +16 -0
  31. data/lib/motion-support/core_ext.rb +13 -0
  32. data/lib/motion-support/inflector.rb +8 -0
  33. data/lib/motion-support.rb +81 -0
  34. data/motion/_stdlib/array.rb +13 -0
  35. data/motion/_stdlib/cgi.rb +22 -0
  36. data/motion/_stdlib/date.rb +81 -0
  37. data/motion/_stdlib/enumerable.rb +9 -0
  38. data/motion/_stdlib/time.rb +19 -0
  39. data/motion/callbacks.rb +511 -0
  40. data/motion/concern.rb +122 -0
  41. data/motion/core_ext/array/access.rb +28 -0
  42. data/motion/core_ext/array/conversions.rb +86 -0
  43. data/motion/core_ext/array/extract_options.rb +11 -0
  44. data/motion/core_ext/array/grouping.rb +99 -0
  45. data/motion/core_ext/array/prepend_and_append.rb +7 -0
  46. data/motion/core_ext/array/wrap.rb +45 -0
  47. data/motion/core_ext/array.rb +19 -0
  48. data/motion/core_ext/class/attribute.rb +119 -0
  49. data/motion/core_ext/class/attribute_accessors.rb +168 -0
  50. data/motion/core_ext/date/acts_like.rb +8 -0
  51. data/motion/core_ext/date/calculations.rb +117 -0
  52. data/motion/core_ext/date/conversions.rb +56 -0
  53. data/motion/core_ext/date_and_time/calculations.rb +232 -0
  54. data/motion/core_ext/enumerable.rb +90 -0
  55. data/motion/core_ext/hash/deep_delete_if.rb +23 -0
  56. data/motion/core_ext/hash/deep_merge.rb +27 -0
  57. data/motion/core_ext/hash/except.rb +15 -0
  58. data/motion/core_ext/hash/indifferent_access.rb +19 -0
  59. data/motion/core_ext/hash/keys.rb +150 -0
  60. data/motion/core_ext/hash/reverse_merge.rb +22 -0
  61. data/motion/core_ext/hash/slice.rb +40 -0
  62. data/motion/core_ext/integer/inflections.rb +27 -0
  63. data/motion/core_ext/integer/multiple.rb +10 -0
  64. data/motion/core_ext/integer/time.rb +41 -0
  65. data/motion/core_ext/kernel/singleton_class.rb +6 -0
  66. data/motion/core_ext/metaclass.rb +8 -0
  67. data/motion/core_ext/module/aliasing.rb +69 -0
  68. data/motion/core_ext/module/anonymous.rb +19 -0
  69. data/motion/core_ext/module/attr_internal.rb +38 -0
  70. data/motion/core_ext/module/attribute_accessors.rb +64 -0
  71. data/motion/core_ext/module/delegation.rb +175 -0
  72. data/motion/core_ext/module/introspection.rb +60 -0
  73. data/motion/core_ext/module/reachable.rb +5 -0
  74. data/motion/core_ext/module/remove_method.rb +12 -0
  75. data/motion/core_ext/ns_dictionary.rb +14 -0
  76. data/motion/core_ext/ns_string.rb +14 -0
  77. data/motion/core_ext/numeric/bytes.rb +44 -0
  78. data/motion/core_ext/numeric/conversions.rb +49 -0
  79. data/motion/core_ext/numeric/time.rb +75 -0
  80. data/motion/core_ext/object/acts_like.rb +10 -0
  81. data/motion/core_ext/object/blank.rb +105 -0
  82. data/motion/core_ext/object/deep_dup.rb +44 -0
  83. data/motion/core_ext/object/duplicable.rb +87 -0
  84. data/motion/core_ext/object/inclusion.rb +15 -0
  85. data/motion/core_ext/object/instance_variables.rb +28 -0
  86. data/motion/core_ext/object/to_param.rb +58 -0
  87. data/motion/core_ext/object/to_query.rb +26 -0
  88. data/motion/core_ext/object/try.rb +78 -0
  89. data/motion/core_ext/range/include_range.rb +23 -0
  90. data/motion/core_ext/range/overlaps.rb +8 -0
  91. data/motion/core_ext/regexp.rb +5 -0
  92. data/motion/core_ext/string/access.rb +104 -0
  93. data/motion/core_ext/string/behavior.rb +6 -0
  94. data/motion/core_ext/string/exclude.rb +11 -0
  95. data/motion/core_ext/string/filters.rb +55 -0
  96. data/motion/core_ext/string/indent.rb +43 -0
  97. data/motion/core_ext/string/inflections.rb +178 -0
  98. data/motion/core_ext/string/starts_ends_with.rb +4 -0
  99. data/motion/core_ext/string/strip.rb +24 -0
  100. data/motion/core_ext/time/acts_like.rb +8 -0
  101. data/motion/core_ext/time/calculations.rb +215 -0
  102. data/motion/core_ext/time/conversions.rb +52 -0
  103. data/motion/descendants_tracker.rb +50 -0
  104. data/motion/duration.rb +104 -0
  105. data/motion/hash_with_indifferent_access.rb +253 -0
  106. data/motion/inflections.rb +67 -0
  107. data/motion/inflector/inflections.rb +203 -0
  108. data/motion/inflector/methods.rb +321 -0
  109. data/motion/logger.rb +47 -0
  110. data/motion/number_helper.rb +54 -0
  111. data/motion/version.rb +3 -0
  112. data/motion_blender-support.gemspec +21 -0
  113. data/spec/motion-support/_helpers/constantize_test_cases.rb +75 -0
  114. data/spec/motion-support/_helpers/inflector_test_cases.rb +270 -0
  115. data/spec/motion-support/callback_spec.rb +702 -0
  116. data/spec/motion-support/concern_spec.rb +93 -0
  117. data/spec/motion-support/core_ext/array/access_spec.rb +29 -0
  118. data/spec/motion-support/core_ext/array/conversion_spec.rb +60 -0
  119. data/spec/motion-support/core_ext/array/extract_options_spec.rb +15 -0
  120. data/spec/motion-support/core_ext/array/grouping_spec.rb +85 -0
  121. data/spec/motion-support/core_ext/array/prepend_and_append_spec.rb +25 -0
  122. data/spec/motion-support/core_ext/array/wrap_spec.rb +19 -0
  123. data/spec/motion-support/core_ext/array_spec.rb +16 -0
  124. data/spec/motion-support/core_ext/class/attribute_accessor_spec.rb +127 -0
  125. data/spec/motion-support/core_ext/class/attribute_spec.rb +92 -0
  126. data/spec/motion-support/core_ext/date/acts_like_spec.rb +11 -0
  127. data/spec/motion-support/core_ext/date/calculation_spec.rb +186 -0
  128. data/spec/motion-support/core_ext/date/conversion_spec.rb +18 -0
  129. data/spec/motion-support/core_ext/date_and_time/calculation_spec.rb +336 -0
  130. data/spec/motion-support/core_ext/enumerable_spec.rb +130 -0
  131. data/spec/motion-support/core_ext/hash/deep_delete_if_spec.rb +19 -0
  132. data/spec/motion-support/core_ext/hash/deep_merge_spec.rb +32 -0
  133. data/spec/motion-support/core_ext/hash/except_spec.rb +43 -0
  134. data/spec/motion-support/core_ext/hash/key_spec.rb +236 -0
  135. data/spec/motion-support/core_ext/hash/reverse_merge_spec.rb +26 -0
  136. data/spec/motion-support/core_ext/hash/slice_spec.rb +61 -0
  137. data/spec/motion-support/core_ext/integer/inflection_spec.rb +23 -0
  138. data/spec/motion-support/core_ext/integer/multiple_spec.rb +19 -0
  139. data/spec/motion-support/core_ext/kernel/singleton_class_spec.rb +9 -0
  140. data/spec/motion-support/core_ext/metaclass_spec.rb +9 -0
  141. data/spec/motion-support/core_ext/module/aliasing_spec.rb +143 -0
  142. data/spec/motion-support/core_ext/module/anonymous_spec.rb +29 -0
  143. data/spec/motion-support/core_ext/module/attr_internal_spec.rb +104 -0
  144. data/spec/motion-support/core_ext/module/attribute_accessor_spec.rb +86 -0
  145. data/spec/motion-support/core_ext/module/delegation_spec.rb +136 -0
  146. data/spec/motion-support/core_ext/module/introspection_spec.rb +70 -0
  147. data/spec/motion-support/core_ext/module/reachable_spec.rb +61 -0
  148. data/spec/motion-support/core_ext/module/remove_method_spec.rb +25 -0
  149. data/spec/motion-support/core_ext/numeric/bytes_spec.rb +43 -0
  150. data/spec/motion-support/core_ext/numeric/conversions_spec.rb +40 -0
  151. data/spec/motion-support/core_ext/object/acts_like_spec.rb +21 -0
  152. data/spec/motion-support/core_ext/object/blank_spec.rb +54 -0
  153. data/spec/motion-support/core_ext/object/deep_dup_spec.rb +54 -0
  154. data/spec/motion-support/core_ext/object/duplicable_spec.rb +31 -0
  155. data/spec/motion-support/core_ext/object/inclusion_spec.rb +34 -0
  156. data/spec/motion-support/core_ext/object/instance_variable_spec.rb +19 -0
  157. data/spec/motion-support/core_ext/object/to_param_spec.rb +75 -0
  158. data/spec/motion-support/core_ext/object/to_query_spec.rb +37 -0
  159. data/spec/motion-support/core_ext/object/try_spec.rb +92 -0
  160. data/spec/motion-support/core_ext/range/include_range_spec.rb +31 -0
  161. data/spec/motion-support/core_ext/range/overlap_spec.rb +43 -0
  162. data/spec/motion-support/core_ext/regexp_spec.rb +7 -0
  163. data/spec/motion-support/core_ext/string/access_spec.rb +53 -0
  164. data/spec/motion-support/core_ext/string/behavior_spec.rb +7 -0
  165. data/spec/motion-support/core_ext/string/exclude_spec.rb +8 -0
  166. data/spec/motion-support/core_ext/string/filter_spec.rb +49 -0
  167. data/spec/motion-support/core_ext/string/indent_spec.rb +56 -0
  168. data/spec/motion-support/core_ext/string/inflection_spec.rb +142 -0
  169. data/spec/motion-support/core_ext/string/starts_end_with_spec.rb +14 -0
  170. data/spec/motion-support/core_ext/string/strip_spec.rb +34 -0
  171. data/spec/motion-support/core_ext/string_spec.rb +88 -0
  172. data/spec/motion-support/core_ext/time/acts_like_spec.rb +11 -0
  173. data/spec/motion-support/core_ext/time/calculation_spec.rb +201 -0
  174. data/spec/motion-support/core_ext/time/conversion_spec.rb +53 -0
  175. data/spec/motion-support/descendants_tracker_spec.rb +58 -0
  176. data/spec/motion-support/duration_spec.rb +107 -0
  177. data/spec/motion-support/hash_with_indifferent_access_spec.rb +605 -0
  178. data/spec/motion-support/inflector_spec.rb +504 -0
  179. data/spec/motion-support/ns_dictionary_spec.rb +89 -0
  180. data/spec/motion-support/ns_string_spec.rb +182 -0
  181. data/spec/motion-support/number_helper_spec.rb +55 -0
  182. metadata +352 -0
@@ -0,0 +1,511 @@
1
+ require_relative 'concern'
2
+
3
+ module MotionSupport
4
+ # Callbacks are code hooks that are run at key points in an object's lifecycle.
5
+ # The typical use case is to have a base class define a set of callbacks
6
+ # relevant to the other functionality it supplies, so that subclasses can
7
+ # install callbacks that enhance or modify the base functionality without
8
+ # needing to override or redefine methods of the base class.
9
+ #
10
+ # Mixing in this module allows you to define the events in the object's
11
+ # lifecycle that will support callbacks (via +ClassMethods.define_callbacks+),
12
+ # set the instance methods, procs, or callback objects to be called (via
13
+ # +ClassMethods.set_callback+), and run the installed callbacks at the
14
+ # appropriate times (via +run_callbacks+).
15
+ #
16
+ # Three kinds of callbacks are supported: before callbacks, run before a
17
+ # certain event; after callbacks, run after the event; and around callbacks,
18
+ # blocks that surround the event, triggering it when they yield. Callback code
19
+ # can be contained in instance methods, procs or lambdas, or callback objects
20
+ # that respond to certain predetermined methods. See +ClassMethods.set_callback+
21
+ # for details.
22
+ #
23
+ # class Record
24
+ # include MotionSupport::Callbacks
25
+ # define_callbacks :save
26
+ #
27
+ # def save
28
+ # run_callbacks :save do
29
+ # puts "- save"
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # class PersonRecord < Record
35
+ # set_callback :save, :before, :saving_message
36
+ # def saving_message
37
+ # puts "saving..."
38
+ # end
39
+ #
40
+ # set_callback :save, :after do |object|
41
+ # puts "saved"
42
+ # end
43
+ # end
44
+ #
45
+ # person = PersonRecord.new
46
+ # person.save
47
+ #
48
+ # Output:
49
+ # saving...
50
+ # - save
51
+ # saved
52
+ module Callbacks
53
+ extend Concern
54
+
55
+ included do
56
+ extend MotionSupport::DescendantsTracker
57
+ end
58
+
59
+ # Runs the callbacks for the given event.
60
+ #
61
+ # Calls the before and around callbacks in the order they were set, yields
62
+ # the block (if given one), and then runs the after callbacks in reverse
63
+ # order.
64
+ #
65
+ # If the callback chain was halted, returns +false+. Otherwise returns the
66
+ # result of the block, or +true+ if no block is given.
67
+ #
68
+ # run_callbacks :save do
69
+ # save
70
+ # end
71
+ def run_callbacks(kind, &block)
72
+ runner_name = self.class.__define_callbacks(kind, self)
73
+ send(runner_name, &block)
74
+ end
75
+
76
+ private
77
+
78
+ # A hook invoked everytime a before callback is halted.
79
+ # This can be overridden in AS::Callback implementors in order
80
+ # to provide better debugging/logging.
81
+ def halted_callback_hook(filter)
82
+ end
83
+
84
+ class Callback #:nodoc:#
85
+ @@_callback_sequence = 0
86
+
87
+ attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
88
+
89
+ def initialize(chain, filter, kind, options, klass)
90
+ @chain, @kind, @klass = chain, kind, klass
91
+ normalize_options!(options)
92
+
93
+ @raw_filter, @options = filter, options
94
+ @filter = _compile_filter(filter)
95
+ recompile_options!
96
+ end
97
+
98
+ def clone(chain, klass)
99
+ obj = super()
100
+ obj.chain = chain
101
+ obj.klass = klass
102
+ obj.options = @options.dup
103
+ obj.options[:if] = @options[:if].dup
104
+ obj.options[:unless] = @options[:unless].dup
105
+ obj
106
+ end
107
+
108
+ def normalize_options!(options)
109
+ options[:if] = Array(options[:if])
110
+ options[:unless] = Array(options[:unless])
111
+ end
112
+
113
+ def name
114
+ chain.name
115
+ end
116
+
117
+ def next_id
118
+ @@_callback_sequence += 1
119
+ end
120
+
121
+ def matches?(_kind, _filter)
122
+ @kind == _kind && @raw_filter == _filter
123
+ end
124
+
125
+ def duplicates?(other)
126
+ matches?(other.kind, other.raw_filter)
127
+ end
128
+
129
+ def _update_filter(filter_options, new_options)
130
+ filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
131
+ filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
132
+ end
133
+
134
+ def recompile!(_options)
135
+ _update_filter(self.options, _options)
136
+
137
+ recompile_options!
138
+ end
139
+
140
+ # Wraps code with filter
141
+ def apply(code)
142
+ case @kind
143
+ when :before
144
+ lambda do |obj, value, halted|
145
+ if !halted && @compiled_options.call(obj)
146
+ result = @filter.call(obj)
147
+
148
+ halted = chain.config[:terminator].call(result)
149
+ if halted
150
+ obj.halted_callback_hook(@raw_filter.inspect)
151
+ end
152
+ end
153
+ code.call(obj, value, halted)
154
+ end
155
+ when :after
156
+ lambda do |obj, value, halted|
157
+ value, halted = *(code.call(obj, value, halted))
158
+ if (!chain.config[:skip_after_callbacks_if_terminated] || !halted) && @compiled_options.call(obj)
159
+ @filter.call(obj)
160
+ end
161
+ [value, halted]
162
+ end
163
+ when :around
164
+ lambda do |obj, value, halted|
165
+ if @compiled_options.call(obj) && !halted
166
+ @filter.call(obj) do
167
+ value, halted = *(code.call(obj, value, halted))
168
+ value
169
+ end
170
+ else
171
+ value, halted = *(code.call(obj, value, halted))
172
+ value
173
+ end
174
+ [value, halted]
175
+ end
176
+ end
177
+ end
178
+
179
+ private
180
+ # Options support the same options as filters themselves (and support
181
+ # symbols, string, procs, and objects), so compile a conditional
182
+ # expression based on the options.
183
+ def recompile_options!
184
+ @conditions = [ lambda { |obj| true } ]
185
+
186
+ unless options[:if].empty?
187
+ @conditions << Array(_compile_filter(options[:if]))
188
+ end
189
+
190
+ unless options[:unless].empty?
191
+ @conditions << Array(_compile_filter(options[:unless])).map {|f| lambda { |obj| !f.call(obj) } }
192
+ end
193
+
194
+ @compiled_options = lambda { |obj| @conditions.flatten.all? { |c| c.call(obj) } }
195
+ end
196
+
197
+ # Filters support:
198
+ #
199
+ # Arrays:: Used in conditions. This is used to specify
200
+ # multiple conditions. Used internally to
201
+ # merge conditions from skip_* filters.
202
+ # Symbols:: A method to call.
203
+ # Procs:: A proc to call with the object.
204
+ # Objects:: An object with a <tt>before_foo</tt> method on it to call.
205
+ #
206
+ # All of these objects are compiled into methods and handled
207
+ # the same after this point:
208
+ #
209
+ # Arrays:: Merged together into a single filter.
210
+ # Symbols:: Already methods.
211
+ # Procs:: define_method'ed into methods.
212
+ # Objects::
213
+ # a method is created that calls the before_foo method
214
+ # on the object.
215
+ def _compile_filter(filter)
216
+ case filter
217
+ when Array
218
+ lambda { |obj, &block| filter.all? {|f| _compile_filter(f).call(obj, &block) } }
219
+ when Symbol, String
220
+ lambda { |obj, &block| obj.send filter, &block }
221
+ when Proc
222
+ filter
223
+ else
224
+ method_name = "_callback_#{@kind}_#{next_id}"
225
+ @klass.send(:define_method, "#{method_name}_object") { filter }
226
+
227
+ scopes = Array(chain.config[:scope])
228
+ method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
229
+
230
+ @klass.class_eval do
231
+ define_method method_name do |&blk|
232
+ send("#{method_name}_object").send(method_to_call, self, &blk)
233
+ end
234
+ end
235
+
236
+ lambda { |obj, &block| obj.send method_name, &block }
237
+ end
238
+ end
239
+ end
240
+
241
+ # An Array with a compile method.
242
+ class CallbackChain < Array #:nodoc:#
243
+ attr_reader :name, :config
244
+
245
+ def initialize(name, config)
246
+ @name = name
247
+ @config = {
248
+ :terminator => lambda { |result| false },
249
+ :scope => [ :kind ]
250
+ }.merge!(config)
251
+ end
252
+
253
+ def compile
254
+ lambda do |obj, &block|
255
+ value = nil
256
+ halted = false
257
+
258
+ callbacks = lambda do |obj, value, halted|
259
+ value = !halted && (block.call if block)
260
+ [value, halted]
261
+ end
262
+
263
+ reverse_each do |callback|
264
+ callbacks = callback.apply(callbacks)
265
+ end
266
+
267
+ value, halted = *(callbacks.call(obj, value, halted))
268
+
269
+ value
270
+ end
271
+ end
272
+
273
+ def append(*callbacks)
274
+ callbacks.each { |c| append_one(c) }
275
+ end
276
+
277
+ def prepend(*callbacks)
278
+ callbacks.each { |c| prepend_one(c) }
279
+ end
280
+
281
+ private
282
+
283
+ def append_one(callback)
284
+ remove_duplicates(callback)
285
+ push(callback)
286
+ end
287
+
288
+ def prepend_one(callback)
289
+ remove_duplicates(callback)
290
+ unshift(callback)
291
+ end
292
+
293
+ def remove_duplicates(callback)
294
+ delete_if { |c| callback.duplicates?(c) }
295
+ end
296
+
297
+ end
298
+
299
+ module ClassMethods
300
+ # This method defines callback chain method for the given kind
301
+ # if it was not yet defined.
302
+ # This generated method plays caching role.
303
+ def __define_callbacks(kind, object) #:nodoc:
304
+ name = __callback_runner_name(kind)
305
+ unless object.respond_to?(name, true)
306
+ block = object.send("_#{kind}_callbacks").compile
307
+ define_method name do |&blk|
308
+ block.call(self, &blk)
309
+ end
310
+ protected name.to_sym
311
+ end
312
+ name
313
+ end
314
+
315
+ def __reset_runner(symbol)
316
+ name = __callback_runner_name(symbol)
317
+ undef_method(name) if method_defined?(name)
318
+ end
319
+
320
+ def __callback_runner_name_cache
321
+ @__callback_runner_name_cache ||= Hash.new {|cache, kind| cache[kind] = __generate_callback_runner_name(kind) }
322
+ end
323
+
324
+ def __generate_callback_runner_name(kind)
325
+ "_run__#{self.name.hash.abs}__#{kind}__callbacks"
326
+ end
327
+
328
+ def __callback_runner_name(kind)
329
+ __callback_runner_name_cache[kind]
330
+ end
331
+
332
+ # This is used internally to append, prepend and skip callbacks to the
333
+ # CallbackChain.
334
+ def __update_callbacks(name, filters = [], block = nil) #:nodoc:
335
+ type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
336
+ options = filters.last.is_a?(Hash) ? filters.pop : {}
337
+ filters.unshift(block) if block
338
+
339
+ ([self] + MotionSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
340
+ chain = target.send("_#{name}_callbacks")
341
+ yield target, chain.dup, type, filters, options
342
+ target.__reset_runner(name)
343
+ end
344
+ end
345
+
346
+ # Install a callback for the given event.
347
+ #
348
+ # set_callback :save, :before, :before_meth
349
+ # set_callback :save, :after, :after_meth, if: :condition
350
+ # set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff }
351
+ #
352
+ # The second arguments indicates whether the callback is to be run +:before+,
353
+ # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
354
+ # means the first example above can also be written as:
355
+ #
356
+ # set_callback :save, :before_meth
357
+ #
358
+ # The callback can specified as a symbol naming an instance method; as a
359
+ # proc, lambda, or block; as a string to be instance evaluated; or as an
360
+ # object that responds to a certain method determined by the <tt>:scope</tt>
361
+ # argument to +define_callback+.
362
+ #
363
+ # If a proc, lambda, or block is given, its body is evaluated in the context
364
+ # of the current object. It can also optionally accept the current object as
365
+ # an argument.
366
+ #
367
+ # Before and around callbacks are called in the order that they are set;
368
+ # after callbacks are called in the reverse order.
369
+ #
370
+ # Around callbacks can access the return value from the event, if it
371
+ # wasn't halted, from the +yield+ call.
372
+ #
373
+ # ===== Options
374
+ #
375
+ # * <tt>:if</tt> - A symbol naming an instance method or a proc; the
376
+ # callback will be called only when it returns a +true+ value.
377
+ # * <tt>:unless</tt> - A symbol naming an instance method or a proc; the
378
+ # callback will be called only when it returns a +false+ value.
379
+ # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
380
+ # existing chain rather than appended.
381
+ def set_callback(name, *filter_list, &block)
382
+ mapped = nil
383
+
384
+ __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
385
+ mapped ||= filters.map do |filter|
386
+ Callback.new(chain, filter, type, options.dup, self)
387
+ end
388
+
389
+ options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
390
+
391
+ target.send("_#{name}_callbacks=", chain)
392
+ end
393
+ end
394
+
395
+ # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
396
+ # <tt>:unless</tt> options may be passed in order to control when the
397
+ # callback is skipped.
398
+ #
399
+ # class Writer < Person
400
+ # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
401
+ # end
402
+ def skip_callback(name, *filter_list, &block)
403
+ __update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
404
+ filters.each do |filter|
405
+ filter = chain.find {|c| c.matches?(type, filter) }
406
+
407
+ if filter && options.any?
408
+ new_filter = filter.clone(chain, self)
409
+ chain.insert(chain.index(filter), new_filter)
410
+ new_filter.recompile!(options)
411
+ end
412
+
413
+ chain.delete(filter)
414
+ end
415
+ target.send("_#{name}_callbacks=", chain)
416
+ end
417
+ end
418
+
419
+ # Remove all set callbacks for the given event.
420
+ def reset_callbacks(symbol)
421
+ callbacks = send("_#{symbol}_callbacks")
422
+
423
+ MotionSupport::DescendantsTracker.descendants(self).each do |target|
424
+ chain = target.send("_#{symbol}_callbacks").dup
425
+ callbacks.each { |c| chain.delete(c) }
426
+ target.send("_#{symbol}_callbacks=", chain)
427
+ target.__reset_runner(symbol)
428
+ end
429
+
430
+ self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
431
+
432
+ __reset_runner(symbol)
433
+ end
434
+
435
+ # Define sets of events in the object lifecycle that support callbacks.
436
+ #
437
+ # define_callbacks :validate
438
+ # define_callbacks :initialize, :save, :destroy
439
+ #
440
+ # ===== Options
441
+ #
442
+ # * <tt>:terminator</tt> - Determines when a before filter will halt the
443
+ # callback chain, preventing following callbacks from being called and
444
+ # the event from being triggered. This is a string to be eval'ed. The
445
+ # result of the callback is available in the +result+ variable.
446
+ #
447
+ # define_callbacks :validate, terminator: 'result == false'
448
+ #
449
+ # In this example, if any before validate callbacks returns +false+,
450
+ # other callbacks are not executed. Defaults to +false+, meaning no value
451
+ # halts the chain.
452
+ #
453
+ # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
454
+ # callbacks should be terminated by the <tt>:terminator</tt> option. By
455
+ # default after callbacks executed no matter if callback chain was
456
+ # terminated or not. Option makes sense only when <tt>:terminator</tt>
457
+ # option is specified.
458
+ #
459
+ # * <tt>:scope</tt> - Indicates which methods should be executed when an
460
+ # object is used as a callback.
461
+ #
462
+ # class Audit
463
+ # def before(caller)
464
+ # puts 'Audit: before is called'
465
+ # end
466
+ #
467
+ # def before_save(caller)
468
+ # puts 'Audit: before_save is called'
469
+ # end
470
+ # end
471
+ #
472
+ # class Account
473
+ # include MotionSupport::Callbacks
474
+ #
475
+ # define_callbacks :save
476
+ # set_callback :save, :before, Audit.new
477
+ #
478
+ # def save
479
+ # run_callbacks :save do
480
+ # puts 'save in main'
481
+ # end
482
+ # end
483
+ # end
484
+ #
485
+ # In the above case whenever you save an account the method
486
+ # <tt>Audit#before</tt> will be called. On the other hand
487
+ #
488
+ # define_callbacks :save, scope: [:kind, :name]
489
+ #
490
+ # would trigger <tt>Audit#before_save</tt> instead. That's constructed
491
+ # by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
492
+ # case "kind" is "before" and "name" is "save". In this context +:kind+
493
+ # and +:name+ have special meanings: +:kind+ refers to the kind of
494
+ # callback (before/after/around) and +:name+ refers to the method on
495
+ # which callbacks are being defined.
496
+ #
497
+ # A declaration like
498
+ #
499
+ # define_callbacks :save, scope: [:name]
500
+ #
501
+ # would call <tt>Audit#save</tt>.
502
+ def define_callbacks(*callbacks)
503
+ config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
504
+ callbacks.each do |callback|
505
+ class_attribute "_#{callback}_callbacks"
506
+ send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
507
+ end
508
+ end
509
+ end
510
+ end
511
+ end
data/motion/concern.rb ADDED
@@ -0,0 +1,122 @@
1
+ module MotionSupport
2
+ # A typical module looks like this:
3
+ #
4
+ # module M
5
+ # def self.included(base)
6
+ # base.extend ClassMethods
7
+ # base.class_eval do
8
+ # scope :disabled, -> { where(disabled: true) }
9
+ # end
10
+ # end
11
+ #
12
+ # module ClassMethods
13
+ # ...
14
+ # end
15
+ # end
16
+ #
17
+ # By using <tt>MotionSupport::Concern</tt> the above module could instead be
18
+ # written as:
19
+ #
20
+ # module M
21
+ # extend MotionSupport::Concern
22
+ #
23
+ # included do
24
+ # scope :disabled, -> { where(disabled: true) }
25
+ # end
26
+ #
27
+ # module ClassMethods
28
+ # ...
29
+ # end
30
+ # end
31
+ #
32
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
33
+ # and a +Bar+ module which depends on the former, we would typically write the
34
+ # following:
35
+ #
36
+ # module Foo
37
+ # def self.included(base)
38
+ # base.class_eval do
39
+ # def self.method_injected_by_foo
40
+ # ...
41
+ # end
42
+ # end
43
+ # end
44
+ # end
45
+ #
46
+ # module Bar
47
+ # def self.included(base)
48
+ # base.method_injected_by_foo
49
+ # end
50
+ # end
51
+ #
52
+ # class Host
53
+ # include Foo # We need to include this dependency for Bar
54
+ # include Bar # Bar is the module that Host really needs
55
+ # end
56
+ #
57
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
58
+ # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
59
+ #
60
+ # module Bar
61
+ # include Foo
62
+ # def self.included(base)
63
+ # base.method_injected_by_foo
64
+ # end
65
+ # end
66
+ #
67
+ # class Host
68
+ # include Bar
69
+ # end
70
+ #
71
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
72
+ # is the +Bar+ module, not the +Host+ class. With <tt>MotionSupport::Concern</tt>,
73
+ # module dependencies are properly resolved:
74
+ #
75
+ # module Foo
76
+ # extend MotionSupport::Concern
77
+ # included do
78
+ # def self.method_injected_by_foo
79
+ # ...
80
+ # end
81
+ # end
82
+ # end
83
+ #
84
+ # module Bar
85
+ # extend MotionSupport::Concern
86
+ # include Foo
87
+ #
88
+ # included do
89
+ # self.method_injected_by_foo
90
+ # end
91
+ # end
92
+ #
93
+ # class Host
94
+ # include Bar # works, Bar takes care now of its dependencies
95
+ # end
96
+ module Concern
97
+ def self.extended(base) #:nodoc:
98
+ base.instance_variable_set("@_dependencies", [])
99
+ end
100
+
101
+ def append_features(base)
102
+ if base.instance_variable_defined?("@_dependencies")
103
+ base.instance_variable_get("@_dependencies") << self
104
+ return false
105
+ else
106
+ return false if base < self
107
+ @_dependencies.each { |dep| base.send(:include, dep) }
108
+ super
109
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
110
+ base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
111
+ end
112
+ end
113
+
114
+ def included(base = nil, &block)
115
+ if base.nil?
116
+ @_included_block = block
117
+ else
118
+ super
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,28 @@
1
+ class Array
2
+ # Returns the tail of the array from +position+.
3
+ #
4
+ # %w( a b c d ).from(0) # => ["a", "b", "c", "d"]
5
+ # %w( a b c d ).from(2) # => ["c", "d"]
6
+ # %w( a b c d ).from(10) # => []
7
+ # %w().from(0) # => []
8
+ def from(position)
9
+ self[position, length] || []
10
+ end
11
+
12
+ # Returns the beginning of the array up to +position+.
13
+ #
14
+ # %w( a b c d ).to(0) # => ["a"]
15
+ # %w( a b c d ).to(2) # => ["a", "b", "c"]
16
+ # %w( a b c d ).to(10) # => ["a", "b", "c", "d"]
17
+ # %w().to(0) # => []
18
+ def to(position)
19
+ first position + 1
20
+ end
21
+
22
+ # Equal to <tt>self[1]</tt>.
23
+ #
24
+ # %w( a b c d e ).second # => "b"
25
+ def second
26
+ self[1]
27
+ end
28
+ end