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,178 +1,3 @@
1
1
  require File.dirname(__FILE__) + "/test_helper.rb"
2
-
3
2
  test_class(:TestLayerState)
4
-
5
- # One of the most frequent examples for the use of aspect oriented programming
6
- # is caching. You have a method, that takes some time to compute a result,
7
- # but the result is always the same, if the parameters are the same. Such
8
- # methods are often called functions. The have no side effects and do not depend
9
- # on inner state.
10
- #
11
- # Perhaps you would like to attach caching to such functions. But only under
12
- # certain circumstances. If your application lacks time, it is a good idea to
13
- # activate result caching. If it lacks memory, it is a bad one.
14
- #
15
- # This perfectly sounds like a problem, that can be solved using
16
- # context-oriented programming.
17
- #
18
- # But for caching, we need a place to store the computed results. One could
19
- # store them in the instance that computes them, but this would be strange,
20
- # because, the code residing in it does not make direct use of this state.
21
- # Perhaps we are using variables, that other methods use for their own
22
- # purpose and this would result in strange results. We could use cryptic
23
- # instance variable names, but all this is just a workaround.
24
- #
25
- # What we want is to attach the state to the code, that uses it. This is the
26
- # main idea of object-oriented design anyway. So ContextR gives you the
27
- # opportunity to save state inside your method modules. Code and state stay
28
- # side by side. This state will reside there even after deactivating the
29
- # layer, so you can reuse it. Perfect for our caching approach.
30
-
31
-
32
- # Fibonacci numbers
33
- # =================
34
-
35
- # We will use the good old Fibonacci numbers to demonstrate our approach. First
36
- # the simple recursive computation, that becomes really slow for larger numbers.
37
- # I know that there is a non-recursive algorithm, working faster, but this
38
- # would not make such a good example. So just assume, that the following
39
- # code is the fastest, you could possibly get.
40
-
41
- module Fibonacci
42
- module ClassMethods
43
- def compute(fixnum)
44
- if fixnum == 1 or fixnum == 0
45
- fixnum
46
- elsif fixnum < 0
47
- raise ArgumentError, "Fibonacci not defined for negative numbers"
48
- else
49
- compute(fixnum - 1) + compute(fixnum - 2)
50
- end
51
- end
52
- end
53
- self.extend(ClassMethods)
54
- end
55
-
56
- example do
57
- result_of(Fibonacci.compute(1)) == 1
58
- result_of(Fibonacci.compute(2)) == 1
59
- result_of(Fibonacci.compute(3)) == 2
60
- end
61
-
62
- # Just to make sure, that it is slow, I will try to compute Fib(100)
63
-
64
- require 'timeout'
65
- example do
66
- timeout_raised = false
67
- begin
68
- Timeout::timeout(0.05) do
69
- Fibonacci.compute(100)
70
- end
71
-
72
- rescue Timeout::Error
73
- timeout_raised = true
74
- end
75
-
76
- result_of(timeout_raised) == true
77
- end
78
-
79
- # Okay, the 0.01 seconds are really impatient, but I know, that caching will
80
- # come to rescue and makes it happen.
81
- #
82
- # Let's define a simple caching method. If I already know the result, return
83
- # it, if not, let the base implementation compute it and save the it into
84
- # our variable.
85
-
86
- module Fibonacci
87
- module ClassMethods
88
- module CacheMethods
89
- def cache
90
- @cache ||= {}
91
- end
92
-
93
- def compute(fixnum)
94
- cache[fixnum] ||= yield(:next, fixnum)
95
- end
96
- end
97
- end
98
-
99
- extend ClassMethods::CacheMethods => :cache
100
- end
101
-
102
- # If you are not familiar with the above syntax, to define context dependent
103
- # behaviour, have a look at test_class_side.rb.
104
- #
105
- # Now let's compute Fib(100) again. Of course with caching enabled
106
-
107
- example do
108
- timeout_raised = false
109
- begin
110
- Timeout::timeout(0.05) do
111
- ContextR::with_layer :cache do
112
- result_of(Fibonacci.compute(100)) == 354_224_848_179_261_915_075
113
- end
114
- end
115
-
116
- rescue Timeout::Error
117
- timeout_raised = true
118
- end
119
-
120
- # This time the time out was not triggered
121
- result_of(timeout_raised) == false
122
- end
123
-
124
- # It is that simple to add state to your method modules. And just to make sure,
125
- # that I did not cheat, I will add a simple case, were instance variables and
126
- # layer specific variables _would_ conflict, but in fact don't.
127
-
128
- class LayerStateExample
129
- attr_accessor :state
130
- module StateMethods
131
- attr_accessor :state
132
- end
133
-
134
- include StateMethods => :test_layer_state
135
- end
136
-
137
- # When StateMethods would be included normally, its attr_accessor would simply
138
- # be the same as in the class. But this does not happen, when using layers.
139
- #
140
- # Let's do a little warm up and make sure, everything works as expected.
141
-
142
- $layer_state_example = LayerStateExample.new
143
-
144
- example do
145
- $layer_state_example.state = true
146
- result_of($layer_state_example.state) == true
147
- $layer_state_example.state = false
148
- result_of($layer_state_example.state) == false
149
-
150
- ContextR::with_layer :test_layer_state do
151
- $layer_state_example.state = true
152
- result_of($layer_state_example.state) == true
153
- $layer_state_example.state = false
154
- result_of($layer_state_example.state) == false
155
- end
156
- end
157
-
158
- # Until now, I did not prove anything. Let's try it.
159
-
160
- example do
161
- # Set the state
162
- $layer_state_example.state = true
163
- ContextR::with_layer :test_layer_state do
164
- $layer_state_example.state = false
165
- end
166
-
167
- # And make sure, that they differ
168
- result_of($layer_state_example.state) == true
169
-
170
- ContextR::with_layer :test_layer_state do
171
- result_of($layer_state_example.state) == false
172
- end
173
- end
174
-
175
- # The last example was very theoretical and looks strange when seen isolated.
176
- # Its main purpose is to show, that layer specific methods and base methods
177
- # do not share their state, and that the layer specific state remains, also
178
- # after layer deactivation. Don't take too serious.
3
+ LiterateMarukuTest.load(__FILE__)
@@ -0,0 +1,21 @@
1
+ ContextR knows two basic reflection mechanisms. One is to query the currently
2
+ active layers.
3
+
4
+ example do
5
+ ContextR::with_layer :a do
6
+ assert_equal([:a], ContextR::active_layers)
7
+ ContextR::with_layer :b do
8
+ assert_equal([:a, :b], ContextR::active_layers)
9
+ end
10
+ assert_equal([:a], ContextR::active_layers)
11
+ end
12
+ end
13
+
14
+ The second is to query all layers, that where ever defined.
15
+
16
+ example do
17
+ assert(ContextR::layers.include?(:a))
18
+ assert(ContextR::layers.include?(:b))
19
+ end
20
+
21
+
@@ -0,0 +1,4 @@
1
+ require File.dirname(__FILE__) + "/test_helper.rb"
2
+
3
+ test_class(:TestMetaApi)
4
+ LiterateMarukuTest.load(__FILE__)
@@ -0,0 +1,142 @@
1
+ This document tries to demonstrate the invocation order within
2
+ context-specific method calls. There are two cases where this is relevant:
3
+ 1. There is more than one layer active, that extends the method.
4
+ 2. There is more than one module in a single layer that extends the method.
5
+
6
+ Unfortunately I could not find any relevant example that clearly demonstrates
7
+ this behaviour. Therefore I will use foo bar code. But I think it is still
8
+ easy to get the message.
9
+
10
+ I. Multiple active layers
11
+ -------------------------
12
+
13
+ Define the basis first.
14
+
15
+ class OrderingTest
16
+ def test_method
17
+ "base_method"
18
+ end
19
+
20
+ module FooMethods
21
+ def test_method
22
+ "foo_before #{super} foo_after"
23
+ end
24
+ end
25
+ module BarMethods
26
+ def test_method
27
+ "bar_before #{super} bar_after"
28
+ end
29
+ end
30
+
31
+ in_layer :foo do
32
+ include FooMethods
33
+ end
34
+ in_layer :bar do
35
+ include BarMethods
36
+ end
37
+ end
38
+
39
+ When multiple layers extend a single method, the order of activation
40
+ determines the order of execution.
41
+
42
+ example do
43
+ instance = OrderingTest.new
44
+ result_of(instance.test_method) == "base_method"
45
+
46
+ ContextR::with_layer :foo do
47
+ result_of(instance.test_method) == "foo_before base_method foo_after"
48
+ end
49
+ ContextR::with_layer :bar do
50
+ result_of(instance.test_method) == "bar_before base_method bar_after"
51
+ end
52
+
53
+ ContextR::with_layer :bar, :foo do
54
+ result_of(instance.test_method) ==
55
+ "bar_before foo_before base_method foo_after bar_after"
56
+ end
57
+
58
+ ContextR::with_layer :bar do
59
+ ContextR::with_layer :foo do
60
+ result_of(instance.test_method) ==
61
+ "bar_before foo_before base_method foo_after bar_after"
62
+ end
63
+ end
64
+
65
+ ContextR::with_layer :foo, :bar do
66
+ result_of(instance.test_method) ==
67
+ "foo_before bar_before base_method bar_after foo_after"
68
+ end
69
+
70
+ ContextR::with_layer :foo do
71
+ ContextR::with_layer :bar do
72
+ result_of(instance.test_method) ==
73
+ "foo_before bar_before base_method bar_after foo_after"
74
+ end
75
+ end
76
+ end
77
+
78
+ As you can see, the innermost layer activation provides the innermost method
79
+ definition. It is not important, whether the layers were activated at once
80
+ or one after the other.
81
+
82
+ Activating an already active layer may update the execution order. The outer
83
+ activation is hidden, but is restored again after leaving the block.
84
+
85
+ example do
86
+ instance = OrderingTest.new
87
+
88
+ ContextR::with_layer :bar, :foo do
89
+ result_of(instance.test_method) ==
90
+ "bar_before foo_before base_method foo_after bar_after"
91
+
92
+ ContextR::with_layer :bar do
93
+ result_of(instance.test_method) ==
94
+ "foo_before bar_before base_method bar_after foo_after"
95
+ end
96
+
97
+ result_of(instance.test_method) ==
98
+ "bar_before foo_before base_method foo_after bar_after"
99
+ end
100
+ end
101
+
102
+
103
+ II. Multiple modules per layer and class
104
+ ----------------------------------------
105
+
106
+ It is also possible to have more than one module define the context-dependent
107
+ behaviour of a class. In this case it may also happen, that multiple modules
108
+ extend the same method definition.
109
+
110
+ In this case we can reuse or already defined class and modules. This time
111
+ we include them into the same layer and see what happens.
112
+
113
+ class OrderingTest
114
+ in_layer :foo_bar do
115
+ include FooMethods
116
+ include BarMethods
117
+ end
118
+ end
119
+
120
+ example do
121
+ instance = OrderingTest.new
122
+ result_of(instance.test_method) == "base_method"
123
+
124
+ ContextR::with_layer :foo_bar do
125
+ result_of(instance.test_method) ==
126
+ "bar_before foo_before base_method foo_after bar_after"
127
+ end
128
+ end
129
+
130
+ This time the last inclusion defines the outermost method. But this is just Ruby's way of (multiple) inheritance and not part of ContextR. For the same reason a second inclusion of `FooMethod`s will not update the execution order.
131
+
132
+ III. Conclusion
133
+ ---------------
134
+
135
+ These should be all case where ordering matters. If you are aware of the
136
+ two basic rules, everything should work as expected.
137
+
138
+ 1. Execution order of different layers is determined by layer activation.
139
+ 2. Execution within layers is ordered in the way Ruby handles inheritance.
140
+
141
+ In an ideal world the the execution order would not be important. But hey,
142
+ this is not the ideal world, so you better know.
@@ -1,146 +1,3 @@
1
1
  require File.dirname(__FILE__) + "/test_helper.rb"
2
-
3
2
  test_class(:TestOrdering)
4
-
5
- # This document tries to demonstrate the invocation order within
6
- # context-specific method calls. There are two cases where this is relevant:
7
- # 1. There is more than one layer active, that extends the method.
8
- # 2. There is more than one module in a single layer that extends the method.
9
- #
10
- # Unfortunately I could not find any relevant example that clearly demonstrates
11
- # this behaviour. Therefore I will use foo bar code. But I think it is still
12
- # easy to get the message.
13
-
14
- # 1. Multiple active layers
15
- # =========================
16
-
17
- class OrderingTest
18
- def test_method
19
- "base_method"
20
- end
21
-
22
- module FooMethods
23
- def test_method
24
- "foo_before #{yield(:next)} foo_after"
25
- end
26
- end
27
- module BarMethods
28
- def test_method
29
- "bar_before #{yield(:next)} bar_after"
30
- end
31
- end
32
-
33
- include FooMethods => :foo
34
- include BarMethods => :bar
35
- end
36
-
37
- # When multiple layers extend a single method, the order of activation
38
- # determines the order of execution.
39
-
40
- example do
41
- instance = OrderingTest.new
42
- result_of(instance.test_method) == "base_method"
43
-
44
- ContextR::with_layer :foo do
45
- result_of(instance.test_method) == "foo_before base_method foo_after"
46
- end
47
- ContextR::with_layer :bar do
48
- result_of(instance.test_method) == "bar_before base_method bar_after"
49
- end
50
-
51
- ContextR::with_layer :bar, :foo do
52
- result_of(instance.test_method) ==
53
- "bar_before foo_before base_method foo_after bar_after"
54
- end
55
-
56
- ContextR::with_layer :bar do
57
- ContextR::with_layer :foo do
58
- result_of(instance.test_method) ==
59
- "bar_before foo_before base_method foo_after bar_after"
60
- end
61
- end
62
-
63
- ContextR::with_layer :foo, :bar do
64
- result_of(instance.test_method) ==
65
- "foo_before bar_before base_method bar_after foo_after"
66
- end
67
-
68
- ContextR::with_layer :foo do
69
- ContextR::with_layer :bar do
70
- result_of(instance.test_method) ==
71
- "foo_before bar_before base_method bar_after foo_after"
72
- end
73
- end
74
- end
75
-
76
- # As you can see, the innermost layer activation provides the innermost method
77
- # definition. It is not important, whether the layers were activated at once
78
- # or one after the other.
79
- #
80
- # Activating and already active layer may update the execution order. The outer
81
- # activation is hidden, but is restored again after leaving the block.
82
-
83
- example do
84
- instance = OrderingTest.new
85
-
86
- ContextR::with_layer :bar, :foo do
87
- result_of(instance.test_method) ==
88
- "bar_before foo_before base_method foo_after bar_after"
89
-
90
- ContextR::with_layer :bar do
91
- result_of(instance.test_method) ==
92
- "foo_before bar_before base_method bar_after foo_after"
93
- end
94
-
95
- result_of(instance.test_method) ==
96
- "bar_before foo_before base_method foo_after bar_after"
97
- end
98
- end
99
-
100
-
101
- # Multiple modules per layer and class
102
- # ====================================
103
-
104
- # It is also possible to have more than one module define the context-dependent
105
- # behaviour of a class. In this case it may also happen, that multiple modules
106
- # extend the same method definition.
107
- #
108
- # In this case we can reuse or already defined class and modules. This time
109
- # we include them into the same layer and see what happens.
110
-
111
- class OrderingTest
112
- include FooMethods => :foo_bar
113
- include BarMethods => :foo_bar
114
- end
115
-
116
- example do
117
- instance = OrderingTest.new
118
- result_of(instance.test_method) == "base_method"
119
-
120
- ContextR::with_layer :foo_bar do
121
- result_of(instance.test_method) ==
122
- "bar_before foo_before base_method foo_after bar_after"
123
- end
124
- end
125
-
126
- # This time the last inclusion defines the outermost method. This can be again
127
- # changed. After repeating an inclusion the ordering is updated.
128
-
129
- example do
130
- class OrderingTest
131
- include FooMethods => :foo_bar
132
- end
133
-
134
- instance = OrderingTest.new
135
- result_of(instance.test_method) == "base_method"
136
-
137
- ContextR::with_layer :foo_bar do
138
- result_of(instance.test_method) ==
139
- "foo_before bar_before base_method bar_after foo_after"
140
- end
141
- end
142
-
143
- # These should be all case where ordering matters. If you are aware of the
144
- # two basic rules, everything should work as expected. In an ideal world the
145
- # the execution order would not be important. But hey, this is not the ideal
146
- # world, so you better know.
3
+ LiterateMarukuTest.load(__FILE__)