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.
Add to your project's Gemfile:
Run in your project root:
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:
module WelcomeHandlers end
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:
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.
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:
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:
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
# 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
Handler Registerable is available as open source under the terms of the Apache-2.0 licence.
Copyright (c) 2018 Sage Group Plc. All rights reserved.