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 Gemfilespec.add_dependency'modware'# in a .gemspecCreate 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:
moduleMyMiddleware# define any of these as needed...defbefore(env)# code to be called before the base implementationenddefafter(env)# code to be called after the base implementationenddefaround(env)# setup/wrapper codeyieldenv# continues execution down the stack# cleanup codeenddefimplement(env)# completely replaces the base implementation or any earlier middleware's implement()endendThe 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.startreturnsenv
A common idiom is to wrap a modware stack around an existing operation:
classWrapsOperation < BaseClassattr_reader:stackdefinitialize(*args)super@stack=Modware::Stack.new(env: [:time,:place,:result])enddefoperation(time,place)stack.start(time: time,place: place){ |env|
env.result=superenv.time,env.place}.resultendendNotice 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.
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!