aspector 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -15,8 +15,6 @@ gem install aspector
15
15
 
16
16
  == Examples
17
17
 
18
- require 'aspector'
19
-
20
18
  class A
21
19
  def test
22
20
  puts 'test'
@@ -25,6 +23,8 @@ gem install aspector
25
23
 
26
24
  ##############################
27
25
 
26
+ require 'aspector'
27
+
28
28
  class TestAspect < Aspector::Base
29
29
  target do
30
30
  def do_this
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.7.0
data/aspector.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{aspector}
8
- s.version = "0.6.0"
8
+ s.version = "0.7.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Guoliang Cao"]
12
- s.date = %q{2011-11-18}
12
+ s.date = %q{2011-12-09}
13
13
  s.description = %q{}
14
14
  s.email = %q{gcao99@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -33,11 +33,14 @@ Gem::Specification.new do |s|
33
33
  "examples/around_example.rb",
34
34
  "examples/aspector_apply_example.rb",
35
35
  "examples/aspector_example.rb",
36
+ "examples/cache_aspect.rb",
36
37
  "lib/aspector.rb",
37
38
  "lib/aspector/advice.rb",
38
39
  "lib/aspector/advice_metadata.rb",
39
- "lib/aspector/aspects.rb",
40
+ "lib/aspector/aspect_instances.rb",
40
41
  "lib/aspector/base.rb",
42
+ "lib/aspector/base_class_methods.rb",
43
+ "lib/aspector/context.rb",
41
44
  "lib/aspector/deferred_logic.rb",
42
45
  "lib/aspector/deferred_option.rb",
43
46
  "lib/aspector/method_matcher.rb",
@@ -0,0 +1,79 @@
1
+ class A
2
+
3
+ def test
4
+ puts 'test'
5
+ 1
6
+ end
7
+
8
+ def test2
9
+ puts 'test2'
10
+ 2
11
+ end
12
+
13
+ end
14
+
15
+ ##############################
16
+
17
+ class SimpleCache
18
+
19
+ @data = {}
20
+
21
+ def self.cache key, ttl
22
+ found = @data[key] # found is like [time, value]
23
+
24
+ if found
25
+ puts "Found in cache: #{key}"
26
+ insertion_time, value = *found
27
+
28
+ return value if Time.now < insertion_time + ttl
29
+
30
+ puts "Expired: #{key}"
31
+ end
32
+
33
+ value = yield
34
+ @data[key] = [Time.now, value]
35
+ value
36
+ end
37
+
38
+ end
39
+
40
+ ##############################
41
+
42
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
43
+
44
+ require 'aspector'
45
+
46
+ class CacheAspect < Aspector::Base
47
+ default :ttl => 60
48
+
49
+ around options[:method], :context_arg => true do |context, &block|
50
+ key = context.method_name
51
+ ttl = context.options[:ttl]
52
+
53
+ SimpleCache.cache key, ttl do
54
+ block.call
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ CacheAspect.apply A, :method => :test, :ttl => 2 # 2 seconds
61
+ CacheAspect.apply A, :method => :test2
62
+
63
+ ##############################
64
+
65
+ a = A.new
66
+
67
+ # Will store value in cache
68
+ a.test
69
+ a.test2
70
+
71
+ # Will get value from cache
72
+ a.test
73
+ a.test2
74
+
75
+ sleep 3
76
+
77
+ a.test # Cache expired
78
+ a.test2 # Cache is still valid
79
+
File without changes
data/lib/aspector/base.rb CHANGED
@@ -3,64 +3,22 @@ require 'erb'
3
3
  module Aspector
4
4
  class Base
5
5
 
6
- METHOD_TEMPLATE = ERB.new <<-CODE
7
- wrapped_method = instance_method(:<%= method %>)
8
-
9
- define_method :<%= method %> do |*args, &block|
10
- result = nil
11
-
12
- # Before advices
13
- <% before_advices.each do |definition| %>
14
- <% if definition.options[:method_name_arg] %>
15
- result = <%= definition.with_method %> '<%= method %>', *args
16
- <% else %>
17
- result = <%= definition.with_method %> *args
18
- <% end %>
6
+ attr :options
19
7
 
20
- return result.value if result.is_a? ::Aspector::ReturnThis
21
- <% if definition.options[:skip_if_false] %>
22
- return unless result
23
- <% end %>
24
- <% end %>
8
+ def initialize target, options = {}
9
+ @target = target
25
10
 
26
- <% if around_advice %>
27
- # around advice
28
- <% if around_advice.options[:method_name_arg] %>
29
- result = <%= around_advice.with_method %> '<%= method %>', *args do |*args|
30
- wrapped_method.bind(self).call *args, &block
31
- end
32
- <% else %>
33
- result = <%= around_advice.with_method %> *args do |*args|
34
- wrapped_method.bind(self).call *args, &block
11
+ default_options = self.class.default_options
12
+ if default_options and not default_options.empty?
13
+ @options = default_options.merge(options)
14
+ else
15
+ @options = options
35
16
  end
36
- <% end %>
37
- <% else %>
38
- # Invoke wrapped method
39
- result = wrapped_method.bind(self).call *args, &block
40
- <% end %>
41
17
 
42
- # After advices
43
- <% after_advices.each do |definition| %>
44
- <% if definition.options[:method_name_arg] and definition.options[:result_arg] %>
45
- result = <%= definition.with_method %> '<%= method %>', result, *args
46
- <% elsif definition.options[:method_name_arg] %>
47
- <%= definition.with_method %> '<%= method %>', *args
48
- <% elsif definition.options[:result_arg] %>
49
- result = <%= definition.with_method %> result, *args
50
- <% else %>
51
- <%= definition.with_method %> *args
52
- <% end %>
53
- <% end %>
54
- result
55
- end
56
- CODE
57
-
58
- attr :options
18
+ # @context is where advices will be applied (i.e. where methods are modified), can be different from target
19
+ @context = get_context
59
20
 
60
- def initialize target, options = {}
61
- @target = target
62
- @options = options.merge(self.class.options)
63
- @context = get_context # Context is where advices will be applied (i.e. where methods are modified)
21
+ after_initialization
64
22
  end
65
23
 
66
24
  def apply
@@ -69,6 +27,7 @@ module Aspector
69
27
  add_to_instances
70
28
  apply_to_methods
71
29
  add_method_hooks
30
+ after_application
72
31
  end
73
32
 
74
33
  def deferred_logic_results logic
@@ -76,31 +35,45 @@ module Aspector
76
35
  end
77
36
 
78
37
  def apply_to_methods
79
- @context.instance_methods.each do |method|
80
- apply_to_method(method)
38
+ @context.public_instance_methods.each do |method|
39
+ apply_to_method(method, :public)
40
+ end
41
+
42
+ @context.protected_instance_methods.each do |method|
43
+ apply_to_method(method, :protected)
81
44
  end
82
45
 
83
46
  if @options[:private_methods]
84
47
  @context.private_instance_methods.each do |method|
85
- apply_to_method(method, true)
48
+ apply_to_method(method, :private)
86
49
  end
87
50
  end
88
51
  end
89
52
 
90
- def apply_to_method method, is_private = false
53
+ def apply_to_method method, scope = nil
91
54
  advices = advices_for_method method
92
55
  return if advices.empty?
93
56
 
94
- recreate_method method, advices, is_private
57
+ scope ||=
58
+ if @context.private_instance_methods.include?(method.to_s)
59
+ :private
60
+ elsif @context.protected_instance_methods.include?(method.to_s)
61
+ :protected
62
+ else
63
+ :public
64
+ end
65
+
66
+ recreate_method method, advices, scope
95
67
  end
96
68
 
97
- def to_hash
98
- {
99
- "type" => self.class.name,
100
- "context" => @context.name,
101
- "options" => @options,
102
- "aspect" => self.class.to_hash
103
- }
69
+ protected
70
+
71
+ # Hook method that runs after an aspect is instantiated
72
+ def after_initialization
73
+ end
74
+
75
+ # Hook method that runs after an aspect is applied
76
+ def after_application
104
77
  end
105
78
 
106
79
  private
@@ -126,6 +99,7 @@ module Aspector
126
99
  self.class.advices.each do |advice|
127
100
  next unless advice.advice_block
128
101
  @context.send :define_method, advice.with_method, advice.advice_block
102
+ @context.send :private, advice.with_method
129
103
  end
130
104
  end
131
105
 
@@ -173,7 +147,7 @@ module Aspector
173
147
  end
174
148
  end
175
149
 
176
- def recreate_method method, advices, is_private
150
+ def recreate_method method, advices, scope
177
151
  @context.instance_variable_set(:@aspector_creating_method, true)
178
152
 
179
153
  before_advices = advices.select {|advice| advice.before? or advice.before_filter? }
@@ -189,7 +163,7 @@ module Aspector
189
163
 
190
164
  recreate_method_with_advices method, before_advices, after_advices, around_advices.first
191
165
 
192
- @context.send :private, method if is_private
166
+ @context.send scope, method if scope != :public
193
167
  ensure
194
168
  @context.instance_variable_set(:@aspector_creating_method, nil)
195
169
  end
@@ -197,99 +171,68 @@ module Aspector
197
171
  def recreate_method_with_advices method, before_advices, after_advices, around_advice
198
172
  code = METHOD_TEMPLATE.result(binding)
199
173
  #puts code
200
- # line no is the actual line no of METHOD_TEMPLATE + 5
201
- @context.class_eval code, __FILE__, 7
174
+ @context.class_eval code, __FILE__, __LINE__ + 4
202
175
  end
203
176
 
204
- class << self
205
-
206
- def advices
207
- @advices ||= []
208
- end
209
-
210
- def options
211
- @options ||= {}
212
- end
213
-
214
- def deferred_logics
215
- @deferred_logics ||= []
216
- end
217
-
218
- def apply target, options = {}
219
- instances = target.instance_variable_get(:@aspector_instances)
220
- return if instances and instances.detect {|instance| instance.is_a?(self) }
221
-
222
- aspect_instance = new(target, options)
223
- aspect_instance.apply
224
- end
225
-
226
- def default options
227
- if @options
228
- @options.merge! options
229
- else
230
- @options = options
231
- end
232
- end
233
-
234
- def before *methods, &block
235
- advices << create_advice(Aspector::AdviceMetadata::BEFORE, self, methods, &block)
236
- end
237
-
238
- def before_filter *methods, &block
239
- advices << create_advice(Aspector::AdviceMetadata::BEFORE_FILTER, self, methods, &block)
240
- end
241
-
242
- def after *methods, &block
243
- advices << create_advice(Aspector::AdviceMetadata::AFTER, self, methods, &block)
244
- end
177
+ METHOD_TEMPLATE = ERB.new <<-CODE
178
+ target = self
179
+ wrapped_method = instance_method(:<%= method %>)
245
180
 
246
- def around *methods, &block
247
- advices << create_advice(Aspector::AdviceMetadata::AROUND, self, methods, &block)
248
- end
181
+ define_method :<%= method %> do |*args, &block|
182
+ result = nil
249
183
 
250
- def target code = nil, &block
251
- logic = DeferredLogic.new(code || block)
252
- deferred_logics << logic
253
- logic
254
- end
184
+ # Before advices
185
+ <% before_advices.each do |advice| %>
186
+ <% if advice.options[:context_arg] %>
187
+ context = Aspector::Context.new(target, <%= self.hash %>, <%= advice.hash %>)
188
+ context.method_name = '<%= method %>'
189
+ result = <%= advice.with_method %> context, *args
190
+ <% else %>
191
+ result = <%= advice.with_method %> *args
192
+ <% end %>
255
193
 
256
- def deferred_option key
257
- DeferredOption.new(key)
258
- end
194
+ return result.value if result.is_a? ::Aspector::ReturnThis
195
+ <% if advice.options[:skip_if_false] %>
196
+ return unless result
197
+ <% end %>
198
+ <% end %>
259
199
 
260
- def to_hash
261
- {
262
- "type" => self.name,
263
- "options" => options,
264
- "advices" => advices.map {|advice| advice.to_s }
265
- }
200
+ <% if around_advice %>
201
+ # around advice
202
+ <% if around_advice.options[:context_arg] %>
203
+ context = Aspector::Context.new(target, <%= self.hash %>, <%= around_advice.hash %>)
204
+ context.method_name = '<%= method %>'
205
+ result = <%= around_advice.with_method %> context, *args do |*args|
206
+ wrapped_method.bind(self).call *args, &block
266
207
  end
267
-
268
- private
269
-
270
- def create_advice meta_data, klass_or_module, *methods, &block
271
- methods.flatten!
272
-
273
- options = meta_data.default_options.clone
274
- options.merge!(methods.pop) if methods.last.is_a? Hash
275
- options.merge!(meta_data.mandatory_options)
276
-
277
- # Convert symbols to strings to avoid inconsistencies
278
- methods.size.times do |i|
279
- methods[i] = methods[i].to_s if methods[i].is_a? Symbol
280
- end
281
-
282
- with_method = methods.pop unless block_given?
283
-
284
- Aspector::Advice.new(self,
285
- meta_data.advice_type,
286
- Aspector::MethodMatcher.new(*methods),
287
- with_method,
288
- options,
289
- &block)
208
+ <% else %>
209
+ result = <%= around_advice.with_method %> *args do |*args|
210
+ wrapped_method.bind(self).call *args, &block
290
211
  end
212
+ <% end %>
213
+ <% else %>
214
+ # Invoke wrapped method
215
+ result = wrapped_method.bind(self).call *args, &block
216
+ <% end %>
291
217
 
218
+ # After advices
219
+ <% after_advices.each do |advice| %>
220
+ <% if advice.options[:context_arg] and advice.options[:result_arg] %>
221
+ context = Aspector::Context.new(target, <%= self.hash %>, <%= advice.hash %>)
222
+ context.method_name = '<%= method %>'
223
+ result = <%= advice.with_method %> context, result, *args
224
+ <% elsif advice.options[:context_arg] %>
225
+ <%= advice.with_method %> context, *args
226
+ <% elsif advice.options[:result_arg] %>
227
+ result = <%= advice.with_method %> result, *args
228
+ <% else %>
229
+ <%= advice.with_method %> *args
230
+ <% end %>
231
+ <% end %>
232
+ result
292
233
  end
234
+ CODE
235
+
293
236
  end
294
237
  end
295
238
 
@@ -0,0 +1,85 @@
1
+ module Aspector
2
+ class Base
3
+ module ClassMethods
4
+ ::Aspector::Base.extend(self)
5
+
6
+ def advices
7
+ @advices ||= []
8
+ end
9
+
10
+ def default_options
11
+ @default_options ||= {}
12
+ end
13
+
14
+ def deferred_logics
15
+ @deferred_logics ||= []
16
+ end
17
+
18
+ def apply target, options = {}
19
+ aspect_instance = new(target, options)
20
+ aspect_instance.apply
21
+ aspect_instance
22
+ end
23
+
24
+ def default options
25
+ if @default_options
26
+ @default_options.merge! options
27
+ else
28
+ @default_options = options
29
+ end
30
+ end
31
+
32
+ def before *methods, &block
33
+ advices << create_advice(Aspector::AdviceMetadata::BEFORE, self, methods, &block)
34
+ end
35
+
36
+ def before_filter *methods, &block
37
+ advices << create_advice(Aspector::AdviceMetadata::BEFORE_FILTER, self, methods, &block)
38
+ end
39
+
40
+ def after *methods, &block
41
+ advices << create_advice(Aspector::AdviceMetadata::AFTER, self, methods, &block)
42
+ end
43
+
44
+ def around *methods, &block
45
+ advices << create_advice(Aspector::AdviceMetadata::AROUND, self, methods, &block)
46
+ end
47
+
48
+ def target code = nil, &block
49
+ logic = DeferredLogic.new(code || block)
50
+ deferred_logics << logic
51
+ logic
52
+ end
53
+
54
+ def options
55
+ DeferredOption.new
56
+ end
57
+
58
+ private
59
+
60
+ def create_advice meta_data, klass_or_module, *methods, &block
61
+ methods.flatten!
62
+
63
+ options = meta_data.default_options.clone
64
+ options.merge!(methods.pop) if methods.last.is_a? Hash
65
+ options.merge!(meta_data.mandatory_options)
66
+
67
+ # Convert symbols to strings to avoid inconsistencies
68
+ methods.size.times do |i|
69
+ methods[i] = methods[i].to_s if methods[i].is_a? Symbol
70
+ end
71
+
72
+ with_method = methods.pop unless block_given?
73
+
74
+ Aspector::Advice.new(self,
75
+ meta_data.advice_type,
76
+ Aspector::MethodMatcher.new(*methods),
77
+ with_method,
78
+ options,
79
+ &block)
80
+ end
81
+
82
+ end
83
+ end
84
+ end
85
+
@@ -0,0 +1,28 @@
1
+ module Aspector
2
+ class Context
3
+
4
+ attr_accessor :method_name
5
+
6
+ def initialize(target, aspect_hash, advice_hash)
7
+ @target = target
8
+ @aspect_hash = aspect_hash
9
+ @advice_hash = advice_hash
10
+ end
11
+
12
+ def aspect
13
+ @aspect ||= @target.instance_variable_get(:@aspector_instances).detect do |aspect|
14
+ aspect.hash == @aspect_hash
15
+ end
16
+ end
17
+
18
+ def advice
19
+ @advice ||= aspect.class.advices.detect { |advice| advice.hash == @advice_hash }
20
+ end
21
+
22
+ def options
23
+ aspect.options
24
+ end
25
+
26
+ end
27
+ end
28
+
@@ -3,8 +3,9 @@ module Aspector
3
3
 
4
4
  attr_reader :key
5
5
 
6
- def initialize key
6
+ def [] key
7
7
  @key = key
8
+ self
8
9
  end
9
10
 
10
11
  end
@@ -2,6 +2,8 @@ module Aspector
2
2
  module ModuleExtension
3
3
  Module.send :include, self
4
4
 
5
+ private
6
+
5
7
  def method_added_aspector method
6
8
  return (block_given? and yield) if
7
9
  @aspector_creating_method or
data/lib/aspector.rb CHANGED
@@ -2,12 +2,14 @@ require 'aspector/object_extension'
2
2
  require 'aspector/module_extension'
3
3
 
4
4
  require 'aspector/base'
5
+ require 'aspector/base_class_methods'
5
6
  require 'aspector/advice'
6
7
  require 'aspector/advice_metadata'
7
8
  require 'aspector/method_matcher'
8
9
  require 'aspector/deferred_logic'
9
10
  require 'aspector/deferred_option'
11
+ require 'aspector/context'
10
12
 
11
- require 'aspector/aspects'
13
+ require 'aspector/aspect_instances'
12
14
 
13
15
  require 'aspector/return_this'
@@ -26,6 +26,56 @@ describe "After advices" do
26
26
  obj.value.should == %w"test do_this"
27
27
  end
28
28
 
29
+ it "context_arg" do
30
+ klass = Class.new do
31
+ def value
32
+ @value ||= []
33
+ end
34
+
35
+ def test
36
+ value << "test"
37
+ end
38
+
39
+ def do_this context, result
40
+ value << "do_this"
41
+ result
42
+ end
43
+ end
44
+
45
+ aspector(klass) do
46
+ after :test, :do_this, :context_arg => true
47
+ end
48
+
49
+ obj = klass.new
50
+ obj.test
51
+ obj.value.should == %w"test do_this"
52
+ end
53
+
54
+ it "result_arg set to false" do
55
+ klass = Class.new do
56
+ def value
57
+ @value ||= []
58
+ end
59
+
60
+ def test
61
+ value << "test"
62
+ 'test'
63
+ end
64
+
65
+ def do_this
66
+ value << "do_this"
67
+ end
68
+ end
69
+
70
+ aspector(klass) do
71
+ after :test, :do_this, :result_arg => false
72
+ end
73
+
74
+ obj = klass.new
75
+ obj.test.should == 'test'
76
+ obj.value.should == %w"test do_this"
77
+ end
78
+
29
79
  it "logic in block" do
30
80
  klass = Class.new do
31
81
  def value
@@ -1,6 +1,6 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
- describe "Aspector::Aspect" do
3
+ describe "Miscellaneous" do
4
4
  it "#apply" do
5
5
  klass = Class.new do
6
6
  def value
@@ -6,7 +6,7 @@ describe "Aspector::Base" do
6
6
  default :test => 'value'
7
7
  end
8
8
 
9
- aspect.options[:test].should == 'value'
9
+ aspect.default_options[:test].should == 'value'
10
10
  end
11
11
 
12
12
  it "deferred_option" do
@@ -21,7 +21,7 @@ describe "Aspector::Base" do
21
21
  end
22
22
 
23
23
  aspect = Aspector do
24
- before deferred_option(:methods) do
24
+ before options[:methods] do
25
25
  value << "do_this"
26
26
  end
27
27
  end
@@ -85,7 +85,7 @@ describe "Aspector" do
85
85
  obj.value.should == %w"before_test test"
86
86
  end
87
87
 
88
- it "should apply only once if called multiple times" do
88
+ it "applied multiple times" do
89
89
  klass = Class.new do
90
90
  def value
91
91
  @value ||= []
@@ -105,7 +105,7 @@ describe "Aspector" do
105
105
 
106
106
  obj = klass.new
107
107
  obj.test
108
- obj.value.should == %w"before_test test"
108
+ obj.value.should == %w"before_test before_test test"
109
109
  end
110
110
 
111
111
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aspector
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 3
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 6
8
+ - 7
9
9
  - 0
10
- version: 0.6.0
10
+ version: 0.7.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Guoliang Cao
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-11-18 00:00:00 -05:00
18
+ date: 2011-12-09 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -265,11 +265,14 @@ files:
265
265
  - examples/around_example.rb
266
266
  - examples/aspector_apply_example.rb
267
267
  - examples/aspector_example.rb
268
+ - examples/cache_aspect.rb
268
269
  - lib/aspector.rb
269
270
  - lib/aspector/advice.rb
270
271
  - lib/aspector/advice_metadata.rb
271
- - lib/aspector/aspects.rb
272
+ - lib/aspector/aspect_instances.rb
272
273
  - lib/aspector/base.rb
274
+ - lib/aspector/base_class_methods.rb
275
+ - lib/aspector/context.rb
273
276
  - lib/aspector/deferred_logic.rb
274
277
  - lib/aspector/deferred_option.rb
275
278
  - lib/aspector/method_matcher.rb