dynamic_variable 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg/
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # Introduction
2
+
3
+ Occasionally a method's behavior should depend on the context in which it is called. What is happening above me on the stack? DynamicVariable helps you in these context dependent situations.
4
+
5
+ DynamicVariable is similar to [dynamic scope](http://c2.com/cgi/wiki?DynamicScoping), [fluid-let](http://www.megasolutions.net/scheme/fluid-binding-25366.aspx), [SRFI 39 parameters](http://srfi.schemers.org/srfi-39/srfi-39.html), [cflow pointcuts](http://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html#d0e5410), even [Scala has its own DynamicVariable](http://www.scala-lang.org/api/current/scala/util/DynamicVariable.html).
6
+
7
+ # Example: Context Dependent Debugging
8
+
9
+ Suppose we have a method which is `frequently_called`. It works fine except when called from `source_of_the_trouble`. Suppose further that `source_of_the_trouble` doesn't directly invoke `frequently_called` instead there's a method `in_the_middle`. Our setup looks like this:
10
+
11
+ def source_of_the_trouble
12
+ in_the_middle
13
+ end
14
+
15
+ def in_the_middle
16
+ frequently_called
17
+ end
18
+
19
+ def frequently_called
20
+ puts "I'm called all the time."
21
+ end
22
+
23
+ 1000.times { in_the_middle }
24
+ source_of_the_trouble
25
+
26
+ With DynamicVariable, we can easily change `frequently_called` so that it only brings up the `debugger` when called from `source_of_the_trouble`:
27
+
28
+ require 'rubygems'
29
+ require 'ruby-debug'
30
+ require 'dynamic_variable'
31
+
32
+ @troubled = DynamicVariable.new(false)
33
+
34
+ def source_of_the_trouble
35
+ @troubled.with(true) { in_the_middle }
36
+ end
37
+
38
+ def in_the_middle
39
+ frequently_called
40
+ end
41
+
42
+ def frequently_called
43
+ debugger if @troubled.value
44
+ puts "I'm called all the time."
45
+ end
46
+
47
+ 1000.times { in_the_middle }
48
+ source_of_the_trouble
49
+
50
+ # Typical Usage
51
+
52
+ A DynamicVariable storing one `:value` and then `:another`:
53
+
54
+ DynamicVariable.new(1) do |dv|
55
+ dv.value.should == 1
56
+ dv.value = 2
57
+ dv.value.should == 2
58
+
59
+ dv.with(:another, :middle, 3) do
60
+ dv.another.should == :middle
61
+ dv.value.should == 3
62
+
63
+ dv.with(:another, :inner, 4) do
64
+ dv.another.should == :inner
65
+ dv.value.should == 4
66
+ end
67
+
68
+ dv.another.should == :middle
69
+ dv.value.should == 3
70
+ end
71
+
72
+ expect do
73
+ dv.another
74
+ end.should raise_error(ArgumentError, "unbound variable :another")
75
+ dv.value.should == 2
76
+ end
77
+
78
+ # Why have a library?
79
+
80
+ Isn't it easy to set and reset a flag as the context changes? Sure, just watch out for raise, throw, and nesting:
81
+
82
+ def simple_with(value)
83
+ old_value = $value
84
+ $value = value
85
+ yield
86
+ ensure
87
+ $value = old_value
88
+ end
89
+
90
+ DynamicVariable adds the ability to reflect on your bindings. You can inspect them, and you can tinker with them if you feel the need:
91
+
92
+ dv = DynamicVariable.new(:v, 1, :w, 2) do |dv|
93
+ dv.with(:w, 3) do
94
+ dv.with(:v, 4) do
95
+ dv.bindings.should == [[:v, 1], [:w, 2], [:w, 3], [:v, 4]]
96
+ dv.bindings(:v).should == [1, 4]
97
+ dv.bindings(:w).should == [2, 3]
98
+
99
+ dv.set_bindings(:v, [-1, -2, -3])
100
+ dv.set_bindings(:w, [-4])
101
+ dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2], [:v, -3]]
102
+ end
103
+
104
+ dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2]]
105
+ end
106
+
107
+ dv.bindings.should == [[:v, -1], [:v, -2]]
108
+ end
109
+
110
+ dv.bindings.should == [[:v, -1]]
111
+
112
+ dv.bindings = []
113
+ dv.bindings.should == []
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "dynamic_variable"
5
+ gemspec.summary = "Provides dynamically scoped variables."
6
+ gemspec.description = "Occasionally a method's behavior should depend on the context in which it is called. What is happening above me on the stack? DynamicVariable helps you in these context dependent situations."
7
+ gemspec.email = "wtaysom@gmail.com"
8
+ gemspec.homepage = "http://github.com/wtaysom/Ruby-DynamicVariable"
9
+ gemspec.authors = ["William Taysom"]
10
+ end
11
+ Jeweler::GemcutterTasks.new
12
+ rescue LoadError
13
+ puts "Jeweler not available. Install it with: gem install jeweler"
14
+ end
15
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,14 @@
1
+ def source_of_the_trouble
2
+ in_the_middle
3
+ end
4
+
5
+ def in_the_middle
6
+ frequently_called
7
+ end
8
+
9
+ def frequently_called
10
+ puts "I'm called all the time."
11
+ end
12
+
13
+ 1000.times { in_the_middle }
14
+ source_of_the_trouble
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'ruby-debug'
3
+ require 'dynamic_variable'
4
+
5
+ @troubled = DynamicVariable.new(false)
6
+
7
+ def source_of_the_trouble
8
+ @troubled.with(true) { in_the_middle }
9
+ end
10
+
11
+ def in_the_middle
12
+ frequently_called
13
+ end
14
+
15
+ def frequently_called
16
+ debugger if @troubled.value
17
+ puts "I'm called all the time."
18
+ end
19
+
20
+ 1000.times { in_the_middle }
21
+ source_of_the_trouble
@@ -0,0 +1,20 @@
1
+ def simple_with(value)
2
+ old_value = $value
3
+ $value = value
4
+ yield
5
+ ensure
6
+ $value = old_value
7
+ end
8
+
9
+
10
+ describe '#simple_with' do it 'should behave' do
11
+
12
+ simple_with(3) do
13
+ simple_with(4) do
14
+ $value.should == 4
15
+ end
16
+ $value.should == 3
17
+ end
18
+ $value.should == nil
19
+
20
+ end end
@@ -0,0 +1,49 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{dynamic_variable}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["William Taysom"]
12
+ s.date = %q{2010-09-09}
13
+ s.description = %q{Occasionally a method's behavior should depend on the context in which it is called. What is happening above me on the stack? DynamicVariable helps you in these context dependent situations.}
14
+ s.email = %q{wtaysom@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README.md",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "doc/readme_code/01example_at_start.rb",
24
+ "doc/readme_code/02example_with_debug.rb",
25
+ "doc/readme_code/03simple_with.rb",
26
+ "dynamic_variable.gemspec",
27
+ "lib/dynamic_variable.rb",
28
+ "spec/dynamic_variable_spec.rb"
29
+ ]
30
+ s.homepage = %q{http://github.com/wtaysom/Ruby-DynamicVariable}
31
+ s.rdoc_options = ["--charset=UTF-8"]
32
+ s.require_paths = ["lib"]
33
+ s.rubygems_version = %q{1.3.7}
34
+ s.summary = %q{Provides dynamically scoped variables.}
35
+ s.test_files = [
36
+ "spec/dynamic_variable_spec.rb"
37
+ ]
38
+
39
+ if s.respond_to? :specification_version then
40
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
41
+ s.specification_version = 3
42
+
43
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
44
+ else
45
+ end
46
+ else
47
+ end
48
+ end
49
+
@@ -0,0 +1,176 @@
1
+ class DynamicVariable
2
+ @@un = Object.new
3
+
4
+ def initialize(*pairs, &block)
5
+ @bindings = []
6
+ if block_given?
7
+ with(*pairs, &block)
8
+ else
9
+ push_pairs(pairs) unless pairs.empty?
10
+ end
11
+ end
12
+
13
+ def push(variable, value)
14
+ pair = [variable, value]
15
+ @bindings << pair
16
+ pair
17
+ end
18
+
19
+ def pop(variable)
20
+ index = rindex(variable)
21
+ @bindings.slice!(index)[1] if index
22
+ end
23
+
24
+ ##
25
+ # with {}
26
+ # varible defaults to :value
27
+ # value defaults to nil
28
+ #
29
+ # with(value) { block }
30
+ # variable defaults to :value
31
+ #
32
+ # with(variable, value) { block }
33
+ #
34
+ # with(variable_1, value_1, ..., variable_n_1, value_n_1, value_n) { block }
35
+ # variable_n defaults to :value
36
+ #
37
+ # with(variable_1, value_1, ..., variable_n, value_n) { block }
38
+ #
39
+ def with(*pairs)
40
+ pairs = [nil] if pairs.empty?
41
+ push_pairs(pairs)
42
+ begin
43
+ yield(self)
44
+ ensure
45
+ pop_pairs(pairs)
46
+ end
47
+ end
48
+
49
+ def with_module
50
+ this = self
51
+ mod = Module.new
52
+ mod.send(:define_method, :with) do |*args, &block|
53
+ this.with(*args, &block)
54
+ end
55
+ mod
56
+ end
57
+
58
+ def [](variable)
59
+ find_binding(variable)[1]
60
+ end
61
+
62
+ def []=(variable, value)
63
+ find_binding(variable)[1] = value
64
+ end
65
+
66
+ def method_missing(variable, *args)
67
+ if args.empty?
68
+ value = self[variable]
69
+ instance_eval %Q{def #{variable}; self[:#{variable}] end}
70
+ else
71
+ writer = variable
72
+ unless args.size == 1 and writer.to_s =~ /(.*)=/
73
+ raise NoMethodError, "undefined method `#{writer}' for #{self.inspect}"
74
+ end
75
+ variable = $1.to_sym
76
+ self[variable] = args[0]
77
+ instance_eval %Q{def #{writer}(v); self[:#{variable}] = v end}
78
+ end
79
+ value
80
+ end
81
+
82
+ def variables
83
+ variables = {}
84
+ @bindings.each{|variable, value| variables[variable] = value}
85
+ variables
86
+ end
87
+
88
+ def variables=(variables)
89
+ variables.each{|variable, value| self[variable] = value}
90
+ end
91
+
92
+ def bindings(variable = @@un)
93
+ if variable == @@un
94
+ @bindings.map{|pair| pair.clone}
95
+ else
96
+ @bindings.select{|var, value| var == variable}.map!{|var, value| value}
97
+ end
98
+ end
99
+
100
+ def bindings=(bindings)
101
+ set_bindings(bindings)
102
+ end
103
+
104
+ def set_bindings(variable, bindings = @@un)
105
+ if bindings == @@un
106
+ bindings = variable
107
+ @bindings = bindings.map do |pair|
108
+ unless pair.is_a? Array and pair.size == 2
109
+ raise ArgumentError,
110
+ "expected [variable, value] pair, got #{pair.inspect}"
111
+ end
112
+ pair.clone
113
+ end
114
+ else
115
+ unless bindings.is_a? Array
116
+ raise ArgumentError,
117
+ "expected bindings to be Array, got a #{bindings.class}"
118
+ end
119
+ index = 0
120
+ old_bindings = @bindings
121
+ @bindings = []
122
+ old_bindings.each do |var, value|
123
+ if var == variable
124
+ unless index < bindings.size
125
+ next
126
+ end
127
+ value = bindings[index]
128
+ index += 1
129
+ end
130
+ push(var, value)
131
+ end
132
+ while index < bindings.size
133
+ value = bindings[index]
134
+ push(variable, value)
135
+ index += 1
136
+ end
137
+ end
138
+ bindings
139
+ end
140
+
141
+ private
142
+
143
+ def find_binding(variable)
144
+ index = rindex(variable)
145
+ unless index
146
+ raise ArgumentError, "unbound variable #{variable.inspect}"
147
+ end
148
+ @bindings[index]
149
+ end
150
+
151
+ def rindex(variable)
152
+ @bindings.rindex{|pair| pair[0] == variable}
153
+ end
154
+
155
+ def push_pairs(pairs)
156
+ pairs.insert(-2, :value) if pairs.size.odd?
157
+ each_pair(pairs) {|variable, value| push(variable, value)}
158
+ end
159
+
160
+ def pop_pairs(pairs)
161
+ each_pair(pairs) {|variable, value| pop(variable)}
162
+ end
163
+
164
+ def each_pair(array)
165
+ pair_ready = false
166
+ head = nil
167
+ array.each do |v|
168
+ if pair_ready
169
+ yield(head, v)
170
+ else
171
+ head = v
172
+ end
173
+ pair_ready = !pair_ready
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,580 @@
1
+ require 'dynamic_variable'
2
+
3
+ describe DynamicVariable do
4
+ subject { DynamicVariable.new }
5
+
6
+ before do
7
+ extend subject.with_module
8
+ end
9
+
10
+ ### Utilities ###
11
+
12
+ def x_and_y_bindings
13
+ [[:x, 1], [:y, 2], [:y, 3], [:x, 4]]
14
+ end
15
+
16
+ def set_x_any_y_bindings
17
+ subject.bindings = x_and_y_bindings
18
+ end
19
+
20
+ def bindings_should_equal_x_and_y_bindings
21
+ subject.bindings.should == x_and_y_bindings
22
+ end
23
+
24
+ def bindings_should_be_empty
25
+ subject.bindings.should == []
26
+ end
27
+
28
+ def expect_should_raise_unbound_variable(&block)
29
+ expect do
30
+ block[]
31
+ end.should raise_error ArgumentError, "unbound variable :z"
32
+ end
33
+
34
+ ### Examples ###
35
+
36
+ example "typical usage" do
37
+ DynamicVariable.new(1) do |dv|
38
+ dv.value.should == 1
39
+ dv.value = 2
40
+ dv.value.should == 2
41
+
42
+ dv.with(:another, :middle, 3) do
43
+ dv.another.should == :middle
44
+ dv.value.should == 3
45
+
46
+ dv.with(:another, :inner, 4) do
47
+ dv.another.should == :inner
48
+ dv.value.should == 4
49
+ end
50
+
51
+ dv.another.should == :middle
52
+ dv.value.should == 3
53
+ end
54
+
55
+ expect do
56
+ dv.another
57
+ end.should raise_error(ArgumentError, "unbound variable :another")
58
+ dv.value.should == 2
59
+ end
60
+ end
61
+
62
+ example "reflection" do
63
+ dv = DynamicVariable.new(:v, 1, :w, 2) do |dv|
64
+ dv.with(:w, 3) do
65
+ dv.with(:v, 4) do
66
+ dv.bindings.should == [[:v, 1], [:w, 2], [:w, 3], [:v, 4]]
67
+ dv.bindings(:v).should == [1, 4]
68
+ dv.bindings(:w).should == [2, 3]
69
+
70
+ dv.set_bindings(:v, [-1, -2, -3])
71
+ dv.set_bindings(:w, [-4])
72
+ dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2], [:v, -3]]
73
+ end
74
+
75
+ dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2]]
76
+ end
77
+
78
+ dv.bindings.should == [[:v, -1], [:v, -2]]
79
+ end
80
+
81
+ dv.bindings.should == [[:v, -1]]
82
+
83
+ dv.bindings = []
84
+ dv.bindings.should == []
85
+ end
86
+
87
+ describe '.new' do
88
+ context "when called" do
89
+ context "with no arguments" do
90
+ it "should have no bindings" do
91
+ DynamicVariable.new.bindings.should == []
92
+ end
93
+ end
94
+
95
+ context "with one argument" do
96
+ it "should bind :value to argument" do
97
+ DynamicVariable.new(:argument).bindings.should ==
98
+ [[:value, :argument]]
99
+ end
100
+ end
101
+
102
+ context "with two arguments" do
103
+ it "should bind the first to the second" do
104
+ DynamicVariable.new(:first, :second).bindings.should ==
105
+ [[:first, :second]]
106
+ end
107
+ end
108
+
109
+ context "with an odd number of arguments" do
110
+ it "should bind :value to the second to last" do
111
+ DynamicVariable.new(:first, :second, :last).bindings.should ==
112
+ [[:first, :second], [:value, :last]]
113
+ end
114
+ end
115
+
116
+ context "with an even number of arguments" do
117
+ it "should bind variable, value pairs" do
118
+ DynamicVariable.new(:var1, :val1, :var2, :val2, :var3,
119
+ :val3).bindings.should ==
120
+ [[:var1, :val1], [:var2, :val2], [:var3, :val3]]
121
+ end
122
+ end
123
+
124
+ context "with a block" do
125
+ it "should pass self to the block" do
126
+ dv_in_block = nil;
127
+ dv = DynamicVariable.new do |dv|
128
+ dv.should be_a(DynamicVariable)
129
+ dv.value.should == nil
130
+ dv_in_block = dv
131
+ end
132
+ dv.should == dv_in_block
133
+ expect do
134
+ dv.value
135
+ end.should raise_error(ArgumentError, "unbound variable :value")
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '#push' do
142
+ it "should return the binding pair" do
143
+ subject.push(:x, 1).should == [:x, 1]
144
+ end
145
+
146
+ it "should add binding pairs" do
147
+ subject.push(:x, 1)
148
+ subject.push(:y, 2)
149
+ subject.push(:z, 3)
150
+ subject.push(:x, 4)
151
+
152
+ subject.bindings.should == [[:x, 1], [:y, 2], [:z, 3], [:x, 4]]
153
+ end
154
+
155
+ it "should allow a variable to be any object" do
156
+ subject.push(1337, 1)
157
+ subject.push(nil, 2)
158
+ subject.push("", 3)
159
+ subject.push([], 4)
160
+
161
+ subject.bindings.should == [[1337, 1], [nil, 2], ["", 3], [[], 4]]
162
+ end
163
+ end
164
+
165
+ describe '#pop' do
166
+ before { set_x_any_y_bindings }
167
+
168
+ it "should return the value of the variable" do
169
+ subject.pop(:x).should == 4
170
+ end
171
+
172
+ it "should remove last occurence of variable" do
173
+ subject.pop(:x)
174
+ subject.bindings.should == [[:x, 1], [:y, 2], [:y, 3]]
175
+
176
+ subject.pop(:x)
177
+ subject.bindings.should == [[:y, 2], [:y, 3]]
178
+ end
179
+
180
+ it "should remove last occurence leaving other variables in order" do
181
+ subject.pop(:y)
182
+ subject.bindings.should == [[:x, 1], [:y, 2], [:x, 4]]
183
+
184
+ subject.pop(:y)
185
+ subject.bindings.should == [[:x, 1], [:x, 4]]
186
+ end
187
+
188
+ context "when variable is not found" do
189
+ it "should return nil and do nothing" do
190
+ subject.pop(:z).should == nil
191
+ bindings_should_equal_x_and_y_bindings
192
+ end
193
+ end
194
+ end
195
+
196
+ describe '#with' do
197
+ it "should make bindings and clean up when done" do
198
+ with(:key, :value) do
199
+ subject.bindings.should == [[:key, :value]]
200
+ end
201
+ bindings_should_be_empty
202
+ end
203
+
204
+ it "should yield self to the block" do
205
+ with do |dv|
206
+ dv.should == subject
207
+ end
208
+ end
209
+
210
+ it "should return the result of its block" do
211
+ with{:result}.should == :result
212
+ end
213
+
214
+ it "should nest nicely" do
215
+ with(:value) do
216
+ subject.bindings.should == [[:value, :value]]
217
+ with(:second_value) do
218
+ subject.bindings.should == [[:value, :value], [:value, :second_value]]
219
+ end
220
+ subject.bindings.should == [[:value, :value]]
221
+ end
222
+ bindings_should_be_empty
223
+ end
224
+
225
+ it "should be exception safe" do
226
+ expect do
227
+ with(:value) do
228
+ subject.bindings.should == [[:value, :value]]
229
+ raise
230
+ end
231
+ end.should raise_error
232
+ bindings_should_be_empty
233
+ end
234
+
235
+ context "when called" do
236
+ context "with no arguments" do
237
+ it "should bind :value to nil" do
238
+ with do
239
+ subject.bindings.should == [[:value, nil]]
240
+ end
241
+ end
242
+ end
243
+
244
+ context "with one argument" do
245
+ it "should bind :value to argument" do
246
+ with(:argument) do
247
+ subject.bindings.should == [[:value, :argument]]
248
+ end
249
+ end
250
+ end
251
+
252
+ context "with two arguments" do
253
+ it "should bind the first to the second" do
254
+ with(:first, :second) do
255
+ subject.bindings.should == [[:first, :second]]
256
+ end
257
+ end
258
+ end
259
+
260
+ context "with an odd number of arguments" do
261
+ it "should bind :value to the second to last" do
262
+ with(:first, :second, :last) do
263
+ subject.bindings.should == [[:first, :second], [:value, :last]]
264
+ end
265
+ end
266
+ end
267
+
268
+ context "with an even number of arguments" do
269
+ it "should bind variable, value pairs" do
270
+ with(:var1, :val1, :var2, :val2, :var3, :val3) do
271
+ subject.bindings.should ==
272
+ [[:var1, :val1], [:var2, :val2], [:var3, :val3]]
273
+ end
274
+ end
275
+ end
276
+
277
+ context "with a variable repeated more than once" do
278
+ it "should bind the variable twice and unbind when done" do
279
+ with(:var, 1, :var, 2) do
280
+ subject.bindings.should == [[:var, 1], [:var, 2]]
281
+ end
282
+ subject.bindings.should == []
283
+ end
284
+ end
285
+
286
+ context "without a block" do
287
+ it "should raise \"no block given\"" do
288
+ expect do
289
+ subject.with(:key, :value)
290
+ end.should raise_error LocalJumpError, /no block given/
291
+ bindings_should_be_empty
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ describe '#with_module' do
298
+ it "should return a Module with a #with method" do
299
+ mod = subject.with_module
300
+ mod.should be_a Module
301
+ methods = mod.instance_methods(false)
302
+ methods.map(&:to_sym).should == [:with]
303
+ end
304
+ end
305
+
306
+ describe '#[]' do
307
+ before { set_x_any_y_bindings }
308
+
309
+ it "should return the nearest binding for a variable" do
310
+ subject[:x].should == 4
311
+ subject[:y].should == 3
312
+ end
313
+
314
+ context "when there is no binding" do
315
+ it "should raise \"unbound variable\"" do
316
+ expect_should_raise_unbound_variable do
317
+ subject[:z]
318
+ end
319
+ end
320
+ end
321
+ end
322
+
323
+ describe '#[]=' do
324
+ before { set_x_any_y_bindings }
325
+
326
+ it "should update nearest binding for a variable" do
327
+ subject[:x] = -1
328
+ subject[:y] = -2
329
+
330
+ subject.bindings.should == [[:x, 1], [:y, 2], [:y, -2], [:x, -1]]
331
+ end
332
+
333
+ it "should return the value" do
334
+ (subject[:x] = -1).should == -1
335
+ end
336
+
337
+ context "when there is no binding" do
338
+ it "should raise \"unbound variable\"" do
339
+ expect_should_raise_unbound_variable do
340
+ subject[:z] = -2
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ describe '#method_missing', " reader and writer generation" do
347
+ before { set_x_any_y_bindings }
348
+
349
+ it "should initially have no variable readers or writers defined" do
350
+ subject.methods(false).should == []
351
+ end
352
+
353
+ it "should generate variable readers as needed" do
354
+ subject.x.should == 4
355
+ subject.methods(false).map(&:to_sym).should == [:x]
356
+ end
357
+
358
+ it "should generate variable writers as needed" do
359
+ (subject.x = 4).should == 4
360
+ subject.methods(false).map(&:to_sym).should == [:x=]
361
+ end
362
+
363
+ context "when variable is unbound" do
364
+ it "should raise \"unbound variable\"" do
365
+ expect_should_raise_unbound_variable do
366
+ subject.z
367
+ end
368
+
369
+ expect_should_raise_unbound_variable do
370
+ subject.z = 3
371
+ end
372
+ end
373
+
374
+ it "should not generate readers or writers" do
375
+ expect do
376
+ subject.z
377
+ subject.z = 3
378
+ end.should raise_error
379
+
380
+ subject.methods(false).should == []
381
+ end
382
+ end
383
+
384
+ context "with to many arguments to be a writer" do
385
+ it "should raise NoMethodError" do
386
+ expect do
387
+ subject.too_many(1, 2)
388
+ end.should raise_error NoMethodError,
389
+ /undefined method `too_many' for/
390
+ end
391
+ end
392
+
393
+ context "with one argument but method name does not end in \"=\"" do
394
+ it "should raise NoMethodError" do
395
+ expect do
396
+ subject.not_writer(1)
397
+ end.should raise_error NoMethodError,
398
+ /undefined method `not_writer' for/
399
+ end
400
+ end
401
+ end
402
+
403
+ describe '#variables' do
404
+ before { set_x_any_y_bindings }
405
+
406
+ it "should return hash of all variables with their current bindings" do
407
+ subject.variables.should == {:x => 4, :y => 3}
408
+ end
409
+ end
410
+
411
+ describe '#variables=' do
412
+ before { set_x_any_y_bindings }
413
+
414
+ it "should update current bindings of variables" do
415
+ subject.variables = {:x => -1, :y => -2}
416
+ subject.bindings.should == [[:x, 1], [:y, 2], [:y, -2], [:x, -1]]
417
+ end
418
+
419
+ it "should return the variables" do
420
+ variables = {:x => -1, :y => -2}
421
+ (subject.variables = variables).should be_equal variables
422
+ end
423
+
424
+ it "should accept assoc array as argument" do
425
+ subject.variables = [[:x, -1], [:y, -2]]
426
+ subject.bindings.should == [[:x, 1], [:y, 2], [:y, -2], [:x, -1]]
427
+ end
428
+
429
+ context "when some variables are not mentioned" do
430
+ it "should retain the old bindings" do
431
+ subject.variables = {:y => -2}
432
+ subject.bindings.should == [[:x, 1], [:y, 2], [:y, -2], [:x, 4]]
433
+ end
434
+ end
435
+
436
+ context "when an unbound variable is mentioned" do
437
+ it "should raise \"unbound variable\"" do
438
+ expect_should_raise_unbound_variable do
439
+ subject.variables = {:z => -3}
440
+ end
441
+ end
442
+
443
+ it "should NOT update atomically" do
444
+ expect_should_raise_unbound_variable do
445
+ subject.variables = [[:y, -2], [:z, -3]]
446
+ end
447
+ subject.y.should == -2
448
+ end
449
+ end
450
+ end
451
+
452
+ describe '#bindings' do
453
+ before { set_x_any_y_bindings }
454
+
455
+ context "with no argument" do
456
+ it "should return an array of all variable bindings" do
457
+ bindings_should_equal_x_and_y_bindings
458
+ end
459
+
460
+ it "should not be affected by changes to the returned array" do
461
+ bindings = subject.bindings
462
+ bindings[0][1] = -1
463
+ bindings_should_equal_x_and_y_bindings
464
+ end
465
+ end
466
+
467
+ context "with variable as argument" do
468
+ it "should return array of all bindings for variable" do
469
+ subject.bindings(:x).should == [1, 4]
470
+ subject.bindings(:y).should == [2, 3]
471
+ end
472
+
473
+ context "when an unbound variable is used" do
474
+ it "should return empty array" do
475
+ subject.bindings(:z).should == []
476
+ end
477
+ end
478
+ end
479
+ end
480
+
481
+ shared_examples_for "bindings writer" do
482
+ it "should update all bindings" do
483
+ subject.send(bindings_writer, x_and_y_bindings)
484
+ bindings_should_equal_x_and_y_bindings
485
+ end
486
+
487
+ it "should return the argument" do
488
+ bindings = x_and_y_bindings
489
+ subject.send(bindings_writer, bindings).should be_equal bindings
490
+ end
491
+
492
+ it "should use a copy of bindings" do
493
+ bindings = x_and_y_bindings
494
+ subject.send(bindings_writer, bindings)
495
+ bindings[0][1] = -9
496
+ bindings_should_equal_x_and_y_bindings
497
+ end
498
+
499
+ context "when bindings does not respond to #map" do
500
+ it "should raise NoMethodError" do
501
+ expect do
502
+ subject.send(bindings_writer, :oops)
503
+ end.should raise_error NoMethodError
504
+ bindings_should_be_empty
505
+ end
506
+ end
507
+
508
+ context "when a binding is not an Array" do
509
+ it "should raise ArgumentError" do
510
+ expect do
511
+ subject.send(bindings_writer, [66])
512
+ end.should raise_error ArgumentError,
513
+ /expected \[variable, value\] pair, got/
514
+ bindings_should_be_empty
515
+ end
516
+ end
517
+
518
+ context "when a binding is of the wrong size" do
519
+ it "should raise ArgumentError" do
520
+ expect do
521
+ subject.send(bindings_writer, [[1, 2, 3]])
522
+ end.should raise_error ArgumentError,
523
+ /expected \[variable, value\] pair, got/
524
+ bindings_should_be_empty
525
+ end
526
+ end
527
+ end
528
+
529
+ describe '#bindings=' do
530
+ let(:bindings_writer) { :bindings= }
531
+
532
+ it_should_behave_like "bindings writer"
533
+ end
534
+
535
+ describe '#set_bindings' do
536
+ context "with one argument" do
537
+ let(:bindings_writer) { :set_bindings }
538
+
539
+ it_should_behave_like "bindings writer"
540
+ end
541
+
542
+ context "with two arguments" do
543
+ before { set_x_any_y_bindings }
544
+
545
+ it "should replace existing bindings for variable" do
546
+ subject.set_bindings(:x, [-1, -2])
547
+ subject.bindings.should == [[:x, -1], [:y, 2], [:y, 3], [:x, -2]]
548
+ end
549
+
550
+ it "should return the bindings array" do
551
+ bindings = [-1, -2]
552
+ subject.set_bindings(:x, bindings).should be_equal bindings
553
+ end
554
+
555
+ context "when there are fewer new bindings than existing bindings" do
556
+ it "should delete bindings from the end" do
557
+ subject.set_bindings(:x, [-1])
558
+ subject.bindings.should == [[:x, -1], [:y, 2], [:y, 3]]
559
+ end
560
+ end
561
+
562
+ context "when there are more new bindings than existing bindings" do
563
+ it "should add bindings to the end" do
564
+ subject.set_bindings(:x, [-1, -2, -3])
565
+ subject.bindings.should ==
566
+ [[:x, -1], [:y, 2], [:y, 3], [:x, -2], [:x, -3]]
567
+ end
568
+ end
569
+
570
+ context "when argument is not an array" do
571
+ it "should raise ArgumentError" do
572
+ expect do
573
+ subject.set_bindings(:x, :not_this)
574
+ end.should raise_error ArgumentError,
575
+ "expected bindings to be Array, got a Symbol"
576
+ end
577
+ end
578
+ end
579
+ end
580
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamic_variable
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 0
10
+ version: 0.0.0
11
+ platform: ruby
12
+ authors:
13
+ - William Taysom
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-09 00:00:00 +08:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Occasionally a method's behavior should depend on the context in which it is called. What is happening above me on the stack? DynamicVariable helps you in these context dependent situations.
23
+ email: wtaysom@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.md
30
+ files:
31
+ - .gitignore
32
+ - README.md
33
+ - Rakefile
34
+ - VERSION
35
+ - doc/readme_code/01example_at_start.rb
36
+ - doc/readme_code/02example_with_debug.rb
37
+ - doc/readme_code/03simple_with.rb
38
+ - dynamic_variable.gemspec
39
+ - lib/dynamic_variable.rb
40
+ - spec/dynamic_variable_spec.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/wtaysom/Ruby-DynamicVariable
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --charset=UTF-8
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.7
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Provides dynamically scoped variables.
75
+ test_files:
76
+ - spec/dynamic_variable_spec.rb