contextr 0.1.0 → 0.1.1

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,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