Module: Smith::Agent::Registry

Extended by:
Dry::Container::Mixin
Defined in:
lib/smith/agent/registry.rb

Class Method Summary collapse

Class Method Details

.clear!Object



39
40
41
42
43
# File 'lib/smith/agent/registry.rb', line 39

def self.clear!
  registry_monitor.synchronize do
    @_container&.clear
  end
end

.delete(name) ⇒ Object



33
34
35
36
37
# File 'lib/smith/agent/registry.rb', line 33

def self.delete(name)
  registry_monitor.synchronize do
    _container.delete(normalize_key(name))
  end
end

.ensure_registered(name, klass) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/smith/agent/registry.rb', line 45

def self.ensure_registered(name, klass)
  validate_agent_class!(klass)
  key = normalize_key(name)

  registry_monitor.synchronize do
    existing = key?(key) ? resolve(key) : nil

    if existing.nil?
      register_unchecked!(key, klass)
    elsif existing.equal?(klass)
      # same object — no-op
    elsif stale_reload_binding?(existing, klass)
      # same class name, different object — Rails reload case
      _container.delete(key)
      register_unchecked!(key, klass)
    else
      raise Smith::AgentRegistryError,
            "agent registry collision for key #{key.inspect}: " \
            "already registered to #{binding_label(existing)}, " \
            "cannot replace with #{binding_label(klass)}"
    end

    klass
  end
end

.fetch!(name, workflow_class: nil, transition_name: nil, role: :agent) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/smith/agent/registry.rb', line 71

def self.fetch!(name, workflow_class: nil, transition_name: nil, role: :agent)
  registry_monitor.synchronize do
    key = normalize_key(name)
    return resolve(key) if key?(key)

    details = []
    details << "workflow #{workflow_class}" if workflow_class
    details << "transition :#{transition_name}" if transition_name
    suffix = details.empty? ? "" : " for #{details.join(', ')}"

    raise Smith::WorkflowError, "unresolved #{role} :#{key}#{suffix}"
  end
end

.find(name) ⇒ Object



15
16
17
18
19
20
# File 'lib/smith/agent/registry.rb', line 15

def self.find(name)
  registry_monitor.synchronize do
    key = normalize_key(name)
    key?(key) ? resolve(key) : nil
  end
end

.normalize_key(name) ⇒ Object



11
12
13
# File 'lib/smith/agent/registry.rb', line 11

def self.normalize_key(name)
  name.to_s
end

.register(key, contents = nil, options = {}, &block) ⇒ Object

Override Dry::Container::Mixin#register to route agent classes through ensure_registered while preserving full generic container semantics (block, options) for non-agent registrations.



25
26
27
28
29
30
31
# File 'lib/smith/agent/registry.rb', line 25

def self.register(key, contents = nil, options = {}, &block)
  if block_given? || !(contents.is_a?(Class) && contents <= Smith::Agent)
    registry_monitor.synchronize { super(key, contents, options, &block) }
  else
    ensure_registered(key, contents)
  end
end

.registry_monitorObject

Re-entrant lock (Monitor, not Mutex) so block-backed resolve inside find/fetch! can safely re-enter the registry without deadlocking on the same thread.



88
89
90
# File 'lib/smith/agent/registry.rb', line 88

def self.registry_monitor
  @_registry_monitor ||= Monitor.new
end

.validate_agent_class!(klass) ⇒ Object



92
93
94
95
96
97
# File 'lib/smith/agent/registry.rb', line 92

def self.validate_agent_class!(klass)
  return if klass.is_a?(Class) && klass <= Smith::Agent

  raise Smith::AgentRegistryError,
        "expected a Smith::Agent subclass, got #{klass.inspect}"
end