motion_blender-support 0.2.7

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 (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