contextr 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,19 @@
1
- require File.dirname(__FILE__) + '/test_helper.rb'
2
-
3
- class TestContextR < Test::Unit::TestCase
4
-
5
- def setup
6
- end
7
-
8
- def test_truth
9
- assert true
10
- end
11
- end
1
+ # ContextR uses RSpec to test its implementation. For the relevant code
2
+ # have a look at the spec folder.
3
+ #
4
+ # Additionally ContextR has lots of descriptive manuals that are automatically
5
+ # converted to tests, to make sure, that all documentation is in sync with
6
+ # the implementation. You may find these documents in this directory. It is
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,207 @@
1
+ require File.dirname(__FILE__) + "/test_helper.rb"
2
+
3
+ test_class(:TestDynamics)
4
+
5
+ # One of the most powerful features of Ruby is the concept of open classes. At
6
+ # everytime, the programmer is able to change class, instances and methods.
7
+ # This has immediate effects on all instances and works like a charm.
8
+ #
9
+ # One of the goals of ContextR 0.1.0 was to bring this power to the
10
+ # context-oriented abstraction. The following examples will simply demonstrate,
11
+ # that it works. Not more, not less.
12
+
13
+ class TrafficLight
14
+ def initialize
15
+ @state = 0
16
+ end
17
+
18
+ def state_ordering
19
+ @state_ordering ||= [:red, :yellow, :green, :yellow]
20
+ end
21
+
22
+ def current
23
+ state_ordering[@state]
24
+ end
25
+
26
+ def next
27
+ @state += 1
28
+ @state = 0 if @state >= state_ordering.size
29
+ current
30
+ end
31
+
32
+ def red
33
+ current == :red
34
+ end
35
+ def yellow
36
+ current == :yellow
37
+ end
38
+ def green
39
+ current == :green
40
+ end
41
+
42
+ def text
43
+ current.to_s
44
+ end
45
+ end
46
+
47
+ # Here we have a simple dutch traffic light. Let's test if it works.
48
+
49
+ $traffic_light = TrafficLight.new
50
+ example do
51
+ # It is always a good idea, to start with red
52
+ result_of($traffic_light.red) == true
53
+
54
+ $traffic_light.next
55
+ result_of($traffic_light.yellow) == true
56
+
57
+ $traffic_light.next
58
+ result_of($traffic_light.green) == true
59
+
60
+ $traffic_light.next
61
+ result_of($traffic_light.yellow) == true
62
+
63
+ $traffic_light.next
64
+ result_of($traffic_light.red) == true
65
+ end
66
+
67
+ # But in Germany the lights work different. The sequence looks like the
68
+ # following
69
+ # red
70
+ # red and yellow
71
+ # green
72
+ # yellow
73
+ # red
74
+ #
75
+ # Let's build it with in an additional :german layer. All we need to do is
76
+ # insert the new state ordering and change the red and yellow methods. They
77
+ # should both return true, when the :red_and_yellow state is active.
78
+
79
+ class TrafficLight
80
+ module GermanSequence
81
+ def state_ordering
82
+ @state_ordering ||= [:red, :red_and_yellow, :green, :yellow]
83
+ end
84
+
85
+ def red
86
+ (yield(:receiver).current == :red_and_yellow) or yield(:next)
87
+ end
88
+ def yellow
89
+ (yield(:receiver).current == :red_and_yellow) or yield(:next)
90
+ end
91
+ end
92
+
93
+ include GermanSequence => :german
94
+ end
95
+
96
+ example do
97
+ ContextR::with_layer :german do
98
+ result_of($traffic_light.red) == true
99
+
100
+ $traffic_light.next
101
+ result_of($traffic_light.red) == true
102
+ result_of($traffic_light.yellow) == true
103
+
104
+ $traffic_light.next
105
+ result_of($traffic_light.green) == true
106
+
107
+ $traffic_light.next
108
+ result_of($traffic_light.yellow) == true
109
+
110
+ $traffic_light.next
111
+ result_of($traffic_light.red) == true
112
+ end
113
+ end
114
+
115
+
116
+ # Now we have a traffic light, that is able to work in the Netherlands and in
117
+ # Germany. But this is just the start. This example should show, that both
118
+ # method modules and the base implementation may be changed at runtime and
119
+ # the changes have immediate effect, just like they do in the basic ruby world.
120
+ #
121
+ # In this example would like to change the textual representation a bit. I
122
+ # think they do not give much information. Let's change extend them.
123
+
124
+ example do
125
+ result_of($traffic_light.text) == "red"
126
+ class TrafficLight
127
+ # When running these test with ruby -w the following line will raise a
128
+ # warning, that you are discarding the old method definition. To avoid
129
+ # these simply undefine it before defining a new implementation.
130
+ def text
131
+ case @state
132
+ when 0 : "It's red. Stop immediately."
133
+ when 1 : "It's yellow. Prepare to start. It will be green soon."
134
+ when 2 : "It's green. Hit it."
135
+ when 3 : "It's yellow. Attention, it will be red soon. You better stop."
136
+ end
137
+ end
138
+ end
139
+ result_of($traffic_light.text) == "It's red. Stop immediately."
140
+ end
141
+
142
+ # Okay this works fine. But we also need to change it in case of the german
143
+ # traffic light.
144
+
145
+ example do
146
+ # The old behaviour
147
+ ContextR::with_layer :german do
148
+ $traffic_light.next
149
+ result_of($traffic_light.text) ==
150
+ "It's yellow. Prepare to start. It will be green soon."
151
+ end
152
+
153
+ # It's redefinition
154
+ class TrafficLight
155
+ module GermanSequence
156
+ def text
157
+ if yield(:receiver).current == :red_and_yellow
158
+ "It's red and yellow at once. It will be green soon."
159
+ else
160
+ yield(:next)
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ # The new behaviour
167
+ ContextR::with_layer :german do
168
+ result_of($traffic_light.text) ==
169
+ "It's red and yellow at once. It will be green soon."
170
+ end
171
+ end
172
+
173
+ # One could argue, that we did not actually change the implementation, but just
174
+ # added a method. Okay. Then let's change this method and translate the text.
175
+ # This is a german traffic light, right?
176
+
177
+ example do
178
+ # The old behaviour
179
+ ContextR::with_layer :german do
180
+ result_of($traffic_light.text) ==
181
+ "It's red and yellow at once. It will be green soon."
182
+ end
183
+
184
+ class TrafficLight
185
+ module GermanSequence
186
+ # When running these test with ruby -w the following line will raise a
187
+ # warning, that you are discarding the old method definition. To avoid
188
+ # these simply undefine it before defining a new implementation.
189
+ def text
190
+ case yield(:receiver).current
191
+ when :red : "Es ist rot. Anhalten."
192
+ when :red_and_yellow : "Es ist gelb und rot gleichzeitig."
193
+ when :green : "Grün. Gib Gas."
194
+ when :yellow : "Das ist gelb. Gleich ist es rot. Halt lieber an."
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ # The new behaviour
201
+ ContextR::with_layer :german do
202
+ result_of($traffic_light.text) == "Es ist gelb und rot gleichzeitig."
203
+ end
204
+ end
205
+
206
+ # This was just a simple demonstration, that all the dynamics that are within
207
+ # Ruby are still present, when you are using ContextR. No need to worry.
data/test/test_helper.rb CHANGED
@@ -1,2 +1,57 @@
1
1
  require 'test/unit'
2
2
  require File.dirname(__FILE__) + '/../lib/contextr'
3
+
4
+ unless Object.const_defined?("ExampleTest")
5
+ module ExampleTest
6
+ module ObjectExtension
7
+ def test_class(name)
8
+ $latest_test_class = Class.new(Test::Unit::TestCase)
9
+ $latest_test_case = 0
10
+ Object.const_set(name, $latest_test_class)
11
+ end
12
+
13
+ def example(&block)
14
+ $latest_test_class.class_eval do
15
+ define_method("test_%03d" % ($latest_test_case += 1), &block)
16
+ end
17
+ end
18
+ end
19
+
20
+ module TestExtension
21
+ def assert_to_s(expected, actual)
22
+ assert_equal(expected, actual.to_s)
23
+ end
24
+
25
+ def result_of(object)
26
+ Result.new(object, self)
27
+ end
28
+
29
+ def output_of(object)
30
+ Output.new(object, self)
31
+ end
32
+
33
+ class Result
34
+ attr_accessor :object, :test_class
35
+ def initialize(object, test_class)
36
+ self.object = object
37
+ self.test_class = test_class
38
+ end
39
+ def ==(string)
40
+ test_class.assert_equal(string, object)
41
+ end
42
+ end
43
+ class Output < Result
44
+ def ==(string)
45
+ test_class.assert_equal(string, object.to_s)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ class Test::Unit::TestCase
52
+ include ExampleTest::TestExtension
53
+ end
54
+ class Object
55
+ include ExampleTest::ObjectExtension
56
+ end
57
+ end
@@ -0,0 +1,311 @@
1
+ require File.dirname(__FILE__) + "/test_helper.rb"
2
+
3
+ test_class(:TestIntroduction)
4
+
5
+ # Let's build a simple student's database.
6
+ # Each University has a name and address. Each student has a name, address
7
+ # and an associated university.
8
+ #
9
+ # We are using a Struct to build our classes in an easy way. This provides all
10
+ # getters, setters and an easy constructor setting all the instance variables.
11
+ #
12
+ # In order to get a nice output in our program we override the #to_s method
13
+ # which is used in many cases by ruby, e.g. in Kernel#puts or in String
14
+ # interpolation.
15
+ #
16
+ # In most of the cases, the name is sufficient to represent each entity, i.e.
17
+ # a student or a university.
18
+
19
+ class University < Struct.new(:name, :address)
20
+ def to_s
21
+ name
22
+ end
23
+ end
24
+
25
+ class Student < Struct.new(:name, :address, :university)
26
+ def to_s
27
+ name
28
+ end
29
+ end
30
+
31
+ # Under certain circumstances we would like to have a more verbose output.
32
+ # This could mean print the university a student belongs to or attach the
33
+ # address to the output.
34
+
35
+
36
+ # Additonal methods
37
+ # =================
38
+
39
+ # In a plain old Ruby project, this would result in additional methods, propably
40
+ # encapsulated in modules, that will be included into our classes. This
41
+ # allows reuse and better encapsulation.
42
+
43
+ module AddressOutput
44
+ def to_s_with_address
45
+ "#{self} (#{self.address})"
46
+ end
47
+ end
48
+
49
+ class University
50
+ include AddressOutput
51
+ end
52
+
53
+ # Now each university got a to_s_with_address method that could be called
54
+ # instead of to_s if you would like to have additional information.
55
+
56
+ class Student
57
+ include AddressOutput
58
+
59
+ def to_s_with_university
60
+ "#{self}; #{self.unversity}"
61
+ end
62
+ def to_s_with_university_and_address
63
+ "#{self.to_s_with_address}; #{self.unversity.to_s_with_address}"
64
+ end
65
+ end
66
+
67
+ # The same for each student. #to_s_with_unversity and
68
+ # #to_s_with_university_and_address give as well additional output.
69
+ #
70
+ # So how can you use it. Let's create some instances first.
71
+
72
+ $hpi = University.new("HPI", "Potsdam")
73
+ $gregor = Student.new("Gregor", "Berlin", $hpi)
74
+
75
+ # An now some output. This could live inside an erb template, a graphical ui or
76
+ # printed to the command line. In all these cases to_s is called automatically
77
+ # by the standard libary to receive a good representation of the object.
78
+ # The output method defined in test_helper.rb simulates this behaviour. All
79
+ # examples are converted to test class automatically, so we can be sure, that
80
+ # this document stays in sync with the libary.
81
+ #
82
+ # puts gregor # => prints "Gregor"
83
+ # "#{gregor}" # => evaluates to "Gregor"
84
+ # <%= gregor %> => as well as this
85
+
86
+ example do
87
+ output_of($gregor) == "Gregor"
88
+ output_of($hpi) == "HPI"
89
+ end
90
+
91
+ # Assume, we would like to print an address list now.
92
+
93
+ example do
94
+ output_of($gregor.to_s_with_address) == "Gregor (Berlin)"
95
+ end
96
+
97
+ # If you want a list with university and addresses, you would use
98
+ # #to_s_with_university_and_address. No automatic call to to_s anymore. If you
99
+ # have your layout in an erb template, you have to change each and every
100
+ # occurrence of your variables.
101
+
102
+
103
+ # Redefining to_s
104
+ # ===============
105
+
106
+ # To solve this problem you could redefine to_s on demand. I will demonstrate
107
+ # this with some meta programming in a fresh class.
108
+
109
+ module GenericToS
110
+ def to_s
111
+ self.class.included_vars.collect do |var|
112
+ self.send(var)
113
+ end.join("; ")
114
+ end
115
+
116
+
117
+ module ClassMethods
118
+ attr_accessor :included_vars
119
+ def set_to_s(*included_vars)
120
+ self.included_vars = included_vars
121
+ end
122
+ end
123
+
124
+ def self.included(base_class)
125
+ base_class.send(:extend, ClassMethods)
126
+ end
127
+ end
128
+
129
+ class Company < Struct.new(:name, :address)
130
+ include GenericToS
131
+ end
132
+
133
+ class Employee < Struct.new(:name, :address, :company)
134
+ include GenericToS
135
+ end
136
+
137
+ # I will not go into detail how this code works, but I will show you how to use
138
+ # it. Let's get some instances first.
139
+
140
+ $ms = Company.new("Microsoft", "Redmond")
141
+ $bill = Employee.new("Bill", "Redmond", $ms)
142
+
143
+ # And now use these instances.
144
+
145
+ example do
146
+ Company.set_to_s(:name)
147
+ Employee.set_to_s(:name)
148
+
149
+ output_of($ms) == "Microsoft"
150
+ output_of($bill) == "Bill"
151
+ end
152
+
153
+ # Let's get the output including the addresses
154
+ example do
155
+ Employee.set_to_s(:name, :address)
156
+
157
+ output_of($bill) == "Bill; Redmond"
158
+ end
159
+
160
+ # And including the employer
161
+ example do
162
+ Employee.set_to_s(:name, :address, :company)
163
+
164
+ output_of($bill) == "Bill; Redmond; Microsoft"
165
+ end
166
+
167
+ # But hey. I wanted to have a list with all addresses, not just to employee's.
168
+ # This should be an address list, right? But we did not tell the Company class
169
+ # to print the address, but just the Employee class.
170
+ #
171
+ # So in our first approach, we had to change each place, where we use the
172
+ # object. In the second approach we have to know all places where an address
173
+ # is stored and apply the changes in there.
174
+ #
175
+ # By the way, what happens, if i was useing a multi-threaded application and
176
+ # one user request a simple name list, and the other switches to an address list
177
+ # in the meantime. Then the output will be mixed - with and without addresses.
178
+ # This is not exactly what we want. So there has to be an easier, thread safe
179
+ # solution.
180
+
181
+
182
+ # ContextR
183
+ # ========
184
+
185
+ # This is were context-oriented programming comes into play. I will again start
186
+ # from the scratch. It is not much and we all know the problem space now.
187
+
188
+ # The same setup, just another setting. First the basic implementation, just
189
+ # like we did it in our first approach
190
+ class Religion < Struct.new(:name, :origin)
191
+ def to_s
192
+ name
193
+ end
194
+ end
195
+ class Believer < Struct.new(:name, :origin, :religion)
196
+ def to_s
197
+ name
198
+ end
199
+ end
200
+
201
+ # Now define the additional behaviour in separate modules. Please don't be
202
+ # scared because of the strange syntax and method calls.
203
+ # yield(:receiver) refers to the "normal" self when these modules are included
204
+ # yield(:next) is much like a super call.
205
+ #
206
+ # Future versions of ContextR will hopefully provide a nicer syntax here.
207
+ module OriginMethods
208
+ def to_s
209
+ "#{yield(:next)} (#{yield(:receiver).origin})"
210
+ end
211
+ end
212
+ module ReligionMethods
213
+ def to_s
214
+ "#{yield(:next)}; #{yield(:receiver).religion}"
215
+ end
216
+ end
217
+
218
+ # Finally we need to link our additional behaviour to our basic classes.
219
+ # We also need to tell the framework, when this behaviour should be applied.
220
+ class Religion
221
+ include OriginMethods => :location
222
+ end
223
+ class Believer
224
+ include OriginMethods => :location
225
+ include ReligionMethods => :believe
226
+ end
227
+
228
+ # The additional context dependent behaviour is organised within layers. A
229
+ # single layer may span multiple classes - in this case the location layer does.
230
+ # To enable the additional code, the programmes shall activate layers. A
231
+ # layer activation is only effective within a block scope and within the
232
+ # current thread.
233
+ #
234
+ # Let's see, how it looks like when we use it.
235
+
236
+ $christianity = Religion.new("Christianity", "Israel")
237
+ $the_pope = Believer.new("Benedikt XVI", "Bavaria", $christianity)
238
+
239
+ example do
240
+ output_of($christianity) == "Christianity"
241
+ output_of($the_pope) == "Benedikt XVI"
242
+ end
243
+
244
+ # Would like to have an address? For this we have to activate the location
245
+ # layer. Now the additional behaviour defined within the layer, will be
246
+ # executed around the base method defined within the class
247
+
248
+ example do
249
+ ContextR.with_layer :location do
250
+ output_of($christianity) == "Christianity (Israel)"
251
+ output_of($the_pope) == "Benedikt XVI (Bavaria)"
252
+ end
253
+ end
254
+
255
+ # Of course the additional behaviour is deactivated automatically after the
256
+ # blocks execution.
257
+
258
+ example do
259
+ output_of($christianity) == "Christianity"
260
+ output_of($the_pope) == "Benedikt XVI"
261
+ end
262
+
263
+ # Everything back to normal.
264
+ #
265
+ # Lets activate the believe layer
266
+
267
+ example do
268
+ ContextR.with_layer :believe do
269
+ output_of($the_pope) == "Benedikt XVI; Christianity"
270
+ end
271
+ end
272
+
273
+ # Now we need both, location and believe. How does it look like? You have to
274
+ # options. You may activate the two one after the other or all at once. It
275
+ # is just a matter of taste, the result remains the same
276
+
277
+ example do
278
+ ContextR.with_layer :believe, :location do
279
+ output_of($the_pope) == "Benedikt XVI (Bavaria); Christianity (Israel)"
280
+ end
281
+ end
282
+
283
+ # As you can see, the activation of the location layer is operative in the
284
+ # whole execution context of the block. Each religion prints its origin, wheter
285
+ # to_s was called directly or indirectly.
286
+ #
287
+ # If you change your mind within your call stack, you may of course deactivate
288
+ # layers again.
289
+
290
+ example do
291
+ ContextR.with_layer :believe do
292
+ ContextR::with_layer :location do
293
+ output_of($the_pope) == "Benedikt XVI (Bavaria); Christianity (Israel)"
294
+
295
+ ContextR.without_layer :believe do
296
+ output_of($the_pope) == "Benedikt XVI (Bavaria)"
297
+ end
298
+
299
+ output_of($the_pope) == "Benedikt XVI (Bavaria); Christianity (Israel)"
300
+ end
301
+ end
302
+ end
303
+
304
+ # These encaspulations may be as complex as your application. ContextR will
305
+ # keep track of all activations and deactivations within the blocks and
306
+ # restore the settings after the block was executed.
307
+ #
308
+ # This was just a short introduction on a problem case, that can be solved
309
+ # with context-oriented programming. You have seen, the advantages and how
310
+ # to use it. In other files in this folder, you can learn more on the dynamics
311
+ # and meta programming interfaces of ContextR