advisor 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.rspec +1 -0
- data/.rubocop.yml +13 -0
- data/.ruby-version +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +55 -0
- data/Rakefile +1 -0
- data/Readme.org +125 -0
- data/advisor.gemspec +26 -0
- data/lib/advisor/advices/call_logger.rb +69 -0
- data/lib/advisor/advices.rb +1 -0
- data/lib/advisor/builtin_advisors.rb +5 -0
- data/lib/advisor/factory.rb +45 -0
- data/lib/advisor/version.rb +3 -0
- data/lib/advisor.rb +4 -0
- data/spec/advisor/advices/call_logger_spec.rb +60 -0
- data/spec/advisor/builtin_advisors_spec.rb +91 -0
- data/spec/advisor/factory_spec.rb +56 -0
- data/spec/spec_helper.rb +5 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dce5c0242a387f50ef334a97a2583cade06759e3
|
4
|
+
data.tar.gz: 3eccbc9c38091e8ab6eafd8f4a64f5fa2ecb07e7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3d749fa9ae030a74fa61ee73b3a96cf739f48c0233ba110575453150d3adb73030a4522fb61d80bc1f1a00b189b6b0cc949f2d5e3120448767c9e9cf84311cc0
|
7
|
+
data.tar.gz: 4427ed176fb927dda723ff41fdbc84ad07107dc02f5b255e6363177abd0f6fc207bf435b6591f68799d9ffb59d4c2f15e646a6a0d51845e1dcb4785dcad34958
|
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
# Gemfile.lock
|
30
|
+
# .ruby-version
|
31
|
+
# .ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.5
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
advisor (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.0.0)
|
10
|
+
astrolabe (1.3.0)
|
11
|
+
parser (>= 2.2.0.pre.3, < 3.0)
|
12
|
+
coderay (1.1.0)
|
13
|
+
diff-lcs (1.2.5)
|
14
|
+
method_source (0.8.2)
|
15
|
+
parser (2.2.0.3)
|
16
|
+
ast (>= 1.1, < 3.0)
|
17
|
+
powerpack (0.1.0)
|
18
|
+
pry (0.10.1)
|
19
|
+
coderay (~> 1.1.0)
|
20
|
+
method_source (~> 0.8.1)
|
21
|
+
slop (~> 3.4)
|
22
|
+
rainbow (2.0.0)
|
23
|
+
rake (10.4.2)
|
24
|
+
rspec (3.2.0)
|
25
|
+
rspec-core (~> 3.2.0)
|
26
|
+
rspec-expectations (~> 3.2.0)
|
27
|
+
rspec-mocks (~> 3.2.0)
|
28
|
+
rspec-core (3.2.3)
|
29
|
+
rspec-support (~> 3.2.0)
|
30
|
+
rspec-expectations (3.2.1)
|
31
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
+
rspec-support (~> 3.2.0)
|
33
|
+
rspec-mocks (3.2.1)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.2.0)
|
36
|
+
rspec-support (3.2.2)
|
37
|
+
rubocop (0.30.0)
|
38
|
+
astrolabe (~> 1.3)
|
39
|
+
parser (>= 2.2.0.1, < 3.0)
|
40
|
+
powerpack (~> 0.1)
|
41
|
+
rainbow (>= 1.99.1, < 3.0)
|
42
|
+
ruby-progressbar (~> 1.4)
|
43
|
+
ruby-progressbar (1.7.5)
|
44
|
+
slop (3.6.0)
|
45
|
+
|
46
|
+
PLATFORMS
|
47
|
+
ruby
|
48
|
+
|
49
|
+
DEPENDENCIES
|
50
|
+
advisor!
|
51
|
+
bundler (~> 1.7)
|
52
|
+
pry
|
53
|
+
rake (~> 10.0)
|
54
|
+
rspec (~> 3.0)
|
55
|
+
rubocop
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/Readme.org
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
* Advisor: Solve your cross-cutting concerns without mumbo-jumbo.
|
2
|
+
|
3
|
+
=Advisor= is Ruby gem that enables you to solve cross-cutting concerns without
|
4
|
+
the usual =method-renaming= present in most alternatives.
|
5
|
+
|
6
|
+
=Advisor= intercepts method calls and allows you to mix cross cutting concerns
|
7
|
+
and tedious book-keeping tasks. Logging, metric reporting, auditing, timing,
|
8
|
+
timeouting can be handled beautifully.
|
9
|
+
|
10
|
+
=Advisor= works with plain Ruby modules and do not mess your stack trace.
|
11
|
+
|
12
|
+
Also, the amount of /intrusion/ required to set up is kept to a minimum while
|
13
|
+
still keeping it /discoverable/. Every affected class must explicitly extend a
|
14
|
+
given module and every affected method call must also be declared.
|
15
|
+
|
16
|
+
*** Usage
|
17
|
+
|
18
|
+
=Advisor= is organized between two main concepts only: =Advisor modules= and
|
19
|
+
=Advice modules=. =Advisor modules= are extensions applied to your classes
|
20
|
+
and =Advice modules= define the actual behavior of the intercepted method
|
21
|
+
calls.
|
22
|
+
|
23
|
+
In order to understand better how =Advisor= works, we are going to use an
|
24
|
+
example:
|
25
|
+
|
26
|
+
***** Example
|
27
|
+
|
28
|
+
Suppose you want to log calls to some methods but don't want to keep
|
29
|
+
repeating the message formatting or messing with the method body.
|
30
|
+
=Advisor= provides a simple built-in module called =Advisor::Loggable=
|
31
|
+
that solves this issue.
|
32
|
+
|
33
|
+
#+begin_src ruby
|
34
|
+
class Account
|
35
|
+
extend Advisor::Loggable
|
36
|
+
|
37
|
+
log_calls_to :deposit
|
38
|
+
|
39
|
+
def deposit(_amount, _origin)
|
40
|
+
#...
|
41
|
+
:done
|
42
|
+
end
|
43
|
+
end
|
44
|
+
#+end_src
|
45
|
+
|
46
|
+
In an interactive console:
|
47
|
+
|
48
|
+
#+begin_src ruby
|
49
|
+
$ Account.new.deposit(300, 'Jane Doe')
|
50
|
+
# => I, [2015-04-11T21:26:42.405180 #13840] INFO -- : [Time=2015-04-11 21:26:42 -0300][Thread=70183196300040]Called: Account#deposit(300, "Jane Doe")
|
51
|
+
# => :done
|
52
|
+
#+end_src
|
53
|
+
|
54
|
+
As you can see, the method call is intercepted and a message is printed to
|
55
|
+
=stdout=.
|
56
|
+
|
57
|
+
=Advisor= achieves this by using Ruby 2.0's =Module#prepend=. If you were
|
58
|
+
to check =Account='s ancestors you would get:
|
59
|
+
|
60
|
+
#+begin_src ruby
|
61
|
+
$ Account.ancestors
|
62
|
+
# => [Advisor::Advices::CallLogger(deposit), Account, Object, Kernel, BasicObject]
|
63
|
+
#+end_src
|
64
|
+
|
65
|
+
As you can see, the =Advisor::Advices::CallLogger(deposit)= module is
|
66
|
+
listed *before* Account itself in the ancestor chain.
|
67
|
+
|
68
|
+
In the next session we are going to explain how to write your own custom
|
69
|
+
advice.
|
70
|
+
|
71
|
+
***** Writing an =Advice=
|
72
|
+
|
73
|
+
An =Advice= defines what to do with the advised method call.
|
74
|
+
|
75
|
+
The required interface for an advice must be like the example bellow:
|
76
|
+
|
77
|
+
#+begin_src ruby
|
78
|
+
class Advice
|
79
|
+
def initialize(receiver, advised_method, call_args, **options)
|
80
|
+
# The constructor of an advice must receive 3 arguments and extra options.
|
81
|
+
# Those extra options are defined when applying the extension to the advised
|
82
|
+
# class.
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.applier_method
|
86
|
+
# Must return the name of the method which must be called in the class body
|
87
|
+
# to define which methods will be intercepted with the advice.
|
88
|
+
|
89
|
+
# In the case of `Advisor::Loggable`, this method returns `:log_calls_to`
|
90
|
+
end
|
91
|
+
|
92
|
+
def call
|
93
|
+
# This is the body of the advice.
|
94
|
+
#
|
95
|
+
# This method will always be called with the block `{ super(*call_args,
|
96
|
+
# &blk) }` That means the method implementation can decide when to run the
|
97
|
+
# advised method call. Check `Advisor::Advices::CallLogger` for an example.
|
98
|
+
end
|
99
|
+
end
|
100
|
+
#+end_src
|
101
|
+
|
102
|
+
***** Creating an =Advisor= module
|
103
|
+
|
104
|
+
Every =Advisor= module must be built from the corresponding =Advice= by
|
105
|
+
using the =Advisor::Factory#build= method.
|
106
|
+
|
107
|
+
=Advisor::Loggable= is built from the =Advisor::Advices::CallLogger=
|
108
|
+
module.
|
109
|
+
|
110
|
+
=Advisor::Loggable= itself is built like this:
|
111
|
+
|
112
|
+
#+begin_src ruby
|
113
|
+
module Advisor
|
114
|
+
Loggable = Factory.new(Advices::CallLogger).build
|
115
|
+
end
|
116
|
+
#+end_src
|
117
|
+
|
118
|
+
Hence, if your custom =Advice= complies to the required interface,
|
119
|
+
=Advisor::Factory= will be able to convert it to an extension module with
|
120
|
+
no problems.
|
121
|
+
|
122
|
+
*** Disclaimer
|
123
|
+
|
124
|
+
This version of the library is still experimental and probably not
|
125
|
+
production ready. Use at your own risk.
|
data/advisor.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'advisor/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'advisor'
|
8
|
+
spec.version = Advisor::VERSION
|
9
|
+
spec.required_ruby_version = '~>2.0'
|
10
|
+
|
11
|
+
spec.summary = 'AOP with anonymous modules'
|
12
|
+
spec.authors = ['Renan Ranelli']
|
13
|
+
spec.email = ['renanranelli@gmail.com']
|
14
|
+
spec.homepage = 'http://github.com/rranelli/advisor'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin\/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rubocop'
|
25
|
+
spec.add_development_dependency 'pry'
|
26
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Advisor
|
4
|
+
module Advices
|
5
|
+
class CallLogger
|
6
|
+
class << self
|
7
|
+
attr_accessor :default_logger
|
8
|
+
end
|
9
|
+
self.default_logger = Logger.new(STDOUT)
|
10
|
+
|
11
|
+
def initialize(object, method, call_args, **opts)
|
12
|
+
@object = object
|
13
|
+
@method = method
|
14
|
+
@call_args = call_args
|
15
|
+
@logger = opts[:logger] || CallLogger.default_logger
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :object, :method, :call_args, :logger
|
19
|
+
|
20
|
+
def self.applier_method
|
21
|
+
:log_calls_to
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
logger.info(success_message)
|
26
|
+
yield
|
27
|
+
rescue => e
|
28
|
+
logger.warn(failure_message(e))
|
29
|
+
raise
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def success_message
|
35
|
+
call_message('Called: ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def failure_message(ex)
|
39
|
+
call_message('Failed: ', "\n#{ex}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def call_message(prefix, suffix = '')
|
43
|
+
"#{time}#{thread}#{id}#{prefix}\
|
44
|
+
#{klass}##{method}(#{arguments})\
|
45
|
+
#{suffix}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def thread
|
49
|
+
"[Thread=#{Thread.current.object_id}]"
|
50
|
+
end
|
51
|
+
|
52
|
+
def time
|
53
|
+
"[Time=#{Time.now}]"
|
54
|
+
end
|
55
|
+
|
56
|
+
def klass
|
57
|
+
object.class
|
58
|
+
end
|
59
|
+
|
60
|
+
def arguments
|
61
|
+
call_args.map(&:inspect).join(', ')
|
62
|
+
end
|
63
|
+
|
64
|
+
def id
|
65
|
+
"[id=#{object.id}]" if object.respond_to?(:id)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'advices/call_logger'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Advisor
|
2
|
+
class Factory
|
3
|
+
def initialize(advice_klass)
|
4
|
+
@advice_klass = advice_klass
|
5
|
+
end
|
6
|
+
|
7
|
+
def build
|
8
|
+
advice_klazz = advice_klass
|
9
|
+
advisor_module = method(:advisor_module)
|
10
|
+
|
11
|
+
Module.new do
|
12
|
+
define_method(advice_klazz.applier_method) do |*methods, **args|
|
13
|
+
methods_str = methods.map(&:to_s).join(', ')
|
14
|
+
|
15
|
+
mod = advisor_module.call(methods, args)
|
16
|
+
mod.module_eval(%(def self.inspect
|
17
|
+
"#{advice_klazz}(#{methods_str})"
|
18
|
+
end))
|
19
|
+
mod.module_eval(%(def self.to_s; inspect; end))
|
20
|
+
|
21
|
+
prepend mod
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
attr_reader :advice_klass
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def advisor_module(methods, args)
|
33
|
+
advice_klazz = advice_klass
|
34
|
+
|
35
|
+
Module.new do
|
36
|
+
methods.each do |method_name|
|
37
|
+
define_method(method_name) do |*call_args, &blk|
|
38
|
+
advice = advice_klazz.new(self, method_name, call_args, **args)
|
39
|
+
advice.call { super(*call_args, &blk) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/advisor.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Advisor
|
4
|
+
module Advices
|
5
|
+
describe CallLogger do
|
6
|
+
subject(:advice) do
|
7
|
+
described_class.new(object, method, args, logger: logger)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:object) { OpenStruct.new(id: 42) }
|
11
|
+
let(:method) { 'the_meaning_of_life' }
|
12
|
+
let(:args) { ['the universe', 'and everything'] }
|
13
|
+
let(:logger) { instance_double(Logger) }
|
14
|
+
|
15
|
+
let(:block) { -> () { :bla } }
|
16
|
+
|
17
|
+
describe '#call' do
|
18
|
+
subject(:call) { advice.call(&block) }
|
19
|
+
|
20
|
+
let(:log_message) do
|
21
|
+
"[Time=#{Time.now}][Thread=#{Thread.current.object_id}][id=42]\
|
22
|
+
Called: OpenStruct#the_meaning_of_life(\"the universe\", \"and everything\")"
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
allow(Time).to receive(:now).and_return(Time.now)
|
27
|
+
allow(logger).to receive(:info)
|
28
|
+
end
|
29
|
+
|
30
|
+
it { is_expected.to eq(:bla) }
|
31
|
+
|
32
|
+
it do
|
33
|
+
expect(logger).to receive(:info).with(log_message)
|
34
|
+
|
35
|
+
call
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when yielding the block raises an exception' do
|
39
|
+
let(:block) { -> () { fail 'deu ruim!' } }
|
40
|
+
|
41
|
+
let(:log_message) do
|
42
|
+
"[Time=#{Time.now}][Thread=#{Thread.current.object_id}][id=42]\
|
43
|
+
Failed: OpenStruct#the_meaning_of_life(\"the universe\", \"and everything\")
|
44
|
+
deu ruim!"
|
45
|
+
end
|
46
|
+
|
47
|
+
before { allow(logger).to receive(:warn) }
|
48
|
+
|
49
|
+
it { expect { call }.to raise_error(StandardError, 'deu ruim!') }
|
50
|
+
|
51
|
+
it do
|
52
|
+
expect(logger).to receive(:warn).with(log_message)
|
53
|
+
|
54
|
+
expect { call }.to raise_error
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Advisor
|
4
|
+
describe Factory do
|
5
|
+
subject(:loggable) do
|
6
|
+
Advices::CallLogger.default_logger = default_logger
|
7
|
+
|
8
|
+
Class.new do
|
9
|
+
extend Loggable
|
10
|
+
log_calls_to :inc
|
11
|
+
|
12
|
+
def inc(number)
|
13
|
+
number + 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def foo(*)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:loggable_instance) { loggable.new }
|
22
|
+
let(:default_logger) { instance_double(Logger, info: nil, warn: nil) }
|
23
|
+
let(:call_logger) do
|
24
|
+
Advices::CallLogger.new(
|
25
|
+
loggable_instance, :inc, [1], logger: default_logger
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
allow(Advices::CallLogger).to receive(:new)
|
31
|
+
.and_return(call_logger)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'instantiates a call logger when calling the advised method' do
|
35
|
+
expect(Advices::CallLogger)
|
36
|
+
.to receive(:new)
|
37
|
+
.with(loggable_instance, :inc, [1], {})
|
38
|
+
.and_call_original
|
39
|
+
|
40
|
+
loggable_instance.inc(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'uses the call_logger to log the method call' do
|
44
|
+
expect(call_logger)
|
45
|
+
.to receive(:call)
|
46
|
+
.and_call_original
|
47
|
+
|
48
|
+
loggable_instance.inc(1)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'does not change the return value' do
|
52
|
+
expect(loggable_instance.inc(1)).to eq(2)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'does not log when calling a non-advised method' do
|
56
|
+
expect(Advices::CallLogger).to_not receive(:new)
|
57
|
+
|
58
|
+
loggable_instance.foo
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#log_calls_to' do
|
62
|
+
subject(:log_calls_to) { loggable.send(:log_calls_to, :foo, :bar) }
|
63
|
+
|
64
|
+
it 'prepends an anonymous module in the ancestor chain' do
|
65
|
+
expect(loggable).to receive(:prepend)
|
66
|
+
.and_call_original
|
67
|
+
|
68
|
+
expect { log_calls_to }.to change { loggable.ancestors.count }.by(1)
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when redefining an advised method' do
|
72
|
+
let(:child_class) do
|
73
|
+
Class.new(loggable) do
|
74
|
+
def inc(number)
|
75
|
+
number + 2
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
let(:child_class_instance) { child_class.new }
|
81
|
+
|
82
|
+
it 'the advice is overridden' do
|
83
|
+
log_calls_to
|
84
|
+
|
85
|
+
expect(Advices::CallLogger).not_to receive(:new)
|
86
|
+
expect(child_class_instance.inc(1)).to eq(3)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
describe Advisor::Factory do
|
2
|
+
subject(:factory) { described_class.new(advice_klass) }
|
3
|
+
|
4
|
+
let(:advice_klass) do
|
5
|
+
Struct.new(:obj, :method, :call_args, :args) do
|
6
|
+
define_method(:call) { 'overridden!' }
|
7
|
+
define_singleton_method(:applier_method) { 'apply_advice_to' }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
let(:advice_instance) do
|
11
|
+
advice_klass.new(advised_instance, :apply_advice_to, [], arg1: 1, arg2: 2)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:advised_klass) do
|
15
|
+
advisor = build
|
16
|
+
|
17
|
+
Struct.new(:advised_method) do
|
18
|
+
extend advisor
|
19
|
+
|
20
|
+
apply_advice_to :advised_method, arg1: 1, arg2: 2
|
21
|
+
end
|
22
|
+
end
|
23
|
+
let(:advised_instance) { advised_klass.new(33) }
|
24
|
+
|
25
|
+
before do
|
26
|
+
allow(advised_klass).to receive(:new)
|
27
|
+
.and_return(advised_instance)
|
28
|
+
|
29
|
+
allow(advice_klass).to receive(:new)
|
30
|
+
.and_return(advice_instance)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#build' do
|
34
|
+
subject(:build) { factory.build }
|
35
|
+
|
36
|
+
it { is_expected.to be_kind_of(Module) }
|
37
|
+
|
38
|
+
describe 'when applying the advice to methods' do
|
39
|
+
subject(:invoke_advised_method) { advised_instance.advised_method }
|
40
|
+
|
41
|
+
it do
|
42
|
+
expect(advice_klass).to receive(:new)
|
43
|
+
.with(advised_instance, :advised_method, [], arg1: 1, arg2: 2)
|
44
|
+
|
45
|
+
invoke_advised_method
|
46
|
+
end
|
47
|
+
|
48
|
+
it do
|
49
|
+
expect(advice_instance).to receive(:call)
|
50
|
+
|
51
|
+
invoke_advised_method
|
52
|
+
end
|
53
|
+
it { is_expected.to eq('overridden!') }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: advisor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Renan Ranelli
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- renanranelli@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- ".ruby-version"
|
94
|
+
- Gemfile
|
95
|
+
- Gemfile.lock
|
96
|
+
- Rakefile
|
97
|
+
- Readme.org
|
98
|
+
- advisor.gemspec
|
99
|
+
- lib/advisor.rb
|
100
|
+
- lib/advisor/advices.rb
|
101
|
+
- lib/advisor/advices/call_logger.rb
|
102
|
+
- lib/advisor/builtin_advisors.rb
|
103
|
+
- lib/advisor/factory.rb
|
104
|
+
- lib/advisor/version.rb
|
105
|
+
- spec/advisor/advices/call_logger_spec.rb
|
106
|
+
- spec/advisor/builtin_advisors_spec.rb
|
107
|
+
- spec/advisor/factory_spec.rb
|
108
|
+
- spec/spec_helper.rb
|
109
|
+
homepage: http://github.com/rranelli/advisor
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - "~>"
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '2.0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.2.2
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: AOP with anonymous modules
|
133
|
+
test_files: []
|
134
|
+
has_rdoc:
|