
= PluginFactory

PluginFactory is a mixin module that turns an including class 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 <tt>create</tt> method to instantiate them:

== 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" )

== How Plugins Are Loaded

The +create+ class method added to your class by PluginFactory searches for your
module using several different strategies. It tries various permutations of the
base class's name in combination with the derivative requested. For example,
assume we want to make a +DataDriver+ base class, and then use plugins to define
drivers for different kinds of data sources:

  require 'pluginfactory'

  class DataDriver
	include PluginFactory
  end

When you attempt to load the 'socket' data-driver class like so:

  DataDriver.create( 'socket' )

PluginFactory searches for modules with the following names:

  'socketdatadriver'
  'socket_datadriver'
  'socketDataDriver'
  'socket_DataDriver'
  'SocketDataDriver'
  'Socket_DataDriver'
  'socket'
  'Socket'

Obviously the last one will load something other than what is intended, so you
can also tell PluginFactory that plugins should be loaded from a subdirectory by
declaring a class method called `derivative_dirs` in the base class. It should
return an Array that contains a list of subdirectories to try:

  class DataDriver
	include PluginFactory
	
	def self::derivative_dirs
	  ['drivers']
	end
  end

This will change the list that is required to:

  'drivers/socketdatadriver'
  'drivers/socket_datadriver'
  'drivers/socketDataDriver'
  'drivers/socket_DataDriver'
  'drivers/SocketDataDriver'
  'drivers/Socket_DataDriver'
  'drivers/socket'
  'drivers/Socket'

If you return more than one subdirectory, each of them will be tried in turn:

  class DataDriver
	include PluginFactory
	
	def self::derivative_dirs
	  ['drivers', 'datadriver']
	end
  end

will change the search to include:

  'drivers/socketdatadriver'
  'drivers/socket_datadriver'
  'drivers/socketDataDriver'
  'drivers/socket_DataDriver'
  'drivers/SocketDataDriver'
  'drivers/Socket_DataDriver'
  'drivers/socket'
  'drivers/Socket'
  'datadriver/socketdatadriver'
  'datadriver/socket_datadriver'
  'datadriver/socketDataDriver'
  'datadriver/socket_DataDriver'
  'datadriver/SocketDataDriver'
  'datadriver/Socket_DataDriver'
  'datadriver/socket'
  'datadriver/Socket'

If the plugin is not found, a FactoryError is raised, and the message will list
all the permutations that were tried.

== Logging

If you need a little more insight into what's going on, PluginFactory exposes a
logging callback which will be called at various points in the process, and will
include details on what's being attempted.

You can activate logging by setting PluginFactory.logger_callback to something
that responds to the #call message. It will be called with two arguments, a
severity level and a message:

  callback.call( :info, "Something happened" )

This is intended to make it easy to hook up to Logger or anything with a similar
interface:

  require 'pluginfactory'
  require 'logger'
  
  class DataDriver
    include PluginFactory
    
    @logger = Logger.new( $stderr )
    @logger.level = Logger::DEBUG
    PluginFactory.logger_callback = lambda do |severity, msg|
      @logger.send(severity, msg)
    end
  end
  
  DataDriver.create( 'ringbuffer' )

this might generate a log that looks like:

  D, [...] DEBUG -- : Loading derivative ringbuffer
  D, [...] DEBUG -- : Subdirs are: [""]
  D, [...] DEBUG -- : Path is: ["ringbufferdatadriver", "ringbufferDataDriver", 
        "ringbuffer"]...
  D, [...] DEBUG -- : Trying ringbufferdatadriver...
  D, [...] DEBUG -- : No module at 'ringbufferdatadriver', trying the next 
        alternative: 'no such file to load -- ringbufferdatadriver'
  D, [...] DEBUG -- : Trying ringbufferDataDriver...
  D, [...] DEBUG -- : No module at 'ringbufferDataDriver', trying the next 
        alternative: 'no such file to load -- ringbufferDataDriver'
  D, [...] DEBUG -- : Trying ringbuffer...
  D, [...] DEBUG -- : No module at 'ringbuffer', trying the next alternative: 
        'no such file to load -- ringbuffer'
  D, [...] DEBUG -- : fatals = []
  E, [...] ERROR -- : Couldn't find a DataDriver named 'ringbuffer': 
        tried ["ringbufferdatadriver", "ringbufferDataDriver", "ringbuffer"]

== Subversion ID

$Id: README 55 2009-02-23 06:34:03Z deveiant $

== Authors

* Martin Chase <stillflame@FaerieMUD.org>
* Michael Granger <ged@FaerieMUD.org>

:include: LICENSE

--

Please see the file LICENSE for licensing details.

