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.
DynamicVariable is similar to dynamic scope, fluid-let, SRFI 39 parameters, cflow pointcuts, even Scala has its own DynamicVariable.
Nothing fancy:
> sudo gem install dynamic_variable
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:
def source_of_the_trouble
in_the_middle
end
def in_the_middle
frequently_called
end
def frequently_called
puts "I'm called all the time."
end
1000.times { in_the_middle }
source_of_the_trouble
With DynamicVariable, we can easily change frequently_called
so that it only brings up the debugger
when called from source_of_the_trouble
:
require 'rubygems'
require 'ruby-debug'
require 'dynamic_variable'
@troubled = DynamicVariable.new(false)
def source_of_the_trouble
@troubled.with(true) { in_the_middle }
end
def in_the_middle
frequently_called
end
def frequently_called
debugger if @troubled.value
puts "I'm called all the time."
end
1000.times { in_the_middle }
source_of_the_trouble
A DynamicVariable storing one :value
and then :another
:
DynamicVariable.new(1) do |dv|
dv.value.should == 1
dv.value = 2
dv.value.should == 2
dv.with(:another, :middle, 3) do
dv.another.should == :middle
dv.value.should == 3
dv.with(:another, :inner, 4) do
dv.another.should == :inner
dv.value.should == 4
end
dv.another.should == :middle
dv.value.should == 3
end
expect do
dv.another
end.should raise_error(ArgumentError, "unbound variable :another")
dv.value.should == 2
end
The Mixin module adds a dynamic variable quality to any class:
class MixinExample
include DynamicVariable::Mixin
attr_accessor :x
def try
self.x = 4
x.should == 4
with(:x, 3) do
x.should == 3
self.x = 2
x.should == 2
with(:x, 1) do
x.should == 1
end
x.should == 2
end
x.should == 4
end
end
MixinExample.new.try
Isn't it easy to set and reset a flag as the context changes? Sure, just watch out for raise, throw, and nesting:
def simple_with(value)
old_value = $value
$value = value
yield
ensure
$value = old_value
end
DynamicVariable adds the ability to reflect on your bindings. You can inspect them, and you can tinker with them if you feel the need:
dv = DynamicVariable.new(:v, 1, :w, 2) do |dv|
dv.with(:w, 3) do
dv.with(:v, 4) do
dv.bindings.should == [[:v, 1], [:w, 2], [:w, 3], [:v, 4]]
dv.bindings(:v).should == [1, 4]
dv.bindings(:w).should == [2, 3]
dv.set_bindings(:v, [-1, -2, -3])
dv.set_bindings(:w, [-4])
dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2], [:v, -3]]
end
dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2]]
end
dv.bindings.should == [[:v, -1], [:v, -2]]
end
dv.bindings.should == [[:v, -1]]
dv.bindings = []
dv.bindings.should == []