dynamic_variable 0.0.0

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