contextr 0.1.1 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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__)
@@ -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,4 @@
1
+ require File.dirname(__FILE__) + "/test_helper.rb"
2
+
3
+ test_class(:TestDynamicScope)
4
+ LiterateMarukuTest.load(__FILE__)
@@ -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.