dynamic_variable 1.0.0 → 1.1.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/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