contextr 0.1.1 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +2 -2
- data/History.txt +8 -0
- data/Manifest.txt +15 -0
- data/Rakefile +2 -2
- data/lib/contextr.rb +2 -1
- data/lib/contextr/class_methods.rb +19 -15
- data/lib/contextr/core_ext/module.rb +18 -39
- data/lib/contextr/core_ext/object.rb +1 -68
- data/lib/contextr/event_machine.rb +3 -3
- data/lib/contextr/inner_class.rb +47 -0
- data/lib/contextr/layer.rb +62 -68
- data/lib/contextr/version.rb +1 -1
- data/lib/ext/dynamic.rb +2 -2
- data/spec/contextr_spec.rb +36 -30
- data/test/lib/example_test.rb +59 -0
- data/test/lib/literate_markaby_test.rb +97 -0
- data/test/lib/literate_maruku_test.rb +108 -0
- data/test/test_class_side.mkd +234 -0
- data/test/test_class_side.rb +1 -222
- data/test/test_contextr.rb +0 -12
- data/test/test_dynamic_scope.mkd +61 -0
- data/test/test_dynamic_scope.rb +4 -0
- data/test/test_dynamics.mkd +201 -0
- data/test/test_dynamics.rb +1 -204
- data/test/test_hello_world.mkd +70 -0
- data/test/test_hello_world.rb +4 -0
- data/test/test_helper.rb +4 -53
- data/test/test_introduction.mkd +325 -0
- data/test/test_introduction.rb +1 -308
- data/test/test_layer_state.mkd +170 -0
- data/test/test_layer_state.rb +1 -176
- data/test/test_meta_api.mkd +21 -0
- data/test/test_meta_api.rb +4 -0
- data/test/test_ordering.mkd +142 -0
- data/test/test_ordering.rb +1 -144
- metadata +65 -41
- metadata.gz.sig +0 -0
data/test/test_layer_state.rb
CHANGED
@@ -1,178 +1,3 @@
|
|
1
1
|
require File.dirname(__FILE__) + "/test_helper.rb"
|
2
|
-
|
3
2
|
test_class(:TestLayerState)
|
4
|
-
|
5
|
-
# One of the most frequent examples for the use of aspect oriented programming
|
6
|
-
# is caching. You have a method, that takes some time to compute a result,
|
7
|
-
# but the result is always the same, if the parameters are the same. Such
|
8
|
-
# methods are often called functions. The have no side effects and do not depend
|
9
|
-
# on inner state.
|
10
|
-
#
|
11
|
-
# Perhaps you would like to attach caching to such functions. But only under
|
12
|
-
# certain circumstances. If your application lacks time, it is a good idea to
|
13
|
-
# activate result caching. If it lacks memory, it is a bad one.
|
14
|
-
#
|
15
|
-
# This perfectly sounds like a problem, that can be solved using
|
16
|
-
# context-oriented programming.
|
17
|
-
#
|
18
|
-
# But for caching, we need a place to store the computed results. One could
|
19
|
-
# store them in the instance that computes them, but this would be strange,
|
20
|
-
# because, the code residing in it does not make direct use of this state.
|
21
|
-
# Perhaps we are using variables, that other methods use for their own
|
22
|
-
# purpose and this would result in strange results. We could use cryptic
|
23
|
-
# instance variable names, but all this is just a workaround.
|
24
|
-
#
|
25
|
-
# What we want is to attach the state to the code, that uses it. This is the
|
26
|
-
# main idea of object-oriented design anyway. So ContextR gives you the
|
27
|
-
# opportunity to save state inside your method modules. Code and state stay
|
28
|
-
# side by side. This state will reside there even after deactivating the
|
29
|
-
# layer, so you can reuse it. Perfect for our caching approach.
|
30
|
-
|
31
|
-
|
32
|
-
# Fibonacci numbers
|
33
|
-
# =================
|
34
|
-
|
35
|
-
# We will use the good old Fibonacci numbers to demonstrate our approach. First
|
36
|
-
# the simple recursive computation, that becomes really slow for larger numbers.
|
37
|
-
# I know that there is a non-recursive algorithm, working faster, but this
|
38
|
-
# would not make such a good example. So just assume, that the following
|
39
|
-
# code is the fastest, you could possibly get.
|
40
|
-
|
41
|
-
module Fibonacci
|
42
|
-
module ClassMethods
|
43
|
-
def compute(fixnum)
|
44
|
-
if fixnum == 1 or fixnum == 0
|
45
|
-
fixnum
|
46
|
-
elsif fixnum < 0
|
47
|
-
raise ArgumentError, "Fibonacci not defined for negative numbers"
|
48
|
-
else
|
49
|
-
compute(fixnum - 1) + compute(fixnum - 2)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
self.extend(ClassMethods)
|
54
|
-
end
|
55
|
-
|
56
|
-
example do
|
57
|
-
result_of(Fibonacci.compute(1)) == 1
|
58
|
-
result_of(Fibonacci.compute(2)) == 1
|
59
|
-
result_of(Fibonacci.compute(3)) == 2
|
60
|
-
end
|
61
|
-
|
62
|
-
# Just to make sure, that it is slow, I will try to compute Fib(100)
|
63
|
-
|
64
|
-
require 'timeout'
|
65
|
-
example do
|
66
|
-
timeout_raised = false
|
67
|
-
begin
|
68
|
-
Timeout::timeout(0.05) do
|
69
|
-
Fibonacci.compute(100)
|
70
|
-
end
|
71
|
-
|
72
|
-
rescue Timeout::Error
|
73
|
-
timeout_raised = true
|
74
|
-
end
|
75
|
-
|
76
|
-
result_of(timeout_raised) == true
|
77
|
-
end
|
78
|
-
|
79
|
-
# Okay, the 0.01 seconds are really impatient, but I know, that caching will
|
80
|
-
# come to rescue and makes it happen.
|
81
|
-
#
|
82
|
-
# Let's define a simple caching method. If I already know the result, return
|
83
|
-
# it, if not, let the base implementation compute it and save the it into
|
84
|
-
# our variable.
|
85
|
-
|
86
|
-
module Fibonacci
|
87
|
-
module ClassMethods
|
88
|
-
module CacheMethods
|
89
|
-
def cache
|
90
|
-
@cache ||= {}
|
91
|
-
end
|
92
|
-
|
93
|
-
def compute(fixnum)
|
94
|
-
cache[fixnum] ||= yield(:next, fixnum)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
extend ClassMethods::CacheMethods => :cache
|
100
|
-
end
|
101
|
-
|
102
|
-
# If you are not familiar with the above syntax, to define context dependent
|
103
|
-
# behaviour, have a look at test_class_side.rb.
|
104
|
-
#
|
105
|
-
# Now let's compute Fib(100) again. Of course with caching enabled
|
106
|
-
|
107
|
-
example do
|
108
|
-
timeout_raised = false
|
109
|
-
begin
|
110
|
-
Timeout::timeout(0.05) do
|
111
|
-
ContextR::with_layer :cache do
|
112
|
-
result_of(Fibonacci.compute(100)) == 354_224_848_179_261_915_075
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
rescue Timeout::Error
|
117
|
-
timeout_raised = true
|
118
|
-
end
|
119
|
-
|
120
|
-
# This time the time out was not triggered
|
121
|
-
result_of(timeout_raised) == false
|
122
|
-
end
|
123
|
-
|
124
|
-
# It is that simple to add state to your method modules. And just to make sure,
|
125
|
-
# that I did not cheat, I will add a simple case, were instance variables and
|
126
|
-
# layer specific variables _would_ conflict, but in fact don't.
|
127
|
-
|
128
|
-
class LayerStateExample
|
129
|
-
attr_accessor :state
|
130
|
-
module StateMethods
|
131
|
-
attr_accessor :state
|
132
|
-
end
|
133
|
-
|
134
|
-
include StateMethods => :test_layer_state
|
135
|
-
end
|
136
|
-
|
137
|
-
# When StateMethods would be included normally, its attr_accessor would simply
|
138
|
-
# be the same as in the class. But this does not happen, when using layers.
|
139
|
-
#
|
140
|
-
# Let's do a little warm up and make sure, everything works as expected.
|
141
|
-
|
142
|
-
$layer_state_example = LayerStateExample.new
|
143
|
-
|
144
|
-
example do
|
145
|
-
$layer_state_example.state = true
|
146
|
-
result_of($layer_state_example.state) == true
|
147
|
-
$layer_state_example.state = false
|
148
|
-
result_of($layer_state_example.state) == false
|
149
|
-
|
150
|
-
ContextR::with_layer :test_layer_state do
|
151
|
-
$layer_state_example.state = true
|
152
|
-
result_of($layer_state_example.state) == true
|
153
|
-
$layer_state_example.state = false
|
154
|
-
result_of($layer_state_example.state) == false
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
# Until now, I did not prove anything. Let's try it.
|
159
|
-
|
160
|
-
example do
|
161
|
-
# Set the state
|
162
|
-
$layer_state_example.state = true
|
163
|
-
ContextR::with_layer :test_layer_state do
|
164
|
-
$layer_state_example.state = false
|
165
|
-
end
|
166
|
-
|
167
|
-
# And make sure, that they differ
|
168
|
-
result_of($layer_state_example.state) == true
|
169
|
-
|
170
|
-
ContextR::with_layer :test_layer_state do
|
171
|
-
result_of($layer_state_example.state) == false
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
# The last example was very theoretical and looks strange when seen isolated.
|
176
|
-
# Its main purpose is to show, that layer specific methods and base methods
|
177
|
-
# do not share their state, and that the layer specific state remains, also
|
178
|
-
# after layer deactivation. Don't take too serious.
|
3
|
+
LiterateMarukuTest.load(__FILE__)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
ContextR knows two basic reflection mechanisms. One is to query the currently
|
2
|
+
active layers.
|
3
|
+
|
4
|
+
example do
|
5
|
+
ContextR::with_layer :a do
|
6
|
+
assert_equal([:a], ContextR::active_layers)
|
7
|
+
ContextR::with_layer :b do
|
8
|
+
assert_equal([:a, :b], ContextR::active_layers)
|
9
|
+
end
|
10
|
+
assert_equal([:a], ContextR::active_layers)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
The second is to query all layers, that where ever defined.
|
15
|
+
|
16
|
+
example do
|
17
|
+
assert(ContextR::layers.include?(:a))
|
18
|
+
assert(ContextR::layers.include?(:b))
|
19
|
+
end
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
This document tries to demonstrate the invocation order within
|
2
|
+
context-specific method calls. There are two cases where this is relevant:
|
3
|
+
1. There is more than one layer active, that extends the method.
|
4
|
+
2. There is more than one module in a single layer that extends the method.
|
5
|
+
|
6
|
+
Unfortunately I could not find any relevant example that clearly demonstrates
|
7
|
+
this behaviour. Therefore I will use foo bar code. But I think it is still
|
8
|
+
easy to get the message.
|
9
|
+
|
10
|
+
I. Multiple active layers
|
11
|
+
-------------------------
|
12
|
+
|
13
|
+
Define the basis first.
|
14
|
+
|
15
|
+
class OrderingTest
|
16
|
+
def test_method
|
17
|
+
"base_method"
|
18
|
+
end
|
19
|
+
|
20
|
+
module FooMethods
|
21
|
+
def test_method
|
22
|
+
"foo_before #{super} foo_after"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
module BarMethods
|
26
|
+
def test_method
|
27
|
+
"bar_before #{super} bar_after"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
in_layer :foo do
|
32
|
+
include FooMethods
|
33
|
+
end
|
34
|
+
in_layer :bar do
|
35
|
+
include BarMethods
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
When multiple layers extend a single method, the order of activation
|
40
|
+
determines the order of execution.
|
41
|
+
|
42
|
+
example do
|
43
|
+
instance = OrderingTest.new
|
44
|
+
result_of(instance.test_method) == "base_method"
|
45
|
+
|
46
|
+
ContextR::with_layer :foo do
|
47
|
+
result_of(instance.test_method) == "foo_before base_method foo_after"
|
48
|
+
end
|
49
|
+
ContextR::with_layer :bar do
|
50
|
+
result_of(instance.test_method) == "bar_before base_method bar_after"
|
51
|
+
end
|
52
|
+
|
53
|
+
ContextR::with_layer :bar, :foo do
|
54
|
+
result_of(instance.test_method) ==
|
55
|
+
"bar_before foo_before base_method foo_after bar_after"
|
56
|
+
end
|
57
|
+
|
58
|
+
ContextR::with_layer :bar do
|
59
|
+
ContextR::with_layer :foo do
|
60
|
+
result_of(instance.test_method) ==
|
61
|
+
"bar_before foo_before base_method foo_after bar_after"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
ContextR::with_layer :foo, :bar do
|
66
|
+
result_of(instance.test_method) ==
|
67
|
+
"foo_before bar_before base_method bar_after foo_after"
|
68
|
+
end
|
69
|
+
|
70
|
+
ContextR::with_layer :foo do
|
71
|
+
ContextR::with_layer :bar do
|
72
|
+
result_of(instance.test_method) ==
|
73
|
+
"foo_before bar_before base_method bar_after foo_after"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
As you can see, the innermost layer activation provides the innermost method
|
79
|
+
definition. It is not important, whether the layers were activated at once
|
80
|
+
or one after the other.
|
81
|
+
|
82
|
+
Activating an already active layer may update the execution order. The outer
|
83
|
+
activation is hidden, but is restored again after leaving the block.
|
84
|
+
|
85
|
+
example do
|
86
|
+
instance = OrderingTest.new
|
87
|
+
|
88
|
+
ContextR::with_layer :bar, :foo do
|
89
|
+
result_of(instance.test_method) ==
|
90
|
+
"bar_before foo_before base_method foo_after bar_after"
|
91
|
+
|
92
|
+
ContextR::with_layer :bar do
|
93
|
+
result_of(instance.test_method) ==
|
94
|
+
"foo_before bar_before base_method bar_after foo_after"
|
95
|
+
end
|
96
|
+
|
97
|
+
result_of(instance.test_method) ==
|
98
|
+
"bar_before foo_before base_method foo_after bar_after"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
II. Multiple modules per layer and class
|
104
|
+
----------------------------------------
|
105
|
+
|
106
|
+
It is also possible to have more than one module define the context-dependent
|
107
|
+
behaviour of a class. In this case it may also happen, that multiple modules
|
108
|
+
extend the same method definition.
|
109
|
+
|
110
|
+
In this case we can reuse or already defined class and modules. This time
|
111
|
+
we include them into the same layer and see what happens.
|
112
|
+
|
113
|
+
class OrderingTest
|
114
|
+
in_layer :foo_bar do
|
115
|
+
include FooMethods
|
116
|
+
include BarMethods
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
example do
|
121
|
+
instance = OrderingTest.new
|
122
|
+
result_of(instance.test_method) == "base_method"
|
123
|
+
|
124
|
+
ContextR::with_layer :foo_bar do
|
125
|
+
result_of(instance.test_method) ==
|
126
|
+
"bar_before foo_before base_method foo_after bar_after"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
This time the last inclusion defines the outermost method. But this is just Ruby's way of (multiple) inheritance and not part of ContextR. For the same reason a second inclusion of `FooMethod`s will not update the execution order.
|
131
|
+
|
132
|
+
III. Conclusion
|
133
|
+
---------------
|
134
|
+
|
135
|
+
These should be all case where ordering matters. If you are aware of the
|
136
|
+
two basic rules, everything should work as expected.
|
137
|
+
|
138
|
+
1. Execution order of different layers is determined by layer activation.
|
139
|
+
2. Execution within layers is ordered in the way Ruby handles inheritance.
|
140
|
+
|
141
|
+
In an ideal world the the execution order would not be important. But hey,
|
142
|
+
this is not the ideal world, so you better know.
|
data/test/test_ordering.rb
CHANGED
@@ -1,146 +1,3 @@
|
|
1
1
|
require File.dirname(__FILE__) + "/test_helper.rb"
|
2
|
-
|
3
2
|
test_class(:TestOrdering)
|
4
|
-
|
5
|
-
# This document tries to demonstrate the invocation order within
|
6
|
-
# context-specific method calls. There are two cases where this is relevant:
|
7
|
-
# 1. There is more than one layer active, that extends the method.
|
8
|
-
# 2. There is more than one module in a single layer that extends the method.
|
9
|
-
#
|
10
|
-
# Unfortunately I could not find any relevant example that clearly demonstrates
|
11
|
-
# this behaviour. Therefore I will use foo bar code. But I think it is still
|
12
|
-
# easy to get the message.
|
13
|
-
|
14
|
-
# 1. Multiple active layers
|
15
|
-
# =========================
|
16
|
-
|
17
|
-
class OrderingTest
|
18
|
-
def test_method
|
19
|
-
"base_method"
|
20
|
-
end
|
21
|
-
|
22
|
-
module FooMethods
|
23
|
-
def test_method
|
24
|
-
"foo_before #{yield(:next)} foo_after"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
module BarMethods
|
28
|
-
def test_method
|
29
|
-
"bar_before #{yield(:next)} bar_after"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
include FooMethods => :foo
|
34
|
-
include BarMethods => :bar
|
35
|
-
end
|
36
|
-
|
37
|
-
# When multiple layers extend a single method, the order of activation
|
38
|
-
# determines the order of execution.
|
39
|
-
|
40
|
-
example do
|
41
|
-
instance = OrderingTest.new
|
42
|
-
result_of(instance.test_method) == "base_method"
|
43
|
-
|
44
|
-
ContextR::with_layer :foo do
|
45
|
-
result_of(instance.test_method) == "foo_before base_method foo_after"
|
46
|
-
end
|
47
|
-
ContextR::with_layer :bar do
|
48
|
-
result_of(instance.test_method) == "bar_before base_method bar_after"
|
49
|
-
end
|
50
|
-
|
51
|
-
ContextR::with_layer :bar, :foo do
|
52
|
-
result_of(instance.test_method) ==
|
53
|
-
"bar_before foo_before base_method foo_after bar_after"
|
54
|
-
end
|
55
|
-
|
56
|
-
ContextR::with_layer :bar do
|
57
|
-
ContextR::with_layer :foo do
|
58
|
-
result_of(instance.test_method) ==
|
59
|
-
"bar_before foo_before base_method foo_after bar_after"
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
ContextR::with_layer :foo, :bar do
|
64
|
-
result_of(instance.test_method) ==
|
65
|
-
"foo_before bar_before base_method bar_after foo_after"
|
66
|
-
end
|
67
|
-
|
68
|
-
ContextR::with_layer :foo do
|
69
|
-
ContextR::with_layer :bar do
|
70
|
-
result_of(instance.test_method) ==
|
71
|
-
"foo_before bar_before base_method bar_after foo_after"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# As you can see, the innermost layer activation provides the innermost method
|
77
|
-
# definition. It is not important, whether the layers were activated at once
|
78
|
-
# or one after the other.
|
79
|
-
#
|
80
|
-
# Activating and already active layer may update the execution order. The outer
|
81
|
-
# activation is hidden, but is restored again after leaving the block.
|
82
|
-
|
83
|
-
example do
|
84
|
-
instance = OrderingTest.new
|
85
|
-
|
86
|
-
ContextR::with_layer :bar, :foo do
|
87
|
-
result_of(instance.test_method) ==
|
88
|
-
"bar_before foo_before base_method foo_after bar_after"
|
89
|
-
|
90
|
-
ContextR::with_layer :bar do
|
91
|
-
result_of(instance.test_method) ==
|
92
|
-
"foo_before bar_before base_method bar_after foo_after"
|
93
|
-
end
|
94
|
-
|
95
|
-
result_of(instance.test_method) ==
|
96
|
-
"bar_before foo_before base_method foo_after bar_after"
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
|
101
|
-
# Multiple modules per layer and class
|
102
|
-
# ====================================
|
103
|
-
|
104
|
-
# It is also possible to have more than one module define the context-dependent
|
105
|
-
# behaviour of a class. In this case it may also happen, that multiple modules
|
106
|
-
# extend the same method definition.
|
107
|
-
#
|
108
|
-
# In this case we can reuse or already defined class and modules. This time
|
109
|
-
# we include them into the same layer and see what happens.
|
110
|
-
|
111
|
-
class OrderingTest
|
112
|
-
include FooMethods => :foo_bar
|
113
|
-
include BarMethods => :foo_bar
|
114
|
-
end
|
115
|
-
|
116
|
-
example do
|
117
|
-
instance = OrderingTest.new
|
118
|
-
result_of(instance.test_method) == "base_method"
|
119
|
-
|
120
|
-
ContextR::with_layer :foo_bar do
|
121
|
-
result_of(instance.test_method) ==
|
122
|
-
"bar_before foo_before base_method foo_after bar_after"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
# This time the last inclusion defines the outermost method. This can be again
|
127
|
-
# changed. After repeating an inclusion the ordering is updated.
|
128
|
-
|
129
|
-
example do
|
130
|
-
class OrderingTest
|
131
|
-
include FooMethods => :foo_bar
|
132
|
-
end
|
133
|
-
|
134
|
-
instance = OrderingTest.new
|
135
|
-
result_of(instance.test_method) == "base_method"
|
136
|
-
|
137
|
-
ContextR::with_layer :foo_bar do
|
138
|
-
result_of(instance.test_method) ==
|
139
|
-
"foo_before bar_before base_method bar_after foo_after"
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
# These should be all case where ordering matters. If you are aware of the
|
144
|
-
# two basic rules, everything should work as expected. In an ideal world the
|
145
|
-
# the execution order would not be important. But hey, this is not the ideal
|
146
|
-
# world, so you better know.
|
3
|
+
LiterateMarukuTest.load(__FILE__)
|