Rspec Screenshot

Rspec Screenshot
Copyright © 2014, 2015 LHA. All rights reserved.

Sunday, September 6, 2015

An Alternative Way to Refine a Class

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