Class: CMDx::Middlewares

Inherits:
Object
  • Object
show all
Defined in:
lib/cmdx/middlewares.rb

Overview

Ordered list of middlewares wrapping a task’s lifecycle. Each middleware is a callable with the signature ‘call(task) { next_link.call }`; Runtime builds a nested chain and requires each middleware to yield to the next.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeMiddlewares

Returns a new instance of Middlewares.



11
12
13
# File 'lib/cmdx/middlewares.rb', line 11

def initialize
  @registry = []
end

Instance Attribute Details

#registryObject (readonly)

Returns the value of attribute registry.



9
10
11
# File 'lib/cmdx/middlewares.rb', line 9

def registry
  @registry
end

Instance Method Details

#deregister(middleware = nil, at: nil) ⇒ Middlewares

Removes a middleware by reference or by index.

Parameters:

  • middleware (#call, nil) (defaults to: nil)

    the exact middleware to remove

  • at (Integer, nil) (defaults to: nil)

    index to remove

Returns:

Raises:

  • (ArgumentError)

    when neither or both of ‘middleware`/`:at` are given, or when `:at` isn’t an Integer



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/cmdx/middlewares.rb', line 71

def deregister(middleware = nil, at: nil)
  if at.nil? && middleware.nil?
    raise ArgumentError, "middleware: provide either a middleware or an at: index"
  elsif !at.nil? && !middleware.nil?
    raise ArgumentError, "middleware: provide either a middleware or an at: index, not both"
  elsif !at.nil? && !at.is_a?(Integer)
    raise ArgumentError, <<~MSG.chomp
      middleware :at must be an Integer (got #{at.class}).
      See https://drexed.github.io/cmdx/middlewares/#ordering
    MSG
  end

  if at.nil?
    registry.reject! { |mw, _opts| mw == middleware }
  else
    registry.delete_at(at)
  end

  self
end

#empty?Boolean

Returns:

  • (Boolean)


93
94
95
# File 'lib/cmdx/middlewares.rb', line 93

def empty?
  registry.empty?
end

#initialize_copy(source) ⇒ void

This method returns an undefined value.

Parameters:



17
18
19
# File 'lib/cmdx/middlewares.rb', line 17

def initialize_copy(source)
  @registry = source.registry.dup
end

#process(task) { ... } ⇒ void

This method returns an undefined value.

Walks the middleware chain around ‘task`’s lifecycle. The final link yields to ‘block`, which is expected to run the actual lifecycle.

Built as an iterative reverse-reduce (matching Callbacks#around), avoiding the per-link recursive lambda invocation of the previous implementation while preserving identical semantics.

Parameters:

Yields:

  • the innermost link — the task’s lifecycle body

Raises:

  • (MiddlewareError)

    when a middleware forgets to yield to ‘next_link`, which would otherwise silently skip the task



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/cmdx/middlewares.rb', line 114

def process(task)
  processed = false
  last_invoked = nil

  innermost = lambda do
    processed = true
    yield
  end

  chain = registry.reverse_each.reduce(innermost) do |succ, (mw, opts)|
    lambda do
      next succ.call unless Util.satisfied?(opts[:if], opts[:unless], task)

      last_invoked = mw
      mw.call(task) { succ.call }
    end
  end

  chain.call

  processed || begin
    offender = last_invoked.is_a?(Class) ? last_invoked : last_invoked.class
    raise MiddlewareError, <<~MSG.chomp
      middleware #{offender} did not yield to next_link.
      See https://drexed.github.io/cmdx/middlewares/#safety
    MSG
  end
end

#register(callable = nil, **options, &block) { ... } ⇒ Middlewares

Inserts a middleware. With no ‘:at`, appends. With `:at`, inserts at the given (clamped) index — supports negative indexing. `:if`/`:unless` gates evaluated against the task at process time.

Parameters:

  • callable (#call, nil) (defaults to: nil)

    provide either this or a block

  • block (#call, nil)

    middleware callable when ‘callable` is omitted

  • options (Hash{Symbol => Object})

Options Hash (**options):

  • :if (Symbol, Proc, #call)

    gate that must evaluate truthy

  • :unless (Symbol, Proc, #call)

    gate that must evaluate falsy

  • :at (Integer)

    insertion index (see implementation)

Yields:

  • the middleware body, receiving ‘(task)` and `next_link` via block

Returns:

Raises:

  • (ArgumentError)

    when both or neither of ‘callable`/block are given, when the callable doesn’t respond to ‘#call`, or when `:at` isn’t an Integer



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/cmdx/middlewares.rb', line 35

def register(callable = nil, **options, &block)
  middleware = callable || block
  at = options.delete(:at)

  if callable && block
    raise ArgumentError, "middleware: provide either a callable or a block, not both"
  elsif !middleware.respond_to?(:call)
    raise ArgumentError, <<~MSG.chomp
      middleware must respond to #call (got #{middleware.class}).
      See https://drexed.github.io/cmdx/middlewares/#signature
    MSG
  elsif !at.nil? && !at.is_a?(Integer)
    raise ArgumentError, <<~MSG.chomp
      middleware :at must be an Integer (got #{at.class}).
      See https://drexed.github.io/cmdx/middlewares/#ordering
    MSG
  end

  entry = [middleware, options.freeze]

  if at.nil?
    registry << entry
  else
    registry.insert(at.clamp(-registry.size - 1, registry.size), entry)
  end

  self
end

#sizeInteger

Returns:

  • (Integer)


98
99
100
# File 'lib/cmdx/middlewares.rb', line 98

def size
  registry.size
end