cheap_advice 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +70 -0
- data/Rakefile +79 -0
- data/VERSION +1 -0
- data/cheap_advice.slides.textile +257 -0
- data/example/ex01.rb +51 -0
- data/example/ex02-trace-0.yml +17 -0
- data/example/ex02-trace-1.yml +17 -0
- data/example/ex02-trace-2.yml +23 -0
- data/example/ex02-trace-4.yml +20 -0
- data/example/ex02-trace-5.yml +22 -0
- data/example/ex02-trace-6.yml +25 -0
- data/example/ex02.rb +99 -0
- data/lib/cheap_advice.rb +511 -0
- data/lib/cheap_advice/configuration.rb +217 -0
- data/lib/cheap_advice/trace.rb +228 -0
- data/spec/cheap_advice_spec.rb +342 -0
- data/spec/spec_helper.rb +12 -0
- metadata +192 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 2.3.0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.5.2"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
# Scarlet
|
14
|
+
gem 'open4'
|
15
|
+
gem 'RedCloth'
|
16
|
+
gem 'rtex'
|
17
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Kurt Stephens
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
= cheap_advice
|
2
|
+
|
3
|
+
Add/remove advice around, before or after methods in Ruby.
|
4
|
+
|
5
|
+
== Example
|
6
|
+
|
7
|
+
class MyClass
|
8
|
+
def foo
|
9
|
+
42
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class MyOtherClass
|
14
|
+
def bar
|
15
|
+
43
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
a = MyClass.new
|
20
|
+
b = MyOtherClass.new
|
21
|
+
|
22
|
+
trace_advice = CheapAdvice.new(:around) do | ar, body |
|
23
|
+
ar.advice[:log].puts "#{Time.now.iso8601(6)} #{ar.rcvr.class} #{ar.method} #{ar.rcvr.object_id}"
|
24
|
+
body.call
|
25
|
+
ar.advice[:log].puts "#{Time.now.iso8601(6)} #{ar.rcvr.class} #{ar.method} #{ar.rcvr.object_id} => #{ar.result.inspect}"
|
26
|
+
end
|
27
|
+
trace_advice[:log] = File.open("trace.log", "a+")
|
28
|
+
trace_advice.advise!(MyClass, :foo)
|
29
|
+
trace_advice.advise!(MyOtherClass, :bar)
|
30
|
+
|
31
|
+
|
32
|
+
a.foo
|
33
|
+
b.bar
|
34
|
+
|
35
|
+
trace_advice.disable!
|
36
|
+
|
37
|
+
a.foo
|
38
|
+
b.bar
|
39
|
+
|
40
|
+
trace.log will contain something like:
|
41
|
+
|
42
|
+
2011-01-17T16:33:27.882122-06:00 MyClass foo 69872883178280
|
43
|
+
2011-01-17T16:33:27.882262-06:00 MyClass foo 69872883178280 => 42
|
44
|
+
2011-01-17T16:33:27.882319-06:00 MyOtherClass bar 69872883178200
|
45
|
+
2011-01-17T16:33:27.882367-06:00 MyOtherClass bar 69872883178200 => 43
|
46
|
+
|
47
|
+
== Other Features
|
48
|
+
|
49
|
+
* CheapAdvice::Configuration provides a generic mechanism to configure multiple advices on different methods.
|
50
|
+
* CheapAdvice::Trace provides an example advice factory for generic method tracing/logging. See examples/ex02.rb.
|
51
|
+
|
52
|
+
== Issues
|
53
|
+
|
54
|
+
* Rails-type autoloading can confuse CheapAdvice.
|
55
|
+
|
56
|
+
== Contributing to cheap_advice
|
57
|
+
|
58
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
59
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
60
|
+
* Fork the project
|
61
|
+
* Start a feature/bugfix branch
|
62
|
+
* Commit and push until you are happy with your contribution
|
63
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
64
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
65
|
+
|
66
|
+
== Copyright
|
67
|
+
|
68
|
+
Copyright (c) 2008-2011 Kurt Stephens. See LICENSE.txt for
|
69
|
+
further details.
|
70
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "cheap_advice"
|
16
|
+
gem.homepage = "http://github.com/kstephens/cheap_advice"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{Add dynamic advice wrappers to methods.}
|
19
|
+
gem.description = %Q{http://kurtstephens.com/pub/cheap_advice.slides/index.html}
|
20
|
+
gem.email = "ks.github@kurtstephens.com"
|
21
|
+
gem.authors = ["Kurt Stephens"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :example do
|
41
|
+
Dir['example/ex*.rb'].sort.each do | ex |
|
42
|
+
cpid = Process.fork do
|
43
|
+
load ex
|
44
|
+
end
|
45
|
+
Process.wait(cpid) or raise "#{ex} failed"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
task :default => :spec
|
50
|
+
task :default => :example
|
51
|
+
|
52
|
+
require 'rake/rdoctask'
|
53
|
+
Rake::RDocTask.new do |rdoc|
|
54
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
55
|
+
|
56
|
+
rdoc.rdoc_dir = 'rdoc'
|
57
|
+
rdoc.title = "cheap_advice #{version}"
|
58
|
+
rdoc.rdoc_files.include('README*')
|
59
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
60
|
+
end
|
61
|
+
|
62
|
+
$SCARLET = ENV['SCARLET'] ||= File.expand_path("../../scarlet/bin/scarlet", __FILE__)
|
63
|
+
|
64
|
+
slides_src = 'cheap_advice.slides.textile'
|
65
|
+
slides_html = 'doc/cheap_advice.slides/index.html'
|
66
|
+
file slides_html => slides_src do
|
67
|
+
slides_dir = File.dirname(slides_html)
|
68
|
+
sh "mkdir -p #{slides_dir}"
|
69
|
+
sh "#{$SCARLET} -f html -g #{slides_dir}"
|
70
|
+
sh "#{$SCARLET} -f html #{slides_src} > #{slides_html}"
|
71
|
+
sh "open #{slides_html}"
|
72
|
+
end
|
73
|
+
|
74
|
+
task :doc => slides_html
|
75
|
+
|
76
|
+
task :publish => :doc do
|
77
|
+
sh "rsync -aruzv doc/cheap_advice.slides/ kscom:kurtstephens.com/pub/cheap_advice.slides/"
|
78
|
+
end
|
79
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,257 @@
|
|
1
|
+
!SLIDE
|
2
|
+
!TITLE Cheap Advice
|
3
|
+
|
4
|
+
h1. Cheap Advice
|
5
|
+
|
6
|
+
* Kurt Stephens
|
7
|
+
* Enova Financial -- "http://enovafinancial.com":http://enovafinancial.com
|
8
|
+
* RubyConf 2011
|
9
|
+
* Slides -- "http://kurtstephens.com/pub/cheap_advice.slides/index.html":http://kurtstephens.com/pub/cheap_advice.slides/index.html
|
10
|
+
* Code -- "http://github.com/kstephens/cheap_advice":http://github.com/kstephens/cheap_advice
|
11
|
+
|
12
|
+
!SLIDE
|
13
|
+
!TITLE Clean Code
|
14
|
+
|
15
|
+
h1. Clean Code
|
16
|
+
|
17
|
+
@@@ ruby
|
18
|
+
|
19
|
+
class MyClass
|
20
|
+
def foo; 42; end
|
21
|
+
end
|
22
|
+
class MyOtherClass
|
23
|
+
def bar; 43; end
|
24
|
+
end
|
25
|
+
a = MyClass.new
|
26
|
+
b = MyOtherClass.new
|
27
|
+
a.foo
|
28
|
+
b.bar
|
29
|
+
|
30
|
+
@@@
|
31
|
+
|
32
|
+
!SLIDE
|
33
|
+
!TITLE Add Logging
|
34
|
+
|
35
|
+
h1. Add Logging
|
36
|
+
|
37
|
+
@@@ ruby
|
38
|
+
|
39
|
+
class MyClass
|
40
|
+
def foo
|
41
|
+
$stderr.puts "#{self}#foo => 42"
|
42
|
+
42
|
43
|
+
end
|
44
|
+
end
|
45
|
+
class MyOtherClass
|
46
|
+
def bar
|
47
|
+
$stderr.puts "#{self}#bar => 43"
|
48
|
+
43
|
49
|
+
end
|
50
|
+
end
|
51
|
+
a = MyClass.new
|
52
|
+
b = MyOtherClass.new
|
53
|
+
a.foo
|
54
|
+
b.bar
|
55
|
+
|
56
|
+
@@@
|
57
|
+
|
58
|
+
!SLIDE
|
59
|
+
!TITLE Add Security
|
60
|
+
|
61
|
+
h1. Add Security
|
62
|
+
|
63
|
+
@@@ ruby
|
64
|
+
|
65
|
+
class MyClass
|
66
|
+
def foo
|
67
|
+
raise "SecurityError" unless $roles.include?("MyClass#foo")
|
68
|
+
$stderr.puts "#{self}#foo => 42"
|
69
|
+
42
|
70
|
+
end
|
71
|
+
end
|
72
|
+
class MyOtherClass
|
73
|
+
def bar
|
74
|
+
raise "SecurityError" unless $roles.include?("MyClass#bar")
|
75
|
+
$stderr.puts "#{self}#bar => 43"
|
76
|
+
43
|
77
|
+
end
|
78
|
+
end
|
79
|
+
a = MyClass.new
|
80
|
+
b = MyOtherClass.new
|
81
|
+
$roles = [ "MyClass#foo" ]
|
82
|
+
a.foo
|
83
|
+
b.bar
|
84
|
+
|
85
|
+
@@@
|
86
|
+
|
87
|
+
!SLIDE
|
88
|
+
!TITLE YUCK!
|
89
|
+
|
90
|
+
h1. ...YUCK!
|
91
|
+
|
92
|
+
@@@ ruby
|
93
|
+
|
94
|
+
class MyClass
|
95
|
+
def foo
|
96
|
+
# YUCK!: raise "SecurityError" unless $roles.include?("MyClass#foo")
|
97
|
+
# YUCK!: $stderr.puts "#{self}#foo => 42"
|
98
|
+
42
|
99
|
+
end
|
100
|
+
end
|
101
|
+
class MyOtherClass
|
102
|
+
def bar
|
103
|
+
# YUCK!: raise "SecurityError" unless $roles.include?("MyClass#bar")
|
104
|
+
# YUCK!: $stderr.puts "#{self}#bar => 43"
|
105
|
+
43
|
106
|
+
end
|
107
|
+
end
|
108
|
+
a = MyClass.new
|
109
|
+
b = MyOtherClass.new
|
110
|
+
$roles = [ "MyClass#foo" ] # ???
|
111
|
+
a.foo
|
112
|
+
b.bar
|
113
|
+
|
114
|
+
@@@
|
115
|
+
|
116
|
+
|
117
|
+
!SLIDE
|
118
|
+
!TITLE Dumb and Clean, Smart and Dirty
|
119
|
+
|
120
|
+
h1. Dumb and Clean, Smart and Dirty
|
121
|
+
|
122
|
+
* Real World is Smart Code.
|
123
|
+
* Dumb Code is Clean, Smart Code is Dirty.
|
124
|
+
* Get Dirt Out Of Code -- (problem-domain vs. solution-domain)
|
125
|
+
* Sweep It Somewhere Else -- (modularize the smart dirt)
|
126
|
+
* Don't be Dirty All the Time -- (dynamic, stateful dirt: logging, debugging, security, etc.)
|
127
|
+
|
128
|
+
!SLIDE
|
129
|
+
!TITLE Separation of Concerns
|
130
|
+
|
131
|
+
h1. Separation of Concerns
|
132
|
+
|
133
|
+
* Logging
|
134
|
+
* Security
|
135
|
+
|
136
|
+
... are not problem-domain issues.
|
137
|
+
|
138
|
+
!SLIDE
|
139
|
+
!TITLE Advice
|
140
|
+
|
141
|
+
h1. Advice
|
142
|
+
|
143
|
+
* Commonplace in the Lisp world, (esp. Emacs).
|
144
|
+
* Advice is function that wraps another function: "before", "after" or "around".
|
145
|
+
* Advice can be added or removed at run-time.
|
146
|
+
|
147
|
+
!SLIDE
|
148
|
+
!TITLE Advice != Aspects
|
149
|
+
|
150
|
+
h1. Advice != Aspects
|
151
|
+
|
152
|
+
* Aspects are woven into code based on complex "codepoint" criteria at build-time (or load-time).
|
153
|
+
* Advice is applied to a more well-known constructs: applicable objects: functions, methods, etc.
|
154
|
+
* Advice are objects.
|
155
|
+
* Advice can be added and removed at run-time.
|
156
|
+
|
157
|
+
!SLIDE
|
158
|
+
!TITLE Cheap Advice
|
159
|
+
|
160
|
+
h1. Cheap Advice
|
161
|
+
|
162
|
+
* ... adds dynamic Advice to Ruby methods.
|
163
|
+
* ... is applied to methods.
|
164
|
+
* ... are stateful objects.
|
165
|
+
* ... can be added and removed at run-time.
|
166
|
+
* ... are configurable.
|
167
|
+
|
168
|
+
!SLIDE
|
169
|
+
!TITLE Logging Advice
|
170
|
+
|
171
|
+
h1. Logging Advice
|
172
|
+
|
173
|
+
@@@ ruby
|
174
|
+
|
175
|
+
# Advice
|
176
|
+
trace_advice = CheapAdvice.new(:around) do | ar, body |
|
177
|
+
ar.advice[:log] <<
|
178
|
+
"#{Time.now.iso8601(6)} " <<
|
179
|
+
"#{ar.rcvr.class} #{ar.meth} #{ar.rcvr.object_id}\n"
|
180
|
+
body.call
|
181
|
+
ar.advice[:log] <<
|
182
|
+
"#{Time.now.iso8601(6)} " <<
|
183
|
+
"#{ar.rcvr.class} #{ar.meth} #{ar.rcvr.object_id} " <<
|
184
|
+
"=> #{ar.result.inspect}\n"
|
185
|
+
end
|
186
|
+
# State attached to trace_advice.
|
187
|
+
trace_advice[:log] = File.open("trace.log", "a+")
|
188
|
+
|
189
|
+
@@@
|
190
|
+
|
191
|
+
!SLIDE
|
192
|
+
!TITLE Applying Advice
|
193
|
+
|
194
|
+
h1. Applying Advice
|
195
|
+
|
196
|
+
@@@ ruby
|
197
|
+
|
198
|
+
...
|
199
|
+
# Activate trace_advice:
|
200
|
+
trace_advice.advise!(MyClass, :foo)
|
201
|
+
trace_advice.advise!(MyOtherClass, :bar)
|
202
|
+
a.foo
|
203
|
+
b.bar
|
204
|
+
|
205
|
+
# Disable trace_advice:
|
206
|
+
trace_advice.disable!
|
207
|
+
a.foo
|
208
|
+
b.bar
|
209
|
+
@@@
|
210
|
+
|
211
|
+
!SLIDE
|
212
|
+
!TITLE Configuration
|
213
|
+
|
214
|
+
h1. Configuration
|
215
|
+
|
216
|
+
@@@ yaml
|
217
|
+
|
218
|
+
:advice:
|
219
|
+
~:
|
220
|
+
:enabled: false
|
221
|
+
:options:
|
222
|
+
:trace:
|
223
|
+
:logger:
|
224
|
+
:name: :default
|
225
|
+
|
226
|
+
'MyClass':
|
227
|
+
:advice: trace
|
228
|
+
|
229
|
+
'MyClass#foo':
|
230
|
+
:enabled: false
|
231
|
+
|
232
|
+
'MyClass#bar':
|
233
|
+
:enabled: false
|
234
|
+
|
235
|
+
@@@
|
236
|
+
|
237
|
+
!SLIDE
|
238
|
+
!TITLE Security Advice
|
239
|
+
|
240
|
+
h1. Security Advice
|
241
|
+
|
242
|
+
You get the idea.
|
243
|
+
|
244
|
+
!SLIDE
|
245
|
+
!TITLE Example
|
246
|
+
|
247
|
+
h1. See example/ex01.rb
|
248
|
+
|
249
|
+
!SLIDE
|
250
|
+
!TITLE CheapAdvice
|
251
|
+
|
252
|
+
h1. Questions?
|
253
|
+
|
254
|
+
h1. Code!
|
255
|
+
|
256
|
+
http://github.com/kstephens/cheap_advice
|
257
|
+
|