aspect4r 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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