aspector 0.13.1 → 0.14.0

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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rubocop.yml +26 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +8 -11
  7. data/Changelog.md +59 -0
  8. data/Gemfile +9 -14
  9. data/Gemfile.lock +84 -50
  10. data/README.md +118 -0
  11. data/Rakefile +6 -22
  12. data/aspector.gemspec +15 -127
  13. data/benchmarks/after_benchmark.rb +28 -0
  14. data/benchmarks/around_advice_benchmark.rb +35 -0
  15. data/benchmarks/around_benchmark.rb +32 -0
  16. data/benchmarks/before_benchmark.rb +28 -0
  17. data/benchmarks/benchmark_helper.rb +17 -0
  18. data/benchmarks/combined_benchmark.rb +36 -0
  19. data/benchmarks/method_invocation_benchmark.rb +30 -0
  20. data/benchmarks/raw_benchmark.rb +39 -0
  21. data/examples/activerecord_hooks.rb +10 -15
  22. data/examples/around_example.rb +20 -31
  23. data/examples/aspector_apply_example.rb +10 -17
  24. data/examples/aspector_example.rb +7 -16
  25. data/examples/cache_aspect.rb +20 -30
  26. data/examples/design_by_contract.rb +20 -44
  27. data/examples/exception_handler.rb +12 -20
  28. data/examples/exception_handler2.rb +16 -24
  29. data/examples/implicit_method_option_test.rb +8 -16
  30. data/examples/interception_options_example.rb +71 -0
  31. data/examples/logging_aspect.rb +16 -24
  32. data/examples/process_aspector.rb +13 -0
  33. data/examples/retry_aspect.rb +20 -20
  34. data/lib/aspector.rb +17 -15
  35. data/lib/aspector/advice.rb +44 -57
  36. data/lib/aspector/advice_metadata.rb +10 -11
  37. data/lib/aspector/aspect_instances.rb +2 -3
  38. data/lib/aspector/base.rb +6 -368
  39. data/lib/aspector/base_class_methods.rb +24 -55
  40. data/lib/aspector/deferred_logic.rb +3 -4
  41. data/lib/aspector/deferred_option.rb +5 -10
  42. data/lib/aspector/interception.rb +356 -0
  43. data/lib/aspector/logger.rb +18 -45
  44. data/lib/aspector/logging.rb +10 -29
  45. data/lib/aspector/method_matcher.rb +5 -6
  46. data/lib/aspector/object_extension.rb +4 -12
  47. data/lib/aspector/version.rb +3 -0
  48. data/spec/examples_spec.rb +59 -0
  49. data/spec/functionals/aspect_for_multiple_targets_spec.rb +54 -0
  50. data/spec/functionals/aspect_interception_options_accessing_spec.rb +112 -0
  51. data/spec/functionals/aspect_on_a_class_spec.rb +159 -0
  52. data/spec/functionals/aspect_on_an_instance_spec.rb +66 -0
  53. data/spec/functionals/aspector_spec.rb +138 -0
  54. data/spec/functionals/aspects_combined_spec.rb +37 -0
  55. data/spec/functionals/aspects_execution_order_spec.rb +61 -0
  56. data/spec/functionals/aspects_on_private_methods_spec.rb +82 -0
  57. data/spec/spec_helper.rb +20 -21
  58. data/spec/support/class_builder.rb +44 -0
  59. data/spec/units/advice_spec.rb +49 -0
  60. data/spec/units/advices/after_spec.rb +328 -0
  61. data/spec/units/advices/around_spec.rb +336 -0
  62. data/spec/units/advices/before_filter_spec.rb +287 -0
  63. data/spec/units/advices/before_spec.rb +237 -0
  64. data/spec/units/advices/raw_spec.rb +67 -0
  65. data/spec/units/base_class_methods_spec.rb +262 -0
  66. data/spec/units/base_spec.rb +133 -0
  67. data/spec/units/deferred_logic_spec.rb +35 -0
  68. data/spec/units/logger_spec.rb +20 -0
  69. data/spec/units/logging_spec.rb +85 -0
  70. data/spec/units/method_matcher_spec.rb +95 -0
  71. data/spec/units/object_extension_spec.rb +11 -0
  72. data/spec/units/special_chars_spec.rb +128 -0
  73. metadata +98 -246
  74. data/.document +0 -5
  75. data/.rvmrc +0 -8
  76. data/README.rdoc +0 -80
  77. data/VERSION +0 -1
  78. data/performance-tests/after_test.rb +0 -25
  79. data/performance-tests/around_advice_benchmark.rb +0 -66
  80. data/performance-tests/around_test.rb +0 -27
  81. data/performance-tests/before_test.rb +0 -25
  82. data/performance-tests/combined_test.rb +0 -33
  83. data/performance-tests/method_invocation_test.rb +0 -25
  84. data/performance-tests/raw_test.rb +0 -37
  85. data/performance-tests/test_helper.rb +0 -9
  86. data/run_all_examples.sh +0 -12
  87. data/spec/functional/advices_on_private_methods_spec.rb +0 -21
  88. data/spec/functional/aspect_on_eigen_class_spec.rb +0 -72
  89. data/spec/functional/aspect_on_object_spec.rb +0 -20
  90. data/spec/functional/aspector_spec.rb +0 -140
  91. data/spec/functional/aspects_combined_spec.rb +0 -48
  92. data/spec/functional/execution_order_spec.rb +0 -42
  93. data/spec/unit/advice_spec.rb +0 -4
  94. data/spec/unit/after_spec.rb +0 -88
  95. data/spec/unit/around_spec.rb +0 -76
  96. data/spec/unit/base_class_methods_spec.rb +0 -28
  97. data/spec/unit/base_spec.rb +0 -112
  98. data/spec/unit/before_spec.rb +0 -125
  99. data/spec/unit/deferred_logic_spec.rb +0 -23
  100. data/spec/unit/method_matcher_spec.rb +0 -43
  101. data/spec/unit/raw_spec.rb +0 -53
  102. data/spec/unit/special_chars_spec.rb +0 -122
@@ -3,21 +3,32 @@ module Aspector
3
3
  module ClassMethods
4
4
  ::Aspector::Base.extend(self)
5
5
 
6
+ Aspector::Advice::TYPES.each do |type_name|
7
+ define_method type_name do |*methods, &block|
8
+ meta = Object.const_get("Aspector::AdviceMetadata::#{type_name.to_s.upcase}")
9
+ advices << advice = _create_advice_(meta, self, methods, &block)
10
+ advice.index = advices.size
11
+ logger.info 'define-advice', advice
12
+ advice
13
+ end
14
+
15
+ private type_name
16
+ end
17
+
6
18
  def enable
7
- logger.log Logging::INFO, 'enable-aspect'
19
+ logger.info 'enable-aspect'
20
+
8
21
  send :define_method, :disabled? do
22
+ false
9
23
  end
10
-
11
- nil
12
24
  end
13
25
 
14
26
  def disable
15
- logger.log Logging::INFO, 'disable-aspect'
27
+ logger.info 'disable-aspect'
28
+
16
29
  send :define_method, :disabled? do
17
30
  true
18
31
  end
19
-
20
- nil
21
32
  end
22
33
 
23
34
  # if ENV["ASPECTOR_LOGGER"] is set, use it
@@ -39,59 +50,18 @@ module Aspector
39
50
  options = rest.last.is_a?(Hash) ? rest.pop : {}
40
51
 
41
52
  targets = rest.unshift target
42
- result = targets.map do |target|
43
- logger.log Logging::INFO, 'apply', target, options.inspect
44
- aspect_instance = new(target, options)
45
- aspect_instance.send :apply
46
- aspect_instance
53
+ targets.map do |target|
54
+ logger.info 'apply', target, options.inspect
55
+ instance = new
56
+ instance.send :apply, target, options
47
57
  end
48
-
49
- result.size == 1 ? result.first : result
50
58
  end
51
-
59
+
52
60
  private
53
61
 
54
62
  def default options
55
- if @default_options
56
- @default_options.merge! options
57
- else
58
- @default_options = options
59
- end
60
- end
61
-
62
- def before *methods, &block
63
- advices << advice = _create_advice_(Aspector::AdviceMetadata::BEFORE, self, methods, &block)
64
- advice.index = advices.size
65
- logger.log Logging::INFO, 'define-advice', advice
66
- advice
67
- end
68
-
69
- def before_filter *methods, &block
70
- advices << advice = _create_advice_(Aspector::AdviceMetadata::BEFORE_FILTER, self, methods, &block)
71
- advice.index = advices.size
72
- logger.log Logging::INFO, 'define-advice', advice
73
- advice
74
- end
75
-
76
- def after *methods, &block
77
- advices << advice = _create_advice_(Aspector::AdviceMetadata::AFTER, self, methods, &block)
78
- advice.index = advices.size
79
- logger.log Logging::INFO, 'define-advice', advice
80
- advice
81
- end
82
-
83
- def around *methods, &block
84
- advices << advice = _create_advice_(Aspector::AdviceMetadata::AROUND, self, methods, &block)
85
- advice.index = advices.size
86
- logger.log Logging::INFO, 'define-advice', advice
87
- advice
88
- end
89
-
90
- def raw *methods, &block
91
- advices << advice = _create_advice_(Aspector::AdviceMetadata::RAW, self, methods, &block)
92
- advice.index = advices.size
93
- logger.log Logging::INFO, 'define-advice', advice
94
- advice
63
+ @default_options ||= {}
64
+ @default_options.merge!(options)
95
65
  end
96
66
 
97
67
  def target code = nil, &block
@@ -115,7 +85,6 @@ module Aspector
115
85
 
116
86
  options = meta_data.default_options.clone
117
87
  options.merge!(methods.pop) if methods.last.is_a? Hash
118
- options.merge!(meta_data.mandatory_options)
119
88
 
120
89
  if meta_data.advice_type == Aspector::Advice::RAW
121
90
  raise "Bad raw advice - code block is required" unless block_given?
@@ -1,15 +1,14 @@
1
1
  module Aspector
2
+ # Object that is used to apply and store deferred logic
2
3
  class DeferredLogic
3
-
4
4
  attr_reader :code
5
5
 
6
- def initialize code
6
+ def initialize(code)
7
7
  @code = code
8
8
  end
9
9
 
10
- def apply target, *args
10
+ def apply(target, *args)
11
11
  target.class_exec(*args, &@code)
12
12
  end
13
-
14
13
  end
15
14
  end
@@ -1,24 +1,19 @@
1
1
  module Aspector
2
+ # Object used to store deferred options
2
3
  class DeferredOption
3
-
4
4
  attr_reader :key
5
-
6
- def initialize key = nil
5
+
6
+ def initialize(key = nil)
7
7
  @key = key
8
8
  end
9
9
 
10
- def [] key
10
+ def [](key)
11
11
  @key = key
12
12
  self
13
13
  end
14
14
 
15
15
  def to_s
16
- if key
17
- "options[#{key.inspect}]"
18
- else
19
- "options[?]"
20
- end
16
+ "options[#{(key || '?').inspect}]"
21
17
  end
22
18
  end
23
19
  end
24
-
@@ -0,0 +1,356 @@
1
+ require 'erb'
2
+
3
+ module Aspector
4
+ class Interception
5
+ attr_reader :aspect, :target, :options
6
+
7
+ def initialize aspect, target, options
8
+ @aspect = aspect
9
+ @target = target
10
+ @options = options
11
+ @wrapped_methods = {}
12
+ end
13
+
14
+ def disabled?
15
+ @aspect.disabled?
16
+ end
17
+
18
+ def advices
19
+ @aspect.class.advices
20
+ end
21
+
22
+ def logger
23
+ @logger ||= Logging.get_logger(self)
24
+ end
25
+
26
+ def apply
27
+ invoke_deferred_logics
28
+ define_methods_for_advice_blocks
29
+ add_to_instances unless @options[:existing_methods_only]
30
+ apply_to_methods unless @options[:new_methods_only]
31
+ add_method_hooks unless @options[:existing_methods_only]
32
+ # TODO: clear deferred logic results if they are not used in any advice
33
+ return
34
+ end
35
+
36
+ def apply_to_methods
37
+ return if advices.empty?
38
+
39
+ # If method/methods option is set and all are String or Symbol, apply to those only, instead of
40
+ # iterating through all methods
41
+ methods = [@options[:method] || @options[:methods]]
42
+ methods.compact!
43
+ methods.flatten!
44
+
45
+ if not methods.empty? and methods.all?{|method| method.is_a? String or method.is_a? Symbol }
46
+ methods.each do |method|
47
+ apply_to_method(method.to_s)
48
+ end
49
+
50
+ return
51
+ end
52
+
53
+ context.public_instance_methods.each do |method|
54
+ apply_to_method(method.to_s, :public)
55
+ end
56
+
57
+ context.protected_instance_methods.each do |method|
58
+ apply_to_method(method.to_s, :protected)
59
+ end
60
+
61
+ if @options[:private_methods]
62
+ context.private_instance_methods.each do |method|
63
+ apply_to_method(method.to_s, :private)
64
+ end
65
+ end
66
+ end
67
+
68
+ def apply_to_method method, scope = nil
69
+ filtered_advices = filter_advices advices, method
70
+ return if filtered_advices.empty?
71
+
72
+ logger.debug 'apply-to-method', method
73
+
74
+ scope ||=
75
+ if context.private_instance_methods.include?(method.to_sym)
76
+ :private
77
+ elsif context.protected_instance_methods.include?(method.to_sym)
78
+ :protected
79
+ else
80
+ :public
81
+ end
82
+
83
+ recreate_method method, filtered_advices, scope
84
+ end
85
+
86
+ private
87
+
88
+ def deferred_logic_results logic
89
+ @deferred_logic_results[logic]
90
+ end
91
+
92
+ def get_wrapped_method_of method
93
+ @wrapped_methods[method]
94
+ end
95
+
96
+ # context is where advices will be applied (i.e. where methods are modified), can be different from target
97
+ def context
98
+ return @target if @target.is_a?(Module) and not @options[:class_methods]
99
+
100
+ class << @target
101
+ self
102
+ end
103
+ end
104
+
105
+ def invoke_deferred_logics
106
+ return unless (logics = @aspect.class.send :_deferred_logics_)
107
+
108
+ logics.each do |logic|
109
+ result = logic.apply context, aspect
110
+ if advices.detect {|advice| advice.use_deferred_logic? logic }
111
+ @deferred_logic_results ||= {}
112
+ @deferred_logic_results[logic] = result
113
+ end
114
+ end
115
+ end
116
+
117
+ def define_methods_for_advice_blocks
118
+ advices.each do |advice|
119
+ next if advice.raw?
120
+ next unless advice.advice_block
121
+ context.send :define_method, advice.with_method, advice.advice_block
122
+ context.send :private, advice.with_method
123
+ end
124
+ end
125
+
126
+ def add_to_instances
127
+ return if advices.empty?
128
+
129
+ aspect_instances = context.instance_variable_get(:@aop_instances)
130
+ unless aspect_instances
131
+ aspect_instances = AspectInstances.new
132
+ context.instance_variable_set(:@aop_instances, aspect_instances)
133
+ end
134
+ aspect_instances << self
135
+ end
136
+
137
+ def add_method_hooks
138
+ return if advices.empty?
139
+
140
+ if @options[:class_methods]
141
+ return unless @target.is_a?(Module)
142
+
143
+ eigen_class = class << @target; self; end
144
+ orig_singleton_method_added = @target.method(:singleton_method_added)
145
+
146
+ eigen_class.send :define_method, :singleton_method_added do |method|
147
+ aop_singleton_method_added(method) do
148
+ orig_singleton_method_added.call(method)
149
+ end
150
+ end
151
+ else
152
+ eigen_class = class << @target; self; end
153
+
154
+ if @target.is_a? Module
155
+ orig_method_added = @target.method(:method_added)
156
+ else
157
+ orig_method_added = eigen_class.method(:method_added)
158
+ end
159
+
160
+ eigen_class.send :define_method, :method_added do |method|
161
+ aop_method_added(method) do
162
+ orig_method_added.call(method)
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ def filter_advices advices, method
169
+ advices.select do |advice|
170
+ advice.match?(method, self)
171
+ end
172
+ end
173
+
174
+ def recreate_method method, advices, scope
175
+ context.instance_variable_set(:@aop_creating_method, true)
176
+
177
+ raw_advices = advices.select {|advice| advice.raw? }
178
+
179
+ if raw_advices.size > 0
180
+ raw_advices.each do |advice|
181
+ if @target.is_a? Module and not @options[:class_methods]
182
+ @target.class_exec method, self, &advice.advice_block
183
+ else
184
+ @target.instance_exec method, self, &advice.advice_block
185
+ end
186
+ end
187
+
188
+ return if raw_advices.size == advices.size
189
+ end
190
+
191
+ begin
192
+ @wrapped_methods[method] = context.instance_method(method)
193
+ rescue
194
+ # ignore undefined method error
195
+ if @options[:existing_methods_only]
196
+ logger.log Logging::WARN, 'method-not-found', method
197
+ end
198
+
199
+ return
200
+ end
201
+
202
+ before_advices = advices.select {|advice| advice.before? || advice.before_filter? }
203
+ after_advices = advices.select {|advice| advice.after? }
204
+ around_advices = advices.select {|advice| advice.around? }
205
+
206
+ (around_advices.size - 1).downto(1) do |i|
207
+ advice = around_advices[i]
208
+ recreate_method_with_advices method, [], [], advice
209
+ end
210
+
211
+ recreate_method_with_advices method, before_advices, after_advices, around_advices.first, true
212
+
213
+ context.send scope, method if scope != :public
214
+ ensure
215
+ context.send :remove_instance_variable, :@aop_creating_method
216
+ end
217
+
218
+ def recreate_method_with_advices method, before_advices, after_advices, around_advice, is_outermost = false
219
+ aspect = @aspect
220
+ logger = @logger
221
+ interception = self
222
+ orig_method = get_wrapped_method_of method
223
+
224
+ code = METHOD_TEMPLATE.result(binding)
225
+ logger.debug 'generate-code', method, code
226
+ context.class_eval code, __FILE__, __LINE__ + 4
227
+ end
228
+
229
+ METHOD_TEMPLATE = ERB.new <<-CODE, nil, "%<>"
230
+ % if around_advice
231
+ wrapped_method = instance_method(:<%= method %>)
232
+ % end
233
+
234
+ define_method :<%= method %> do |*args, &block|
235
+ % if logger.debug?
236
+ logger.debug '<%= method %>', 'enter-generated-method'
237
+ % end
238
+
239
+ if aspect.disabled?
240
+ % if logger.debug?
241
+ logger.debug '<%= method %>', 'exit--generated-method'
242
+ % end
243
+
244
+ return orig_method.bind(self).call(*args, &block)
245
+ end
246
+
247
+ % before_advices.each do |advice|
248
+ % if logger.debug?
249
+ logger.debug '<%= method %>', 'before-invoke-advice', '<%= advice.name %>'
250
+ % end
251
+
252
+ % if advice.advice_code
253
+ result = (<%= advice.advice_code %>)
254
+ % else
255
+ result = <%= advice.with_method %> <%
256
+ if advice.options[:interception_arg] %>interception, <% end %><%
257
+ if advice.options[:method_arg] %>'<%= method %>', <% end
258
+ %>*args
259
+ % end
260
+
261
+ % if logger.debug?
262
+ logger.debug '<%= method %>', 'after--invoke-advice', '<%= advice.name %>'
263
+ % end
264
+
265
+ % if advice.before_filter?
266
+ unless result
267
+ % if logger.debug?
268
+ logger.debug '<%= method %>', 'exit-method-due-to-before-filter', '<%= advice.name %>'
269
+ % end
270
+
271
+ return
272
+ end
273
+ % end
274
+ % end
275
+
276
+ % if around_advice
277
+ % if logger.debug?
278
+ logger.debug '<%= method %>', 'before-invoke-advice', '<%= around_advice.name %>'
279
+ % end
280
+
281
+ % if around_advice.advice_code
282
+ result = (<%= around_advice.advice_code.gsub('INVOKE_PROXY', 'wrapped_method.bind(self).call(*args, &block)') %>)
283
+ % else
284
+ % if logger.debug?
285
+ proxy = lambda do |*args, &block|
286
+ logger.debug '<%= method %>', 'before-invoke-proxy'
287
+ res = wrapped_method.bind(self).call *args, &block
288
+ logger.debug '<%= method %>', 'after--invoke-proxy'
289
+ res
290
+ end
291
+ result = <%= around_advice.with_method %> <%
292
+ if around_advice.options[:interception_arg] %>interception, <% end %><%
293
+ if around_advice.options[:method_arg] %>'<%= method %>', <% end
294
+ %>proxy, *args, &block
295
+ % else
296
+ result = <%= around_advice.with_method %> <%
297
+ if around_advice.options[:interception_arg] %>interception, <% end %><%
298
+ if around_advice.options[:method_arg] %>'<%= method %>', <% end
299
+ %>wrapped_method.bind(self), *args, &block
300
+ % end
301
+ % end
302
+
303
+ % if logger.debug?
304
+ logger.debug '<%= method %>', 'after--invoke-advice', '<%= around_advice.name %>'
305
+ % end
306
+ % else
307
+ # Invoke original method
308
+ % if logger.debug?
309
+ logger.debug '<%= method %>', 'before-wrapped-method'
310
+ % end
311
+
312
+ result = orig_method.bind(self).call *args, &block
313
+ % if logger.debug?
314
+ logger.debug '<%= method %>', 'after--wrapped-method'
315
+ % end
316
+ % end
317
+
318
+ % unless after_advices.empty?
319
+ % after_advices.each do |advice|
320
+ % if logger.debug?
321
+ logger.debug '<%= method %>', 'before-invoke-advice', '<%= advice.name %>'
322
+ % end
323
+
324
+ % if advice.advice_code
325
+ result = (<%= advice.advice_code %>)
326
+ % else
327
+ % if advice.options[:result_arg]
328
+ result = <%= advice.with_method %> <%
329
+ if advice.options[:interception_arg] %>interception, <% end %><%
330
+ if advice.options[:method_arg] %>'<%= method %>', <% end %><%
331
+ if advice.options[:result_arg] %>result, <% end
332
+ %>*args
333
+ % else
334
+ <%= advice.with_method %> <%
335
+ if advice.options[:interception_arg] %>interception, <% end %><%
336
+ if advice.options[:method_arg] %>'<%= method %>', <% end
337
+ %>*args
338
+ % end
339
+ % end
340
+
341
+ % if logger.debug?
342
+ logger.debug '<%= method %>', 'after--invoke-advice', '<%= advice.name %>'
343
+ % end
344
+ % end
345
+ % end
346
+
347
+ % if logger.debug?
348
+ logger.debug '<%= method %>', 'exit--generated-method'
349
+ % end
350
+
351
+ result
352
+ end
353
+ CODE
354
+ end
355
+ end
356
+