Skip to content

Latest commit

 

History

History
238 lines (181 loc) · 5.99 KB

README.md

File metadata and controls

238 lines (181 loc) · 5.99 KB

Rainman Rainman Build Status

Rainman is an experiment in writing drivers and handlers. It is a Ruby implementation of the abstract factory pattern. Abstract factories provide the general API used to interact with any number of interfaces. Interfaces perform actual operations. Rainman provides a simple DSL for implementing this design.

Drivers & Handlers

In Rainman, drivers represent abstract factories and handlers represent the interfaces those factories interact with. In simpler terms, drivers define what things you can do; handlers define how to do those things.

Creating a driver

Rainman drivers are implemented as Modules. They must be extended with Rainman::Driver and use the driver DSL to define their public API. An example Domain driver might look like this:

require 'rainman'

# The Domain module handles creating and deleting domains, and listing
# nameservers
module Domain
  extend Rainman::Driver

  # Set global configuration variables you need to access across all handlers.
  config[:username]   = 'username'
  config[:user_agent] = 'SuperAwesome Domain Manager'

  # Register Domain::Abc as a handler. An optional block yields a config hash
  # which can be used to store variables needed by the handler class, in this
  # case a username and password specific for Domain::Abc.
  register_handler :abc do
    config[:username] = 'username'
    config[:password] = 'pass'
  end

  # Register Domain::Xyz as a handler.
  register_handler :xyz do
    config[:username] = 'username'
    config[:password] = 'pass'

    # Specify a required parameter
    #
    # When defined in a handler, parameters cascade down to all of its
    # actions.
    param :username, :required => true
  end

  # Register Domain.create as a public method. An optional block yields a
  # config hash that can be used to specify validations to be run before the
  # method is invoked.
  define_action :create do
    # Specify an optional parameter
    #
    # We required username above for Xyz. This will make that parameter
    # optional when Xyz's `create` method is called.
    param :username, :required => false
  end

  # Register Domain.destroy as a public method
  define_action :destroy

  # Register Domain.namservers.list as a public method
  namespace :nameservers do
    define_action :list
  end
end

Implementing handlers

Driver handlers are implemented as classes. They must be within the namespace of the driver Module. Using the example above, here are example handlers for Abc and Xyz:

class Domain::Abc
  # Public: Creates a new domain.
  #
  # Returns a Hash.
  def create(params = {})
  end

  # Public: Destroy a domain
  #
  # Returns true or false.
  def destroy(params = {})
  end
end

class Domain::Xyz
  # Public: Creates a new domain.
  #
  # Returns a Hash.
  def create(params = {})
  end

  # Public: Destroy a domain
  #
  # Returns true or false.
  def destroy(params = {})
  end
end

Handler classes automatically have config and params class methods available. These are hashes that contain configs/params specific to the handler. The config hash is meant to store things like usernames, passwords, etc, that would be used by a handler. The params hash is to store parameters that must be set for a handler to perform a valid request.

The example driver above also defined nameservers namespace with a list action (eg: Domain.nameservers.list). To implement this, a Nameservers class is created within each handler's namespace:

class Domain::Abc::Nameserver

  # Public: Lists nameservers for this domain.
  #
  # Returns an Array.
  def list(params = {})
  end
end

class Domain::Xyz::Nameserver

  # Public: Lists nameservers for this domain.
  #
  # Returns an Array.
  def list(params = {})
  end
end

Using a driver

With a driver and handler defined, the driver can now be used in a few different ways.

General

A driver's actions are available as singleton methods. By default, actions are sent to the current handler, or a default handler if a handler is not currently in use.

# Create a domain
Domain.create({})

# Destroy a domain
Domain.destroy({})

# List domain nameservers
Domain.nameservers.list({})

Changing handlers

It is possible to change the handler used at runtime using the with_handler method. This method temporarily changes the current handler. This means, if you have a default handler set, and use with_handler, that default handler is preserved.

Domain.with_handler(:abc) do |driver|
  # Here, current_handler is now set to :abc
  driver.create
end

You can also change the current handler for the duration of your code/session.

Domain.set_current_handler :xyz
Domain.create # create an :xyz domain

Domain.set_current_handler :abc
Domain.create   # create an :abc domain
Domain.transfer # transfer an :abc domain

It is highly suggested you stick to using with_handler unless you have a reason.

Including drivers in other classes

A driver can be included in another class and it's actions are available as instance methods.

class Service
  include Domain
end

s = Service.new

s.create

s.destroy

s.nameservers.list

s.with_handler(:abc) do |driver|
  driver.create
end

s.set_current_handler :zyz
s.create

Note on Patches/Pull Requests

  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit, do not bump version. (If you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull).
  • Send me a pull request. Bonus points for topic branches.

Contributors

Copyright

Copyright (c) 2011 Site5 LLC. See LICENSE for details.