Modware is a library for using middleware (pipeline) patterns in Ruby projects. It features a simple interface and supports "callback" style semantics in the middleware stack, including before, after, and around methods.
Installation
As usual:
gem 'modware' # in a Gemfile spec.add_dependency 'modware' # in a .gemspec
Usage
Creating a stack
Create a stack using:
stack = Modware::Stack.new(env: klass)
where klass is a Class for the environment instance that will be passed to the layers of the stack. As a shorthand for the common case, you can simply pass an array of keys, e.g.
stack = Modware::Stack.new(env: [:name, :options, :results])
and Modware will define a class that accepts those keys as keyword arguments, and has accessor methods for each
Defining middleware
Define middleware by creating a module that defines one or more of these middleware methods:
module MyMiddleware # define any of these as needed... def before(env) # code to be called before the base implementation end def after(env) # code to be called after the base implementation end def around(env) # setup/wrapper code yield env # continues execution down the stack # cleanup code end def implement(env) # completely replaces the base implementation or any earlier middleware's implement() end end
The module may use instance variables and define other methods as needed (e.g. to abide by Metz' rule #2).
To add the middleware to a stack:
Middleware is always added to the end of the stack.
Executing a stack
To execute a stack do:
stack.start(*args) { |env| # base implementation }
The execution sequence of the stack is as follows:
- Create environment instance
env = env_klass.new(*args) - Call each middleware
before(env)method, in the order they were added - Call each middleware
around(env)method, in the order they were added. This bottoms out with the lastimplement(env)method to be added, if any, otherwise the base implementation - Call each middleware
after(env)method, in the order they were added stack.startreturnsenv
Example: wrapping an existing operation
A common idiom is to wrap a modware stack around an existing operation:
class WrapsOperation < BaseClass attr_reader :stack def initialize(*args) super @stack = Modware::Stack.new(env: [:time, :place, :result]) end def operation(time, place) stack.start(time: time, place: place) { |env| env.result = super env.time, env.place }.result end end
Notice in the operation wrapper method:
- The
envinstance gets initialized with the method arguments - The base implmenetation of
operationgets its arguments from theenvinstance, giving clients a chance to modify them in:beforeor:aroundmethods. - The result of the base implementation gets stored in
env, giving clients a chance to modify it in:aroundor:aftermethods. - When stack execution finishes, it returns
env, from which the wrapper returns the result.
Helpers
Modware.is_middleware?(mod)returns truthy ifmod's instance methods include any of the middleware methods:before,:after,:around, or:implement
See also
The middleware gem works well, following a rack-like execution model.
Change Log
- 1.0.2 - Add Ruby 3.1 support
- 1.0.1 - Fix KeyStruct replacement
- 1.0.0 - use a major bump due to breakage with the 0.2.0 version and existing gems
- 0.2.0 - Add ruby 3.0 support and drop ruby < 2.5 (yanked)
- 0.1.3 - Remove its-it dependency :( #2
- 0.1.2 - More thread safety in Stack#start
- 0.1.1 - Thread safety in Stack#start
- 0.1.0 - Initial release
Contributing
Contributions welcome -- feel free to open issues or submit pull requests. Thanks!