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