This Jackbox feature provides an alternative way to refine classes, that also illustrates some additional uses of our Modular Closures©/Code Injectors. For this to happen, we rely once again on our trusty method #lets. We will delve right into the code:
module Work
lets String do
def self.new(*args)
"+++#{super}+++"
end
end
end
class WorkAholic
include Work
def work_method
String('Men-At-Work')
end
end
str = WorkAholic.new.work_method # Our String re-class
str.should == '+++Men-At-Work+++'
str = String.new('men-at-work') # Regular String
str = 'men-at-work'
str = String('Men-At-Work') # Regular Kernel version
str = 'Men-At-Work'
|
Two important things here are first that String is re-classed only within the scope of Work, and second that the top level String is left untouched. In addition, each name space can have its own private re-class of String. We can have a specialized version of the class for each name space and avoid un-necessarily polluting any other name spaces. Bringing Injectors into the mix we can "refine" this pattern a little more. Take a look at the following:
# Injector declaration
Sr1 = trait :StringRefinements do # use tagging
lets String do
def self.new *args, &code
super(*args, &code) + ' is a special string'
end
end
end
class OurClass
include Sr1 # our tag
def foo_bar
String('foo and bar')
end
end
c = OurClass.new
c.foo_bar.class.should == String
c.foo_bar.should == 'foo and bar is a special string'
# A common jack
jack :Log do
require 'logger'
def to_log
Logger.new($stdout).warn(self)
end
end
StringRefinements do
String() do
def extra # add more stuff into current re-class
:extra
end
inject Log()
end
end
c.foo_bar.should == 'foo and bar is a special string'
c.foo_bar.extra.should == :extra
$stdout.should_receive(:write).at_least(1)
c.foo_bar.to_log
# New Version
Sr2 = StringRefinements do # a new tag
lets String do # a new re-class
def to_s
super + '****'
end
inject Log()
end
end
# c is still the same
c.foo_bar.should == 'foo and bar is a special string'
c.foo_bar.extra.should == :extra
class OurOtherClass
include Sr2 # Apply new tag to another class
def foo_bar
String('foo and bar')
end
end
d = OurOtherClass.new
d.foo_bar.should == 'foo and bar'
d.foo_bar.to_s.should == 'foo and bar****'
expect{ d.foo_bar.extra }.to raise_error(NoMethodError)
d.foo_bar.to_log
|
This allows us to create versioned re-classes. Since re-classes are methods, albeit special constructor methods, these can mix in with other methods in the modular closure version giving you many variants. You can have multiple re-class versions, or can use them together with other patterns.
Furthermore, it is easy to determine if a re-class exists and use it instead of the original class by simply testing for its presence in the given namespace in order to change its behavior through further injector applications. Check out the following snippets:
reclass? String
# or ...
module MyModule
reclass? String
end
# also ...
#############################
# Use a certain re-classing if it exists
# if not use the regular original class
#############################
String("my string") rescue String.new("my string")
|
So in general terms re-classes offer an alternative way to create and use certain refinements to a class which also makes use of the other properties of our modular closures/injectors. Finally, re-classes work with versions of Ruby **1.9.3 thru 2.2.1**.
Thanks,
lha
No comments:
Post a Comment