aop 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 265bbfbbde5fedca853374f381977f960186c174
4
- data.tar.gz: 5ba07e066d01dfb9647f8294f29b1508604fe2ea
3
+ metadata.gz: ab47f56fd5a02a5de555ceb2ace3ffeed0300db0
4
+ data.tar.gz: 8657d57606125d660e8cbbd544054ddfb7988381
5
5
  SHA512:
6
- metadata.gz: 4e762bcad98bbbf72d5f867d22bb20e1add43d02a66e0f63f1da21c1804b335b539bee2eea2c2956c0a7900153931285052a4ee21413bc78e620575c0caefb79
7
- data.tar.gz: aa177e07c23669b04a02ac5de5e4ca9bedb2dacc7e92dd8124419b63fd5d5924fa0d236fee9dc01471ec32c8a3454e20cb19f59eb1c9e3b8c77cf1cb969092df
6
+ metadata.gz: fcfc3efd8608afcc4890a712d7b57468ef795b22e4dae6405827105ab95a8c84c1f4c62f0f2c50855a1cbd5508504b263e7d0ca203b571db1661783730e5e06e
7
+ data.tar.gz: 77bef4440f0080fda36afd8399c5cdebf9502b0aebe9db42c31353e36d051cf7c1b1d7f6bbd704d1143584ea4e749483bb5dd8469fe015e251c4603d9c1e1935
data/Gemfile CHANGED
@@ -5,4 +5,9 @@ gemspec
5
5
 
6
6
  group :test do
7
7
  gem 'rspec'
8
+ end
9
+
10
+ group :development do
11
+ gem "method_profiler"
12
+ gem "ruby-prof"
8
13
  end
data/README.md CHANGED
@@ -86,6 +86,26 @@ module Analytics
86
86
  end
87
87
  ```
88
88
 
89
+ ### Handling missed pointcuts
90
+
91
+ When pointcut is gone, for example when method or class gets renamed, it is a potential bug, because some code will not be run. This library tackles this problem by failing hard when pointcut can not be found.
92
+
93
+ Example:
94
+
95
+ ```ruby
96
+ Aop["Admin#sign_in:after"].advice do |target, *args, &blk|
97
+ # .. do something ..
98
+ end
99
+ ```
100
+
101
+ Then somebody renames `Admin#sign_in` to `Admin#logout`, and when you run the code you will get:
102
+
103
+ ```
104
+ Aop::PointcutNotFound: Unable to find pointcut Admin#sign_in
105
+ Reason: #<NameError: undefined method `sign_in' for class `Admin'>
106
+ .. backtrace ..
107
+ ```
108
+
89
109
  ## Contributing
90
110
 
91
111
  1. Fork it ( https://github.com/waterlink/aop/fork )
@@ -0,0 +1,56 @@
1
+ require "aop"
2
+ require "benchmark"
3
+ require "method_profiler"
4
+
5
+ class Example
6
+ def normal_add(a, b)
7
+ a + b
8
+ end
9
+
10
+ def heavy_add(a, b)
11
+ a + b
12
+ end
13
+ end
14
+
15
+ Aop["Example#heavy_add:before"].advice do |example, a, b|
16
+ # do nothing
17
+ nil
18
+ end
19
+
20
+ def benchmark
21
+ example = Example.new
22
+
23
+ Benchmark.bm 30 do |x|
24
+ x.report 'normal add' do
25
+ 1000000.times do
26
+ example.normal_add(rand(1000), rand(1000))
27
+ end
28
+ end
29
+
30
+ x.report 'add with :before' do
31
+ 1000000.times do
32
+ example.heavy_add(rand(1000), rand(1000))
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def profile
39
+ example = Example.new
40
+
41
+ observers = []
42
+ observers << MethodProfiler.observe(Example)
43
+ observers << MethodProfiler.observe(Aop)
44
+ observers << MethodProfiler.observe(Aop::Pointcut)
45
+ observers << MethodProfiler.observe(Aop::Pointcut::MethodReference)
46
+ observers << MethodProfiler.observe(Aop::Pointcut::MethodReference::Singleton)
47
+
48
+ 10000.times do
49
+ example.heavy_add(rand(1000), rand(1000))
50
+ end
51
+
52
+ observers.each { |o| puts o.report }
53
+ end
54
+
55
+ benchmark
56
+ profile
@@ -5,13 +5,32 @@ module Aop
5
5
  Pointcut.new(pointcut_spec)
6
6
  end
7
7
 
8
+ class PointcutNotFound < StandardError
9
+ attr_reader :original_error
10
+
11
+ def initialize(pointcut_spec, original_error)
12
+ super("Unable to find pointcut #{pointcut_spec}")
13
+ @original_error = original_error
14
+ end
15
+
16
+ def to_s
17
+ "#{super}\n\tReason: #{original_error.inspect}"
18
+ end
19
+ end
20
+
8
21
  class Pointcut
9
22
  def initialize(spec)
10
23
  @spec = spec
11
24
 
12
25
  @class_spec = spec.scan(/^[^#\.]+/).first || ""
13
26
  @class_names = @class_spec.split(",")
14
- @classes = @class_names.map { |name| Object.const_get(name) }
27
+ @classes = @class_names.map do |name|
28
+ begin
29
+ Object.const_get(name)
30
+ rescue NameError => err
31
+ raise PointcutNotFound.new(spec, err)
32
+ end
33
+ end
15
34
 
16
35
  @method_spec = spec.scan(/[#\.][^,#\.:]+/)
17
36
  @methods = @method_spec.map { |m| MethodReference.from(m) }
@@ -92,6 +111,8 @@ module Aop
92
111
  alias_method(new_name, name)
93
112
  define_method(name, &with)
94
113
  end
114
+ rescue NameError => err
115
+ raise PointcutNotFound.new(method_spec(target), err)
95
116
  end
96
117
 
97
118
  def call(target, *args, &blk)
@@ -100,15 +121,31 @@ module Aop
100
121
 
101
122
  private
102
123
 
124
+ def method_spec(target)
125
+ "#{target_name(target)}#{method_notation}#{@name}"
126
+ end
127
+
128
+ def method_notation
129
+ "#"
130
+ end
131
+
103
132
  def alias_target(target)
104
133
  target
105
134
  end
106
135
 
136
+ def target_name(target)
137
+ target.name || target.inspect
138
+ end
139
+
107
140
  def alias_name
108
- @_alias_name ||= "__aop_#{SecureRandom.hex(10)}"
141
+ @_alias_name ||= :"__aop_#{SecureRandom.hex(10)}"
109
142
  end
110
143
 
111
144
  class Singleton < self
145
+ def method_notation
146
+ "."
147
+ end
148
+
112
149
  def alias_target(target)
113
150
  class << target; self; end
114
151
  end
@@ -1,3 +1,3 @@
1
1
  module Aop
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -30,6 +30,74 @@ RSpec.describe "Advanced advices" do
30
30
  end
31
31
  end
32
32
 
33
+ describe "missed pointcuts" do
34
+ describe "missed class" do
35
+ let(:advice) {
36
+ Aop["NonexistentKlass#helloworld,.greeting:before"].advice do |target, *args, &blk|
37
+ :do_something
38
+ end
39
+ }
40
+
41
+ it "fails with proper message" do
42
+ expect { advice }.to raise_error(
43
+ Aop::PointcutNotFound,
44
+ /Unable to find pointcut NonexistentKlass#helloworld,.greeting/
45
+ )
46
+ end
47
+
48
+ it "declares proper reason" do
49
+ expect { advice }.to raise_error(
50
+ Aop::PointcutNotFound,
51
+ /Reason: #<NameError: (Missing or |)uninitialized constant(:|) NonexistentKlass>/
52
+ )
53
+ end
54
+ end
55
+
56
+ describe "missed instance method" do
57
+ let(:advice) {
58
+ Aop["BankAccount.transfer,#greeting:before"].advice do |target, *args, &blk|
59
+ :do_something
60
+ end
61
+ }
62
+
63
+ it "fails with proper message" do
64
+ expect { advice }.to raise_error(
65
+ Aop::PointcutNotFound,
66
+ /Unable to find pointcut BankAccount#greeting/
67
+ )
68
+ end
69
+
70
+ it "declares proper reason" do
71
+ expect { advice }.to raise_error(
72
+ Aop::PointcutNotFound,
73
+ /Reason: #<NameError: undefined method [`']greeting' for class [`']BankAccount'>/
74
+ )
75
+ end
76
+ end
77
+
78
+ describe "missed class method" do
79
+ let(:advice) {
80
+ Aop["BankAccount#transfer,.greeting:before"].advice do |target, *args, &blk|
81
+ :do_something
82
+ end
83
+ }
84
+
85
+ it "fails with proper message" do
86
+ expect { advice }.to raise_error(
87
+ Aop::PointcutNotFound,
88
+ /Unable to find pointcut BankAccount.greeting/
89
+ )
90
+ end
91
+
92
+ it "declares proper reason" do
93
+ expect { advice }.to raise_error(
94
+ Aop::PointcutNotFound,
95
+ /Reason: #<NameError: (undefined method|Unable to find) [`']greeting' for (class|object) ([`']Class'|[`']#<Class:0x[0-9a-f]+>'|BankAccount)>/
96
+ )
97
+ end
98
+ end
99
+ end
100
+
33
101
  describe "multiple classes, methods and advices" do
34
102
  before do
35
103
  Aop["BankAccount,CashAccount#transfer,#withdraw,.transfer:before,:after"].advice do |*args, &blk|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Fedorov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-08 00:00:00.000000000 Z
11
+ date: 2015-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -53,6 +53,7 @@ files:
53
53
  - README.md
54
54
  - Rakefile
55
55
  - aop.gemspec
56
+ - benchmarks/before.rb
56
57
  - lib/aop.rb
57
58
  - lib/aop/pointcut.rb
58
59
  - lib/aop/version.rb