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,207 +1,4 @@
1
1
  require File.dirname(__FILE__) + "/test_helper.rb"
2
2
 
3
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.
4
+ LiterateMarukuTest.load(__FILE__)
@@ -0,0 +1,70 @@
1
+ Hello World for ContextR
2
+
3
+ example do
4
+ class MyApplication
5
+ def greet
6
+ "Hello, World"
7
+ end
8
+ end
9
+
10
+ app = MyApplication.new
11
+ assert_equal "Hello, World", app.greet
12
+ end
13
+
14
+ And what about request coming from down under? Let's introduce Localization.
15
+
16
+ example do
17
+ class MyApplication
18
+ in_layer :au do
19
+ def greet
20
+ "G'day, mate"
21
+ end
22
+ end
23
+ end
24
+
25
+ app = MyApplication.new
26
+
27
+ assert_equal "Hello, World", app.greet
28
+
29
+ ContextR::with_layer :au do
30
+ assert_equal "G'day, mate", app.greet
31
+ end
32
+ end
33
+
34
+ And what if down under changed its habbits? Let's redefine it.
35
+
36
+ example do
37
+ class MyApplication
38
+ in_layer :au do
39
+ def greet
40
+ yield(:next) + " and God Save the Queen"
41
+ end
42
+ end
43
+ end
44
+
45
+ app = MyApplication.new
46
+
47
+ assert_equal "Hello, World", app.greet
48
+
49
+ ContextR::with_layer :au do
50
+ assert_equal "Hello, World and God Save the Queen", app.greet
51
+ end
52
+ end
53
+
54
+ The key here is, that the method `greet` method in the `:au` layer is redefined
55
+ and no new method is added.
56
+
57
+ example do
58
+ class MyApplication
59
+ in_layer :au do
60
+ $inner_module_1 = self
61
+ end
62
+ end
63
+ class MyApplication
64
+ in_layer :au do
65
+ $inner_module_2 = self
66
+ end
67
+ end
68
+
69
+ assert(($inner_module_1 == $inner_module_2), "Modules should be equal")
70
+ end
@@ -0,0 +1,4 @@
1
+ require File.dirname(__FILE__) + "/test_helper.rb"
2
+
3
+ test_class(:TestHelloWorld)
4
+ LiterateMarukuTest.load(__FILE__)
@@ -1,57 +1,8 @@
1
+ require 'rubygems'
1
2
  require 'test/unit'
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
3
 
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
4
+ require File.dirname(__FILE__) + '/../lib/contextr'
50
5
 
51
- class Test::Unit::TestCase
52
- include ExampleTest::TestExtension
53
- end
54
- class Object
55
- include ExampleTest::ObjectExtension
56
- end
6
+ %w{example literate_maruku}.each do |lib|
7
+ require File.dirname(__FILE__) + "/lib/#{lib}_test"
57
8
  end
@@ -0,0 +1,325 @@
1
+ Let's build a simple student's database. Each University has a name and
2
+ address. Each student has a name, address and an associated university.
3
+
4
+ We are using a `Struct` to build our classes in an easy way. This provides
5
+ all getters, setters and an easy constructor setting all the instance
6
+ variables.
7
+
8
+ **Note**: In order to get a nice output in our program we override the #to\_s
9
+ method which is used in many cases by ruby, e.g. in Kernel#puts or in String
10
+ interpolation.
11
+
12
+ In most of the cases, the name is sufficient to represent each entity, i.e.
13
+ a student or a university.
14
+
15
+ class University < Struct.new(:name, :address)
16
+ def to_s
17
+ name
18
+ end
19
+ end
20
+
21
+ class Student < Struct.new(:name, :address, :university)
22
+ def to_s
23
+ name
24
+ end
25
+ end
26
+
27
+ Under certain circumstances we would like to have a more verbose output.
28
+ This could mean print the university a student belongs to or attach the
29
+ address to the output.
30
+
31
+ Additonal methods
32
+ -----------------
33
+
34
+ In a plain old Ruby project, this would result in additional methods,
35
+ probably encapsulated in modules, that will be included into our classes.
36
+ This allows reuse and better encapsulation.
37
+
38
+ module AddressOutput
39
+ def to_s_with_address
40
+ "#{self} (#{self.address})"
41
+ end
42
+ end
43
+
44
+ class University
45
+ include AddressOutput
46
+ end
47
+
48
+ Now each university got a to\_s\_with\_address method that could be called
49
+ instead of to\_s if you would like to have additional information.
50
+
51
+ class Student
52
+ include AddressOutput
53
+
54
+ def to_s_with_university
55
+ "#{self}; #{self.unversity}"
56
+ end
57
+ def to_s_with_university_and_address
58
+ "#{self.to_s_with_address}; #{self.unversity.to_s_with_address}"
59
+ end
60
+ end
61
+
62
+ The same for each student. #to\_s\_with\_unversity
63
+ and #to\_s\_with\_university\_and\_address give as well additional output.
64
+
65
+ So how can you use it. Let's create some instances first.
66
+
67
+ $hpi = University.new("HPI", "Potsdam")
68
+ $gregor = Student.new("Gregor", "Berlin", $hpi)
69
+
70
+ An now some output.
71
+
72
+ **Note**: This could live inside an erb template, a graphical user
73
+ interface or printed to the command line. In all these cases to\_s is called
74
+ automatically by the standard library to receive a good representation of
75
+ the object.
76
+ The output method defined in test\_helper.rb simulates this behaviour. All
77
+ examples are converted to test class automatically, so we can be sure, that
78
+ this document stays in sync with the library.
79
+
80
+ puts $gregor # => prints "Gregor"
81
+ "#{$gregor}" # => evaluates to "Gregor"
82
+ <%= $gregor %> => as well as this
83
+ {:execute=false}
84
+
85
+ output_of($gregor) == "Gregor"
86
+ output_of($hpi) == "HPI"
87
+ {:execute=false}
88
+
89
+ Assume, we would like to print an address list now.
90
+
91
+ example do
92
+ output_of($gregor.to_s_with_address) == "Gregor (Berlin)"
93
+ end
94
+
95
+ If you want a list with university and addresses, you would
96
+ use #to\_s\_with\_university\_and\_address. No automatic call to to\_s anymore.
97
+ If you have your layout in an erb template, you have to change each and every
98
+ occurrence of your variables.
99
+
100
+
101
+ Redefining to\_s
102
+ ---------------
103
+
104
+ To solve this problem you could redefine to\_s on demand. I will demonstrate
105
+ this with some meta programming in a fresh class.
106
+
107
+ module GenericToS
108
+ def to_s
109
+ self.class.included_vars.collect do |var|
110
+ self.send(var)
111
+ end.join("; ")
112
+ end
113
+
114
+
115
+ module ClassMethods
116
+ attr_accessor :included_vars
117
+ def set_to_s(*included_vars)
118
+ self.included_vars = included_vars
119
+ end
120
+ end
121
+
122
+ def self.included(base_class)
123
+ base_class.send(:extend, ClassMethods)
124
+ end
125
+ end
126
+
127
+ class Company < Struct.new(:name, :address)
128
+ include GenericToS
129
+ end
130
+
131
+ class Employee < Struct.new(:name, :address, :company)
132
+ include GenericToS
133
+ end
134
+
135
+ I will not go into detail how this code works, but I will show you how to
136
+ use it. Let's get some instances first.
137
+
138
+ $ms = Company.new("Microsoft", "Redmond")
139
+ $bill = Employee.new("Bill", "Redmond", $ms)
140
+
141
+ And now use these instances.
142
+
143
+ example do
144
+ Company.set_to_s(:name)
145
+ Employee.set_to_s(:name)
146
+
147
+ output_of($ms) == "Microsoft"
148
+ output_of($bill) == "Bill"
149
+ end
150
+
151
+ Let's get the output including the addresses
152
+
153
+ example do
154
+ Employee.set_to_s(:name, :address)
155
+
156
+ output_of($bill) == "Bill; Redmond"
157
+ end
158
+
159
+ And including the employer
160
+
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 the
168
+ employee's. This should be an address list, right? But we did not tell
169
+ the Company class 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 using a multi-threaded application and
176
+ one user request a simple name list, and the other switches to an address
177
+ list in the meantime. Then the output will be mixed - with and without
178
+ addresses. This is not exactly what we want. So there has to be an easier,
179
+ thread safe solution.
180
+
181
+
182
+ ContextR
183
+ --------
184
+
185
+ This is were context-oriented programming comes into play. I will again
186
+ start from the scratch. It is not much and we all know the problem space
187
+ now.
188
+
189
+ The same setup, just another setting. First the basic implementation, just
190
+ like we did it in our first approach.
191
+
192
+ class Religion < Struct.new(:name, :origin)
193
+ def to_s
194
+ name
195
+ end
196
+ end
197
+ class Believer < Struct.new(:name, :origin, :religion)
198
+ def to_s
199
+ name
200
+ end
201
+ end
202
+
203
+ Now define the additional behaviour in separate modules. Please don't be
204
+ scared because of the strange syntax and method calls.
205
+ yield(:receiver) refers to the "normal" self when these modules are
206
+ included.
207
+
208
+ Future versions of ContextR will hopefully provide a nicer syntax here.
209
+
210
+ Finally we need to link our additional behaviour to our basic classes.
211
+ We also need to tell the framework, when this behaviour should be applied.
212
+
213
+ module OriginMethods
214
+ def to_s
215
+ "#{super} (#{yield(:receiver).origin})"
216
+ end
217
+ end
218
+
219
+ class Religion
220
+ in_layer :location do
221
+ include OriginMethods
222
+ end
223
+ end
224
+ class Believer
225
+ in_layer :location do
226
+ include OriginMethods
227
+ end
228
+ in_layer :believe do
229
+ def to_s
230
+ "#{super}; #{yield(:receiver).religion}"
231
+ end
232
+ end
233
+ end
234
+
235
+ The additional context dependent behaviour is organised within layers. A
236
+ single layer may span multiple classes - in this case the location layer
237
+ does. To enable the additional code, the programmes shall activate layers.
238
+ A layer activation is only effective within a block scope and within the
239
+ current thread.
240
+
241
+ Let's see, how it looks like when we use it.
242
+
243
+ $christianity = Religion.new("Christianity", "Israel")
244
+ $the_pope = Believer.new("Benedikt XVI", "Bavaria", $christianity)
245
+
246
+ example do
247
+ output_of($christianity) == "Christianity"
248
+ output_of($the_pope) == "Benedikt XVI"
249
+ end
250
+
251
+ Would like to have an address? For this we have to activate the location
252
+ layer. Now the additional behaviour defined within the layer, will be
253
+ executed around the base method defined within the class.
254
+
255
+ example do
256
+ ContextR.with_layer :location do
257
+ output_of($christianity) == "Christianity (Israel)"
258
+ output_of($the_pope) == "Benedikt XVI (Bavaria)"
259
+ end
260
+ end
261
+
262
+
263
+ Of course the additional behaviour is deactivated automatically after the
264
+ blocks execution.
265
+
266
+ example do
267
+ output_of($christianity) == "Christianity"
268
+ output_of($the_pope) == "Benedikt XVI"
269
+ end
270
+
271
+ Everything back to normal.
272
+
273
+ Lets activate the believe layer:
274
+
275
+ example do
276
+ ContextR.with_layer :believe do
277
+ output_of($the_pope) == "Benedikt XVI; Christianity"
278
+ end
279
+ end
280
+
281
+ Now we need both, location and believe. How does it look like? You have to
282
+ options. You may activate the two one after the other or all at once. It
283
+ is just a matter of taste, the result remains the same.
284
+
285
+ example do
286
+ ContextR.with_layer :believe, :location do
287
+ output_of($the_pope) == "Benedikt XVI (Bavaria); Christianity (Israel)"
288
+ end
289
+ end
290
+
291
+ As you can see, the activation of the location layer is operative in the
292
+ whole execution context of the block. Each religion prints its origin,
293
+ whether to\_s was called directly or indirectly.
294
+
295
+ If you change your mind within your call stack, you may of course
296
+ deactivate layers again.
297
+
298
+ example do
299
+ ContextR.with_layer :believe do
300
+ ContextR::with_layer :location do
301
+ output_of($the_pope) ==
302
+ "Benedikt XVI (Bavaria); Christianity (Israel)"
303
+
304
+ ContextR.without_layer :believe do
305
+ output_of($the_pope) == "Benedikt XVI (Bavaria)"
306
+ end
307
+
308
+ output_of($the_pope) ==
309
+ "Benedikt XVI (Bavaria); Christianity (Israel)"
310
+ end
311
+ end
312
+ end
313
+
314
+ example do
315
+ assert_equal(["to_s"], Religion.in_layer(:location).instance_methods)
316
+ end
317
+
318
+ These encapsulations may be as complex as your application. ContextR will
319
+ keep track of all activations and deactivations within the blocks and
320
+ restore the settings after the block was executed.
321
+
322
+ This was just a short introduction on a problem case, that can be solved
323
+ with context-oriented programming. You have seen, the advantages and how
324
+ to use it. In other files in this folder, you can learn more on the
325
+ dynamics and meta programming interfaces of ContextR.