contextr 0.1.1 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,7 +2,7 @@ module ContextR #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
- TINY = 1
5
+ TINY = 9
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -65,9 +65,9 @@ module Dynamic
65
65
  save[key] = self[key]
66
66
  self[key] = value
67
67
  }
68
- return_value = block.call
68
+ block.call
69
+ ensure
69
70
  variables.update save
70
- return_value
71
71
  end
72
72
 
73
73
  def method_missing(name, *args)
@@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/spec_helper.rb'
2
2
 
3
3
  module AddressMethods
4
4
  def to_s
5
- "#{yield(:next)} (#{yield(:receiver).address})"
5
+ "#{super} (#{yield(:receiver).address})"
6
6
  end
7
7
  end
8
8
 
@@ -11,7 +11,9 @@ class University < Struct.new(:name, :address)
11
11
  name
12
12
  end
13
13
 
14
- include AddressMethods => :address
14
+ in_layer :address do
15
+ include AddressMethods
16
+ end
15
17
  end
16
18
 
17
19
  class Student < Struct.new(:name, :address, :education)
@@ -19,35 +21,35 @@ class Student < Struct.new(:name, :address, :education)
19
21
  name
20
22
  end
21
23
 
22
- include AddressMethods => :address
23
-
24
- module EducationMethods
24
+ in_layer :address do
25
+ include AddressMethods
26
+ end
27
+ in_layer :education do
25
28
  def to_s
26
- "#{yield(:next)}, #{yield(:receiver).education}"
29
+ "#{super}, #{yield(:receiver).education}"
27
30
  end
28
31
  end
29
-
30
- include EducationMethods => :education
31
32
  end
32
33
 
33
34
  class Ordering
34
- def test
35
+ def inner_outer
35
36
  "base"
36
37
  end
37
38
 
38
39
  module InnerMethods
39
- def test
40
- "inner #{yield(:next)} inner"
40
+ def inner_outer
41
+ "inner #{super} inner"
41
42
  end
42
43
  end
43
- include InnerMethods => :multiple_modules
44
-
45
44
  module OuterMethods
46
- def test
47
- "outer #{yield(:next)} outer"
45
+ def inner_outer
46
+ "outer #{super} outer"
48
47
  end
49
48
  end
50
- include OuterMethods => :multiple_modules
49
+ in_layer :multiple_modules do
50
+ include InnerMethods
51
+ include OuterMethods
52
+ end
51
53
  end
52
54
 
53
55
 
@@ -104,22 +106,26 @@ describe "A contextified object" do
104
106
 
105
107
  it "should also activate multiple modules per layer" do
106
108
  ContextR::with_layers :multiple_modules do
107
- Ordering.new.test.should == "outer inner base inner outer"
109
+ Ordering.new.inner_outer.should == "outer inner base inner outer"
108
110
  end
109
111
  end
110
112
 
111
113
  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}"
114
+ class Student
115
+ in_layer :education do
116
+ def to_s
117
+ "#{super} @ #{yield(:receiver).education}"
118
+ end
115
119
  end
116
120
  end
117
121
  ContextR::with_layer :education do
118
122
  @student.to_s.should == "Gregor Schmidt @ HPI"
119
123
  end
120
- module Student::EducationMethods
121
- def to_s
122
- "#{yield(:next)}, #{yield(:receiver).education}"
124
+ class Student
125
+ in_layer :education do
126
+ def to_s
127
+ "#{super}, #{yield(:receiver).education}"
128
+ end
123
129
  end
124
130
  end
125
131
  end
@@ -149,14 +155,13 @@ describe "A method modules defining context dependent behaviour" do
149
155
 
150
156
  it "should have inner state" do
151
157
  class Student
152
- module LogMethods
158
+ in_layer :log do
153
159
  def to_s
154
160
  @i ||= 0
155
161
  @i += 1
156
- "#{@i}: #{yield(:next)}"
162
+ "#{@i}: #{super}"
157
163
  end
158
164
  end
159
- include LogMethods => :log
160
165
  end
161
166
  ContextR::with_layer :log do
162
167
  @student.to_s.should == "1: Gregor Schmidt"
@@ -172,11 +177,11 @@ describe "A method modules defining context dependent behaviour" do
172
177
 
173
178
  it "should not lose its state after redefinition of the module" do
174
179
  class Student
175
- module LogMethods
180
+ in_layer :log do
176
181
  def to_s
177
182
  @i ||= 0
178
183
  @i += 1
179
- "(#{@i}) #{yield(:next)}"
184
+ "(#{@i}) #{super}"
180
185
  end
181
186
  end
182
187
  end
@@ -210,7 +215,8 @@ describe "ContextR" do
210
215
  end
211
216
 
212
217
  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]
218
+ [:address, :education, :log, :multiple_modules].each do |layer|
219
+ ContextR::layers.sort_by{ |s| s.to_s }.should include(layer)
220
+ end
215
221
  end
216
222
  end
@@ -0,0 +1,59 @@
1
+ module ExampleTest
2
+ module ClassMethods
3
+ attr_accessor :latest_test_class
4
+ attr_accessor :latest_test_case
5
+ end
6
+ extend ClassMethods
7
+
8
+ module ObjectExtension
9
+ def test_class(name)
10
+ ExampleTest::latest_test_class = Class.new(Test::Unit::TestCase)
11
+ ExampleTest::latest_test_case = 0
12
+ Object.const_set(name, ExampleTest::latest_test_class)
13
+ end
14
+
15
+ def example(&block)
16
+ ExampleTest::latest_test_class.class_eval do
17
+ define_method("test_%03d" % (ExampleTest::latest_test_case += 1),
18
+ &block)
19
+ end
20
+ end
21
+ end
22
+
23
+ module TestExtension
24
+ def assert_to_s(expected, actual)
25
+ assert_equal(expected, actual.to_s)
26
+ end
27
+
28
+ def result_of(object)
29
+ Result.new(object, self)
30
+ end
31
+
32
+ def output_of(object)
33
+ Output.new(object, self)
34
+ end
35
+
36
+ class Result
37
+ attr_accessor :object, :test_class
38
+ def initialize(object, test_class)
39
+ self.object = object
40
+ self.test_class = test_class
41
+ end
42
+ def ==(string)
43
+ test_class.assert_equal(string, object)
44
+ end
45
+ end
46
+ class Output < Result
47
+ def ==(string)
48
+ test_class.assert_equal(string, object.to_s)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ class Test::Unit::TestCase
55
+ include ExampleTest::TestExtension
56
+ end
57
+ class Object
58
+ include ExampleTest::ObjectExtension
59
+ end
@@ -0,0 +1,97 @@
1
+ require 'markaby'
2
+ if PLATFORM == "java"
3
+ class Markaby::Builder
4
+ def pre_block(block)
5
+ end
6
+ end
7
+ else
8
+ require 'ruby2ruby'
9
+ end
10
+
11
+ module LiterateMarkabyTest
12
+ TARGET_DIR = File.dirname(__FILE__) + "/../../website/test/"
13
+ module ObjectExtension
14
+ def test(name, &block)
15
+ mab = Markaby::Builder.new
16
+
17
+ mab.test_class = Class.new(Test::Unit::TestCase)
18
+ mab.latest_test_case = 0
19
+
20
+ Object.const_set(name, mab.test_class)
21
+ mab.xhtml_strict do
22
+ head do
23
+ title { name }
24
+ end
25
+ body do
26
+ h1 { name }
27
+ div(&block)
28
+ end
29
+ end
30
+
31
+ Dir.mkdir(TARGET_DIR) unless File.directory?(TARGET_DIR)
32
+ File.open(TARGET_DIR + name.to_s.underscore + ".html", "w") do |f|
33
+ f.puts mab.to_s
34
+ end
35
+ end
36
+ end
37
+
38
+ module MarkabyBuilderExtension
39
+ attr_accessor :test_class, :latest_test_case
40
+ def pre_block(block)
41
+ self.pre(block.to_ruby.gsub(/^proc \{\n(.*)\n\}$/m, '\1'))
42
+ end
43
+
44
+ def output(&block)
45
+ block.call
46
+ pre_block(block)
47
+ end
48
+ def example(&block)
49
+ name = "test_%03d" % (self.latest_test_case += 1)
50
+ test_class.class_eval do
51
+ define_method(name, &block)
52
+ end
53
+ pre_block(block)
54
+ end
55
+ end
56
+
57
+ module TestExtension
58
+ def assert_to_s(expected, actual)
59
+ assert_equal(expected, actual.to_s)
60
+ end
61
+
62
+ def result_of(object)
63
+ Result.new(object, self)
64
+ end
65
+
66
+ def output_of(object)
67
+ Output.new(object, self)
68
+ end
69
+
70
+ class Result
71
+ attr_accessor :object, :test_class
72
+ def initialize(object, test_class)
73
+ self.object = object
74
+ self.test_class = test_class
75
+ end
76
+ def ==(string)
77
+ test_class.assert_equal(string, object)
78
+ end
79
+ end
80
+ class Output < Result
81
+ def ==(string)
82
+ test_class.assert_equal(string, object.to_s)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ #class Test::Unit::TestCase
89
+ # include LiterateMarkabyTest::TestExtension
90
+ #end
91
+ class Object
92
+ include LiterateMarkabyTest::ObjectExtension
93
+ end
94
+ class Markaby::Builder
95
+ include LiterateMarkabyTest::MarkabyBuilderExtension
96
+ end
97
+
@@ -0,0 +1,108 @@
1
+ gem "literate_maruku"
2
+ require "literate_maruku"
3
+ require 'markaby'
4
+ require 'active_support'
5
+
6
+ class Fixnum
7
+ def ordinal
8
+ # teens
9
+ return 'th' if (10..19).include?(self % 100)
10
+ # others
11
+ case self % 10
12
+ when 1: return 'st'
13
+ when 2: return 'nd'
14
+ when 3: return 'rd'
15
+ else return 'th'
16
+ end
17
+ end
18
+ end
19
+
20
+ class Time
21
+ def pretty
22
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
23
+ end
24
+ end
25
+
26
+ module LiterateMarukuTest
27
+ BASE_DIR = File.dirname(__FILE__) + "/../"
28
+ TARGET_DIR = File.dirname(__FILE__) + "/../../website/test/"
29
+
30
+ def self.load(file)
31
+ content = LiterateMaruku.require(
32
+ BASE_DIR + "#{File.basename(file, '.rb')}.mkd",
33
+ :inline => true,
34
+ :attributes => {:execute => true})
35
+
36
+ download = "http://rubyforge.org/projects/contextr"
37
+ version = ContextR::VERSION::STRING
38
+ modified = Time.now
39
+ sub_title = File.basename(file, '.rb').gsub("test_", "").titleize
40
+
41
+ doc = Markaby::Builder.new.xhtml_strict do
42
+ head do
43
+ title "ContextR - #{sub_title} - Documentation"
44
+ link :href => "../stylesheets/screen.css", :rel=>'stylesheet',
45
+ :type=>'text/css', :media => "screen"
46
+ script :src => "../javascripts/rounded_corners_lite.inc.js",
47
+ :type =>"text/javascript"
48
+ script %Q{
49
+ window.onload = function() {
50
+ settings = {
51
+ tl: { radius: 10 },
52
+ tr: { radius: 10 },
53
+ bl: { radius: 10 },
54
+ br: { radius: 10 },
55
+ antiAlias: true,
56
+ autoPad: true,
57
+ validTags: ["div"]
58
+ }
59
+ var versionBox = new curvyCorners(settings,
60
+ document.getElementById("version"));
61
+ versionBox.applyCornersToAll();
62
+ }
63
+ }, :type => "text/javascript"
64
+ end
65
+ body do
66
+ div.main! do
67
+ h1 sub_title
68
+ div.version! :class => "clickable",
69
+ :onclick => "document.location='#{download}'; return false" do
70
+ p "Get Version"
71
+ a version, :href => download, :class => "numbers"
72
+ end
73
+ h1 do
74
+ self << "&#x2192; &#8216;"
75
+ a "contextr", :href => "http://contextr.rubyforge.org/"
76
+ self << "&#8217;"
77
+ end
78
+
79
+ ul.navi! do
80
+ Dir[File.dirname(file) + "/test_*.mkd"].each do |mkd_file_name|
81
+ li do
82
+ name = File.basename(mkd_file_name, ".mkd").gsub("test_", "")
83
+ a name.titleize, :href => name + ".html"
84
+ end
85
+ end
86
+ end
87
+
88
+ self << content
89
+ p.coda do
90
+ text modified.pretty
91
+ br
92
+ text "Theme extended from "
93
+ a "Paul Battley", :href => "http://rb2js.rubyforge.org/"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ File.open(TARGET_DIR +
99
+ "#{File.basename(file, '.rb').gsub("test_", "")}.html", "w") do |f|
100
+ f.puts(%q{<!DOCTYPE html
101
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
102
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">})
103
+ doc.to_s.each do |chunk|
104
+ f.puts(chunk)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,234 @@
1
+ **TODO: Update to use new in\_layer API**
2
+
3
+ In Ruby there are multiple ways of defining behaviour on the class side. That
4
+ are messages that are send to the class, not to the instance. Class side
5
+ behaviour is often useful for functional methods, i.e. methods that do not
6
+ rely on inner state and have no side effects. Mathematical functions have
7
+ these characteristics - that is where the name probably comes from.
8
+
9
+ **Note**: Java programmers may know these methods as static. It is similar but
10
+ not exactly the same.
11
+
12
+
13
+ Using `def self.method_name`
14
+ -----------------------------
15
+
16
+ The simpliest way of defining class side behaviour is prepending self. to the
17
+ method definition. This way, the method is attached to the surrounding class
18
+ and not instance. A simple example:
19
+
20
+ class SimpleMath
21
+ def self.pi
22
+ 3.14159265
23
+ end
24
+ end
25
+
26
+ example do
27
+ result_of(SimpleMath.pi) == 3.14159265
28
+ end
29
+
30
+
31
+ Using `class << self`
32
+ ---------------------
33
+
34
+ When you are having lots of class side methods as well as instance side ones,
35
+ it can be difficult to spot the little self. in front of the method name.
36
+ Probably you like to group them more explicitly. You could use Ruby's
37
+ eigenclass principle for that. It will look like the following:
38
+
39
+ class SimpleMath
40
+ class << self
41
+ def e
42
+ 2.71828183
43
+ end
44
+ end
45
+ end
46
+
47
+ example do
48
+ result_of(SimpleMath.e) == 2.71828183
49
+ end
50
+
51
+ **Note**: Eigenclasses are also known as *singleton class* or *meta class*.
52
+ I prefer eigenclass, because it not used with different meanings in other
53
+ contexts, which eases talking about it with experts in different languages.
54
+
55
+ Using a module
56
+ --------------
57
+
58
+ For even more encapsulation you could also use modules and extend the class
59
+ definition with them. I am using extend here on purpose. Module's include
60
+ method adds the behaviour to the instance side, extend to the class side.
61
+ Or to rephrase it: Module's include method adds the behaviour to instances,
62
+ extend to the class itself.
63
+
64
+ class SimpleMath
65
+ module ClassMethods
66
+ def golden_ratio
67
+ 1.6180339887
68
+ end
69
+ end
70
+
71
+ extend ClassMethods
72
+ end
73
+
74
+ example do
75
+ result_of(SimpleMath.golden_ratio) == 1.6180339887
76
+ end
77
+
78
+ The last method is e.g. used in the web framework Ruby on Rails. Often
79
+ a variation of it is used to define class and instance side behaviour for
80
+ mixin modules
81
+
82
+ module MathMixin
83
+ def counter
84
+ @counter ||= 0
85
+ @counter += 1
86
+ end
87
+
88
+ module ClassMethods
89
+ def sqrt(x)
90
+ x ** 0.5
91
+ end
92
+ end
93
+
94
+ def self.included(base)
95
+ base.extend ClassMethods
96
+ end
97
+ end
98
+
99
+ class SimpleMath
100
+ include MathMixin
101
+ end
102
+
103
+ example do
104
+ result_of(SimpleMath.sqrt(4)) == 2
105
+
106
+ my_simple_math = SimpleMath.new
107
+ result_of(my_simple_math.counter) == 1
108
+ result_of(my_simple_math.counter) == 2
109
+ end
110
+
111
+ This is regarded as the most elegant way of defining class and instance
112
+ methods for a mixin module. And the basic functionality is the same as in the
113
+ previous example.
114
+
115
+ After we now know how to define class side behaviour, everybody is curious
116
+ to know how to extend this behaviour using context-oriented programming and
117
+ ContextR.
118
+
119
+ Additionial, context-dependent behaviour is defined in `in_layer` blocks.
120
+ These are then attached to the layer, in which the behaviour should reside.
121
+ For examples on the instance side have a look at the bottom of
122
+ `test_introduction`.
123
+
124
+ Let's look how we can achieve the same on the class side for each of the
125
+ different methods of defining class side behaviour.
126
+
127
+
128
+ Using `def self.method_name`
129
+ ----------------------------
130
+
131
+ **TODO: Allow def self.method\_name to add class side behaviour**
132
+
133
+ Okay, we won't get rid of the modules, used to encapsulate the
134
+ context-dependent behaviour, so the extension is a bit noisier, than the
135
+ basic notation.
136
+
137
+ class SimpleMath
138
+ class << self
139
+ in_layer :access_control do
140
+ def pi
141
+ "You are not allowed to access this method"
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ But we can use the same principles, like we did for the instance side. Simply
148
+ use a hash to tell extend, that the module should only be used in a certain
149
+ layer.
150
+
151
+ example do
152
+ result_of(SimpleMath.pi) == 3.14159265
153
+
154
+ ContextR::with_layer :access_control do
155
+ result_of(SimpleMath.pi) == "You are not allowed to access this method"
156
+ end
157
+ end
158
+
159
+
160
+ Using `class << self`
161
+ ---------------------
162
+
163
+ When your using the eigenclass, you are able to use to good old in\_layer to
164
+ manage the extension.
165
+
166
+ class SimpleMath
167
+ class << self
168
+ in_layer :english do
169
+ def e
170
+ "Euler's constant"
171
+ end
172
+ end
173
+ end
174
+
175
+ class << self
176
+ in_layer :german do
177
+ def e
178
+ "Eulersche Zahl"
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ example do
185
+ result_of(SimpleMath.e) == 2.71828183
186
+
187
+ ContextR::with_layer :german do
188
+ result_of(SimpleMath.e) == "Eulersche Zahl"
189
+ end
190
+ ContextR::with_layer :english do
191
+ result_of(SimpleMath.e) == "Euler's constant"
192
+ end
193
+ end
194
+
195
+
196
+ Using a module
197
+ --------------
198
+
199
+ Hey, this is what we did all the time, so it is only natural to have the same
200
+ syntax to extend a class using in module for context-dependent behaviour. But
201
+ for the sake of completeness, I will attach another example.
202
+
203
+ class SimpleMath
204
+ class << self
205
+ in_layer :exact_computation do
206
+ def golden_ratio
207
+ sleep(0.01) # In real life this would take a bit longer,
208
+ # but I don't have the time.
209
+ 1.6180339887_4989484820_4586834365_6381177203_0917980576
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ example do
216
+ result_of(SimpleMath.golden_ratio) == 1.6180339887
217
+
218
+ ContextR::with_layer :exact_computation do
219
+ result_of(SimpleMath.golden_ratio) ==
220
+ 1.6180339887_4989484820_4586834365_6381177203_0917980576
221
+ end
222
+ end
223
+
224
+
225
+ Conclusion
226
+ ----------
227
+
228
+ In general, there are two options to define context-dependent class side
229
+ behaviour. Use in\_layer in the eigenclass or use extend anywhere else. Both
230
+ options result in the same behaviour, just like the different options in
231
+ plain ruby look different, but have the same effect.
232
+
233
+ The programmer is free to use, whatever suites best. This is still Ruby.
234
+