contextr 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
- class << self
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
@@ -1,11 +1,216 @@
1
1
  require File.dirname(__FILE__) + '/spec_helper.rb'
2
2
 
3
- # Time to add your specs!
4
- # http://rspec.rubyforge.org/
5
- describe "Place your specs here" do
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 "find this spec in spec directory" do
8
- violated "Be sure to write your specs"
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
- end
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
@@ -1 +1,2 @@
1
1
  require 'spec'
2
+ require File.dirname(__FILE__) + "/../lib/contextr"
@@ -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.