philiprehberger-expiring_map
Thread-safe hash with per-key TTL and automatic expiration
Requirements
- Ruby >= 3.1
Installation
Add to your Gemfile:
gem "philiprehberger-expiring_map"
Or install directly:
gem install philiprehberger-expiring_map
Usage
require "philiprehberger/expiring_map"
cache = Philiprehberger::ExpiringMap.new(default_ttl: 300)
cache.set(:session, 'abc123')
cache.get(:session) # => 'abc123'
Per-Key TTL
cache.set(:token, 'xyz', ttl: 60) # expires in 60 seconds
cache.set(:config, data, ttl: 3600) # expires in 1 hour
cache.ttl(:token) # => remaining seconds
Fetch-or-compute
cache = Philiprehberger::ExpiringMap.new(default_ttl: 300)
# On miss, the block is evaluated, the result stored, and returned.
# On hit, the stored value is returned and the block is not called.
user = cache.fetch(:user_42) { User.find(42) }
# Override the TTL for this insert only:
token = cache.fetch(:token, ttl: 60) { Auth.issue_token }
Max Size with Eviction
cache = Philiprehberger::ExpiringMap.new(default_ttl: 300, max_size: 1000)
# Oldest entries are evicted when capacity is reached
Expiration Callback
cache.on_expire do |key, value|
logger.info("Expired: #{key}")
end
Touch to Reset TTL
cache.set(:session, data)
cache.touch(:session) # resets TTL to default
Bulk Operations
cache.set_many({ user: "alice", role: "admin", theme: "dark" })
cache.set_many({ token: "abc", nonce: "xyz" }, ttl: 60)
result = cache.get_many(:user, :role, :missing)
# => { user: "alice", role: "admin", missing: nil }
Statistics
cache.set(:a, 1)
cache.get(:a) # hit
cache.get(:missing) # miss
cache.stats
# => { hits: 1, misses: 1, expirations: 0, evictions: 0, size: 1 }
Keys and Values
cache.set(:x, 10)
cache.set(:y, 20)
cache.keys # => [:x, :y]
cache.values # => [10, 20]
Predicate Deletion
cache.set(:a, 1)
cache.set(:b, 10)
cache.delete_if { |_key, value| value >= 10 } # => 1
Enumerable
cache.each { |key, value| puts "#{key}: #{value}" }
cache.select { |_k, v| v > 10 }
API
| Method | Description |
|---|---|
.new(default_ttl:, max_size:) |
Create a new expiring map |
#set(key, value, ttl:) |
Store a value with optional per-key TTL |
#get(key) |
Retrieve a value, nil if expired or missing |
#fetch(key, ttl:) { block } |
Return stored value or atomically memoize block result on miss |
#set_many(hash, ttl:) |
Bulk insert from a hash |
#get_many(*keys) |
Bulk get returning hash of key => value |
#delete(key) |
Remove and return a value |
| `#delete_if { \ | k, v\ |
#ttl(key) |
Return remaining TTL in seconds |
#touch(key) |
Reset TTL to default |
#size |
Count of non-expired entries |
#keys |
Array of non-expired keys |
#values |
Array of non-expired values |
#stats |
Hash of hits, misses, expirations, evictions, size |
| `#on_expire { \ | k, v\ |
#clear |
Remove all entries |
| `#each { \ | k, v\ |
Development
bundle install
bundle exec rspec
bundle exec rubocop
Support
If you find this project useful: