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,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__)