Class: Smith::PersistenceAdapters::Memory

Inherits:
Object
  • Object
show all
Defined in:
lib/smith/persistence_adapters/memory.rb

Overview

In-process Hash adapter. Thread-safe via Monitor. No I/O, no transient errors. Designed for tests and quick smoke runs.

Tracks TTL via stamped expiry times so it behaves consistently with other adapters' TTL semantics. Implements store_versioned via the monitor, enabling optimistic-locking tests without Redis.

Auto-selected by Smith.persistence_adapter when both Smith.config.persistence_adapter is nil AND Smith.config.test_mode is true (typically set in spec_helper.rb).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(identity: "memory:#{SecureRandom.uuid}") ⇒ Memory

Returns a new instance of Memory.



21
22
23
24
25
26
# File 'lib/smith/persistence_adapters/memory.rb', line 21

def initialize(identity: "memory:#{SecureRandom.uuid}")
  @persistence_identity = identity.to_s.dup.freeze
  @store = {}
  @heartbeats = {}
  @monitor = Monitor.new
end

Instance Attribute Details

#persistence_identityObject (readonly)

Returns the value of attribute persistence_identity.



19
20
21
# File 'lib/smith/persistence_adapters/memory.rb', line 19

def persistence_identity
  @persistence_identity
end

Instance Method Details

#clear!Object



106
107
108
# File 'lib/smith/persistence_adapters/memory.rb', line 106

def clear!
  @monitor.synchronize { @store.clear }
end

#delete(key) ⇒ Object



43
44
45
46
47
48
# File 'lib/smith/persistence_adapters/memory.rb', line 43

def delete(key)
  @monitor.synchronize do
    @store.delete(key)
    @heartbeats.delete(key)
  end
end

#fetch(key) ⇒ Object



34
35
36
37
38
39
40
41
# File 'lib/smith/persistence_adapters/memory.rb', line 34

def fetch(key)
  @monitor.synchronize do
    entry = live_entry(key)
    next nil if entry.nil?

    copy_payload(entry[:payload])
  end
end

#last_heartbeat(key) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/smith/persistence_adapters/memory.rb', line 59

def last_heartbeat(key)
  @monitor.synchronize do
    entry = @heartbeats[key]
    next nil if entry.nil?

    if entry[:expires_at] && entry[:expires_at] < Time.now.utc
      @heartbeats.delete(key)
      next nil
    end

    entry[:at]
  end
end

#record_heartbeat(key, ttl: Smith.config.persistence_ttl) ⇒ Object



53
54
55
56
57
# File 'lib/smith/persistence_adapters/memory.rb', line 53

def record_heartbeat(key, ttl: Smith.config.persistence_ttl)
  @monitor.synchronize do
    @heartbeats[key] = { at: Time.now.utc, expires_at: ttl ? Time.now.utc + ttl : nil }
  end
end

#replace_exact(key, payload, expected_payload:, ttl: Smith.config.persistence_ttl) ⇒ Object



96
97
98
99
100
101
102
103
104
# File 'lib/smith/persistence_adapters/memory.rb', line 96

def replace_exact(key, payload, expected_payload:, ttl: Smith.config.persistence_ttl)
  @monitor.synchronize do
    entry = live_entry(key)
    raise PersistencePayloadConflict.new(key:) unless entry && entry[:payload] == expected_payload

    @store[key] = entry_for(payload, ttl)
  end
  payload
end

#store(key, payload, ttl: Smith.config.persistence_ttl) ⇒ Object



28
29
30
31
32
# File 'lib/smith/persistence_adapters/memory.rb', line 28

def store(key, payload, ttl: Smith.config.persistence_ttl)
  @monitor.synchronize do
    @store[key] = entry_for(payload, ttl)
  end
end

#store_versioned(key, payload, expected_version:, ttl: Smith.config.persistence_ttl) ⇒ Object

Optimistic locking via Monitor-synchronized version compare. Raises Smith::PersistenceVersionConflict when the stored payload's version differs from expected_version. The version is read from the payload's JSON persistence_version field (same shape Redis and ActiveRecord stores use, so the contract is consistent across all versioned adapters).



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/smith/persistence_adapters/memory.rb', line 79

def store_versioned(key, payload, expected_version:, ttl: Smith.config.persistence_ttl)
  @monitor.synchronize do
    entry = live_entry(key)
    if entry
      current_version = parse_version(entry[:payload])
      if current_version != expected_version
        raise Smith::PersistenceVersionConflict.new(
          key: key, expected: expected_version, actual: current_version
        )
      end
    else
      VersionExpectation.validate_missing!(key, expected_version)
    end
    @store[key] = entry_for(payload, ttl)
  end
end

#transaction_identityObject



51
# File 'lib/smith/persistence_adapters/memory.rb', line 51

def transaction_identity = nil

#transaction_open?Boolean

Returns:

  • (Boolean)


50
# File 'lib/smith/persistence_adapters/memory.rb', line 50

def transaction_open? = false