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.
As usual:
gem 'modware' # in a Gemfile
spec.add_dependency 'modware' # in a .gemspec
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
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:
stack.add(MyMiddleware)
Middleware is always added to the end of the 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.start
returnsenv
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
env
instance gets initialized with the method arguments - The base implmenetation of
operation
gets its arguments from theenv
instance, giving clients a chance to modify them in:before
or:around
methods. - The result of the base implementation gets stored in
env
, giving clients a chance to modify it in:around
or:after
methods. - When stack execution finishes, it returns
env
, from which the wrapper returns the result.
Modware.is_middleware?(mod)
returns truthy ifmod
's instance methods include any of the middleware methods:before
,:after
,:around
, or:implement
The middleware gem works well, following a rack-like execution model.
- 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
Contributions welcome -- feel free to open issues or submit pull requests. Thanks!