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.
@@ -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.