Class: Middlegem::Stack

Inherits:
Object
  • Object
show all
Defined in:
lib/middlegem/stack.rb

Overview

Stack is a class that represents a chain of middlewares, which, when called, can arbitrarily transform a given input. Most of the functionality provided by middlegem lies in this class. Using Stack is simple: create a new instance with the desired definition, add whatever middlewares you want to use, and #call it.

A very basic example of usage is:

class LastNameMiddleware < Middlegem::Middleware
  def call(name)
    "The Honorable #{name}"
  end
end

class EmailStringMiddleware < Middlegem::Middleware
  def initialize(email)
    @email = email
  end

  def call(name)
    "#{@email} <#{name}>"
  end
end

definitions = [
  LastNameMiddleware,
  EmailStringMiddleware
]

stack = Middlegem::Stack.new(Middlegem::ArrayDefinition.new(definitions))
stack.middlewares += [EmailStringMiddleware.new('[email protected]'), LastNameMiddleware.new]

stack.call('Jacob') # => "[email protected] <The Honorable Jacob>"

Notice that, even though the EmailStringMiddleware was added before the LastNameMiddleware, the LastNameMiddleware was still run first since it was defined first. That is a core principle of middlegem—rather than providing extensive methods to insert middleware in a specific place along the chain, middlegem allows you to define the order explicitly. Also note that there are a variety of ways that you could specify the middleware order by extending Definition.

See Also:

Author:

  • Jacob Lockard

Since:

  • 0.1.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(definition, middlewares: []) ⇒ Stack

Creates a new instance of Middlegem::Stack with the given middleware definition and, optionally, an array of middlewares. Note that middlewares will be validated, not immediately, but before being run.

Parameters:

  • definition (Definition)

    the middleware definition to use to determine what middleware to permit in this stack and in what order to run them. May be any object that is a valid definition according to Definition.valid?.

  • middlwares (Array<Object>)

    an optional array of initial middlewares in this stack.

Since:

  • 0.1.0



72
73
74
75
76
77
78
79
# File 'lib/middlegem/stack.rb', line 72

def initialize(definition, middlewares: [])
  unless Definition.valid?(definition)
    raise InvalidDefinitionError, "The middleware definition #{definition} is invalid!"
  end

  @definition = definition
  @middlewares = middlewares
end

Instance Attribute Details

#definitionDefinition (readonly)

The Definition used to determine what middlewares are permitted in this stack and in what order they should be run. Note that this attribute may be any object that is a valid definition according to Definition.valid?.

Returns:

  • (Definition)

    the middleware definition of this middleware stack.

Since:

  • 0.1.0



63
64
65
# File 'lib/middlegem/stack.rb', line 63

def definition
  @definition
end

#middlewaresArray<Object>

An array containing the middlewares represented by this Middlegem::Stack. You can insert middlewares in any way you like by accessing this attribute directly and using ruby’s built-in array methods. If desired, you can even assign a new array to it. All middlewares will be validated before being run. To be run, a middleware must be valid as defined by Middleware.valid? and it must be defined according to the Definition instance in #definition.

Returns:

  • (Array<Object>)

    the middlewares contained in this stack.

Since:

  • 0.1.0



57
58
59
# File 'lib/middlegem/stack.rb', line 57

def middlewares
  @middlewares
end

Instance Method Details

#call(*args) ⇒ Array<Object>

Transforms the given input by calling all the middlewares in this stack, as defined by the middleware #definition. Note that, as mentioned in Middleware, middlewares in middlegem transform argument lists. Thus, the arguments are already splatted—there is no need to pass a single array of arguments as the only parameter, unless you actually want to transform just a single array. Also, middleware must return an array of arguments, which will be splatted when passed to Middleware#call.

Midlewares are validated before being run or sorted. If a middleware is encountered that is either invalid or unpermitted, an appropriate error will be raised.

Parameters:

  • args (Array<Object>)

    the array of input arguments.

Returns:

  • (Array<Object>)

    the output of the last middleware in the chain.

Raises:

Since:

  • 0.1.0



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/middlegem/stack.rb', line 98

def call(*args)
  # Validate the middlewares.
  middlewares.each { |m| ensure_valid!(m) }

  # Sort the middlewares.
  sorted = definition.sort(middlewares)

  # Run each middleware with the output of the previous one, ensuring that each output is
  # valid. For the first middleware, use `args` as the input.
  last_output = args
  sorted.each do |middleware|
    last_output = middleware.call(*last_output)
    ensure_valid_output!(middleware, last_output)
  end

  last_output
end

#ensure_valid!(middleware) ⇒ void (private)

This method returns an undefined value.

Ensures that the given middleware is a valid middleware for this middleware stack, raising an appropriate error if not. A middleware is valid if:

  1. it is valid according to Middleware.valid?, and

  2. it is defined according to #definition.

Parameters:

  • middleware (Object)

    the middleware to validate.

Since:

  • 0.1.0



124
125
126
127
128
129
130
131
132
# File 'lib/middlegem/stack.rb', line 124

def ensure_valid!(middleware)
  unless Middleware.valid?(middleware)
    raise InvalidMiddlewareError, "The middleware #{middleware} is not a valid middleware!"
  end

  unless definition.defined?(middleware)
    raise UnpermittedMiddlewareError, "The middleware #{middleware} has not been defined!"
  end
end

#ensure_valid_output!(middleware, output) ⇒ void (private)

This method returns an undefined value.

Ensures that the given output of the given middleware is valid for all the intents and purposes of this stack. Essentially, the output is valid if it can be passed, splatted, to the next middleware, or returned at the end of the stack. Currently, this method only checks whether the output is an “array” (as defined by the splat operator). If the output is invalid, an appropriate error will be raised.

Parameters:

  • middleware (Object)

    the middleware whose output is being validated. This object is only used to generate appropriate error messages.

  • output (Object)

    the middleware output to validate.

Since:

  • 0.1.0



143
144
145
146
147
148
149
# File 'lib/middlegem/stack.rb', line 143

def ensure_valid_output!(middleware, output)
  unless output == [*output]
    raise InvalidMiddlewareOutputError, <<~ERR
      The middleware #{middleware} outputted #{output}, which is not an array!
    ERR
  end
end