contextr 0.1.1 → 0.1.9
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.
- 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__)
|