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_class_side.rb
CHANGED
@@ -1,225 +1,4 @@
|
|
1
1
|
require File.dirname(__FILE__) + "/test_helper.rb"
|
2
2
|
|
3
3
|
test_class(:TestClassSide)
|
4
|
-
|
5
|
-
# In Ruby there are multiple ways of defining behaviour on the class side. That
|
6
|
-
# are messages that are send to the class, not on the instance. Class side
|
7
|
-
# behaviour is often useful for functional methods, i.e. methods that do not
|
8
|
-
# rely on inner state and have no side effects. Mathematical functions have
|
9
|
-
# these characteristics - that is where the name probably comes from.
|
10
|
-
|
11
|
-
|
12
|
-
# Using def self.method_name
|
13
|
-
# ==========================
|
14
|
-
|
15
|
-
# The simpliest way of defining class side behaviour is prepending self. to the
|
16
|
-
# method definition. This way, the method is attached to the surrounding class
|
17
|
-
# and not instance. A simple example:
|
18
|
-
|
19
|
-
class SimpleMath
|
20
|
-
def self.pi
|
21
|
-
3.14159265
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
example do
|
26
|
-
result_of(SimpleMath.pi) == 3.14159265
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
# Using class << self
|
31
|
-
# ===================
|
32
|
-
|
33
|
-
# When you are having lots of class side methods as well as instance side ones,
|
34
|
-
# it can be difficult to spot the little self. in front of the method name.
|
35
|
-
# Probably you like to group them more explicitly. You could use Ruby's
|
36
|
-
# eigenclass principle for that. It will look like the following:
|
37
|
-
|
38
|
-
class SimpleMath
|
39
|
-
class << self
|
40
|
-
def e
|
41
|
-
2.71828183
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
example do
|
47
|
-
result_of(SimpleMath.e) == 2.71828183
|
48
|
-
end
|
49
|
-
|
50
|
-
|
51
|
-
# Using a module
|
52
|
-
# ==============
|
53
|
-
|
54
|
-
# For even more encapsulation you could also use modules and extend the class
|
55
|
-
# definition with them. I am using extend here on purpose. Module's include
|
56
|
-
# method adds the behaviour to the instance side, extend to the class side.
|
57
|
-
|
58
|
-
class SimpleMath
|
59
|
-
module ClassMethods
|
60
|
-
def golden_ratio
|
61
|
-
1.6180339887
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
extend ClassMethods
|
66
|
-
end
|
67
|
-
|
68
|
-
example do
|
69
|
-
result_of(SimpleMath.golden_ratio) == 1.6180339887
|
70
|
-
end
|
71
|
-
|
72
|
-
# The last method is e.g. often used in the web framework Ruby on Rails. Often
|
73
|
-
# a variation of it is used to define class and instance side behaviour for
|
74
|
-
# mixin modules
|
75
|
-
|
76
|
-
module MathMixin
|
77
|
-
def counter
|
78
|
-
@counter ||= 0
|
79
|
-
@counter += 1
|
80
|
-
end
|
81
|
-
|
82
|
-
module ClassMethods
|
83
|
-
def sqrt(x)
|
84
|
-
x ** 0.5
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def self.included(base)
|
89
|
-
base.send(:extend, ClassMethods)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
class SimpleMath
|
94
|
-
include MathMixin
|
95
|
-
end
|
96
|
-
|
97
|
-
example do
|
98
|
-
result_of(SimpleMath.sqrt(4)) == 2
|
99
|
-
|
100
|
-
my_simple_math = SimpleMath.new
|
101
|
-
result_of(my_simple_math.counter) == 1
|
102
|
-
result_of(my_simple_math.counter) == 2
|
103
|
-
end
|
104
|
-
|
105
|
-
# This is regarded as the most elegant way of defining class and instance
|
106
|
-
# methods for a mixin module. And the basic functionality is the same as in the
|
107
|
-
# previous example.
|
108
|
-
|
109
|
-
# After we now know how to define class side behaviour, everybody is curious
|
110
|
-
# to know how to extend this behaviour using context-oriented programming and
|
111
|
-
# ContextR.
|
112
|
-
#
|
113
|
-
# Additionial, context-dependent behaviour is defined in modules. These modules
|
114
|
-
# are then attached to the class with a selector representing the layer, in
|
115
|
-
# which the behaviour should reside. For examples on the instance side
|
116
|
-
# have a look at the bottom of test_introduction.
|
117
|
-
#
|
118
|
-
# Let's how we can achieve the same on the class side for each of the different
|
119
|
-
# methods of defining class side behaviour.
|
120
|
-
|
121
|
-
|
122
|
-
# Using def self.method_name
|
123
|
-
# ==========================
|
124
|
-
|
125
|
-
# Okay, we won't get rid of the modules, used to encapsulate the
|
126
|
-
# context-dependent behaviour, so the extension is a bit noisier, than the
|
127
|
-
# basic notation.
|
128
|
-
|
129
|
-
class SimpleMath
|
130
|
-
module AccessControlMethods
|
131
|
-
def pi
|
132
|
-
"You are not allowed to access this method"
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
extend AccessControlMethods => :access_control
|
137
|
-
end
|
138
|
-
|
139
|
-
# But we can use the same principles, like we did for the instance side. Simply
|
140
|
-
# use a hash to tell extend, that the module should only be used in a certain
|
141
|
-
# layer.
|
142
|
-
|
143
|
-
example do
|
144
|
-
result_of(SimpleMath.pi) == 3.14159265
|
145
|
-
|
146
|
-
ContextR::with_layer :access_control do
|
147
|
-
result_of(SimpleMath.pi) == "You are not allowed to access this method"
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
|
152
|
-
# Using class << self
|
153
|
-
# ===================
|
154
|
-
|
155
|
-
# When your using the eigenclass, you are able to use to good old include to
|
156
|
-
# manage the extension. But nobody stops you if you fell in love with extend.
|
157
|
-
|
158
|
-
class SimpleMath
|
159
|
-
class << self
|
160
|
-
module EnglishMethods
|
161
|
-
def e
|
162
|
-
"Euler's constant"
|
163
|
-
end
|
164
|
-
end
|
165
|
-
include EnglishMethods => :english
|
166
|
-
end
|
167
|
-
|
168
|
-
module GermanMethods
|
169
|
-
def e
|
170
|
-
"Eulersche Zahl"
|
171
|
-
end
|
172
|
-
end
|
173
|
-
extend GermanMethods => :german
|
174
|
-
end
|
175
|
-
|
176
|
-
example do
|
177
|
-
result_of(SimpleMath.e) == 2.71828183
|
178
|
-
|
179
|
-
ContextR::with_layer :german do
|
180
|
-
result_of(SimpleMath.e) == "Eulersche Zahl"
|
181
|
-
end
|
182
|
-
ContextR::with_layer :english do
|
183
|
-
result_of(SimpleMath.e) == "Euler's constant"
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
|
188
|
-
# Using a module
|
189
|
-
# ==============
|
190
|
-
|
191
|
-
# Hey, this is what we did all the time, so it is only natural to have the same
|
192
|
-
# syntax to extend a class using in module for context-dependent behaviour. But
|
193
|
-
# for the sake of completeness, I will attach another example.
|
194
|
-
|
195
|
-
class SimpleMath
|
196
|
-
module ExactComputationMethods
|
197
|
-
def golden_ratio
|
198
|
-
sleep(0.01) # In real life this would take a bit longer,
|
199
|
-
# but I don't have the time.
|
200
|
-
1.6180339887_4989484820_4586834365_6381177203_0917980576
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
extend ExactComputationMethods => :exact_computation
|
205
|
-
end
|
206
|
-
|
207
|
-
example do
|
208
|
-
result_of(SimpleMath.golden_ratio) == 1.6180339887
|
209
|
-
|
210
|
-
ContextR::with_layer :exact_computation do
|
211
|
-
result_of(SimpleMath.golden_ratio) ==
|
212
|
-
1.6180339887_4989484820_4586834365_6381177203_0917980576
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
|
217
|
-
# Conclusion
|
218
|
-
# ==========
|
219
|
-
|
220
|
-
# In general, there are two options to define context-dependent class side
|
221
|
-
# behaviour. Use include in the eigenclass or use extend anywhere else. Both
|
222
|
-
# options result in the same behaviour, just like the different options in
|
223
|
-
# plain ruby look different, but have the same effect.
|
224
|
-
#
|
225
|
-
# The programmer is free to use, whatever suites best. This is still Ruby.
|
4
|
+
LiterateMarukuTest.load(__FILE__)
|
data/test/test_contextr.rb
CHANGED
@@ -5,15 +5,3 @@
|
|
5
5
|
# converted to tests, to make sure, that all documentation is in sync with
|
6
6
|
# the implementation. You may find these documents in this directory. It is
|
7
7
|
# just, that they do not look like test, but they are. Believe me.
|
8
|
-
#
|
9
|
-
# require File.dirname(__FILE__) + '/test_helper.rb'
|
10
|
-
#
|
11
|
-
# class TestContextR < Test::Unit::TestCase
|
12
|
-
#
|
13
|
-
# def setup
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# def test_truth
|
17
|
-
# assert true
|
18
|
-
# end
|
19
|
-
# end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
This explanation is a bit tricky. First of all thread programming is always
|
2
|
+
tricky and then, testing threads to gain repeatable results makes it even less
|
3
|
+
readable.
|
4
|
+
|
5
|
+
I tried to improve it by defining a little helper method called `step`. It shall
|
6
|
+
test if all chunks are executed in the expected order and additionally check
|
7
|
+
if the expected layers are activated.
|
8
|
+
|
9
|
+
|
10
|
+
Layer activation shall be dynamically scoped. This is basically no problem, but
|
11
|
+
it gets messy when doing thread programming and switching between scopes. The
|
12
|
+
ugly part is done by Christian Neukirchen's dynamic.rb library.
|
13
|
+
|
14
|
+
The following example does not demonstrate anything useful. It is just a more or
|
15
|
+
less readable test. Follow the `step`s, if you want to get the execution order.
|
16
|
+
As you may see, leaving the inner block in step 5 results in the "lost" layer
|
17
|
+
`:b` and it is "restored" in step 6. This is the base line of this test. All
|
18
|
+
the rest is support code.
|
19
|
+
|
20
|
+
example do
|
21
|
+
def step(index, *layers)
|
22
|
+
@step ||= 0
|
23
|
+
assert_equal(index, @step += 1) if index
|
24
|
+
assert_equal(layers, ContextR::active_layers)
|
25
|
+
end
|
26
|
+
|
27
|
+
mutex = Mutex.new
|
28
|
+
|
29
|
+
one_block = lambda do
|
30
|
+
mutex.lock
|
31
|
+
ContextR::with_layer :b do
|
32
|
+
step(3, :a, :b)
|
33
|
+
mutex.unlock
|
34
|
+
sleep(0.1)
|
35
|
+
mutex.lock
|
36
|
+
step(5, :a, :b)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
two_block = lambda do
|
41
|
+
mutex.lock
|
42
|
+
step(4, :a)
|
43
|
+
mutex.unlock
|
44
|
+
end
|
45
|
+
|
46
|
+
step(1)
|
47
|
+
ContextR::with_layer :a do
|
48
|
+
step(2, :a)
|
49
|
+
|
50
|
+
one = Thread.new(&one_block)
|
51
|
+
two = Thread.new(&two_block)
|
52
|
+
|
53
|
+
step(nil, :a)
|
54
|
+
|
55
|
+
one.join
|
56
|
+
two.join
|
57
|
+
|
58
|
+
step(6, :a)
|
59
|
+
end
|
60
|
+
step(7)
|
61
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
One of the most powerful features of Ruby is the concept of open classes. At
|
2
|
+
everytime, the programmer is able to change class, instances and methods.
|
3
|
+
This has immediate effects on all instances and works like a charm.
|
4
|
+
|
5
|
+
One of the goals of ContextR 0.1.0 was to bring this power to the
|
6
|
+
context-oriented abstraction. The following examples will simply demonstrate,
|
7
|
+
that it works. Not more, not less.
|
8
|
+
|
9
|
+
class TrafficLight
|
10
|
+
def initialize
|
11
|
+
@state = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def state_ordering
|
15
|
+
@state_ordering ||= [:red, :yellow, :green, :yellow]
|
16
|
+
end
|
17
|
+
|
18
|
+
def current
|
19
|
+
state_ordering[@state]
|
20
|
+
end
|
21
|
+
|
22
|
+
def next
|
23
|
+
@state += 1
|
24
|
+
@state = 0 if @state >= state_ordering.size
|
25
|
+
current
|
26
|
+
end
|
27
|
+
|
28
|
+
def red
|
29
|
+
current == :red
|
30
|
+
end
|
31
|
+
def yellow
|
32
|
+
current == :yellow
|
33
|
+
end
|
34
|
+
def green
|
35
|
+
current == :green
|
36
|
+
end
|
37
|
+
|
38
|
+
def text
|
39
|
+
current.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Here we have a simple dutch traffic light. Let's test if it works.
|
44
|
+
|
45
|
+
$traffic_light = TrafficLight.new
|
46
|
+
example do
|
47
|
+
# It is always a good idea, to start with red
|
48
|
+
result_of($traffic_light.red) == true
|
49
|
+
|
50
|
+
$traffic_light.next
|
51
|
+
result_of($traffic_light.yellow) == true
|
52
|
+
|
53
|
+
$traffic_light.next
|
54
|
+
result_of($traffic_light.green) == true
|
55
|
+
|
56
|
+
$traffic_light.next
|
57
|
+
result_of($traffic_light.yellow) == true
|
58
|
+
|
59
|
+
$traffic_light.next
|
60
|
+
result_of($traffic_light.red) == true
|
61
|
+
end
|
62
|
+
|
63
|
+
But in Germany the lights work different. The sequence looks like the
|
64
|
+
following
|
65
|
+
red
|
66
|
+
red and yellow
|
67
|
+
green
|
68
|
+
yellow
|
69
|
+
red
|
70
|
+
|
71
|
+
Let's build it with in an additional :german layer. All we need to do is
|
72
|
+
insert the new state ordering and change the red and yellow methods. They
|
73
|
+
should both return true, when the :red_and_yellow state is active.
|
74
|
+
|
75
|
+
class TrafficLight
|
76
|
+
in_layer :german do
|
77
|
+
def state_ordering
|
78
|
+
@state_ordering ||= [:red, :red_and_yellow, :green, :yellow]
|
79
|
+
end
|
80
|
+
|
81
|
+
def red
|
82
|
+
(yield(:receiver).current == :red_and_yellow) or yield(:next)
|
83
|
+
end
|
84
|
+
def yellow
|
85
|
+
(yield(:receiver).current == :red_and_yellow) or yield(:next)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
example do
|
91
|
+
ContextR::with_layer :german do
|
92
|
+
result_of($traffic_light.red) == true
|
93
|
+
|
94
|
+
$traffic_light.next
|
95
|
+
result_of($traffic_light.red) == true
|
96
|
+
result_of($traffic_light.yellow) == true
|
97
|
+
|
98
|
+
$traffic_light.next
|
99
|
+
result_of($traffic_light.green) == true
|
100
|
+
|
101
|
+
$traffic_light.next
|
102
|
+
result_of($traffic_light.yellow) == true
|
103
|
+
|
104
|
+
$traffic_light.next
|
105
|
+
result_of($traffic_light.red) == true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
Now we have a traffic light, that is able to work in the Netherlands and in
|
111
|
+
Germany. But this is just the start. This example should show, that both
|
112
|
+
method modules and the base implementation may be changed at runtime and
|
113
|
+
the changes have immediate effect, just like they do in the basic ruby world.
|
114
|
+
|
115
|
+
In this example would like to change the textual representation a bit. I
|
116
|
+
think they do not give much information. Let's change extend them.
|
117
|
+
|
118
|
+
example do
|
119
|
+
result_of($traffic_light.text) == "red"
|
120
|
+
class TrafficLight
|
121
|
+
# When running these test with ruby -w the following line will raise a
|
122
|
+
# warning, that you are discarding the old method definition. To avoid
|
123
|
+
# these simply undefine it before defining a new implementation.
|
124
|
+
def text
|
125
|
+
case @state
|
126
|
+
when 0 : "It's red. Stop immediately."
|
127
|
+
when 1 : "It's yellow. Prepare to start. It will be green soon."
|
128
|
+
when 2 : "It's green. Hit it."
|
129
|
+
when 3 : "It's yellow. Attention, it will be red soon. You better stop."
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
result_of($traffic_light.text) == "It's red. Stop immediately."
|
134
|
+
end
|
135
|
+
|
136
|
+
Okay this works fine. But we also need to change it in case of the german
|
137
|
+
traffic light.
|
138
|
+
|
139
|
+
example do
|
140
|
+
# The old behaviour
|
141
|
+
ContextR::with_layer :german do
|
142
|
+
$traffic_light.next
|
143
|
+
result_of($traffic_light.text) ==
|
144
|
+
"It's yellow. Prepare to start. It will be green soon."
|
145
|
+
end
|
146
|
+
|
147
|
+
# It's redefinition
|
148
|
+
class TrafficLight
|
149
|
+
in_layer :german do
|
150
|
+
def text
|
151
|
+
if yield(:receiver).current == :red_and_yellow
|
152
|
+
"It's red and yellow at once. It will be green soon."
|
153
|
+
else
|
154
|
+
yield(:next)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# The new behaviour
|
161
|
+
ContextR::with_layer :german do
|
162
|
+
result_of($traffic_light.text) ==
|
163
|
+
"It's red and yellow at once. It will be green soon."
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
One could argue, that we did not actually change the implementation, but just
|
168
|
+
added a method. Okay. Then let's change this method and translate the text.
|
169
|
+
This is a german traffic light, right?
|
170
|
+
|
171
|
+
example do
|
172
|
+
# The old behaviour
|
173
|
+
ContextR::with_layer :german do
|
174
|
+
result_of($traffic_light.text) ==
|
175
|
+
"It's red and yellow at once. It will be green soon."
|
176
|
+
end
|
177
|
+
|
178
|
+
class TrafficLight
|
179
|
+
in_layer :german do
|
180
|
+
# When running these test with ruby -w the following line will raise
|
181
|
+
# a warning, that you are discarding the old method definition. To
|
182
|
+
# avoid these simply undefine it before defining a new implementation.
|
183
|
+
def text
|
184
|
+
case yield(:receiver).current
|
185
|
+
when :red : "Es ist rot. Anhalten."
|
186
|
+
when :red_and_yellow : "Es ist gelb und rot gleichzeitig."
|
187
|
+
when :green : "Grün. Gib Gas."
|
188
|
+
when :yellow : "Das ist gelb. Gleich ist es rot. Halt lieber an."
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# The new behaviour
|
195
|
+
ContextR::with_layer :german do
|
196
|
+
result_of($traffic_light.text) == "Es ist gelb und rot gleichzeitig."
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
This was just a simple demonstration, that all the dynamics that are within
|
201
|
+
Ruby are still present, when you are using ContextR. No need to worry.
|