Sage/handler_registerable

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# HandlerRegisterable

[![Build Status](https://travis-ci.org/Sage/handler_registerable.svg?branch=master)](https://travis-ci.org/Sage/handler_registerable)
[![Maintainability](https://api.codeclimate.com/v1/badges/aa4fce157655c3ee8497/maintainability)](https://codeclimate.com/github/Sage/handler_registerable/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/aa4fce157655c3ee8497/test_coverage)](https://codeclimate.com/github/Sage/handler_registerable/test_coverage)
[![Gem Version](https://badge.fury.io/rb/handler_registerable.svg)](https://badge.fury.io/rb/handler_registerable)

## Description

Registered handlers allow common code to collaborate with a handler which is designed to meet a more concrete requirement.

The basic premise is that a registry is created for a particular concern, and this registry allows each specific handler to be added. The registry handlers will conform to a documented API, and may often provide a base class or default implementations.

Your application and context specific details can be written in a specific handler, and then added to the registry. At run time, code that needs to collaborate with the appropriate handler from the registry is correctly given the appropriate handler.

![Demo Gif](assets/handler_registerable.gif)

## Installation

Add to your project's Gemfile:

```ruby
gem 'handler_registerable'
```

Run in your project root:

```
bundle install
```

## Usage

This gem provides a Ruby module which can be extended into a class/module to provide the following class methods:

`registered_handlers` - returns a hash which can be modified
`obtain` - loops through all registered handlers and returns the first one that handles the given condition (tested by calling the `handles?` class method on each handler)

### Creating your registry

A registry is usually a module within a given namespace. For example let's create a welcome handler:

```ruby
module WelcomeHandlers
end
```

```ruby
module WelcomeHandlers
  extend HandlerRegisterable::Registry
end
```

### Base classes and registering

To make life easier you can provide a base class which allows users to subclass and register themselves within the registry:

```ruby
module WelcomeHandlers
  class Base
    def initialize(context)
      @context = context
    end

    # Registers a handler with the given identifier.
    #
    # @param [Symbol] handler the identifier to represent this handler class.
    def self.register(handler)
      WelcomeHandlers.register self, handler
    end
  end
end
```

It's important to document the public API which handlers are required to implement. These methods will be dependant on the purpose of the registry, but all will require the appropriate `handles?` API for that registry.

```ruby
module WelcomeHandlers
  class Example < Base
    register :first_time_user

    def self.handles?(context)
      context.some_attribute == 'example'
    end
  end
end
```

> Note: Handlers don't necessarily need to be within the registry's namespace (in fact in application code it will be in your own namespaces).

The exact arguments and conditions for the `handles?` class method in the handlers is specific to the purpose of the registry. This should always be a single arg, but if you need more than one, you can use a hash. However, you should look to keep the details to a minimum.

### Defining and Registering

Let's now define the public API for our Welcome handlers

* `welcome_message` - returns the string for this type of business
* `expiry_reminders` - array of time periods from trial expiration to email reminders

Here's a couple of example handlers:

```ruby
class ProductOneWelcome < WelcomeHandler::Base
  register :product_one

  def self.handles?(user)
    user.product == 'product_one'
  end

  def welcome_message
    'Thanks for choosing product one'
  end

  def expiry_reminders
    [2.weeks, 1.week, 2.days]
  end
end

class ProductTwoeWelcome < WelcomeHandler::Base
  register :product_two

  def self.handles?(user)
    user.product == 'product_two'
  end

  def welcome_message
    'Thanks for choosing product two'
  end

  def expiry_reminders
    [1.week, 2.days, 1.day]
  end
end
```

### Setting a Default

When no handler is found, it is possible to specify a default handler to use instead:

```ruby
module WelcomeHandlers
  class Default < Base
    WelcomeHandlers.default = self

    def welcome_message
      'A default welcome message'
    end
  end
end
```

### Using the registry

The way the registry is used is through the `obtain` method:

```ruby
# code in a signup to a service object

def complete_signup(user)
  welcome = WelcomeHandlers.obtain(user)
  user.send_message(welcome.welcome_message)
  ReminderJob.schedule_for_user(user, welcome.expiry_reminders)
end
```


## License

Handler Registerable is available as open source under the terms of the
[Apache-2.0 licence](https://github.com/Sage/handler_registerable/blob/master/LICENSE).

Copyright (c) 2018 Sage Group Plc. All rights reserved.