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_introduction.rb
CHANGED
@@ -1,311 +1,4 @@
|
|
1
1
|
require File.dirname(__FILE__) + "/test_helper.rb"
|
2
2
|
|
3
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
|
4
|
+
LiterateMarukuTest.load(__FILE__)
|
@@ -0,0 +1,170 @@
|
|
1
|
+
One of the most frequent examples for the use of aspect oriented programming
|
2
|
+
is caching. You have a method, that takes some time to compute a result,
|
3
|
+
but the result is always the same, if the parameters are the same. Such
|
4
|
+
methods are often called functions. The have no side effects and do not depend
|
5
|
+
on inner state.
|
6
|
+
|
7
|
+
Perhaps you would like to attach caching to such functions. But only under
|
8
|
+
certain circumstances. If your application lacks time, it is a good idea to
|
9
|
+
activate result caching. If it lacks memory, it is a bad one.
|
10
|
+
|
11
|
+
This perfectly sounds like a problem, that can be solved using
|
12
|
+
context-oriented programming.
|
13
|
+
|
14
|
+
But for caching, we need a place to store the computed results. One could
|
15
|
+
store them in the instance that computes them, but this would be strange,
|
16
|
+
because, the code residing in it does not make direct use of this state.
|
17
|
+
Perhaps we are using variables, that other methods use for their own
|
18
|
+
purpose and this would result in strange results. We could use cryptic
|
19
|
+
instance variable names, but all this is just a workaround.
|
20
|
+
|
21
|
+
What we want is to attach the state to the code, that uses it. This is the
|
22
|
+
main idea of object-oriented design anyway. So ContextR gives you the
|
23
|
+
opportunity to save state inside your method modules. Code and state stay
|
24
|
+
side by side. This state will reside there even after deactivating the
|
25
|
+
layer, so you can reuse it. Perfect for our caching approach.
|
26
|
+
|
27
|
+
|
28
|
+
Fibonacci numbers
|
29
|
+
-----------------
|
30
|
+
|
31
|
+
We will use the good old Fibonacci numbers to demonstrate our approach. First
|
32
|
+
the simple recursive computation, that becomes really slow for larger numbers.
|
33
|
+
I know that there is a non-recursive algorithm, working faster, but this
|
34
|
+
would not make such a good example. So just assume, that the following
|
35
|
+
code is the fastest, you could possibly get.
|
36
|
+
|
37
|
+
module Fibonacci
|
38
|
+
module ClassMethods
|
39
|
+
def compute(fixnum)
|
40
|
+
if fixnum == 1 or fixnum == 0
|
41
|
+
fixnum
|
42
|
+
elsif fixnum < 0
|
43
|
+
raise ArgumentError, "Fibonacci not defined for negative numbers"
|
44
|
+
else
|
45
|
+
compute(fixnum - 1) + compute(fixnum - 2)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
self.extend(ClassMethods)
|
50
|
+
end
|
51
|
+
|
52
|
+
example do
|
53
|
+
result_of(Fibonacci.compute(1)) == 1
|
54
|
+
result_of(Fibonacci.compute(2)) == 1
|
55
|
+
result_of(Fibonacci.compute(3)) == 2
|
56
|
+
end
|
57
|
+
|
58
|
+
Just to make sure, that it is slow, I will try to compute Fib(100)
|
59
|
+
|
60
|
+
require 'timeout'
|
61
|
+
example do
|
62
|
+
timeout_raised = false
|
63
|
+
begin
|
64
|
+
Timeout::timeout(0.05) do
|
65
|
+
Fibonacci.compute(100)
|
66
|
+
end
|
67
|
+
|
68
|
+
rescue Timeout::Error
|
69
|
+
timeout_raised = true
|
70
|
+
end
|
71
|
+
|
72
|
+
result_of(timeout_raised) == true
|
73
|
+
end
|
74
|
+
|
75
|
+
Okay, the 0.01 seconds are really impatient, but I know, that caching will
|
76
|
+
come to rescue and makes it happen.
|
77
|
+
|
78
|
+
Let's define a simple caching method. If I already know the result, return
|
79
|
+
it, if not, let the base implementation compute it and save the it into
|
80
|
+
our variable.
|
81
|
+
|
82
|
+
module Fibonacci
|
83
|
+
module ClassMethods
|
84
|
+
in_layer :cache do
|
85
|
+
def cache
|
86
|
+
@cache ||= {}
|
87
|
+
end
|
88
|
+
|
89
|
+
def compute(fixnum)
|
90
|
+
cache[fixnum] ||= yield(:next, fixnum)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
If you are not familiar with the above syntax, to define context dependent
|
97
|
+
behaviour, have a look at `test_class_side.rb`.
|
98
|
+
|
99
|
+
Now let's compute Fib(100) again. Of course with caching enabled
|
100
|
+
|
101
|
+
example do
|
102
|
+
timeout_raised = false
|
103
|
+
begin
|
104
|
+
Timeout::timeout(0.05) do
|
105
|
+
ContextR::with_layer :cache do
|
106
|
+
result_of(Fibonacci.compute(100)) == 354_224_848_179_261_915_075
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
rescue Timeout::Error
|
111
|
+
timeout_raised = true
|
112
|
+
end
|
113
|
+
|
114
|
+
# This time the time out was not triggered
|
115
|
+
result_of(timeout_raised) == false
|
116
|
+
end
|
117
|
+
|
118
|
+
It is that simple to add state to your method modules. And just to make sure,
|
119
|
+
that I did not cheat, I will add a simple case, were instance variables and
|
120
|
+
layer specific variables _would_ conflict, but in fact don't.
|
121
|
+
|
122
|
+
class LayerStateExample
|
123
|
+
attr_accessor :state
|
124
|
+
in_layer :test_layer_state do
|
125
|
+
attr_accessor :state
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
When StateMethods would be included normally, its `attr_accessor` would simply
|
130
|
+
be the same as in the class. But this does not happen, when using layers.
|
131
|
+
|
132
|
+
Let's do a little warm up and make sure, everything works as expected.
|
133
|
+
|
134
|
+
$layer_state_example = LayerStateExample.new
|
135
|
+
|
136
|
+
example do
|
137
|
+
$layer_state_example.state = true
|
138
|
+
result_of($layer_state_example.state) == true
|
139
|
+
$layer_state_example.state = false
|
140
|
+
result_of($layer_state_example.state) == false
|
141
|
+
|
142
|
+
ContextR::with_layer :test_layer_state do
|
143
|
+
$layer_state_example.state = true
|
144
|
+
result_of($layer_state_example.state) == true
|
145
|
+
$layer_state_example.state = false
|
146
|
+
result_of($layer_state_example.state) == false
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
Until now, I did not prove anything. Let's try it.
|
151
|
+
|
152
|
+
example do
|
153
|
+
# Set the state
|
154
|
+
$layer_state_example.state = true
|
155
|
+
ContextR::with_layer :test_layer_state do
|
156
|
+
$layer_state_example.state = false
|
157
|
+
end
|
158
|
+
|
159
|
+
# And make sure, that they differ
|
160
|
+
result_of($layer_state_example.state) == true
|
161
|
+
|
162
|
+
ContextR::with_layer :test_layer_state do
|
163
|
+
result_of($layer_state_example.state) == false
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
The last example was very theoretical and looks strange when seen isolated.
|
168
|
+
Its main purpose is to show, that layer specific methods and base methods
|
169
|
+
do not share their state, and that the layer specific state remains, also
|
170
|
+
after layer deactivation. Don't take too serious.
|