
Ruby is a great language for doing things in unexpected ways. I had such an occasion recently, where I wanted the functionality of a child object but without the hassle and time of setting one up. Plus, I wanted the data to be delivered from the parent object, so I didn’t want to worry about managing/syncing context or binding between the parent and the child..
So I spent three times as long building this “Stubber” class which can be used to generate arbitrary “child-like” behaviors. You can think of Stubber like Struct on steroids. Rather than just giving you access to variables in a structure, it lets you assign any Ruby code to the structure. Of course this can get really weird if you misuse it, but show me something in Ruby where that’s not the case. In the example attached, we create a method called “constraint” and we use Stubber class to “stub out” two method calls to this method. So the caller can say:
obj.constraint.to_sql
But Stubber will actually allow us to “point” this to any arbitrary Ruby code that we want. In the example below we redirect this call to “self.item” which in this context means that these two call are identical:
obj.constraint.to_sql
obj.item
But you can use Stubber for any related purpose. I haven’t looked very hard to see if Stubber is thread-safe but my instinct tells me that @cur_binding class variable makes Stubber NOT thread-safe, so be aware of that if you do more than just play with this neat toy.
Stubber class itself:
# note Stubber is probably not thread-safe..
class Stubber
# create an initial stub on create
def initialize(stub_method_name, actual_method_code)
@stubs = Hash.new
self.add_stub(stub_method_name, actual_method_code)
end
# add additional stubs as desired
def add_stub(stub_method_name, actual_method_code)
@stubs[stub_method_name.to_sym] = actual_method_code
end
# call this to "execute" stubber (which really sets the current binding
# and passes itself to the caller)
def stub(b)
@cur_binding = b
return self
end
def method_missing(symbol, *args)
stub_code = @stubs[symbol.to_sym]
if stub_code
eval(stub_code, @cur_binding)
else
super(symbol, *args)
end
end
end
Example code that makes use of Stubber:
class StubMe
def initialize
@stubber = Stubber.new(:to_sql, "self.item")
@stubber.add_stub(:hello_world, "'Hello World the hard way!'")
end
def item
"Hello world"
end
def constraint
# here's the main call that "activates" stubber
@stubber.stub(binding)
end
end
stub_this = StubMe.new
#this returns self.item, via a call to Stubber
puts stub_this.constraint.to_sql
#returns "Hello World the hard way!" via Stubber
puts stub_this.constraint.hello_world
#correctly raises exception when method not found by Stubber
puts stub_this.constraint.goobly_gook