contextr 0.1.0 → 0.1.1
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 -0
- data/COPYING.txt +340 -0
- data/History.txt +11 -0
- data/LICENSE.txt +58 -0
- data/Manifest.txt +8 -8
- data/README.txt +15 -2
- data/examples/README +9 -0
- data/lib/contextr/class_methods.rb +23 -4
- data/lib/contextr/core_ext/module.rb +48 -9
- data/lib/contextr/core_ext/object.rb +68 -1
- data/lib/contextr/event_machine.rb +1 -1
- data/lib/contextr/layer.rb +5 -3
- data/lib/contextr/modules/mutex_code.rb +1 -1
- data/lib/contextr/modules/unique_id.rb +1 -1
- data/lib/contextr/public_api.rb +17 -15
- data/lib/contextr/version.rb +1 -1
- data/lib/ext/active_support_subset.rb +86 -4
- data/lib/ext/dynamic.rb +2 -1
- data/spec/contextr_spec.rb +211 -6
- data/spec/spec_helper.rb +1 -0
- data/test/test_class_side.rb +225 -0
- data/test/test_contextr.rb +19 -11
- data/test/test_dynamics.rb +207 -0
- data/test/test_helper.rb +55 -0
- data/test/test_introduction.rb +311 -0
- data/test/test_layer_state.rb +178 -0
- data/test/test_ordering.rb +146 -0
- metadata +39 -12
- metadata.gz.sig +0 -0
- data/License.txt +0 -20
- data/examples/general.rb +0 -152
- data/examples/ordering.rb +0 -29
- data/website/index.html +0 -116
- data/website/index.txt +0 -61
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -138
- data/website/template.rhtml +0 -48
data/lib/ext/dynamic.rb
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
#
|
10
10
|
# (c) 2005 - Christian Neukirchen - http://chneukirchen.org
|
11
11
|
module Dynamic
|
12
|
-
|
12
|
+
module ClassMethods #:nodoc:
|
13
13
|
Thread.main[:DYNAMIC] = Hash.new { |hash, key|
|
14
14
|
raise NameError, "no such dynamic variable: #{key}"
|
15
15
|
}
|
@@ -80,4 +80,5 @@ module Dynamic
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
83
|
+
extend ClassMethods
|
83
84
|
end
|
data/spec/contextr_spec.rb
CHANGED
@@ -1,11 +1,216 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
module AddressMethods
|
4
|
+
def to_s
|
5
|
+
"#{yield(:next)} (#{yield(:receiver).address})"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class University < Struct.new(:name, :address)
|
10
|
+
def to_s
|
11
|
+
name
|
12
|
+
end
|
13
|
+
|
14
|
+
include AddressMethods => :address
|
15
|
+
end
|
16
|
+
|
17
|
+
class Student < Struct.new(:name, :address, :education)
|
18
|
+
def to_s
|
19
|
+
name
|
20
|
+
end
|
21
|
+
|
22
|
+
include AddressMethods => :address
|
23
|
+
|
24
|
+
module EducationMethods
|
25
|
+
def to_s
|
26
|
+
"#{yield(:next)}, #{yield(:receiver).education}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
include EducationMethods => :education
|
31
|
+
end
|
32
|
+
|
33
|
+
class Ordering
|
34
|
+
def test
|
35
|
+
"base"
|
36
|
+
end
|
37
|
+
|
38
|
+
module InnerMethods
|
39
|
+
def test
|
40
|
+
"inner #{yield(:next)} inner"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
include InnerMethods => :multiple_modules
|
44
|
+
|
45
|
+
module OuterMethods
|
46
|
+
def test
|
47
|
+
"outer #{yield(:next)} outer"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
include OuterMethods => :multiple_modules
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
describe "A contextified object" do
|
55
|
+
before do
|
56
|
+
institute = University.new("HPI", "Potsdam")
|
57
|
+
@student = Student.new("Gregor Schmidt", "Berlin", institute)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should show base behaviour without activated layers" do
|
61
|
+
@student.to_s.should == "Gregor Schmidt"
|
62
|
+
end
|
6
63
|
|
7
|
-
it "
|
8
|
-
|
64
|
+
it "should show specific behaviour with a single activated layer" do
|
65
|
+
ContextR::with_layer :address do
|
66
|
+
@student.to_s.should == "Gregor Schmidt (Berlin)"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should show base behaviour after deactivating all layers" do
|
71
|
+
ContextR::with_layer :address do
|
72
|
+
@student.to_s.should == "Gregor Schmidt (Berlin)"
|
73
|
+
end
|
74
|
+
@student.to_s.should == "Gregor Schmidt"
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should show specific behaviour down the whole stack for all layers" do
|
78
|
+
ContextR::with_layers :education, :address do
|
79
|
+
@student.to_s.should == "Gregor Schmidt (Berlin), HPI (Potsdam)"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should take care of layer activation odering" do
|
84
|
+
ContextR::with_layers :education do
|
85
|
+
ContextR::with_layers :address do
|
86
|
+
@student.to_s.should == "Gregor Schmidt (Berlin), HPI (Potsdam)"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
ContextR::with_layers :address do
|
90
|
+
ContextR::with_layers :education do
|
91
|
+
@student.to_s.should == "Gregor Schmidt, HPI (Potsdam) (Berlin)"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should avoid double activation, but update ordering" do
|
97
|
+
ContextR::with_layers :education, :address do
|
98
|
+
ContextR::active_layers.should == [:education, :address]
|
99
|
+
ContextR::with_layer :education do
|
100
|
+
ContextR::active_layers.should == [:address, :education]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should also activate multiple modules per layer" do
|
106
|
+
ContextR::with_layers :multiple_modules do
|
107
|
+
Ordering.new.test.should == "outer inner base inner outer"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should show new specific behaviour after changing module definitions" do
|
112
|
+
module Student::EducationMethods
|
113
|
+
def to_s
|
114
|
+
"#{yield(:next)} @ #{yield(:receiver).education}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
ContextR::with_layer :education do
|
118
|
+
@student.to_s.should == "Gregor Schmidt @ HPI"
|
119
|
+
end
|
120
|
+
module Student::EducationMethods
|
121
|
+
def to_s
|
122
|
+
"#{yield(:next)}, #{yield(:receiver).education}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should still work after changing contextified instance methods" do
|
128
|
+
class Student
|
129
|
+
def to_s
|
130
|
+
"Student: #{name}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
ContextR::with_layer :education do
|
134
|
+
@student.to_s.should == "Student: Gregor Schmidt, HPI"
|
135
|
+
end
|
136
|
+
class Student
|
137
|
+
def to_s
|
138
|
+
name
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "A method modules defining context dependent behaviour" do
|
145
|
+
before do
|
146
|
+
institute = University.new("HPI", "Potsdam")
|
147
|
+
@student = Student.new("Gregor Schmidt", "Berlin", institute)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should have inner state" do
|
151
|
+
class Student
|
152
|
+
module LogMethods
|
153
|
+
def to_s
|
154
|
+
@i ||= 0
|
155
|
+
@i += 1
|
156
|
+
"#{@i}: #{yield(:next)}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
include LogMethods => :log
|
160
|
+
end
|
161
|
+
ContextR::with_layer :log do
|
162
|
+
@student.to_s.should == "1: Gregor Schmidt"
|
163
|
+
@student.to_s.should == "2: Gregor Schmidt"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should not lose its state after layer deactivation" do
|
168
|
+
ContextR::with_layer :log do
|
169
|
+
@student.to_s.should == "3: Gregor Schmidt"
|
170
|
+
end
|
9
171
|
end
|
10
172
|
|
11
|
-
|
173
|
+
it "should not lose its state after redefinition of the module" do
|
174
|
+
class Student
|
175
|
+
module LogMethods
|
176
|
+
def to_s
|
177
|
+
@i ||= 0
|
178
|
+
@i += 1
|
179
|
+
"(#{@i}) #{yield(:next)}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
ContextR::with_layer :log do
|
184
|
+
@student.to_s.should == "(4) Gregor Schmidt"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should not lose its state after redefinition of base method" do
|
189
|
+
class Student
|
190
|
+
def to_s
|
191
|
+
name + " x"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
ContextR::with_layer :log do
|
195
|
+
@student.to_s.should == "(5) Gregor Schmidt x"
|
196
|
+
end
|
197
|
+
class Student
|
198
|
+
def to_s
|
199
|
+
name
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "ContextR" do
|
206
|
+
it "should provide a method to query for all active layers" do
|
207
|
+
ContextR::with_layer :log do
|
208
|
+
ContextR::active_layers.should == [:log]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should provide a method to query for all layers ever defined" do
|
213
|
+
ContextR::layers.sort_by{ |s| s.to_s }.should ==
|
214
|
+
[:address, :education, :log, :multiple_modules]
|
215
|
+
end
|
216
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,225 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/test_helper.rb"
|
2
|
+
|
3
|
+
test_class(:TestClassSide)
|
4
|
+
|
5
|
+
# In Ruby there are multiple ways of defining behaviour on the class side. That
|
6
|
+
# are messages that are send to the class, not on the instance. Class side
|
7
|
+
# behaviour is often useful for functional methods, i.e. methods that do not
|
8
|
+
# rely on inner state and have no side effects. Mathematical functions have
|
9
|
+
# these characteristics - that is where the name probably comes from.
|
10
|
+
|
11
|
+
|
12
|
+
# Using def self.method_name
|
13
|
+
# ==========================
|
14
|
+
|
15
|
+
# The simpliest way of defining class side behaviour is prepending self. to the
|
16
|
+
# method definition. This way, the method is attached to the surrounding class
|
17
|
+
# and not instance. A simple example:
|
18
|
+
|
19
|
+
class SimpleMath
|
20
|
+
def self.pi
|
21
|
+
3.14159265
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
example do
|
26
|
+
result_of(SimpleMath.pi) == 3.14159265
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# Using class << self
|
31
|
+
# ===================
|
32
|
+
|
33
|
+
# When you are having lots of class side methods as well as instance side ones,
|
34
|
+
# it can be difficult to spot the little self. in front of the method name.
|
35
|
+
# Probably you like to group them more explicitly. You could use Ruby's
|
36
|
+
# eigenclass principle for that. It will look like the following:
|
37
|
+
|
38
|
+
class SimpleMath
|
39
|
+
class << self
|
40
|
+
def e
|
41
|
+
2.71828183
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
example do
|
47
|
+
result_of(SimpleMath.e) == 2.71828183
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# Using a module
|
52
|
+
# ==============
|
53
|
+
|
54
|
+
# For even more encapsulation you could also use modules and extend the class
|
55
|
+
# definition with them. I am using extend here on purpose. Module's include
|
56
|
+
# method adds the behaviour to the instance side, extend to the class side.
|
57
|
+
|
58
|
+
class SimpleMath
|
59
|
+
module ClassMethods
|
60
|
+
def golden_ratio
|
61
|
+
1.6180339887
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
extend ClassMethods
|
66
|
+
end
|
67
|
+
|
68
|
+
example do
|
69
|
+
result_of(SimpleMath.golden_ratio) == 1.6180339887
|
70
|
+
end
|
71
|
+
|
72
|
+
# The last method is e.g. often used in the web framework Ruby on Rails. Often
|
73
|
+
# a variation of it is used to define class and instance side behaviour for
|
74
|
+
# mixin modules
|
75
|
+
|
76
|
+
module MathMixin
|
77
|
+
def counter
|
78
|
+
@counter ||= 0
|
79
|
+
@counter += 1
|
80
|
+
end
|
81
|
+
|
82
|
+
module ClassMethods
|
83
|
+
def sqrt(x)
|
84
|
+
x ** 0.5
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.included(base)
|
89
|
+
base.send(:extend, ClassMethods)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class SimpleMath
|
94
|
+
include MathMixin
|
95
|
+
end
|
96
|
+
|
97
|
+
example do
|
98
|
+
result_of(SimpleMath.sqrt(4)) == 2
|
99
|
+
|
100
|
+
my_simple_math = SimpleMath.new
|
101
|
+
result_of(my_simple_math.counter) == 1
|
102
|
+
result_of(my_simple_math.counter) == 2
|
103
|
+
end
|
104
|
+
|
105
|
+
# This is regarded as the most elegant way of defining class and instance
|
106
|
+
# methods for a mixin module. And the basic functionality is the same as in the
|
107
|
+
# previous example.
|
108
|
+
|
109
|
+
# After we now know how to define class side behaviour, everybody is curious
|
110
|
+
# to know how to extend this behaviour using context-oriented programming and
|
111
|
+
# ContextR.
|
112
|
+
#
|
113
|
+
# Additionial, context-dependent behaviour is defined in modules. These modules
|
114
|
+
# are then attached to the class with a selector representing the layer, in
|
115
|
+
# which the behaviour should reside. For examples on the instance side
|
116
|
+
# have a look at the bottom of test_introduction.
|
117
|
+
#
|
118
|
+
# Let's how we can achieve the same on the class side for each of the different
|
119
|
+
# methods of defining class side behaviour.
|
120
|
+
|
121
|
+
|
122
|
+
# Using def self.method_name
|
123
|
+
# ==========================
|
124
|
+
|
125
|
+
# Okay, we won't get rid of the modules, used to encapsulate the
|
126
|
+
# context-dependent behaviour, so the extension is a bit noisier, than the
|
127
|
+
# basic notation.
|
128
|
+
|
129
|
+
class SimpleMath
|
130
|
+
module AccessControlMethods
|
131
|
+
def pi
|
132
|
+
"You are not allowed to access this method"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
extend AccessControlMethods => :access_control
|
137
|
+
end
|
138
|
+
|
139
|
+
# But we can use the same principles, like we did for the instance side. Simply
|
140
|
+
# use a hash to tell extend, that the module should only be used in a certain
|
141
|
+
# layer.
|
142
|
+
|
143
|
+
example do
|
144
|
+
result_of(SimpleMath.pi) == 3.14159265
|
145
|
+
|
146
|
+
ContextR::with_layer :access_control do
|
147
|
+
result_of(SimpleMath.pi) == "You are not allowed to access this method"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
# Using class << self
|
153
|
+
# ===================
|
154
|
+
|
155
|
+
# When your using the eigenclass, you are able to use to good old include to
|
156
|
+
# manage the extension. But nobody stops you if you fell in love with extend.
|
157
|
+
|
158
|
+
class SimpleMath
|
159
|
+
class << self
|
160
|
+
module EnglishMethods
|
161
|
+
def e
|
162
|
+
"Euler's constant"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
include EnglishMethods => :english
|
166
|
+
end
|
167
|
+
|
168
|
+
module GermanMethods
|
169
|
+
def e
|
170
|
+
"Eulersche Zahl"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
extend GermanMethods => :german
|
174
|
+
end
|
175
|
+
|
176
|
+
example do
|
177
|
+
result_of(SimpleMath.e) == 2.71828183
|
178
|
+
|
179
|
+
ContextR::with_layer :german do
|
180
|
+
result_of(SimpleMath.e) == "Eulersche Zahl"
|
181
|
+
end
|
182
|
+
ContextR::with_layer :english do
|
183
|
+
result_of(SimpleMath.e) == "Euler's constant"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
# Using a module
|
189
|
+
# ==============
|
190
|
+
|
191
|
+
# Hey, this is what we did all the time, so it is only natural to have the same
|
192
|
+
# syntax to extend a class using in module for context-dependent behaviour. But
|
193
|
+
# for the sake of completeness, I will attach another example.
|
194
|
+
|
195
|
+
class SimpleMath
|
196
|
+
module ExactComputationMethods
|
197
|
+
def golden_ratio
|
198
|
+
sleep(0.01) # In real life this would take a bit longer,
|
199
|
+
# but I don't have the time.
|
200
|
+
1.6180339887_4989484820_4586834365_6381177203_0917980576
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
extend ExactComputationMethods => :exact_computation
|
205
|
+
end
|
206
|
+
|
207
|
+
example do
|
208
|
+
result_of(SimpleMath.golden_ratio) == 1.6180339887
|
209
|
+
|
210
|
+
ContextR::with_layer :exact_computation do
|
211
|
+
result_of(SimpleMath.golden_ratio) ==
|
212
|
+
1.6180339887_4989484820_4586834365_6381177203_0917980576
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# Conclusion
|
218
|
+
# ==========
|
219
|
+
|
220
|
+
# In general, there are two options to define context-dependent class side
|
221
|
+
# behaviour. Use include in the eigenclass or use extend anywhere else. Both
|
222
|
+
# options result in the same behaviour, just like the different options in
|
223
|
+
# plain ruby look different, but have the same effect.
|
224
|
+
#
|
225
|
+
# The programmer is free to use, whatever suites best. This is still Ruby.
|