Modware
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 .gemspecUsage
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
endThe 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.
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
endNotice 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!