Class: Wayfarer::Middleware::DependencyGraph

Inherits:
Object
  • Object
show all
Includes:
TSort
Defined in:
lib/wayfarer/middleware/dependency_graph.rb

Overview

Immutable dependency graph that resolves middleware ordering via before:/after: constraints. Every mutation (add, remove, replace) returns a new instance with an eagerly compiled Chain and API module.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

CyclicDependencyError =
Class.new(StandardError)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(entries: {}, insertion_order: []) ⇒ DependencyGraph

Returns a new instance of DependencyGraph.

Parameters:

  • entries (Hash{Class => Entry}) (defaults to: {})

    middleware entries keyed by class

  • insertion_order (Array<Class>) (defaults to: [])

    classes in registration order



31
32
33
34
35
36
# File 'lib/wayfarer/middleware/dependency_graph.rb', line 31

def initialize(entries: {}, insertion_order: [])
  @entries = entries.freeze
  @insertion_order = insertion_order.freeze
  @chain = compile_chain
  @api = compile_api
end

Instance Attribute Details

#apiModule (readonly)

Returns combined API concern for all registered middlewares.

Returns:

  • (Module)

    combined API concern for all registered middlewares



27
28
29
# File 'lib/wayfarer/middleware/dependency_graph.rb', line 27

def api
  @api
end

#chainChain (readonly)

Returns the compiled middleware chain.

Returns:

  • (Chain)

    the compiled middleware chain



24
25
26
# File 'lib/wayfarer/middleware/dependency_graph.rb', line 24

def chain
  @chain
end

#entriesHash{Class => Entry} (readonly)

Returns middleware entries keyed by class.

Returns:

  • (Hash{Class => Entry})

    middleware entries keyed by class



18
19
20
# File 'lib/wayfarer/middleware/dependency_graph.rb', line 18

def entries
  @entries
end

#insertion_orderArray<Class> (readonly)

Returns classes in registration order.

Returns:

  • (Array<Class>)

    classes in registration order



21
22
23
# File 'lib/wayfarer/middleware/dependency_graph.rb', line 21

def insertion_order
  @insertion_order
end

Instance Method Details

#add(klass, args = [], before: [], after: []) ⇒ DependencyGraph

Returns a new graph with the middleware added. Merges constraints if the middleware is already registered. Auto-registers any middlewares referenced in before:/after: that aren't yet in the graph.

Parameters:

  • klass (Class)

    middleware class to add

  • args (Array) (defaults to: [])

    constructor arguments for the middleware

  • before (Array<Class>) (defaults to: [])

    middlewares that must come after this one

  • after (Array<Class>) (defaults to: [])

    middlewares that must come before this one

Returns:



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/wayfarer/middleware/dependency_graph.rb', line 47

def add(klass, args = [], before: [], after: []) # rubocop:disable Metrics/AbcSize
  new_entries = entries.transform_values do |entry|
    Entry.new(
      klass: entry.klass,
      args: entry.args,
      before: entry.before.dup,
      after: entry.after.dup
    )
  end

  new_order = insertion_order.dup

  (before + after).each { |dep| ensure_registered(new_entries, new_order, dep) }

  if new_entries.key?(klass)
    entry = new_entries[klass]
    entry.args = args unless args.empty?
    entry.before.merge(before)
    entry.after.merge(after)
  else
    new_entries[klass] = Entry.new(klass: klass, args: args, before: Set.new(before), after: Set.new(after))
    new_order << klass
  end

  self.class.new(entries: new_entries, insertion_order: new_order)
end

#remove(klass) ⇒ DependencyGraph

Returns a new graph with the middleware removed. Prunes all before:/after: references to it from remaining entries.

Parameters:

  • klass (Class)

    middleware class to remove

Returns:

Raises:

  • (ArgumentError)

    if the middleware isn't in the graph



80
81
82
83
84
85
86
87
88
89
# File 'lib/wayfarer/middleware/dependency_graph.rb', line 80

def remove(klass)
  raise ArgumentError, "#{klass} not found in graph" unless entries.key?(klass)

  new_entries = entries.except(klass).transform_values do |entry|
    Entry.new(klass: entry.klass, args: entry.args, before: entry.before - [klass], after: entry.after - [klass])
  end
  new_order = insertion_order - [klass]

  self.class.new(entries: new_entries, insertion_order: new_order)
end

#replace(old_klass, new_klass, args = []) ⇒ DependencyGraph

Returns a new graph with old_klass swapped for new_klass. The replacement inherits old_klass's constraints and position. All references in other entries are updated.

Parameters:

  • old_klass (Class)

    middleware class to replace

  • new_klass (Class)

    replacement middleware class

  • args (Array) (defaults to: [])

    constructor arguments for the replacement

Returns:

Raises:

  • (ArgumentError)

    if old_klass isn't in the graph



100
101
102
103
104
105
106
107
108
109
110
# File 'lib/wayfarer/middleware/dependency_graph.rb', line 100

def replace(old_klass, new_klass, args = [])
  raise ArgumentError, "#{old_klass} not found in graph" unless entries.key?(old_klass)

  new_entries = swap_references(old_klass, new_klass)
  new_entries[new_klass] = Entry.new(
    klass: new_klass, args: args, before: entries[old_klass].before.dup, after: entries[old_klass].after.dup
  )
  new_order = insertion_order.map { |k| k == old_klass ? new_klass : k }

  self.class.new(entries: new_entries, insertion_order: new_order)
end