Module: PluginFactory
- Defined in:
- lib/pluginfactory.rb
Overview
This module contains the PluginFactory mixin. Including PluginFactory in your class turns it into a factory for its derivatives, capable of searching for and loading them by name. This is useful when you have an abstract base class which defines an interface and basic functionality for a part of a larger system, and a collection of subclasses which implement the interface for different underlying functionality.
An example of where this might be useful is in a program which talks to a database. To avoid coupling it to a specific database, you use a Driver class which encapsulates your program’s interaction with the database behind a useful interface. Now you can create a concrete implementation of the Driver class for each kind of database you wish to talk to. If you make the base Driver class a PluginFactory, too, you can add new drivers simply by dropping them in a directory and using the Driver’s create method to instantiate them:
Creation Argument Variants
The create class method added to your class by PluginFactory searches for your module using
Synopsis
in driver.rb:
require “PluginFactory“
class Driver
include PluginFactory def self::derivative_dirs ["drivers"] end
end
in drivers/mysql.rb:
require ‘driver’
class MysqlDriver < Driver
...implementation...
end
in /usr/lib/ruby/1.8/PostgresDriver.rb:
require ‘driver’
class PostgresDriver < Driver
...implementation...
end
elsewhere
require ‘driver’
config[:driver_type] #=> “mysql” driver = Driver.create( config[:driver_type] ) driver.class #=> MysqlDriver pgdriver = Driver.create( “PostGresDriver” )
Authors
- Martin Chase
- Michael Granger
:include: LICENSE
Please see the file LICENSE for licensing details.
Constant Summary
- VERSION =
Library version
'1.0.7'
Class Attribute Summary (collapse)
-
+ (Object) default_logger
The logger that will be used when the logging subsystem is reset.
-
+ (Object) logger
(also: log)
The logger that’s currently in effect.
Class Method Summary (collapse)
-
+ (Object) extend_object(obj)
Add the @derivatives instance variable to including classes.
-
+ (Object) included(klass)
Inclusion callback — extends the including class.
- + (Object) log
- + (Object) log
-
+ (Object) logger_callback(callback)
Deprecated: use the Logger object at #log to manipulate logging instead of this method.
-
+ (Object) reset_logger
Reset the global logger object to the default.
-
+ (Boolean) using_default_logger?
Returns true if the global logger has not been set to something other than the default one.
Instance Method Summary (collapse)
-
- (Object) create(class_name, *args, &block)
Given the class_name of the class to instantiate, and other arguments bound for the constructor of the new object, this method loads the derivative class if it is not loaded already (raising a LoadError if an appropriately-named file cannot be found), and instantiates it with the given args.
-
- (Object) derivative_classes
(also: #derivativeClasses)
Returns an Array of registered derivatives.
- - (Object) derivativeClasses
-
- (Object) derivatives
Return the Hash of derivative classes, keyed by various versions of the class name.
-
- (Object) factory_type
(also: #factoryType)
Returns the type name used when searching for a derivative.
- - (Object) factoryType
-
- (Object) get_module_name(class_name)
(also: #getModuleName)
Build and return the unique part of the given class_name either by stripping leading namespaces if the name already has the name of the factory type in it (eg., ‘My::FooService’ for Service, or by appending the factory type if it doesn’t..
-
- (Object) get_subclass(class_name)
(also: #getSubclass)
Given a class_name like that of the first argument to #create, attempt to load the corresponding class if it is not already loaded and return the class object.
- - (Object) getModuleName
- - (Object) getSubclass
-
- (Object) inherited(subclass)
Inheritance callback — Register subclasses in the derivatives hash so that ::create knows about them.
-
- (Object) load_derivative(class_name)
(also: #loadDerivative)
Calculates an appropriate filename for the derived class using the name of the base class and tries to load it via require.
- - (Object) loadDerivative
-
- (Object) make_require_path(modname, subdir)
(also: #makeRequirePath)
Make a list of permutations of the given modname for the given subdir.
- - (Object) makeRequirePath
-
- (Object) require_derivative(mod_name)
(also: #requireDerivative)
If the factory responds to the #derivative_dirs method, call it and use the returned array as a list of directories to search for the module with the specified mod_name.
- - (Object) requireDerivative
Class Attribute Details
+ (Object) default_logger
The logger that will be used when the logging subsystem is reset
92 93 94 |
# File 'lib/pluginfactory.rb', line 92 def default_logger @default_logger end |
+ (Object) logger Also known as: log
The logger that’s currently in effect
95 96 97 |
# File 'lib/pluginfactory.rb', line 95 def logger @logger end |
Class Method Details
+ (Object) extend_object(obj)
Add the @derivatives instance variable to including classes.
137 138 139 140 |
# File 'lib/pluginfactory.rb', line 137 def self::extend_object( obj ) obj.instance_variable_set( :@derivatives, {} ) super end |
+ (Object) included(klass)
Inclusion callback — extends the including class. This is here so you can either ‘include’ or ‘extend’.
131 132 133 |
# File 'lib/pluginfactory.rb', line 131 def self::included( klass ) klass.extend( self ) end |
+ (Object) log
96 97 98 |
# File 'lib/pluginfactory.rb', line 96 def logger @logger end |
+ (Object) log=
97 98 99 |
# File 'lib/pluginfactory.rb', line 97 def logger=(value) @logger = value end |
+ (Object) logger_callback=(callback)
Deprecated: use the Logger object at #log to manipulate logging instead of this method.
103 104 105 106 107 108 109 110 111 112 |
# File 'lib/pluginfactory.rb', line 103 def self::logger_callback=( callback ) if callback.nil? self.logger.formatter = nil else self.logger.formatter = lambda {|lvl, _, _, msg| callback.call(lvl.downcase.to_sym, msg) '' } end end |
+ (Object) reset_logger
Reset the global logger object to the default
116 117 118 119 |
# File 'lib/pluginfactory.rb', line 116 def self::reset_logger self.logger = self.default_logger self.logger.level = Logger::WARN end |
+ (Boolean) using_default_logger?
Returns true if the global logger has not been set to something other than the default one.
124 125 126 |
# File 'lib/pluginfactory.rb', line 124 def self::using_default_logger? return self.logger == self.default_logger end |
Instance Method Details
- (Object) create(class_name, *args, &block)
Given the class_name of the class to instantiate, and other arguments bound for the constructor of the new object, this method loads the derivative class if it is not loaded already (raising a LoadError if an appropriately-named file cannot be found), and instantiates it with the given args. The class_name may be the the fully qualified name of the class, the class object itself, or the unique part of the class name. The following examples would all try to load and instantiate a class called “FooListener” if Listener included Factory
obj = Listener.create( 'FooListener' ) obj = Listener.create( FooListener ) obj = Listener.create( 'Foo' )
232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/pluginfactory.rb', line 232 def create( class_name, *args, &block ) subclass = get_subclass( class_name ) begin return subclass.new( *args, &block ) rescue => err nicetrace = err.backtrace.reject {|frame| /#{__FILE__}/ =~ frame} msg = "When creating '#{class_name}': " + err. Kernel.raise( err, msg, nicetrace ) end end |
- (Object) derivative_classes Also known as: derivativeClasses
Returns an Array of registered derivatives
214 215 216 |
# File 'lib/pluginfactory.rb', line 214 def derivative_classes self.derivatives.values.uniq end |
- (Object) derivativeClasses
217 218 219 |
# File 'lib/pluginfactory.rb', line 217 def derivative_classes self.derivatives.values.uniq end |
- (Object) derivatives
Return the Hash of derivative classes, keyed by various versions of the class name.
149 150 151 152 153 154 155 156 |
# File 'lib/pluginfactory.rb', line 149 def derivatives ancestors.each do |klass| if klass.instance_variables.include?( :@derivatives ) || klass.instance_variables.include?( "@derivatives" ) return klass.instance_variable_get( :@derivatives ) end end end |
- (Object) factory_type Also known as: factoryType
Returns the type name used when searching for a derivative.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/pluginfactory.rb', line 160 def factory_type base = nil self.ancestors.each do |klass| if klass.instance_variables.include?( :@derivatives ) || klass.instance_variables.include?( "@derivatives" ) base = klass break end end raise FactoryError, "Couldn't find factory base for #{self.name}" if base.nil? if base.name =~ /^.*::(.*)/ return $1 else return base.name end end |
- (Object) factoryType
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/pluginfactory.rb', line 179 def factory_type base = nil self.ancestors.each do |klass| if klass.instance_variables.include?( :@derivatives ) || klass.instance_variables.include?( "@derivatives" ) base = klass break end end raise FactoryError, "Couldn't find factory base for #{self.name}" if base.nil? if base.name =~ /^.*::(.*)/ return $1 else return base.name end end |
- (Object) get_module_name(class_name) Also known as: getModuleName
Build and return the unique part of the given class_name either by stripping leading namespaces if the name already has the name of the factory type in it (eg., ‘My::FooService’ for Service, or by appending the factory type if it doesn’t.
312 313 314 315 316 317 318 319 320 |
# File 'lib/pluginfactory.rb', line 312 def get_module_name( class_name ) if class_name =~ /\w+#{self.factory_type}/ mod_name = class_name.sub( /(?:.*::)?(\w+)(?:#{self.factory_type})/, "\\1" ) else mod_name = class_name end return mod_name end |
- (Object) get_subclass(class_name) Also known as: getSubclass
Given a class_name like that of the first argument to #create, attempt to load the corresponding class if it is not already loaded and return the class object.
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/pluginfactory.rb', line 248 def get_subclass( class_name ) return self if ( self.name == class_name || class_name == '' ) if class_name.is_a?( Class ) return class_name if class_name <= self raise ArgumentError, "%s is not a descendent of %s" % [class_name, self] end class_name = class_name.to_s # If the derivatives hash doesn't already contain the class, try to load it unless self.derivatives.has_key?( class_name.downcase ) self.load_derivative( class_name ) subclass = self.derivatives[ class_name.downcase ] unless subclass.is_a?( Class ) raise FactoryError, "load_derivative(%s) added something other than a class "\ "to the registry for %s: %p" % [ class_name, self.name, subclass ] end end return self.derivatives[ class_name.downcase ] end |
- (Object) getModuleName
321 322 323 324 325 326 327 328 329 |
# File 'lib/pluginfactory.rb', line 321 def get_module_name( class_name ) if class_name =~ /\w+#{self.factory_type}/ mod_name = class_name.sub( /(?:.*::)?(\w+)(?:#{self.factory_type})/, "\\1" ) else mod_name = class_name end return mod_name end |
- (Object) getSubclass
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/pluginfactory.rb', line 272 def get_subclass( class_name ) return self if ( self.name == class_name || class_name == '' ) if class_name.is_a?( Class ) return class_name if class_name <= self raise ArgumentError, "%s is not a descendent of %s" % [class_name, self] end class_name = class_name.to_s # If the derivatives hash doesn't already contain the class, try to load it unless self.derivatives.has_key?( class_name.downcase ) self.load_derivative( class_name ) subclass = self.derivatives[ class_name.downcase ] unless subclass.is_a?( Class ) raise FactoryError, "load_derivative(%s) added something other than a class "\ "to the registry for %s: %p" % [ class_name, self.name, subclass ] end end return self.derivatives[ class_name.downcase ] end |
- (Object) inherited(subclass)
Inheritance callback — Register subclasses in the derivatives hash so that ::create knows about them.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/pluginfactory.rb', line 184 def inherited( subclass ) keys = [ subclass ] # If it's not an anonymous class, make some keys out of variants of its name if subclass.name simple_name = subclass.name.sub( /#<Class:0x[[:xdigit:]]+>::/i, '' ) keys << simple_name << simple_name.downcase # Handle class names like 'FooBar' for 'Bar' factories. PluginFactory.log.debug "Inherited %p for %p-type plugins" % [ subclass, self.factory_type ] if subclass.name.match( /(?:.*::)?(\w+)(?:#{self.factory_type})/i ) keys << Regexp.last_match[1].downcase else keys << subclass.name.sub( /.*::/, '' ).downcase end else PluginFactory.log.debug " no name-based variants for anonymous subclass %p" % [ subclass ] end keys.compact.uniq.each do |key| PluginFactory.log.info "Registering %s derivative of %s as %p" % [ subclass.name, self.name, key ] self.derivatives[ key ] = subclass end super end |
- (Object) load_derivative(class_name) Also known as: loadDerivative
Calculates an appropriate filename for the derived class using the name of the base class and tries to load it via require. If the including class responds to a method named derivativeDirs, its return value (either a String, or an array of Strings) is added to the list of prefix directories to try when attempting to require a modules. Eg., if class.derivativeDirs returns ['foo','bar'] the require line is tried with both 'foo/' and 'bar/' prepended to it.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/pluginfactory.rb', line 284 def load_derivative( class_name ) PluginFactory.log.debug "Loading derivative #{class_name}" # Get the unique part of the derived class name and try to # load it from one of the derivative subdirs, if there are # any. mod_name = self.get_module_name( class_name ) result = self.require_derivative( mod_name ) # Check to see if the specified listener is now loaded. If it # is not, raise an error to that effect. unless self.derivatives[ class_name.downcase ] errmsg = "Require of '%s' succeeded, but didn't load a %s named '%s' for some reason." % [ result, self.factory_type, class_name.downcase, ] PluginFactory.log.error( errmsg ) raise FactoryError, errmsg, caller(3) end end |
- (Object) loadDerivative
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/pluginfactory.rb', line 305 def load_derivative( class_name ) PluginFactory.log.debug "Loading derivative #{class_name}" # Get the unique part of the derived class name and try to # load it from one of the derivative subdirs, if there are # any. mod_name = self.get_module_name( class_name ) result = self.require_derivative( mod_name ) # Check to see if the specified listener is now loaded. If it # is not, raise an error to that effect. unless self.derivatives[ class_name.downcase ] errmsg = "Require of '%s' succeeded, but didn't load a %s named '%s' for some reason." % [ result, self.factory_type, class_name.downcase, ] PluginFactory.log.error( errmsg ) raise FactoryError, errmsg, caller(3) end end |
- (Object) make_require_path(modname, subdir) Also known as: makeRequirePath
Make a list of permutations of the given modname for the given subdir. Called on a DataDriver class with the arguments ‘Socket’ and ‘drivers’, returns:
["drivers/socketdatadriver", "drivers/socketDataDriver", "drivers/SocketDataDriver", "drivers/socket", "drivers/Socket"]
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/pluginfactory.rb', line 397 def make_require_path( modname, subdir ) path = [] myname = self.factory_type # Make permutations of the two parts path << modname path << modname.downcase path << modname + myname path << modname.downcase + myname path << modname.downcase + myname.downcase path << modname + '_' + myname path << modname.downcase + '_' + myname path << modname.downcase + '_' + myname.downcase # If a non-empty subdir was given, prepend it to all the items in the # path unless subdir.nil? or subdir.empty? path.collect! {|m| File.join(subdir, m)} end PluginFactory.log.debug "Path is: #{path.uniq.reverse.inspect}..." return path.uniq.reverse end |
- (Object) makeRequirePath
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 |
# File 'lib/pluginfactory.rb', line 420 def make_require_path( modname, subdir ) path = [] myname = self.factory_type # Make permutations of the two parts path << modname path << modname.downcase path << modname + myname path << modname.downcase + myname path << modname.downcase + myname.downcase path << modname + '_' + myname path << modname.downcase + '_' + myname path << modname.downcase + '_' + myname.downcase # If a non-empty subdir was given, prepend it to all the items in the # path unless subdir.nil? or subdir.empty? path.collect! {|m| File.join(subdir, m)} end PluginFactory.log.debug "Path is: #{path.uniq.reverse.inspect}..." return path.uniq.reverse end |
- (Object) require_derivative(mod_name) Also known as: requireDerivative
If the factory responds to the #derivative_dirs method, call it and use the returned array as a list of directories to search for the module with the specified mod_name.
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/pluginfactory.rb', line 327 def require_derivative( mod_name ) # See if we have a list of special subdirs that derivatives # live in if ( self.respond_to?(:derivative_dirs) ) subdirs = self.derivative_dirs elsif ( self.respond_to?(:derivativeDirs) ) subdirs = self.derivativeDirs # If not, just try requiring it from $LOAD_PATH else subdirs = [''] end subdirs = [ subdirs ] unless subdirs.is_a?( Array ) PluginFactory.log.debug "Subdirs are: %p" % [subdirs] fatals = [] tries = [] # Iterate over the subdirs until we successfully require a # module. subdirs.collect {|dir| dir.strip}.each do |subdir| self.make_require_path( mod_name, subdir ).each do |path| PluginFactory.log.debug "Trying #{path}..." tries << path # Try to require the module, saving errors and jumping # out of the catch block on success. begin require( path.untaint ) rescue LoadError => err PluginFactory.log.debug "No module at '%s', trying the next alternative: '%s'" % [ path, err. ] rescue Exception => err fatals << err PluginFactory.log.error "Found '#{path}', but encountered an error: %s\n\t%s" % [ err., err.backtrace.join("\n\t") ] else PluginFactory.log.info "Loaded '#{path}' without error." return path end end end PluginFactory.log.debug "fatals = %p" % [ fatals ] # Re-raise is there was a file found, but it didn't load for # some reason. if fatals.empty? errmsg = "Couldn't find a %s named '%s': tried %p" % [ self.factory_type, mod_name, tries ] PluginFactory.log.error( errmsg ) raise FactoryError, errmsg else PluginFactory.log.debug "Re-raising first fatal error" Kernel.raise( fatals.first ) end end |
- (Object) requireDerivative
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/pluginfactory.rb', line 389 def require_derivative( mod_name ) # See if we have a list of special subdirs that derivatives # live in if ( self.respond_to?(:derivative_dirs) ) subdirs = self.derivative_dirs elsif ( self.respond_to?(:derivativeDirs) ) subdirs = self.derivativeDirs # If not, just try requiring it from $LOAD_PATH else subdirs = [''] end subdirs = [ subdirs ] unless subdirs.is_a?( Array ) PluginFactory.log.debug "Subdirs are: %p" % [subdirs] fatals = [] tries = [] # Iterate over the subdirs until we successfully require a # module. subdirs.collect {|dir| dir.strip}.each do |subdir| self.make_require_path( mod_name, subdir ).each do |path| PluginFactory.log.debug "Trying #{path}..." tries << path # Try to require the module, saving errors and jumping # out of the catch block on success. begin require( path.untaint ) rescue LoadError => err PluginFactory.log.debug "No module at '%s', trying the next alternative: '%s'" % [ path, err. ] rescue Exception => err fatals << err PluginFactory.log.error "Found '#{path}', but encountered an error: %s\n\t%s" % [ err., err.backtrace.join("\n\t") ] else PluginFactory.log.info "Loaded '#{path}' without error." return path end end end PluginFactory.log.debug "fatals = %p" % [ fatals ] # Re-raise is there was a file found, but it didn't load for # some reason. if fatals.empty? errmsg = "Couldn't find a %s named '%s': tried %p" % [ self.factory_type, mod_name, tries ] PluginFactory.log.error( errmsg ) raise FactoryError, errmsg else PluginFactory.log.debug "Re-raising first fatal error" Kernel.raise( fatals.first ) end end |