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.
- data.tar.gz.sig +2 -2
- data/History.txt +8 -0
- data/Manifest.txt +15 -0
- data/Rakefile +2 -2
- data/lib/contextr.rb +2 -1
- data/lib/contextr/class_methods.rb +19 -15
- data/lib/contextr/core_ext/module.rb +18 -39
- data/lib/contextr/core_ext/object.rb +1 -68
- data/lib/contextr/event_machine.rb +3 -3
- data/lib/contextr/inner_class.rb +47 -0
- data/lib/contextr/layer.rb +62 -68
- data/lib/contextr/version.rb +1 -1
- data/lib/ext/dynamic.rb +2 -2
- data/spec/contextr_spec.rb +36 -30
- data/test/lib/example_test.rb +59 -0
- data/test/lib/literate_markaby_test.rb +97 -0
- data/test/lib/literate_maruku_test.rb +108 -0
- data/test/test_class_side.mkd +234 -0
- data/test/test_class_side.rb +1 -222
- data/test/test_contextr.rb +0 -12
- data/test/test_dynamic_scope.mkd +61 -0
- data/test/test_dynamic_scope.rb +4 -0
- data/test/test_dynamics.mkd +201 -0
- data/test/test_dynamics.rb +1 -204
- data/test/test_hello_world.mkd +70 -0
- data/test/test_hello_world.rb +4 -0
- data/test/test_helper.rb +4 -53
- data/test/test_introduction.mkd +325 -0
- data/test/test_introduction.rb +1 -308
- data/test/test_layer_state.mkd +170 -0
- data/test/test_layer_state.rb +1 -176
- data/test/test_meta_api.mkd +21 -0
- data/test/test_meta_api.rb +4 -0
- data/test/test_ordering.mkd +142 -0
- data/test/test_ordering.rb +1 -144
- metadata +65 -41
- metadata.gz.sig +0 -0
data/test/test_dynamics.rb
CHANGED
@@ -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
|
data/test/test_helper.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
52
|
-
|
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.
|