class Middlegem::ArrayDefinition

{ArrayDefinition} is an implementation of {Definition} that allows middlewares to be explicitly defined and ordered by class in an array. A basic example of usage is:

definition = Middlegem::ArrayDefinition.new([
  MiddlewareOne,   # appends '1'
  MiddlewareTwo,   # appends '2'
  MiddlewareThree, # appends '3'
  MiddlewareFinal  # appends '.'
])

stack = Middlegem::Stack.new(definition, middlewares: [
  MiddlewareThree.new,
  MiddlewareFinal.new,
  MiddlewareOne.new,
  MiddlewareTwo.new
])

stack.call('hello') # => ['hello123.']

Notice that the middlewares are called in the order they are specified in the definition array.

If two or more middlewares are encountered that have the same class, they will be left in the order they were added. This behavior can be overriden by setting a tie resolver. The following code, for example, raises an error when multiple MiddlewareFinal middlewares are encountered:

middlewares = [
  MiddlewareOne,
  MiddlewareTwo,
  MiddlewareThree,
  MiddlewareFinal
]

tie_resolver = proc do |ties|
  raise "Can't run multiple MiddlewareFinals!" if ties.first.is_a? MiddlewareFinal
  ties
end

definition = Middlegem::ArrayDefinition.new(middlewares, resolver: tie_resolver)

stack = Middlegem::Stack.new(definition, middlewares: [
  MiddlewareTwo.new,
  MiddlewareOne.new,
  MiddlewareFinal.new,
  MiddlewareThree.new,
  MiddlewareFinal.new
])

stack.call('hello') # => RuntimeError (Can't run multiple MiddlewareFinals!)

When the two MiddlewareFinal instances are encountered, the tie resolver is run, which raises the error.

Of course, this is only scratching the surface of what is possible with a custom tie resolver. You might, for example, simply skip other instances of MiddlewareFinal, rather than raising an error. A word of caution is in order, however! It is not recommended to try anything too complicated with the tie resolver because it is run for all ties whatsoever. That means that, while you could technically try to sort middlewares with the same class based on some other factor—there is even an example in spec/middlegem/array_definition_spec.rb—it potentially results in long if/else or case/when constructions because each type must be dealt with separately. Use at your own risk!

In general, if you need to use a tie resolver for anything but the most basic of tasks, you should probably just create your own {Definition} implementation with the required functionality. {ArrayDefinition} is intended primarily for defining middlewares according to their classes and nothing more.

@author Jacob Lockard @since 0.1.0 @see Definition

Attributes

defined_classes[RW]

An array of the middleware classes defined by this {ArrayDefinition}. Middlewares will only be permitted if their class is in this array will be run in the order specified here. @return [Array<Class>] the array of defined classes.

resolver[R]

The callable object to use to break ties when sorting middlewares. When multiple middlewares of the same type are encountered, this object will be called with an array of all tied middlewares. The resolver should sort and return the array as appropriate. @return [#call] the middleware tie resolver.

Public Class Methods

new(defined_classes, resolver: nil) click to toggle source

Creates a new instance of {ArrayDefinition} with the given array of defined classes and, optionally, a custom tie resolver. @param defined_classes [Object<Class>] an ordered array of classes to be defined by this

{ArrayDefinition} (see {#defined_classes}).

@param resolver [#call, nil] a callable object to use when middlewares of the same class

are encountered (see {#resolver}). If a +nil+ resolver is passed (the default), the
default resolver will be used, which keeps tied middlewares in the order they are passed
to {#sort}.
Calls superclass method
# File lib/middlegem/array_definition.rb, line 96
def initialize(defined_classes, resolver: nil)
  resolver = ->(*ties) { ties } if resolver.nil?

  @defined_classes = defined_classes
  @resolver = resolver

  super()
end

Public Instance Methods

defined?(middleware) click to toggle source

Determines whether the given middleware is defined according to this {ArrayDefinition} by checking whether its class is contained in the list of defined classes (i.e. {#defined_classes}). @param middleware [Object] the middleware to check. @return [bool] whether the middleware is defined.

# File lib/middlegem/array_definition.rb, line 110
def defined?(middleware)
  defined_classes.any? { |c| matches_class?(middleware, c) }
end
sort(middlewares) click to toggle source

Sorts the given array of middlewares according to this {ArrayDefinition}. Middlewares are sorted according to the order in which their classes are specified in {#defined_classes}. If multiple middlewares of the same type are encountered, they will be resolved with the {#resolver}. @param middlewares [Array<Object>] the middlewares to sort. @return [Array<Object>] the sorted middlewares.

# File lib/middlegem/array_definition.rb, line 120
def sort(middlewares)
  defined_classes.map { |c| resolver.call(matches(middlewares, c)) }.flatten
end

Protected Instance Methods

matches_class?(middleware, klass) click to toggle source

Should determine whether the given middleware's evaluated class is equal to the given one. The default implementation naturally just uses instance_of?, but you are free to override this method for other situations. You may want is use is_a? instead, for example, or perhaps a middleware's “class” is based on some other criterion. @param middleware [Object] the middleware to check. @param klass [Class] the class against which to check the middleware. @return [Boolean] whether the given middleware has the given class. @since 0.2.0

# File lib/middlegem/array_definition.rb, line 134
def matches_class?(middleware, klass)
  middleware.instance_of? klass
end

Private Instance Methods

matches(middlewares, klass) click to toggle source

Gets all the middlewares in the given array whose class is the given class. @param middlewares [Array<Object>] the array of middlewares to search. @param klass [Class] the class to search for. @return [Array<Object>] the matched middlewares.

# File lib/middlegem/array_definition.rb, line 144
def matches(middlewares, klass)
  middlewares.select { |m| matches_class?(m, klass) }
end