aspect4r 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/LICENSE +20 -0
  4. data/NOTES.rdoc +42 -0
  5. data/README.rdoc +70 -0
  6. data/Rakefile +45 -0
  7. data/VERSION +1 -0
  8. data/aspect4r.gemspec +108 -0
  9. data/examples/after_example.rb +30 -0
  10. data/examples/around_example.rb +29 -0
  11. data/examples/before_example.rb +62 -0
  12. data/examples/combined_example.rb +39 -0
  13. data/lib/aspect4r/after.rb +29 -0
  14. data/lib/aspect4r/around.rb +29 -0
  15. data/lib/aspect4r/base.rb +32 -0
  16. data/lib/aspect4r/before.rb +37 -0
  17. data/lib/aspect4r/classic.rb +12 -0
  18. data/lib/aspect4r/errors.rb +5 -0
  19. data/lib/aspect4r/extensions/class_extension.rb +20 -0
  20. data/lib/aspect4r/extensions/module_extension.rb +30 -0
  21. data/lib/aspect4r/helper.rb +171 -0
  22. data/lib/aspect4r/model/advice.rb +29 -0
  23. data/lib/aspect4r/model/advice_metadata.rb +27 -0
  24. data/lib/aspect4r/model/advices_for_method.rb +45 -0
  25. data/lib/aspect4r/model/aspect_data.rb +19 -0
  26. data/lib/aspect4r/return_this.rb +9 -0
  27. data/lib/aspect4r.rb +10 -0
  28. data/spec/aspect4r/after_spec.rb +121 -0
  29. data/spec/aspect4r/around_spec.rb +128 -0
  30. data/spec/aspect4r/before_spec.rb +148 -0
  31. data/spec/aspect4r/class_inheritance_spec.rb +254 -0
  32. data/spec/aspect4r/classic_spec.rb +44 -0
  33. data/spec/aspect4r/helper_spec.rb +117 -0
  34. data/spec/aspect4r/inheritance_inclusion_combined_spec.rb +98 -0
  35. data/spec/aspect4r/module_inclusion_spec.rb +208 -0
  36. data/spec/aspect4r/super_in_method_spec.rb +118 -0
  37. data/spec/aspect4r_spec.rb +295 -0
  38. data/spec/spec.opts +1 -0
  39. data/spec/spec_helper.rb +11 -0
  40. data/test/after_test.rb +30 -0
  41. data/test/around_test.rb +28 -0
  42. data/test/before_test.rb +28 -0
  43. data/test/combined_test.rb +40 -0
  44. data/test/method_invocation_test.rb +28 -0
  45. data/test/test_helper.rb +9 -0
  46. metadata +147 -0
@@ -0,0 +1,171 @@
1
+ require 'erb'
2
+
3
+ module Aspect4r
4
+ module Helper
5
+ def self.find_available_method_name klass, method_name_prefix
6
+ 0.upto(10000) do |i|
7
+ m = "#{method_name_prefix}#{i}_#{klass.hash}"
8
+ return m unless klass.private_instance_methods(false).include?(m)
9
+ end
10
+ end
11
+
12
+ def self.creating_method?
13
+ @creating_method
14
+ end
15
+
16
+ # Store original method in aspect data and refer to it whenever recreating method
17
+ def self.process_advice meta_data, klass_or_module, *methods, &block
18
+ methods.flatten!
19
+
20
+ options = meta_data.default_options.clone
21
+ options.merge!(methods.pop) if methods.last.is_a? Hash
22
+ options.merge!(meta_data.mandatory_options)
23
+
24
+ if block_given?
25
+ with_method = find_available_method_name klass_or_module, meta_data.with_method_prefix
26
+ klass_or_module.send :define_method, with_method, &block
27
+ klass_or_module.send :private, with_method
28
+ else
29
+ with_method = methods.pop
30
+ end
31
+
32
+ methods.each do |method|
33
+ method = method.to_sym
34
+ klass_or_module.a4r_data.methods_with_advices << method
35
+
36
+ aspect = klass_or_module.a4r_data[method] ||= Aspect4r::Model::AdvicesForMethod.new(method)
37
+ aspect.add Aspect4r::Model::Advice.new(meta_data.advice_type, with_method, options)
38
+
39
+ if not aspect.wrapped_method and klass_or_module.instance_methods.include?(method.to_s)
40
+ aspect.wrapped_method = klass_or_module.instance_method(method)
41
+ end
42
+
43
+ create_method klass_or_module, method if aspect.wrapped_method
44
+ end
45
+ end
46
+
47
+ # method - target method
48
+ def self.create_method klass, method
49
+ @creating_method = true
50
+
51
+ aspect = klass.a4r_data[method.to_sym]
52
+
53
+ if aspect.nil? or aspect.empty?
54
+ # There is no aspect defined.
55
+ @creating_method = nil
56
+ return
57
+ end
58
+
59
+ grouped_advices = []
60
+ inner_most = true
61
+
62
+ aspect.advices.each do |advice|
63
+ if advice.around? and not grouped_advices.empty?
64
+ # wrap up advices before current advice
65
+ create_method_for_before_after_advices klass, method, grouped_advices, inner_most
66
+
67
+ inner_most = false
68
+
69
+ grouped_advices = []
70
+ end
71
+
72
+ # handle current advice
73
+ if advice.around?
74
+ create_method_for_around_advice klass, method, advice, inner_most
75
+ inner_most = false
76
+ else
77
+ grouped_advices << advice
78
+ end
79
+ end
80
+
81
+ # create wrap method for before/after advices which are not wrapped inside around advice.
82
+ unless grouped_advices.empty?
83
+ create_method_for_before_after_advices klass, method, grouped_advices, inner_most unless grouped_advices.empty?
84
+ end
85
+
86
+ @creating_method = nil
87
+ end
88
+
89
+ # method
90
+ # advice
91
+ WRAP_METHOD_TEMPLATE = ERB.new <<-CODE, nil, '<>'
92
+ <% if inner_most %>
93
+ wrapped_method = a4r_data[:<%= method %>].wrapped_method
94
+ <% else %>
95
+ wrapped_method = instance_method(:<%= method %>)
96
+ <% end %>
97
+
98
+ define_method :<%= method %> do |*args|
99
+ <% if advice.options[:method_name_arg] %>
100
+ result = <%= advice.with_method %> '<%= method %>', wrapped_method, *args
101
+ <% else %>
102
+ result = <%= advice.with_method %> wrapped_method, *args
103
+ <% end %>
104
+
105
+ result
106
+ end
107
+ CODE
108
+
109
+ def self.create_method_for_around_advice klass, method, advice, inner_most
110
+ code = WRAP_METHOD_TEMPLATE.result(binding)
111
+ # puts code
112
+ klass.class_eval code, __FILE__
113
+ end
114
+
115
+ # method
116
+ # before_advices
117
+ # after_advices
118
+ METHOD_TEMPLATE = ERB.new <<-CODE, nil, '<>'
119
+ <% if inner_most %>
120
+ wrapped_method = a4r_data[:<%= method %>].wrapped_method
121
+ <% else %>
122
+ wrapped_method = instance_method(:<%= method %>)
123
+ <% end %>
124
+
125
+ define_method :<%= method %> do |*args|
126
+ result = nil
127
+
128
+ # Before advices
129
+ <% before_advices.each do |definition| %>
130
+ <% if definition.options[:method_name_arg] %>
131
+ result = <%= definition.with_method %> '<%= method %>', *args
132
+ <% else %>
133
+ result = <%= definition.with_method %> *args
134
+ <% end %>
135
+
136
+ return result.value if result.is_a? ReturnThis
137
+ <% if definition.options[:skip_if_false] %>
138
+ return unless result
139
+ <% end %>
140
+ <% end %>
141
+
142
+ # Call wrapped method
143
+ result = wrapped_method.bind(self).call *args
144
+
145
+ # After advices
146
+ <% after_advices.each do |definition| %>
147
+ <% if definition.options[:method_name_arg] and definition.options[:result_arg] %>
148
+ result = <%= definition.with_method %> '<%= method %>', result, *args
149
+ <% elsif definition.options[:method_name_arg] %>
150
+ <%= definition.with_method %> '<%= method %>', *args
151
+ <% elsif definition.options[:result_arg] %>
152
+ result = <%= definition.with_method %> result, *args
153
+ <% else %>
154
+ <%= definition.with_method %> *args
155
+ <% end %>
156
+ <% end %>
157
+
158
+ result
159
+ end
160
+ CODE
161
+
162
+ def self.create_method_for_before_after_advices klass, method, advices, inner_most
163
+ before_advices = advices.select {|advice| advice.before? }
164
+ after_advices = advices.select {|advice| advice.after? }
165
+
166
+ code = METHOD_TEMPLATE.result(binding)
167
+ # puts code
168
+ klass.class_eval code, __FILE__
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,29 @@
1
+ module Aspect4r
2
+ module Model
3
+ class Advice
4
+ BEFORE = 1
5
+ AFTER = 2
6
+ AROUND = 3
7
+
8
+ attr_accessor :type, :with_method, :options
9
+
10
+ def initialize type, with_method, options = {}
11
+ @type = type
12
+ @with_method = with_method
13
+ @options = options
14
+ end
15
+
16
+ def name
17
+ options[:name] || with_method
18
+ end
19
+
20
+ %w(before after around).each do |aspect|
21
+ class_eval <<-CODE
22
+ def #{aspect}?
23
+ type == #{aspect.upcase}
24
+ end
25
+ CODE
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ module Aspect4r
2
+ module Model
3
+ class AdviceMetadata
4
+ attr_reader :advice_type, :default_options, :mandatory_options
5
+
6
+ def initialize advice_type, default_options = {}, mandatory_options = {}
7
+ @advice_type = advice_type
8
+ @default_options = default_options || {}
9
+ @mandatory_options = mandatory_options || {}
10
+ end
11
+
12
+ def with_method_prefix
13
+ case advice_type
14
+ when Aspect4r::Model::Advice::BEFORE then "a4r_before_"
15
+ when Aspect4r::Model::Advice::AFTER then "a4r_after_"
16
+ when Aspect4r::Model::Advice::AROUND then "a4r_around_"
17
+ else raise "Aspect4r internal error."
18
+ end
19
+ end
20
+
21
+ BEFORE = new Aspect4r::Model::Advice::BEFORE, nil, :skip_if_false => false
22
+ BEFORE_FILTER = new Aspect4r::Model::Advice::BEFORE, nil, :skip_if_false => true
23
+ AFTER = new Aspect4r::Model::Advice::AFTER, :result_arg => true
24
+ AROUND = new Aspect4r::Model::Advice::AROUND
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ module Aspect4r
2
+ module Model
3
+ class AdvicesForMethod
4
+ attr_reader :method
5
+ attr_accessor :wrapped_method
6
+
7
+ def initialize method
8
+ @method = method
9
+ end
10
+
11
+ def advices
12
+ @advices ||= []
13
+ end
14
+
15
+ def add new_advice
16
+ advices << new_advice unless include?(new_advice)
17
+ end
18
+
19
+ def empty?
20
+ @advices.nil? or @advices.empty?
21
+ end
22
+
23
+ def include? new_advice
24
+ advices.detect { |advice| advice.name == new_advice.name }
25
+ end
26
+
27
+ def merge! another
28
+ unless another.nil? or another.empty?
29
+ another.advices.each do |advice|
30
+ advices.push advice unless include?(advice)
31
+ end
32
+ end
33
+
34
+ self
35
+ end
36
+
37
+ def clone
38
+ o = self.class.new(method)
39
+ o.advices.push *advices unless empty?
40
+
41
+ o
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ require 'set'
2
+
3
+ module Aspect4r
4
+ module Model
5
+ class AspectData < Hash
6
+ def initialize *args
7
+ super
8
+ end
9
+
10
+ def advices
11
+ @advices ||= {}
12
+ end
13
+
14
+ def methods_with_advices
15
+ @methods_with_advices ||= Set.new
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Aspect4r
2
+ class ReturnThis
3
+ attr :value
4
+
5
+ def initialize value
6
+ @value = value
7
+ end
8
+ end
9
+ end
data/lib/aspect4r.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'aspect4r/before'
2
+ require 'aspect4r/after'
3
+ require 'aspect4r/around'
4
+
5
+ module Aspect4r
6
+ def self.included(base)
7
+ base.send(:include, Base::InstanceMethods)
8
+ base.extend Base::ClassMethods, Before::ClassMethods, After::ClassMethods, Around::ClassMethods
9
+ end
10
+ end
@@ -0,0 +1,121 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Aspect4r::After do
4
+ before do
5
+ @klass = Class.new do
6
+ include Aspect4r::After
7
+
8
+ attr_accessor :value
9
+
10
+ def initialize
11
+ @value = 'init'
12
+ end
13
+
14
+ def test value
15
+ @value = value
16
+ 'test_return'
17
+ end
18
+ end
19
+ end
20
+
21
+ it "should run advice method after original method" do
22
+ @klass.class_eval do
23
+ def do_something result, value
24
+ 'do_something'
25
+ end
26
+
27
+ after :test, :do_something
28
+ end
29
+
30
+ o = @klass.new
31
+ o.test('something').should == 'do_something'
32
+ end
33
+
34
+ it "should run advice block after original method" do
35
+ i = 100
36
+
37
+ @klass.class_eval do
38
+ after :test do |result, value|
39
+ i = 200
40
+ 'after_test'
41
+ end
42
+ end
43
+
44
+ o = @klass.new
45
+ o.test('something').should == 'after_test'
46
+
47
+ o.value.should == 'something'
48
+
49
+ i.should == 200
50
+ end
51
+
52
+ it "should flatten arguments" do
53
+ i = 100
54
+
55
+ @klass.class_eval do
56
+ after [:test] do |result, value|
57
+ i = 200
58
+ end
59
+ end
60
+
61
+ o = @klass.new
62
+ o.test('something')
63
+
64
+ i.should == 200
65
+ end
66
+
67
+ it "should have access to instance variables inside advice block" do
68
+ @klass.class_eval do
69
+ after :test do |result, value|
70
+ @var = 1
71
+ end
72
+ end
73
+
74
+ o = @klass.new
75
+ o.test('something')
76
+
77
+ o.instance_variable_get(:@var).should == 1
78
+ end
79
+
80
+ it "should pass method name to advice block(or method) as first arg if method_name_arg is true" do
81
+ s = nil
82
+
83
+ @klass.class_eval do
84
+ after :test, :method_name_arg => true do |method, result, value|
85
+ s = method
86
+ result
87
+ end
88
+ end
89
+
90
+ o = @klass.new
91
+ o.test('something')
92
+
93
+ s.should == 'test'
94
+ end
95
+
96
+ it "should pass result to advice method(or block) and return modified result" do
97
+ @klass.class_eval do
98
+ def do_something result, value
99
+ result + ' enhanced'
100
+ end
101
+
102
+ after :test, :do_something
103
+ end
104
+
105
+ o = @klass.new
106
+ o.test('something').should == 'test_return enhanced'
107
+ end
108
+
109
+ it "should not pass result and not change result if result_arg is set to false" do
110
+ @klass.class_eval do
111
+ def do_something value
112
+ 'do_something'
113
+ end
114
+
115
+ after :test, :do_something, :result_arg => false
116
+ end
117
+
118
+ o = @klass.new
119
+ o.test('something').should == 'test_return'
120
+ end
121
+ end
@@ -0,0 +1,128 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Aspect4r::Around do
4
+ before do
5
+ @klass = Class.new do
6
+ include Aspect4r::Around
7
+
8
+ attr_accessor :value
9
+
10
+ def initialize
11
+ @value = 'init'
12
+ end
13
+
14
+ def test value
15
+ @value = value
16
+ 'test_return'
17
+ end
18
+ end
19
+ end
20
+
21
+ it "should run advice method instead of original method" do
22
+ @klass.class_eval do
23
+ def do_something orig, value
24
+ raise 'error'
25
+ end
26
+
27
+ around :test, :do_something
28
+ end
29
+
30
+ o = @klass.new
31
+ lambda { o.test('something') }.should raise_error
32
+ end
33
+
34
+ it "should run advice block instead of original method" do
35
+ i = 100
36
+
37
+ @klass.class_eval do
38
+ around :test do |orig, value|
39
+ i = 200
40
+ 'around_block_return'
41
+ end
42
+ end
43
+
44
+ o = @klass.new
45
+ o.test('something').should == 'around_block_return'
46
+
47
+ o.value.should == 'init'
48
+
49
+ i.should == 200
50
+ end
51
+
52
+ it "should flatten arguments" do
53
+ i = 100
54
+
55
+ @klass.class_eval do
56
+ around [:test] do |orig, value|
57
+ i = 200
58
+ end
59
+ end
60
+
61
+ o = @klass.new
62
+ o.test('something')
63
+
64
+ i.should == 200
65
+ end
66
+
67
+ it "should have access to instance variables inside advice block" do
68
+ @klass.class_eval do
69
+ around :test do |orig, value|
70
+ @var = 1
71
+ end
72
+ end
73
+
74
+ o = @klass.new
75
+ o.test('something')
76
+
77
+ o.instance_variable_get(:@var).should == 1
78
+ end
79
+
80
+ it "should be able to invoke original method from advice block" do
81
+ i = 100
82
+
83
+ @klass.class_eval do
84
+ around :test do |proxy, value|
85
+ i = 200
86
+ a4r_invoke proxy, value
87
+ end
88
+ end
89
+
90
+ o = @klass.new
91
+ o.test('something').should == 'test_return'
92
+
93
+ o.value.should == 'something'
94
+
95
+ i.should == 200
96
+ end
97
+
98
+ it "should be able to invoke original method from advice method" do
99
+ @klass.class_eval do
100
+ def do_something proxy, value
101
+ a4r_invoke proxy, value
102
+ end
103
+
104
+ around :test, :do_something
105
+ end
106
+
107
+ o = @klass.new
108
+ o.test('something').should == 'test_return'
109
+
110
+ o.value.should == 'something'
111
+ end
112
+
113
+ it "should pass method name to advice block(or method) as first arg if method_name_arg is true" do
114
+ s = nil
115
+
116
+ @klass.class_eval do
117
+ around :test, :method_name_arg => true do |method, proxy, value|
118
+ s = method
119
+ a4r_invoke proxy, value
120
+ end
121
+ end
122
+
123
+ o = @klass.new
124
+ o.test('something')
125
+
126
+ s.should == 'test'
127
+ end
128
+ end
@@ -0,0 +1,148 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Aspect4r::Before do
4
+ before do
5
+ @klass = Class.new do
6
+ include Aspect4r::Before
7
+
8
+ attr :value
9
+
10
+ def initialize
11
+ @value = 'init'
12
+ end
13
+
14
+ def test value
15
+ @value = value
16
+ end
17
+ end
18
+ end
19
+
20
+ it "should run advice method before original method" do
21
+ @klass.class_eval do
22
+ def do_something value
23
+ raise 'error'
24
+ end
25
+
26
+ before :test, :do_something
27
+ end
28
+
29
+ o = @klass.new
30
+ lambda { o.test('something') }.should raise_error('error')
31
+ end
32
+
33
+ it "should run advice block before original method" do
34
+ i = 100
35
+
36
+ @klass.class_eval do
37
+ before :test do |value|
38
+ i = 200
39
+ end
40
+ end
41
+
42
+ o = @klass.new
43
+ o.test('something').should == 'something'
44
+
45
+ o.value.should == 'something'
46
+
47
+ i.should == 200
48
+ end
49
+
50
+ it "should flatten arguments" do
51
+ i = 100
52
+
53
+ @klass.class_eval do
54
+ before [:test] do |value|
55
+ i = 200
56
+ end
57
+ end
58
+
59
+ o = @klass.new
60
+ o.test('something').should == 'something'
61
+
62
+ o.value.should == 'something'
63
+
64
+ i.should == 200
65
+ end
66
+
67
+ it "should have access to instance variables inside advice block" do
68
+ @klass.class_eval do
69
+ before :test do |value|
70
+ @var = 1
71
+ end
72
+ end
73
+
74
+ o = @klass.new
75
+ o.test('something')
76
+
77
+ o.instance_variable_get(:@var).should == 1
78
+ end
79
+
80
+ it "should not skip method if before advice returned false" do
81
+ @klass.class_eval do
82
+ before :test do |value|
83
+ false
84
+ end
85
+ end
86
+
87
+ o = @klass.new
88
+ o.test('something')
89
+
90
+ o.value.should == 'something'
91
+ end
92
+
93
+ it "should pass method name as first arg to before advice block(or method) if method_name_arg is true" do
94
+ s = nil
95
+
96
+ @klass.class_eval do
97
+ before :test, :method_name_arg => true do |method, value|
98
+ s = method
99
+ end
100
+ end
101
+
102
+ o = @klass.new
103
+ o.test('something')
104
+
105
+ s.should == 'test'
106
+ end
107
+
108
+ it "should skip original method if before advice returned instance of ReturnThis" do
109
+ @klass.class_eval do
110
+ def do_something value
111
+ Aspect4r::ReturnThis.new('do_something')
112
+ end
113
+
114
+ before :test, :do_something
115
+ end
116
+
117
+ o = @klass.new
118
+ o.test('something').should == 'do_something'
119
+
120
+ o.value.should == 'init'
121
+ end
122
+
123
+ it "should skip original method if before_filter advice returned false" do
124
+ @klass.class_eval do
125
+ before_filter :test do
126
+ false
127
+ end
128
+ end
129
+
130
+ o = @klass.new
131
+ o.test('something').should be_false
132
+
133
+ o.value.should == 'init'
134
+ end
135
+
136
+ it "should not skip original method if before_filter did not return false or nil" do
137
+ @klass.class_eval do
138
+ before_filter :test do
139
+ true
140
+ end
141
+ end
142
+
143
+ o = @klass.new
144
+ o.test('something')
145
+
146
+ o.value.should == 'something'
147
+ end
148
+ end