dynamic_variable 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog.md ADDED
@@ -0,0 +1,9 @@
1
+ # dynamic_variable 1.1.0 2010-09-12
2
+
3
+ * Add DynamicVariable#default_variable attribute.
4
+ * Add DynamicVariable::Mixin.
5
+ * Remove DynamicVariable#with_module.
6
+
7
+ # dynamic_variable 1.0.0 2010-09-09
8
+
9
+ * First release.
data/README.md CHANGED
@@ -4,6 +4,12 @@ Occasionally a method's behavior should depend on the context in which it is cal
4
4
 
5
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
6
 
7
+ # Install
8
+
9
+ Nothing fancy:
10
+
11
+ > sudo gem install dynamic_variable
12
+
7
13
  # Example: Context Dependent Debugging
8
14
 
9
15
  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:
@@ -75,6 +81,33 @@ A DynamicVariable storing one `:value` and then `:another`:
75
81
  dv.value.should == 2
76
82
  end
77
83
 
84
+ # DynamicVariable::Mixin
85
+
86
+ The Mixin module adds a dynamic variable quality to any class:
87
+
88
+ class MixinExample
89
+ include DynamicVariable::Mixin
90
+
91
+ attr_accessor :x
92
+
93
+ def try
94
+ self.x = 4
95
+ x.should == 4
96
+ with(:x, 3) do
97
+ x.should == 3
98
+ self.x = 2
99
+ x.should == 2
100
+ with(:x, 1) do
101
+ x.should == 1
102
+ end
103
+ x.should == 2
104
+ end
105
+ x.should == 4
106
+ end
107
+ end
108
+
109
+ MixinExample.new.try
110
+
78
111
  # Why have a library?
79
112
 
80
113
  Isn't it easy to set and reset a flag as the context changes? Sure, just watch out for raise, throw, and nesting:
data/Rakefile CHANGED
@@ -11,5 +11,4 @@ begin
11
11
  Jeweler::GemcutterTasks.new
12
12
  rescue LoadError
13
13
  puts "Jeweler not available. Install it with: gem install jeweler"
14
- end
15
-
14
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__)+'/../../lib/dynamic_variable'
2
+
3
+ describe 'DynamicVariable::Mixin' do it "should behave" do
4
+
5
+ class MixinExample
6
+ include DynamicVariable::Mixin
7
+
8
+ attr_accessor :x
9
+
10
+ def try
11
+ self.x = 4
12
+ x.should == 4
13
+ with(:x, 3) do
14
+ x.should == 3
15
+ self.x = 2
16
+ x.should == 2
17
+ with(:x, 1) do
18
+ x.should == 1
19
+ end
20
+ x.should == 2
21
+ end
22
+ x.should == 4
23
+ end
24
+ end
25
+
26
+ MixinExample.new.try
27
+
28
+ end end
@@ -7,7 +7,7 @@ ensure
7
7
  end
8
8
 
9
9
 
10
- describe '#simple_with' do it 'should behave' do
10
+ describe '#simple_with' do it "should behave" do
11
11
 
12
12
  simple_with(3) do
13
13
  simple_with(4) do
@@ -5,24 +5,27 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dynamic_variable}
8
- s.version = "1.0.0"
8
+ s.version = "1.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["William Taysom"]
12
- s.date = %q{2010-09-09}
12
+ s.date = %q{2010-09-12}
13
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
14
  s.email = %q{wtaysom@gmail.com}
15
15
  s.extra_rdoc_files = [
16
- "README.md"
16
+ "ChangeLog.md",
17
+ "README.md"
17
18
  ]
18
19
  s.files = [
19
20
  ".gitignore",
21
+ "ChangeLog.md",
20
22
  "README.md",
21
23
  "Rakefile",
22
24
  "VERSION",
23
25
  "doc/readme_code/01example_at_start.rb",
24
26
  "doc/readme_code/02example_with_debug.rb",
25
- "doc/readme_code/03simple_with.rb",
27
+ "doc/readme_code/03mixin.rb",
28
+ "doc/readme_code/04simple_with.rb",
26
29
  "dynamic_variable.gemspec",
27
30
  "lib/dynamic_variable.rb",
28
31
  "spec/dynamic_variable_spec.rb"
@@ -1,7 +1,8 @@
1
1
  class DynamicVariable
2
- @@un = Object.new
2
+ attr_accessor :default_variable
3
3
 
4
4
  def initialize(*pairs, &block)
5
+ self.default_variable = :value
5
6
  @bindings = []
6
7
  if block_given?
7
8
  with(*pairs, &block)
@@ -23,38 +24,29 @@ class DynamicVariable
23
24
 
24
25
  ##
25
26
  # with {}
26
- # varible defaults to :value
27
+ # varible defaults to default_variable
27
28
  # value defaults to nil
28
29
  #
29
30
  # with(value) { block }
30
- # variable defaults to :value
31
+ # variable defaults to default_variable
31
32
  #
32
33
  # with(variable, value) { block }
33
34
  #
34
35
  # with(variable_1, value_1, ..., variable_n_1, value_n_1, value_n) { block }
35
- # variable_n defaults to :value
36
+ # variable_n defaults to default_variable
36
37
  #
37
38
  # with(variable_1, value_1, ..., variable_n, value_n) { block }
38
39
  #
39
40
  def with(*pairs)
40
41
  pairs = [nil] if pairs.empty?
41
- push_pairs(pairs)
42
42
  begin
43
+ push_pairs(pairs)
43
44
  yield(self)
44
45
  ensure
45
46
  pop_pairs(pairs)
46
47
  end
47
48
  end
48
49
 
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
50
  def [](variable)
59
51
  find_binding(variable)[1]
60
52
  end
@@ -70,7 +62,8 @@ class DynamicVariable
70
62
  else
71
63
  writer = variable
72
64
  unless args.size == 1 and writer.to_s =~ /(.*)=/
73
- raise NoMethodError, "undefined method `#{writer}' for #{self.inspect}"
65
+ raise NoMethodError,
66
+ "undefined method `#{writer}' for #{self.inspect}"
74
67
  end
75
68
  variable = $1.to_sym
76
69
  self[variable] = args[0]
@@ -89,8 +82,8 @@ class DynamicVariable
89
82
  variables.each{|variable, value| self[variable] = value}
90
83
  end
91
84
 
92
- def bindings(variable = @@un)
93
- if variable == @@un
85
+ def bindings(variable = (un = true))
86
+ if un
94
87
  @bindings.map{|pair| pair.clone}
95
88
  else
96
89
  @bindings.select{|var, value| var == variable}.map!{|var, value| value}
@@ -101,8 +94,8 @@ class DynamicVariable
101
94
  set_bindings(bindings)
102
95
  end
103
96
 
104
- def set_bindings(variable, bindings = @@un)
105
- if bindings == @@un
97
+ def set_bindings(variable, bindings = (un = true))
98
+ if un
106
99
  bindings = variable
107
100
  @bindings = bindings.map do |pair|
108
101
  unless pair.is_a? Array and pair.size == 2
@@ -138,6 +131,86 @@ class DynamicVariable
138
131
  bindings
139
132
  end
140
133
 
134
+ module Mixin
135
+ class MixedDynamicVariable < DynamicVariable
136
+ def initialize(mix)
137
+ super()
138
+ @mix = mix
139
+ end
140
+
141
+ def push(variable, value)
142
+ old_value = @mix.send(variable)
143
+ begin
144
+ pair = begin
145
+ find_binding(variable)
146
+ rescue ArgumentError
147
+ super
148
+ end
149
+ pair[1] = old_value
150
+
151
+ @mix.send(variable.to_s+"=", value)
152
+ super
153
+ rescue Exception
154
+ @bindings.pop
155
+ raise
156
+ end
157
+ end
158
+
159
+ def pop(variable)
160
+ value = super
161
+ begin
162
+ old_value = find_binding(variable)[1]
163
+ rescue ArgumentError
164
+ return value
165
+ end
166
+ @mix.send(variable.to_s+"=", old_value)
167
+ value
168
+ ensure
169
+ if @bindings.count{|var, value| var == variable} == 1
170
+ super
171
+ end
172
+ end
173
+
174
+ def [](variable)
175
+ find_binding(variable)[1] = @mix.send(variable)
176
+ end
177
+
178
+ def []=(variable, value)
179
+ @mix.send(variable.to_s+"=", super)
180
+ end
181
+
182
+ alias original_variables variables
183
+ private :original_variables
184
+
185
+ def variables
186
+ variables = super
187
+ variables.each_key do |variable|
188
+ variables[variable] = self[variable]
189
+ end
190
+ end
191
+
192
+ def bindings(*args)
193
+ variables
194
+ super
195
+ end
196
+
197
+ def set_bindings(*args)
198
+ bindings = super
199
+ self.variables = original_variables
200
+ bindings
201
+ end
202
+ end
203
+
204
+ def dynamic_variable
205
+ @dynamic_variable_mixin__dynamic_variable ||=
206
+ MixedDynamicVariable.new(self)
207
+ end
208
+
209
+ def with(*pairs, &block)
210
+ dynamic_variable.with(*pairs, &block)
211
+ end
212
+ end
213
+
141
214
  private
142
215
 
143
216
  def find_binding(variable)
@@ -153,7 +226,7 @@ private
153
226
  end
154
227
 
155
228
  def push_pairs(pairs)
156
- pairs.insert(-2, :value) if pairs.size.odd?
229
+ pairs.insert(-2, default_variable) if pairs.size.odd?
157
230
  each_pair(pairs) {|variable, value| push(variable, value)}
158
231
  end
159
232
 
@@ -3,10 +3,6 @@ require 'dynamic_variable'
3
3
  describe DynamicVariable do
4
4
  subject { DynamicVariable.new }
5
5
 
6
- before do
7
- extend subject.with_module
8
- end
9
-
10
6
  ### Utilities ###
11
7
 
12
8
  def x_and_y_bindings
@@ -195,27 +191,28 @@ describe DynamicVariable do
195
191
 
196
192
  describe '#with' do
197
193
  it "should make bindings and clean up when done" do
198
- with(:key, :value) do
194
+ subject.with(:key, :value) do
199
195
  subject.bindings.should == [[:key, :value]]
200
196
  end
201
197
  bindings_should_be_empty
202
198
  end
203
199
 
204
200
  it "should yield self to the block" do
205
- with do |dv|
201
+ subject.with do |dv|
206
202
  dv.should == subject
207
203
  end
208
204
  end
209
205
 
210
206
  it "should return the result of its block" do
211
- with{:result}.should == :result
207
+ subject.with{:result}.should == :result
212
208
  end
213
209
 
214
210
  it "should nest nicely" do
215
- with(:value) do
211
+ subject.with(:value) do
216
212
  subject.bindings.should == [[:value, :value]]
217
- with(:second_value) do
218
- subject.bindings.should == [[:value, :value], [:value, :second_value]]
213
+ subject.with(:second_value) do
214
+ subject.bindings.should == [[:value, :value],
215
+ [:value, :second_value]]
219
216
  end
220
217
  subject.bindings.should == [[:value, :value]]
221
218
  end
@@ -224,7 +221,7 @@ describe DynamicVariable do
224
221
 
225
222
  it "should be exception safe" do
226
223
  expect do
227
- with(:value) do
224
+ subject.with(:value) do
228
225
  subject.bindings.should == [[:value, :value]]
229
226
  raise
230
227
  end
@@ -235,7 +232,7 @@ describe DynamicVariable do
235
232
  context "when called" do
236
233
  context "with no arguments" do
237
234
  it "should bind :value to nil" do
238
- with do
235
+ subject.with do
239
236
  subject.bindings.should == [[:value, nil]]
240
237
  end
241
238
  end
@@ -243,7 +240,7 @@ describe DynamicVariable do
243
240
 
244
241
  context "with one argument" do
245
242
  it "should bind :value to argument" do
246
- with(:argument) do
243
+ subject.with(:argument) do
247
244
  subject.bindings.should == [[:value, :argument]]
248
245
  end
249
246
  end
@@ -251,7 +248,7 @@ describe DynamicVariable do
251
248
 
252
249
  context "with two arguments" do
253
250
  it "should bind the first to the second" do
254
- with(:first, :second) do
251
+ subject.with(:first, :second) do
255
252
  subject.bindings.should == [[:first, :second]]
256
253
  end
257
254
  end
@@ -259,7 +256,7 @@ describe DynamicVariable do
259
256
 
260
257
  context "with an odd number of arguments" do
261
258
  it "should bind :value to the second to last" do
262
- with(:first, :second, :last) do
259
+ subject.with(:first, :second, :last) do
263
260
  subject.bindings.should == [[:first, :second], [:value, :last]]
264
261
  end
265
262
  end
@@ -267,7 +264,7 @@ describe DynamicVariable do
267
264
 
268
265
  context "with an even number of arguments" do
269
266
  it "should bind variable, value pairs" do
270
- with(:var1, :val1, :var2, :val2, :var3, :val3) do
267
+ subject.with(:var1, :val1, :var2, :val2, :var3, :val3) do
271
268
  subject.bindings.should ==
272
269
  [[:var1, :val1], [:var2, :val2], [:var3, :val3]]
273
270
  end
@@ -276,7 +273,7 @@ describe DynamicVariable do
276
273
 
277
274
  context "with a variable repeated more than once" do
278
275
  it "should bind the variable twice and unbind when done" do
279
- with(:var, 1, :var, 2) do
276
+ subject.with(:var, 1, :var, 2) do
280
277
  subject.bindings.should == [[:var, 1], [:var, 2]]
281
278
  end
282
279
  subject.bindings.should == []
@@ -294,12 +291,25 @@ describe DynamicVariable do
294
291
  end
295
292
  end
296
293
 
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]
294
+ describe '#default_variable' do
295
+ it 'should default to :value' do
296
+ subject.default_variable.should == :value
297
+ end
298
+ end
299
+
300
+ describe '#default_variable=' do
301
+ context 'when #with is called' do
302
+ it 'should use default_variable for 0 and odd numbers of bindings' do
303
+ subject.default_variable = :v
304
+ subject.with do
305
+ subject.with(1) do
306
+ subject.with(:v, 2, 3) do
307
+ subject.bindings.should ==
308
+ [[:v, nil], [:v, 1], [:v, 2], [:v, 3]]
309
+ end
310
+ end
311
+ end
312
+ end
303
313
  end
304
314
  end
305
315
 
@@ -577,4 +587,165 @@ describe DynamicVariable do
577
587
  end
578
588
  end
579
589
  end
590
+
591
+ describe DynamicVariable::Mixin do
592
+ class MixinExample
593
+ include DynamicVariable::Mixin
594
+
595
+ attr_accessor :x, :y
596
+ end
597
+
598
+ subject { MixinExample.new }
599
+
600
+ describe '#with' do
601
+ it "should bind attributes dynamically" do
602
+ subject.x = 5
603
+ subject.with(:x, 4, :y, 3) do
604
+ subject.x.should == 4
605
+ subject.y.should == 3
606
+ subject.y = 2
607
+ subject.y.should == 2
608
+ subject.with(:x, 1, :y, 0) do
609
+ subject.x.should == 1
610
+ subject.y.should == 0
611
+ end
612
+ subject.x.should == 4
613
+ subject.y.should == 2
614
+ end
615
+ subject.x.should == 5
616
+ subject.y.should == nil
617
+ end
618
+
619
+ context "when readers and writers raise errors" do
620
+ context "when reader raises during push" do
621
+ it "should unbind bound variables" do
622
+ subject.x = 5
623
+
624
+ class <<subject
625
+ def y
626
+ raise "oops"
627
+ end
628
+ end
629
+
630
+ did_call_block = false
631
+ expect do
632
+ subject.with(:x, 4, :y, 3) do
633
+ did_call_block = true
634
+ end
635
+ end.should raise_error("oops")
636
+
637
+ subject.x.should == 5
638
+ subject.dynamic_variable.bindings.should == []
639
+ did_call_block.should be_false
640
+ end
641
+ end
642
+
643
+ context "when writer raises during push" do
644
+ it "should unbind bound variables" do
645
+ subject.x = 5
646
+
647
+ class <<subject
648
+ def x=(x)
649
+ raise "oops"
650
+ end
651
+ end
652
+
653
+ did_call_block = false
654
+ expect do
655
+ subject.with(:x, 4, :y, 3) do
656
+ did_call_block = true
657
+ end
658
+ end.should raise_error("oops")
659
+
660
+ subject.x.should == 5
661
+ subject.dynamic_variable.bindings.should == []
662
+ did_call_block.should be_false
663
+ end
664
+ end
665
+
666
+ context "when writer raises during pop" do
667
+ it "should unbind bound variables" do
668
+ subject.x = 5
669
+
670
+ did_call_block = false
671
+ expect do
672
+ subject.with(:x, 4, :y, 3) do
673
+ did_call_block = true
674
+
675
+ class <<subject
676
+ def y=(y)
677
+ raise "oops"
678
+ end
679
+ end
680
+ end
681
+ end.should raise_error("oops")
682
+
683
+ subject.x.should == 5
684
+ subject.dynamic_variable.bindings.should == []
685
+ did_call_block.should be_true
686
+ end
687
+ end
688
+ end
689
+ end
690
+
691
+ describe '#dynamic_variable' do
692
+ context "when reading a variable through" do
693
+ def self.it_should_be_up_to_date
694
+ it "should be up to date" do
695
+ subject.with(:x, 4, :y, 3) do
696
+ subject.x = 2
697
+ subject.y = 1
698
+
699
+ yield(subject.dynamic_variable)
700
+ end
701
+ end
702
+ end
703
+
704
+ context "#[]" do
705
+ it_should_be_up_to_date do |dv|
706
+ dv.x.should == 2
707
+ end
708
+ end
709
+
710
+ context "#variables" do
711
+ it_should_be_up_to_date do |dv|
712
+ dv.variables.should == {:x => 2, :y => 1}
713
+ end
714
+ end
715
+
716
+ context "#bindings" do
717
+ it_should_be_up_to_date do |dv|
718
+ dv.bindings.should == [[:x, nil], [:x, 2], [:y, nil], [:y, 1]]
719
+ end
720
+ end
721
+ end
722
+
723
+ context "when writing variable through" do
724
+ def self.it_should_be_updated
725
+ it "should be updated" do
726
+ subject.with(:x, 4, :y, 3) do
727
+ yield(subject, subject.dynamic_variable)
728
+ end
729
+ end
730
+ end
731
+
732
+ context "#[]=" do
733
+ it_should_be_updated do |subject, dv|
734
+ dv.x = 2
735
+ subject.x.should == 2
736
+ end
737
+ end
738
+
739
+ context "#bindings=" do
740
+ it_should_be_updated do |subject, dv|
741
+ dv.bindings.should == [[:x, nil], [:x, 4], [:y, nil], [:y, 3]]
742
+
743
+ dv.bindings = [[:x, nil], [:x, 2], [:y, nil], [:y, 1]]
744
+ subject.x.should == 2
745
+ subject.y.should == 1
746
+ end
747
+ end
748
+ end
749
+ end
750
+ end
580
751
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamic_variable
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 1.0.0
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - William Taysom
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-09 00:00:00 +08:00
18
+ date: 2010-09-12 00:00:00 +08:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -26,15 +26,18 @@ executables: []
26
26
  extensions: []
27
27
 
28
28
  extra_rdoc_files:
29
+ - ChangeLog.md
29
30
  - README.md
30
31
  files:
31
32
  - .gitignore
33
+ - ChangeLog.md
32
34
  - README.md
33
35
  - Rakefile
34
36
  - VERSION
35
37
  - doc/readme_code/01example_at_start.rb
36
38
  - doc/readme_code/02example_with_debug.rb
37
- - doc/readme_code/03simple_with.rb
39
+ - doc/readme_code/03mixin.rb
40
+ - doc/readme_code/04simple_with.rb
38
41
  - dynamic_variable.gemspec
39
42
  - lib/dynamic_variable.rb
40
43
  - spec/dynamic_variable_spec.rb