contextr 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,178 @@
1
+ require File.dirname(__FILE__) + "/test_helper.rb"
2
+
3
+ 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.
@@ -0,0 +1,146 @@
1
+ require File.dirname(__FILE__) + "/test_helper.rb"
2
+
3
+ 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.
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: contextr
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.0
7
- date: 2007-09-07 00:00:00 +02:00
6
+ version: 0.1.1
7
+ date: 2007-09-16 00:00:00 +02:00
8
8
  summary: The goal is to equip Ruby with an API to allow context-oriented programming.
9
9
  require_paths:
10
10
  - lib
@@ -25,17 +25,39 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
+ - |
29
+ -----BEGIN CERTIFICATE-----
30
+ MIIDODCCAiCgAwIBAgIBADANBgkqhkiG9w0BAQUFADBCMQ0wCwYDVQQDDARydWJ5
31
+ MR0wGwYKCZImiZPyLGQBGRYNc2NobWlkdHdpc3NlcjESMBAGCgmSJomT8ixkARkW
32
+ AmRlMB4XDTA3MDkxNjEwMzkyN1oXDTA4MDkxNTEwMzkyN1owQjENMAsGA1UEAwwE
33
+ cnVieTEdMBsGCgmSJomT8ixkARkWDXNjaG1pZHR3aXNzZXIxEjAQBgoJkiaJk/Is
34
+ ZAEZFgJkZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOzbl83o33wh
35
+ veLMQ2JyEstgOulisHRBFpfbF9LbdavuS/EdoOUeEPkziWL/ZI0jvUo0MGmQ/8Of
36
+ F9DJbvNbhDdg0bK7BMe4R/I3Wpu49mX+7pOsdlC44nzJkVG1DQ67qWp6jx1zvDRc
37
+ iCoXaQKnROtsx6bCavVvm4P7XLrAQvs7l+1Ke5KLkXRtJ9xJWtAyBLRFoM4e6DeT
38
+ Py0DsixF9Zb5Nrb7UvK0CN8m6dulsKXNRDVQLHkFa5Zg/BEb0RI93LPmeBt8KGIE
39
+ eYVjk+6z+py03D18xd9KsUhOB/0lC0a5vWSZIKfZnxf1uYY9TTZVqTGGUMFi1sD4
40
+ k2TYBqQVsS8CAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
41
+ BBYEFAeq4388e2PUlF/YeemzaY8neefWMA0GCSqGSIb3DQEBBQUAA4IBAQAArQQo
42
+ 3teQbCcYmZFTrdOzujMca6F4JVzTp+yTnOsp1/5hiBEUMc3GreCVAPh2tU9+IpuB
43
+ Lif+s5nLfYdI+JrpCHDzm+ecJEJ7u7gxidzUwEBPYpVuU32ALge7fuWWhQfi29JY
44
+ QwNZgIkGe34z3a2+r2GLBns/GY7t0Lomv6U2SvwLreLc8g7thr2hZfgEJidvcrJR
45
+ Q6amsFqY06NalH+I175Bp4y9rR7IArgNDS3I5Cly5/heVKK2SRm5Z+IACmKMxIXh
46
+ hzBK8YDsrjUvPNIJn9yl0LeEsZ5VhupI2OXr6Cqa/tVMJq0oiTsLfel3Tsb9Ph83
47
+ y3O9DT3o4BiyPe77
48
+ -----END CERTIFICATE-----
49
+
28
50
  post_install_message:
29
51
  authors:
30
52
  - Gregor Schmidt
31
53
  files:
54
+ - COPYING.txt
32
55
  - History.txt
33
- - License.txt
56
+ - LICENSE.txt
34
57
  - Manifest.txt
35
58
  - README.txt
36
59
  - Rakefile
37
- - examples/general.rb
38
- - examples/ordering.rb
60
+ - examples/README
39
61
  - lib/contextr.rb
40
62
  - lib/contextr/class_methods.rb
41
63
  - lib/contextr/core_ext.rb
@@ -54,25 +76,30 @@ files:
54
76
  - spec/contextr_spec.rb
55
77
  - spec/spec.opts
56
78
  - spec/spec_helper.rb
79
+ - test/test_class_side.rb
57
80
  - test/test_contextr.rb
81
+ - test/test_dynamics.rb
58
82
  - test/test_helper.rb
59
- - website/index.html
60
- - website/index.txt
61
- - website/javascripts/rounded_corners_lite.inc.js
62
- - website/stylesheets/screen.css
63
- - website/template.rhtml
83
+ - test/test_introduction.rb
84
+ - test/test_layer_state.rb
85
+ - test/test_ordering.rb
64
86
  test_files:
87
+ - test/test_class_side.rb
65
88
  - test/test_contextr.rb
89
+ - test/test_dynamics.rb
66
90
  - test/test_helper.rb
91
+ - test/test_introduction.rb
92
+ - test/test_layer_state.rb
93
+ - test/test_ordering.rb
67
94
  rdoc_options:
68
95
  - --main
69
96
  - README.txt
70
97
  extra_rdoc_files:
98
+ - COPYING.txt
71
99
  - History.txt
72
- - License.txt
100
+ - LICENSE.txt
73
101
  - Manifest.txt
74
102
  - README.txt
75
- - website/index.txt
76
103
  executables: []
77
104
 
78
105
  extensions: []
metadata.gz.sig ADDED
Binary file
data/License.txt DELETED
@@ -1,20 +0,0 @@
1
- Copyright (c) 2007 FIXME full name
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/examples/general.rb DELETED
@@ -1,152 +0,0 @@
1
- require File.dirname(__FILE__) + "/../lib/contextr.rb"
2
-
3
- module Common
4
- module AddressMethods
5
- def to_s
6
- [yield(:next), yield(:receiver).address].join("; ")
7
- end
8
- end
9
- end
10
-
11
- class Student < Struct.new(:first_name, :last_name, :address, :university)
12
- def to_s
13
- [first_name, last_name].join(" ")
14
- end
15
-
16
- module EducationMethods
17
- def to_s
18
- [yield(:next), yield(:receiver).university].join("; ")
19
- end
20
- end
21
-
22
- include Common
23
- include EducationMethods => :education
24
- include AddressMethods => :address
25
- end
26
-
27
- class University < Struct.new(:name, :address)
28
- def to_s
29
- name
30
- end
31
-
32
- include Common
33
- include AddressMethods => :address
34
- end
35
-
36
- module Fibonacci
37
- module ClassMethods
38
- def compute(fixnum)
39
- if fixnum == 1 or fixnum == 0
40
- fixnum
41
- elsif fixnum < 0
42
- raise ArgumentError, "Fibonacci not defined for negative numbers"
43
- else
44
- compute(fixnum - 1) + compute(fixnum - 2)
45
- end
46
- end
47
-
48
- module LoggingMethods
49
- def compute(fixnum)
50
- print "."
51
- yield(:next, fixnum)
52
- end
53
- end
54
-
55
- module CacheMethods
56
- def cache
57
- @cache ||={}
58
- end
59
-
60
- def compute(fixnum)
61
- cache[fixnum] ||= yield(:next, fixnum)
62
- end
63
- end
64
- end
65
-
66
- self.extend(ClassMethods)
67
- include :class_side => true,
68
- ClassMethods::CacheMethods => :cache,
69
- ClassMethods::LoggingMethods => :logging
70
- end
71
-
72
- puts
73
- puts "Example on Instance Side"
74
- module Example
75
- module ClassMethods
76
- def show
77
- me = Student.new("Gregor", "Schmidt", "Berlin")
78
- hpi = University.new("HPI", "Potsdam")
79
- me.university = hpi
80
- print(me)
81
- end
82
-
83
- def print(me)
84
- puts me
85
- with_addresses do
86
- puts me
87
- end
88
- with_education do
89
- puts me
90
- end
91
- with_education do
92
- with_addresses do
93
- puts me
94
- end
95
- end
96
- end
97
-
98
- def with_addresses
99
- ContextR::with_layers :address do
100
- yield
101
- end
102
- end
103
-
104
- def with_education
105
- ContextR::with_layers :education do
106
- yield
107
- end
108
- end
109
- end
110
- self.extend(ClassMethods)
111
- end
112
- Example.show
113
-
114
- puts
115
- puts "Example with changed instance methods after behaviour registration"
116
- class Student
117
- def to_s
118
- [first_name, last_name].reverse.join(", ")
119
- end
120
- end
121
- Example.show
122
-
123
- puts
124
- puts "Example with changed behaviour methods after their registration"
125
- module Student::EducationMethods
126
- def to_s
127
- [yield(:next), yield(:receiver).university].join("\n - ")
128
- end
129
- end
130
- Example.show
131
-
132
- puts
133
- puts "Example on Class Side"
134
- class ExampleClassSide
135
- module ClassMethods
136
- def show(int = 3)
137
- puts Fibonacci.compute(int)
138
- ContextR::with_layers :cache do
139
- puts Fibonacci.compute(int)
140
- end
141
- ContextR::with_layers :logging do
142
- puts Fibonacci.compute(2 * int)
143
-
144
- ContextR::with_layers :cache do
145
- puts Fibonacci.compute(2 * int)
146
- end
147
- end
148
- end
149
- end
150
- self.extend(ClassMethods)
151
- end
152
- ExampleClassSide.show