# on ruby's ml, ruby-talk, it's often been lamented that there no way to # un-extend an object or otherwise have methods available in a dsl which # evaporate afterwards. at least when the desired scope of the dsl is the # object itself. for an example lamentation see # # http://groups.google.com/group/ruby-talk-google/browse_thread/thread/338fd9e68c28e889?hl=en # # i've encounted this issue a few times myself and, for the first time, came # up with a simple way to temporarily add methods to an object in the scope of # a dsl call, which is presented here as a design pattern. # # as an example of what we'd like to take the following # def foo() 40 end def bar() 2 end puts( foo + bar ) #=> 42 # obviously this prints 42. we'd *like* a dsl which operates in the scope of # the calling object (instance_eval) but which re-defines the 'foo' method to # return not the integer 40, but 40.0 (floating point) but *only* the extent # of the dsl call - restoring the 'foo' method on return. here you can see # the desired result and, below in the 'BEGIN' block the annotated # implementation. # puts dsl{ foo + bar } #=> 42.0 # of course we expect our 'self' to be unaltered, that is to say that 'foo' # continues to return 42 outside the dsl # puts( foo + bar ) #=> 42 # finally, the implementation, with explanation # BEGIN { # of course we bundle as much as possible in our dsl's namespace # module Dsl # we define all of our dsl methods inside a closure. this will act as a # 'base-class' for the module we intend to extend objects with # Methods = lambda do def foo() 40.0 end end # because modules do not support inheritence we build a new one that has # the Methods injected into it, and which has support to remove said # methods. note that we cannot use a static module or we'd remove methods # from it! # def Methods.new Module.new do module_eval &Methods def self.remove! instance_methods.each{|m| remove_method m} end end end # this method applies methods to an object using a new module, returning # that module to be used later to remove them # def Methods.for object singleton_class_for(object) do @dsl_methods = Dsl::Methods.new include @dsl_methods = Dsl::Methods.new @dsl_methods end end # just a utility method for accessing the singleton class and eval'ing # code in it # def Methods.singleton_class_for object, &block singleton_class = class << object; self; end block ? singleton_class.module_eval(&block) : singleton_class end end # lastly we provide a top level hook to use the dsl # def dsl &block methods = Dsl::Methods.for self instance_eval(&block) ensure methods.remove! end }