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/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.