aquarium 0.1.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +59 -2
- data/README +33 -16
- data/RELEASE-PLAN +28 -5
- data/UPGRADE +11 -0
- data/examples/aspect_design_example.rb +2 -2
- data/examples/aspect_design_example_spec.rb +2 -2
- data/examples/design_by_contract_example.rb +4 -4
- data/examples/design_by_contract_example_spec.rb +4 -4
- data/examples/method_missing_example.rb +4 -1
- data/examples/method_missing_example_spec.rb +4 -1
- data/examples/method_tracing_example.rb +2 -2
- data/examples/method_tracing_example_spec.rb +16 -16
- data/lib/aquarium/aspects/advice.rb +47 -25
- data/lib/aquarium/aspects/aspect.rb +81 -39
- data/lib/aquarium/aspects/dsl/aspect_dsl.rb +1 -1
- data/lib/aquarium/aspects/exclusion_handler.rb +2 -2
- data/lib/aquarium/aspects/join_point.rb +28 -28
- data/lib/aquarium/aspects/pointcut.rb +61 -15
- data/lib/aquarium/extras/design_by_contract.rb +7 -7
- data/lib/aquarium/finders.rb +0 -1
- data/lib/aquarium/finders/method_finder.rb +10 -20
- data/lib/aquarium/finders/type_finder.rb +141 -75
- data/lib/aquarium/utils.rb +1 -0
- data/lib/aquarium/utils/logic_error.rb +9 -0
- data/lib/aquarium/utils/method_utils.rb +4 -3
- data/lib/aquarium/utils/nil_object.rb +1 -0
- data/lib/aquarium/utils/type_utils.rb +19 -0
- data/lib/aquarium/version.rb +2 -2
- data/spec/aquarium/aspects/advice_chain_node_spec.rb +2 -2
- data/spec/aquarium/aspects/advice_spec.rb +28 -5
- data/spec/aquarium/aspects/aspect_invocation_spec.rb +522 -289
- data/spec/aquarium/aspects/aspect_spec.rb +59 -41
- data/spec/aquarium/aspects/aspect_with_nested_types_spec.rb +7 -7
- data/spec/aquarium/aspects/aspect_with_subtypes_spec.rb +2 -2
- data/spec/aquarium/aspects/concurrent_aspects_spec.rb +1 -2
- data/spec/aquarium/aspects/concurrent_aspects_with_objects_and_types_spec.rb +1 -1
- data/spec/aquarium/aspects/dsl/aspect_dsl_spec.rb +34 -34
- data/spec/aquarium/aspects/join_point_spec.rb +79 -0
- data/spec/aquarium/aspects/pointcut_or_composition_spec.rb +13 -3
- data/spec/aquarium/aspects/pointcut_spec.rb +310 -63
- data/spec/aquarium/extras/design_by_contract_spec.rb +4 -4
- data/spec/aquarium/finders/method_finder_spec.rb +208 -54
- data/spec/aquarium/finders/type_finder_spec.rb +24 -88
- data/spec/aquarium/finders/type_finder_with_descendents_and_ancestors_spec.rb +206 -0
- data/spec/aquarium/spec_example_classes.rb +75 -12
- data/spec/aquarium/utils/logic_error_spec.rb +10 -0
- data/spec/aquarium/utils/type_utils_sample_classes.rb +203 -0
- data/spec/aquarium/utils/type_utils_spec.rb +47 -1
- metadata +48 -39
- data/lib/aquarium/finders/object_finder.rb +0 -75
- data/spec/aquarium/finders/object_finder_spec.rb +0 -231
data/CHANGES
CHANGED
@@ -1,7 +1,64 @@
|
|
1
|
+
== Version 0.2.0
|
2
|
+
|
3
|
+
V0.2.0 changes the parameter list used for advice blocks and adds numerous enhancements, robustness
|
4
|
+
improvements and more complete "spec'ing".
|
5
|
+
|
6
|
+
Bug fixes:
|
7
|
+
none
|
8
|
+
|
9
|
+
Enhancements:
|
10
|
+
13402 Support a subclass syntax like AspectJ's "Type+"
|
11
|
+
13984 More flexible argument list to the advise block
|
12
|
+
14053 Remove JoinPoint#type, JoinPoint#type=, JoinPoint#object, and JoinPoint#object=
|
13
|
+
14061 Add a control flow mechanism to skipping (sic) intermediate advice
|
14
|
+
15164 Deprecate ObjectFinder
|
15
|
+
15413 Remove ObjectFinder
|
16
|
+
15710 Eliminate redundant public methods in various "finders"
|
17
|
+
|
18
|
+
#13402 adds new invocation options to specify types and their descendents (subclasses or modules
|
19
|
+
that include the specified module(s)) and ancestors. The latter should be used cautiously as it
|
20
|
+
will include things like Kernel, Object, and Class! I used new command options rather than the
|
21
|
+
AspectJ "+" suffix (and the proposed, but never implemented "-" suffix for ancestors), because
|
22
|
+
the "+" would be confusing with regular expressions and not in the spirit of trying to make
|
23
|
+
the pointcut language "easy to read". So, the following are now available:
|
24
|
+
:type_and_ancestors
|
25
|
+
:types_and_ancestors
|
26
|
+
:type_and_descendents
|
27
|
+
:types_and_descendents
|
28
|
+
And the corresponding:
|
29
|
+
:exclude_type_and_ancestors
|
30
|
+
:exclude_types_and_ancestors
|
31
|
+
:exclude_type_and_descendents
|
32
|
+
:exclude_types_and_descendents
|
33
|
+
If you want both the ancestors and descendents, just use both options with the same value.
|
34
|
+
|
35
|
+
#13984 adds the object as the second argument to the advice block parameter list. This change
|
36
|
+
reflects the fact that the object is often needed, but calling jp.context.advised_object is a
|
37
|
+
bit tedious. THIS CHANGE BREAKS BACKWARDS COMPATIBILITY!! An exception is raised if advice has
|
38
|
+
the signature |jp, *args|.
|
39
|
+
|
40
|
+
#14061 adds a new method, JoinPoint#invoke_original_join_point, which will invoke the original
|
41
|
+
method without intermediate advice. If called within around advice, you can write advice that
|
42
|
+
vetoes all subsequent advice, yet invokes the original method. Use this technique cautiously,
|
43
|
+
however, since you may not always know what other advices are involved and what side effects
|
44
|
+
this control-flow change might cause.
|
45
|
+
|
46
|
+
#15164 and 15413 remove ObjectFinder because it is not used and it requires ObjectSpace, which
|
47
|
+
has high overhead and won't be enabled, by default, in JRuby (it will be optional).
|
48
|
+
|
49
|
+
#15710 removes redundant methods that were becoming a maintenance issue, in particular,
|
50
|
+
MethodFinder#find_all_by and TypeFinder#find_by_name. This is a non-backwards-compatible API
|
51
|
+
change.
|
52
|
+
|
53
|
+
Finally, note that I have not yet been able to resolve bug #15202, "Intermittent confusion
|
54
|
+
between classes and objects when invoking advice." I believe this is a very rare occurrence
|
55
|
+
and only likely to ever happen during the "torture-test" of running the RSpec suite. Please post
|
56
|
+
a comment to Tracker if you encounter it!
|
57
|
+
|
1
58
|
== Version 0.1.8
|
2
59
|
|
3
|
-
V0.1.7 did not successfully "register" at rubyforge. This releases fixes that problem and also
|
4
|
-
several feature enhancements and refactorings. There are no known upgrade issues.
|
60
|
+
V0.1.7 did not successfully "register" at rubyforge. This releases fixes that problem and also
|
61
|
+
adds several feature enhancements and refactorings. There are no known upgrade issues.
|
5
62
|
|
6
63
|
Bug fixes:
|
7
64
|
none
|
data/README
CHANGED
@@ -50,7 +50,7 @@ Many of AspectJ's behaviors that aren't currently supported are planned for futu
|
|
50
50
|
* At this time, the pointcut language supported by Aquarium is not nearly as feature-rich as AspectJ's language. For example, there are no runtime pointcut designators supported, such as "if" conditions and "cflow" (context flow). Most of AspectJ pointcut language features are planned, however.
|
51
51
|
* While AspectJ provides "intertype declaration" capabilities (i.e., adding state and behavior to existing classes), Ruby's native metaprogramming support satisfies this need. There may be convenience hooks added in a future release, however.
|
52
52
|
* User defined advice precedence is not supported. However, advice precedence is unambiguous; the last aspects created while modules are loaded at runtime have higher precedence than earlier aspects. Ensuring a particular order is not always easy, of course.
|
53
|
-
* Unlike AspectJ, Aquarium can advise individual objects, can remove advice, and it supports named advice that can be defined separately from the aspects that use the advice.
|
53
|
+
* Unlike AspectJ, Aquarium can advise individual objects, can remove advice, and it supports named advice that can be defined separately from the aspects that use the advice. Aquarium can also advise ancestor (parent) types, not just derived (descendent) types of specified types.
|
54
54
|
|
55
55
|
=== Examples
|
56
56
|
|
@@ -61,20 +61,20 @@ In most cases, you can either declare the appropriate classes or use the optiona
|
|
61
61
|
Here is an example that traces invocations of all public instance methods (included inherited ones) of the classes or modules Foo and Bar.
|
62
62
|
|
63
63
|
require 'aquarium'
|
64
|
-
Aspect.new :around, :types => [Foo, Bar], :methods => :all do |join_point, *args|
|
65
|
-
p "Entering: #{join_point.target_type.name}##{join_point.method_name}"
|
64
|
+
Aspect.new :around, :types => [Foo, Bar], :methods => :all do |join_point, object, *args|
|
65
|
+
p "Entering: #{join_point.target_type.name}##{join_point.method_name} for object #{object}"
|
66
66
|
result = join_point.proceed
|
67
|
-
p "Leaving: #{join_point.target_type.name}##{join_point.method_name}"
|
67
|
+
p "Leaving: #{join_point.target_type.name}##{join_point.method_name} for object #{object}"
|
68
68
|
result # block needs to return the result of the "proceed"!
|
69
69
|
end
|
70
70
|
|
71
71
|
The advice to execute at each join point is the block. The pointcut is the set of all public instance methods in Foo and Bar. (There are additional options available for specifying class methods, protected methods, excluding inherited (ancestor) methods, etc.) Here is the same example using the convenience DSL that adds aspect methods to Object (available only if you require aquarium/aspects/dsl/object_dsl', since other toolkits, like Rails, define similar methods on Object!).
|
72
72
|
|
73
73
|
require 'aquarium/aspects/dsl/object_dsl'
|
74
|
-
around :types => [Foo, Bar], :methods => :all do |join_point, *args|
|
75
|
-
p "Entering: #{join_point.target_type.name}##{join_point.method_name}"
|
74
|
+
around :types => [Foo, Bar], :methods => :all do |join_point, object, *args|
|
75
|
+
p "Entering: #{join_point.target_type.name}##{join_point.method_name} for object #{object}"
|
76
76
|
result = join_point.proceed
|
77
|
-
p "Leaving: #{join_point.target_type.name}##{join_point.method_name}"
|
77
|
+
p "Leaving: #{join_point.target_type.name}##{join_point.method_name} for object #{object}"
|
78
78
|
result # block needs to return the result of the "proceed"!
|
79
79
|
end
|
80
80
|
|
@@ -106,7 +106,7 @@ If you use the DSL inside a class and omit the :type(s) and :object(s) options,
|
|
106
106
|
end
|
107
107
|
...
|
108
108
|
class Foo
|
109
|
-
around :critical_operation do |join_point, *args|
|
109
|
+
around :critical_operation do |join_point, object, *args|
|
110
110
|
p "Entering: Foo#critical_operation"
|
111
111
|
result = join_point.proceed
|
112
112
|
p "Leaving: Foo#critical_operation"
|
@@ -116,22 +116,22 @@ If you use the DSL inside a class and omit the :type(s) and :object(s) options,
|
|
116
116
|
|
117
117
|
It is important to note that aspect "instances" usually behave like class (static) variables, in terms of the lifetime of their effects. In the example shown, class Foo is permanently modified to do the print statements shown for all "critical methods", unless you save the result of calling "around" to a variable, e.g., critical_operation_logging, and you explicitly call "critical_operation_logging.unadvise" at some future time. Put another way, the effects scope just like changes made when you reopen a class or module.
|
118
118
|
|
119
|
-
A common mistake is to create an aspect in an initialize method and assign it to an attribute. This usually means that you are creating long-lived, redundant aspects every time an instance is created. The aspect modifications remain in effect even when the instances themselves are garbage collected!
|
119
|
+
A common mistake is to create an aspect in an initialize method and assign it to an attribute. This usually means that you are creating long-lived, redundant aspects every time an instance of your class is created. The aspect modifications remain in effect even when the instances themselves are garbage collected!
|
120
120
|
|
121
121
|
Here are some more succinct examples, illustrating the API (using the DSL methods).
|
122
122
|
|
123
123
|
You can pass in pointcuts defined elsewhere:
|
124
124
|
|
125
125
|
my_pointcut = Pointcut.new :types => /Foo::Bar::/, :methods => /^do_/
|
126
|
-
around :pointcuts => my_pointcut do |jp, *args| ... # Pass in a pointcut
|
127
|
-
around :pointcuts => [my_pointcut, ...] do |jp, *args| ... # Pass in a pointcut array
|
126
|
+
around :pointcuts => my_pointcut do |jp, obj, *args| ... # Pass in a pointcut
|
127
|
+
around :pointcuts => [my_pointcut, ...] do |jp, obj, *args| ... # Pass in a pointcut array
|
128
128
|
|
129
129
|
As a convenience, since a JoinPoint is like a Pointcut with one element, you can pass a JoinPoint object where Pointcut objects are expected:
|
130
130
|
|
131
131
|
my_join_point1 = JoinPoint.new :type => Foo::Bar, :method => do_this
|
132
132
|
my_join_point2 = JoinPoint.new :type => Foo::Bar, :method => do_that
|
133
|
-
around :pointcuts => my_join_point1 do |jp, *args| ...
|
134
|
-
around :pointcuts => [my_join_point1, my_join_point2, ...] do |jp, *args| ...
|
133
|
+
around :pointcuts => my_join_point1 do |jp, obj, *args| ...
|
134
|
+
around :pointcuts => [my_join_point1, my_join_point2, ...] do |jp, obj, *args| ...
|
135
135
|
|
136
136
|
You can specify a single type, a type name, a type regular expression, or an array of the same. Note that :type and :types are synonymous "sugar".
|
137
137
|
|
@@ -142,6 +142,13 @@ You can specify a single type, a type name, a type regular expression, or an arr
|
|
142
142
|
around :types => /A::.*Helper$/, ...
|
143
143
|
around :types => [/A::.*Helper$/, /B::Foo.*/], ...
|
144
144
|
|
145
|
+
You can specify types and their descendents (subclasses or included modules) or ancestors.
|
146
|
+
|
147
|
+
around :type_and_ancestors = A, ...
|
148
|
+
around :types_and_ancestors = A, ...
|
149
|
+
around :type_and_descendents = A, ...
|
150
|
+
around :types_and_descendents = A, ...
|
151
|
+
|
145
152
|
You can specify a single object or an array of objects. Note that :object and :objects are synonymous.
|
146
153
|
|
147
154
|
a1 = A.new
|
@@ -204,6 +211,10 @@ of the same types. (This is not likely to happen a lot in real applications, but
|
|
204
211
|
around ..., :exclude_join_points = [jp, ...]
|
205
212
|
around ..., :exclude_type = t, ...
|
206
213
|
around ..., :exclude_types = [t, ...]
|
214
|
+
around ..., :exclude_type_and_ancestors = t, ...
|
215
|
+
around ..., :exclude_types_and_ancestors = [t, ...]
|
216
|
+
around ..., :exclude_type_and_descendents = t, ...
|
217
|
+
around ..., :exclude_types_and_descendents = [t, ...]
|
207
218
|
around ..., :exclude_object = o, ...
|
208
219
|
around ..., :exclude_objects = [o, ...]
|
209
220
|
around ..., :exclude_method = m, ...
|
@@ -243,18 +254,24 @@ You can advice methods both before after. This is different from around advice,
|
|
243
254
|
before_and_after_raising_within :types =>, ... # synonym
|
244
255
|
before_and_after_raising_within_or_returning_from :types =>, ... # synonym
|
245
256
|
|
246
|
-
|
257
|
+
If you pass a block to Aspect.new, it will be the advice. When invoked, the advice will be passed the following three arguments,
|
258
|
+
1) the JoinPoint, which will contain a JoinPoint::Context object with useful context information,
|
259
|
+
2) the object being sent the current message, and
|
260
|
+
3) the parameters passed with the original message.
|
261
|
+
Recall that Proc don't check the number of arguments (while lambdas do), so if you don't care about any trailing parameters, you can leave them out of the parameter list. Recall that the other difference between the two is that a return statement in a Proc returns from the method that contains it. As rule, do NOT use return statements in advices!
|
247
262
|
|
248
|
-
around :type => [...], :methods => :all do |join_point, *args|
|
263
|
+
around :type => [...], :methods => :all do |join_point, object, *args|
|
249
264
|
advice_to_execute_before_the_jp
|
250
265
|
result = join_point.proceed # Invoke the join point, passing *args implicitly (you can override...)
|
251
266
|
advice_to_execute_after_the_jp
|
252
267
|
result # return the result of the "proceed", unless you override the value.
|
253
268
|
end
|
254
|
-
around(:type => [...], :methods => :all) {|join_point, *args| ...} # (...) necessary for precedence...
|
269
|
+
around(:type => [...], :methods => :all) {|join_point, object, *args| ...} # (...) necessary for precedence...
|
255
270
|
|
256
271
|
In the example, we show that you must be careful to return the correct value, usually the value returned by "proceed" or a value created by the block itself.
|
257
272
|
|
273
|
+
Note, prior to V0.2.0, the advice argument list was |join_point, *args|. Aquarium will look for such obsolete signatures (by looking at the arity of the proc) and raise an exception, if found. This check will be removed in a future release.
|
274
|
+
|
258
275
|
Rather than passing a block as the advice, you can pass a previously-created Proc:
|
259
276
|
|
260
277
|
around :type => [...], :methods => :all, :advice => advice
|
data/RELEASE-PLAN
CHANGED
@@ -15,11 +15,34 @@ This change will obvious break existing aspects.
|
|
15
15
|
|
16
16
|
=== Versions 0.3+.0
|
17
17
|
|
18
|
-
More refinements to the API and functionality
|
19
|
-
|
20
|
-
|
21
|
-
etc.
|
18
|
+
More refinements and simpliciations to the API and functionality, including much-needed redundancy
|
19
|
+
reduction. I haven't used mocks much in the specs, but they are clearly needed now to improve
|
20
|
+
performance of RSpec runs.
|
22
21
|
|
22
|
+
The main thrust of new feature work will be expanding the pointcut language to include
|
23
|
+
conditionals and stack context constructs, as well as more intuitive ways of expressing sets of
|
24
|
+
types, such as types nested arbitrarily deep in module "namespaces" (e.g., #13403), etc.
|
25
|
+
|
26
|
+
I'm also thinking about an alternative syntax for the DSL. Instead of just this:
|
27
|
+
|
28
|
+
Aspect.new :around :pointcuts => [pc1, pc2, ...] do |jp, object, *args|
|
29
|
+
# advise
|
30
|
+
end
|
31
|
+
|
32
|
+
How about something like the following?
|
33
|
+
|
34
|
+
around do
|
35
|
+
pointcuts pc1 or pc2
|
36
|
+
advise_with do |jp, object, *args|
|
37
|
+
# advise
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
I'm not sure it adds much (at this stage of thinking about it...) except that it could make
|
42
|
+
composition of complete pointcuts easier.
|
43
|
+
|
23
44
|
=== Version 1.0.0
|
24
45
|
|
25
|
-
Reasonable stable and full-featured API and DSL.
|
46
|
+
Reasonable stable and full-featured API and DSL. Also, to justify Aquarium's existence ;), I want
|
47
|
+
to produce some non-trivial examples of refactoring known APIs and demonstrating improved clarity,
|
48
|
+
productivity, modularity, etc., etc.
|
data/UPGRADE
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
== Upgrading to Aquarium-0.2.0
|
2
|
+
|
3
|
+
This release changes the expected advice parameter list from |join_point, *method_args| to
|
4
|
+
|join_point, object, *method_args|, where "object" is the current object receiving the message
|
5
|
+
corresponding to the advised join point. Therefore, when you upgrade, you will need to modify your
|
6
|
+
advices to take this extra argument, even if you don't use it. Since an advice of |jp, *args| would
|
7
|
+
silently "absorb" the object into the beginning of "*args", thereby potentially causing confusion,
|
8
|
+
Aquarium will raise an exception if the advice block or proc has this obsolete signature.
|
9
|
+
|
10
|
+
Note that "JoinPoint#Context.advised_object" is still supported, even if it is now less useful.
|
11
|
+
|
1
12
|
== Upgrading to Aquarium-0.1.8
|
2
13
|
|
3
14
|
V0.1.7 did not successfully "register" at rubyforge. This releases fixes that problem and also adds
|
@@ -34,9 +34,9 @@ include Aquarium::Aspects
|
|
34
34
|
|
35
35
|
# Observe state changes in the class, using the class-defined pointcut.
|
36
36
|
|
37
|
-
observer = Aspect.new :after, :pointcut => Aquarium::ClassWithStateAndBehavior::STATE_CHANGE do |jp, *args|
|
37
|
+
observer = Aspect.new :after, :pointcut => Aquarium::ClassWithStateAndBehavior::STATE_CHANGE do |jp, obj, *args|
|
38
38
|
p "State has changed. "
|
39
|
-
state =
|
39
|
+
state = obj.state
|
40
40
|
p " New state is #{state.nil? ? 'nil' : state.inspect}"
|
41
41
|
p " Equivalent to *args: #{args.inspect}"
|
42
42
|
end
|
@@ -33,8 +33,8 @@ include Aquarium::Aspects
|
|
33
33
|
describe "An example of an aspect using a class-defined pointcut." do
|
34
34
|
it "should observe state changes in the class." do
|
35
35
|
@new_state = nil
|
36
|
-
observer = Aspect.new :after, :pointcut => Aquarium::ClassWithStateAndBehavior::STATE_CHANGE do |jp, *args|
|
37
|
-
@new_state =
|
36
|
+
observer = Aspect.new :after, :pointcut => Aquarium::ClassWithStateAndBehavior::STATE_CHANGE do |jp, obj, *args|
|
37
|
+
@new_state = obj.state
|
38
38
|
@new_state.should be_eql(*args)
|
39
39
|
end
|
40
40
|
object = Aquarium::ClassWithStateAndBehavior.new(:a1, :a2, :a3)
|
@@ -15,7 +15,7 @@ module Aquarium
|
|
15
15
|
p "inside :action"
|
16
16
|
end
|
17
17
|
|
18
|
-
precondition :method => :action, :message => "Must pass more than one argument." do |jp, *args|
|
18
|
+
precondition :method => :action, :message => "Must pass more than one argument." do |jp, obj, *args|
|
19
19
|
args.size > 0
|
20
20
|
end
|
21
21
|
end
|
@@ -37,7 +37,7 @@ module Aquarium
|
|
37
37
|
end
|
38
38
|
|
39
39
|
postcondition :method => :action,
|
40
|
-
:message => "Must pass more than one argument and first argument must be non-empty." do |jp, *args|
|
40
|
+
:message => "Must pass more than one argument and first argument must be non-empty." do |jp, obj, *args|
|
41
41
|
args.size > 0 && ! args[0].empty?
|
42
42
|
end
|
43
43
|
end
|
@@ -71,8 +71,8 @@ module Aquarium
|
|
71
71
|
@invar = 1
|
72
72
|
end
|
73
73
|
|
74
|
-
invariant :methods => /action$/, :message => "Must not change the @invar value." do |jp, *args|
|
75
|
-
|
74
|
+
invariant :methods => /action$/, :message => "Must not change the @invar value." do |jp, obj, *args|
|
75
|
+
obj.invar == 0
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
@@ -16,7 +16,7 @@ module Aquarium
|
|
16
16
|
end
|
17
17
|
attr_reader :state
|
18
18
|
|
19
|
-
precondition :method => :action, :message => "Must pass more than one argument." do |jp, *args|
|
19
|
+
precondition :method => :action, :message => "Must pass more than one argument." do |jp, obj, *args|
|
20
20
|
args.size > 0
|
21
21
|
end
|
22
22
|
end
|
@@ -42,7 +42,7 @@ module Aquarium
|
|
42
42
|
attr_reader :state
|
43
43
|
|
44
44
|
postcondition :method => :action,
|
45
|
-
:message => "Must pass more than one argument and first argument must be non-empty." do |jp, *args|
|
45
|
+
:message => "Must pass more than one argument and first argument must be non-empty." do |jp, obj, *args|
|
46
46
|
args.size > 0 && ! args[0].empty?
|
47
47
|
end
|
48
48
|
end
|
@@ -73,8 +73,8 @@ module Aquarium
|
|
73
73
|
@invar = 1
|
74
74
|
end
|
75
75
|
|
76
|
-
invariant :methods => /action$/, :message => "Must not change the @invar value." do |jp, *args|
|
77
|
-
|
76
|
+
invariant :methods => /action$/, :message => "Must not change the @invar value." do |jp, obj, *args|
|
77
|
+
obj.invar == 0
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
@@ -19,6 +19,9 @@ module Aquarium
|
|
19
19
|
def method_missing sym, *args
|
20
20
|
p "Echoing: #{sym.to_s}: #{args.join(" ")}"
|
21
21
|
end
|
22
|
+
def respond_to? sym, include_private = false
|
23
|
+
true
|
24
|
+
end
|
22
25
|
end
|
23
26
|
end
|
24
27
|
|
@@ -28,7 +31,7 @@ echo1.say "hello", "world!"
|
|
28
31
|
echo1.log "something", "interesting..."
|
29
32
|
echo1.shout "theater", "in", "a", "crowded", "firehouse!"
|
30
33
|
|
31
|
-
Aquarium::Aspects::Aspect.new :around, :type => Aquarium::Echo, :method => :method_missing do |join_point, sym, *args|
|
34
|
+
Aquarium::Aspects::Aspect.new :around, :type => Aquarium::Echo, :method => :method_missing do |join_point, obj, sym, *args|
|
32
35
|
if sym == :log
|
33
36
|
p "--- Sending to log: #{args.join(" ")}"
|
34
37
|
else
|
@@ -20,6 +20,9 @@ module Aquarium
|
|
20
20
|
@log << "Echoing: #{sym.to_s}: #{args.join(" ")}"
|
21
21
|
end
|
22
22
|
def logged_messages; @log; end
|
23
|
+
def respond_to? sym, include_private = false
|
24
|
+
true
|
25
|
+
end
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
@@ -39,7 +42,7 @@ end
|
|
39
42
|
describe "An example of a class' method_missing with around advice" do
|
40
43
|
it "should only handle invocations not processed by the around advice." do
|
41
44
|
@intercepted_message = nil
|
42
|
-
aspect = Aquarium::Aspects::Aspect.new :around, :type => Aquarium::Echo, :method => :method_missing do |join_point, sym, *args|
|
45
|
+
aspect = Aquarium::Aspects::Aspect.new :around, :type => Aquarium::Echo, :method => :method_missing do |join_point, obj, sym, *args|
|
43
46
|
if sym == :log
|
44
47
|
@intercepted_message = "log: #{args.join(" ")}"
|
45
48
|
else
|
@@ -39,7 +39,7 @@ bar1.do_something_else :b3, :b4
|
|
39
39
|
include Aquarium::Aspects
|
40
40
|
|
41
41
|
Aspect.new :around, :types => [Aquarium::Foo, Aquarium::Bar], :methods => :all,
|
42
|
-
:method_options => :exclude_ancestor_methods do |execution_point, *args|
|
42
|
+
:method_options => :exclude_ancestor_methods do |execution_point, obj, *args|
|
43
43
|
begin
|
44
44
|
p "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
45
45
|
execution_point.proceed
|
@@ -58,7 +58,7 @@ bar1.do_something_else :b7, :b8
|
|
58
58
|
# The "begin/ensure/end" idiom shown causes the advice to return the correct value; the result
|
59
59
|
# of the "proceed", rather than the value returned by "p"!
|
60
60
|
Aspect.new :around, :types => [Aquarium::Foo, Aquarium::Bar], :methods => :initialize,
|
61
|
-
:method_options => :private do |execution_point, *args|
|
61
|
+
:method_options => :private do |execution_point, obj, *args|
|
62
62
|
begin
|
63
63
|
p "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
64
64
|
execution_point.proceed
|
@@ -56,13 +56,13 @@ describe "An example with advice on the public instance methods (excluding ances
|
|
56
56
|
it "should trace all calls to the public methods defined by Foo" do
|
57
57
|
# The "begin/ensure/end" idiom shown causes the advice to return the correct value; the result
|
58
58
|
# of the "proceed", rather than the value returned by "p"!
|
59
|
-
aspect = Aquarium::Aspects::Aspect.new :around,
|
59
|
+
aspect = Aquarium::Aspects::Aspect.new :around,
|
60
|
+
:type => Aquarium::Foo, :methods => :all, :method_options => :exclude_ancestor_methods do |execution_point, obj, *args|
|
60
61
|
begin
|
61
|
-
|
62
|
-
o.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
62
|
+
obj.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
63
63
|
execution_point.proceed
|
64
64
|
ensure
|
65
|
-
|
65
|
+
obj.log "Leaving: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
@@ -79,13 +79,13 @@ end
|
|
79
79
|
|
80
80
|
describe "An example with advice on the public instance methods (excluding ancestor methods) of Bar" do
|
81
81
|
it "should not trace any calls to the public methods defined by the included BarModule" do
|
82
|
-
aspect = Aquarium::Aspects::Aspect.new :around,
|
82
|
+
aspect = Aquarium::Aspects::Aspect.new :around,
|
83
|
+
:type => Aquarium::Bar, :methods => :all, :method_options => :exclude_ancestor_methods do |execution_point, obj, *args|
|
83
84
|
begin
|
84
|
-
|
85
|
-
o.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
85
|
+
obj.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
86
86
|
execution_point.proceed
|
87
87
|
ensure
|
88
|
-
|
88
|
+
obj.log "Leaving: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
@@ -98,13 +98,13 @@ end
|
|
98
98
|
|
99
99
|
describe "An example with advice on the public instance methods (including ancestor methods) of Bar" do
|
100
100
|
it "should trace all calls to the public methods defined by the included BarModule" do
|
101
|
-
aspect = Aquarium::Aspects::Aspect.new :around,
|
101
|
+
aspect = Aquarium::Aspects::Aspect.new :around,
|
102
|
+
:type => Aquarium::Bar, :methods => /^do_/ do |execution_point, obj, *args|
|
102
103
|
begin
|
103
|
-
|
104
|
-
o.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
104
|
+
obj.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
105
105
|
execution_point.proceed
|
106
106
|
ensure
|
107
|
-
|
107
|
+
obj.log "Leaving: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
@@ -123,13 +123,13 @@ end
|
|
123
123
|
describe "An example with advice on the private initialize method of Foo and Bar" do
|
124
124
|
it "should trace all calls to initialize" do
|
125
125
|
before_methods = Aquarium::Foo.private_instance_methods.sort #- Object.private_methods.sort
|
126
|
-
aspect = Aquarium::Aspects::Aspect.new :around,
|
126
|
+
aspect = Aquarium::Aspects::Aspect.new :around,
|
127
|
+
:types => [Aquarium::Foo, Aquarium::Bar], :methods => :initialize, :method_options => :private do |execution_point, obj, *args|
|
127
128
|
begin
|
128
|
-
|
129
|
-
o.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
129
|
+
obj.log "Entering: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
130
130
|
execution_point.proceed
|
131
131
|
ensure
|
132
|
-
|
132
|
+
obj.log "Leaving: #{execution_point.target_type.name}##{execution_point.method_name}: args = #{args.inspect}"
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|