philiprehberger-cache_kit 0.1.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +34 -0
- data/lib/philiprehberger/cache_kit/store.rb +57 -31
- data/lib/philiprehberger/cache_kit/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b4acd246ec80526d77eb23a49ecac10bcb00cbeaed209e279381099b14346f19
|
|
4
|
+
data.tar.gz: de8a6c3c962e0836c859dd97042b1885cc553e2edb41ee633707e431d3f73480
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b3269a4eeebbb1b2020b421b8288e45a2aa78f48f61d55cc4fbf470b052137f85fea597ae215b83ea68ae862c8da326bbd6b4a82238d81511ce5a9f5a190572
|
|
7
|
+
data.tar.gz: '08dbc8a4505fbf216efede795a81ea828eb16e5f7a8a27793f8022225508ed58d8a11fbe68eeaae60212a0dbbb88136c087c538a2ef24bea82358063720de955'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
- Add License badge to README
|
|
6
|
+
- Add bug_tracker_uri to gemspec
|
|
7
|
+
|
|
3
8
|
All notable changes to this gem will be documented in this file.
|
|
4
9
|
|
|
5
10
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
@@ -7,6 +12,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
12
|
|
|
8
13
|
## [Unreleased]
|
|
9
14
|
|
|
15
|
+
## [0.2.1] - 2026-03-13
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Fix SyntaxError: setter method `[]=` cannot use endless method definition
|
|
19
|
+
|
|
20
|
+
## [0.2.0] - 2026-03-13
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `keys` method returns all non-expired cache keys
|
|
24
|
+
- `[]` and `[]=` hash-like accessors for get/set
|
|
25
|
+
- `stats` method returns size, hits, misses, and eviction counts
|
|
26
|
+
- `prune` method removes all expired entries and returns count removed
|
|
27
|
+
- Hit/miss/eviction tracking counters (thread-safe)
|
|
28
|
+
|
|
10
29
|
## [0.1.0] - 2026-03-10
|
|
11
30
|
|
|
12
31
|
### Added
|
data/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/philiprehberger/rb-cache-kit/actions/workflows/ci.yml)
|
|
4
4
|
[](https://rubygems.org/gems/philiprehberger-cache_kit)
|
|
5
|
+
[](LICENSE)
|
|
5
6
|
|
|
6
7
|
In-memory LRU cache with TTL, tags, and thread safety for Ruby.
|
|
7
8
|
|
|
@@ -68,6 +69,13 @@ cache.set("post:1", data3, tags: ["posts"])
|
|
|
68
69
|
cache.invalidate_tag("users") # removes user:1 and user:2, keeps post:1
|
|
69
70
|
```
|
|
70
71
|
|
|
72
|
+
### Hash-like Access
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
cache["user:1"] = { name: "Alice" }
|
|
76
|
+
cache["user:1"] # => { name: "Alice" }
|
|
77
|
+
```
|
|
78
|
+
|
|
71
79
|
### LRU Eviction
|
|
72
80
|
|
|
73
81
|
```ruby
|
|
@@ -82,6 +90,27 @@ cache.get("a") # => nil
|
|
|
82
90
|
cache.get("d") # => 4
|
|
83
91
|
```
|
|
84
92
|
|
|
93
|
+
### Pruning Expired Entries
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
cache.set("a", 1, ttl: 1)
|
|
97
|
+
cache.set("b", 2, ttl: 1)
|
|
98
|
+
cache.set("c", 3)
|
|
99
|
+
# ... after TTL expires ...
|
|
100
|
+
cache.prune # => 2 (removed expired entries)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Stats
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
cache.set("a", 1)
|
|
107
|
+
cache.get("a") # hit
|
|
108
|
+
cache.get("missing") # miss
|
|
109
|
+
|
|
110
|
+
cache.stats
|
|
111
|
+
# => { size: 1, hits: 1, misses: 1, evictions: 0 }
|
|
112
|
+
```
|
|
113
|
+
|
|
85
114
|
## API
|
|
86
115
|
|
|
87
116
|
| Method | Description |
|
|
@@ -95,6 +124,11 @@ cache.get("d") # => 4
|
|
|
95
124
|
| `Store#clear` | Remove all entries |
|
|
96
125
|
| `Store#size` | Number of entries |
|
|
97
126
|
| `Store#key?(key)` | Check if a key exists and is not expired |
|
|
127
|
+
| `Store#keys` | Returns all non-expired keys |
|
|
128
|
+
| `Store#[](key)` | Hash-like read (alias for `get`) |
|
|
129
|
+
| `Store#[]=(key, value)` | Hash-like write (alias for `set` without TTL/tags) |
|
|
130
|
+
| `Store#stats` | Returns `{ size:, hits:, misses:, evictions: }` |
|
|
131
|
+
| `Store#prune` | Remove all expired entries, returns count removed |
|
|
98
132
|
|
|
99
133
|
## Development
|
|
100
134
|
|
|
@@ -10,6 +10,9 @@ module Philiprehberger
|
|
|
10
10
|
@data = {}
|
|
11
11
|
@order = []
|
|
12
12
|
@mutex = Mutex.new
|
|
13
|
+
@hits = 0
|
|
14
|
+
@misses = 0
|
|
15
|
+
@evictions = 0
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
# Get a value by key. Returns nil if missing or expired.
|
|
@@ -17,18 +20,7 @@ module Philiprehberger
|
|
|
17
20
|
# @param key [String] the cache key
|
|
18
21
|
# @return the cached value, or nil
|
|
19
22
|
def get(key)
|
|
20
|
-
@mutex.synchronize
|
|
21
|
-
entry = @data[key]
|
|
22
|
-
return nil unless entry
|
|
23
|
-
|
|
24
|
-
if entry.expired?
|
|
25
|
-
remove_entry(key)
|
|
26
|
-
return nil
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
touch(key)
|
|
30
|
-
entry.value
|
|
31
|
-
end
|
|
23
|
+
@mutex.synchronize { fetch_entry(key) }
|
|
32
24
|
end
|
|
33
25
|
|
|
34
26
|
# Store a value.
|
|
@@ -65,16 +57,9 @@ module Philiprehberger
|
|
|
65
57
|
computed
|
|
66
58
|
end
|
|
67
59
|
|
|
68
|
-
# Delete a specific key.
|
|
69
|
-
#
|
|
70
|
-
# @param key [String] the cache key
|
|
71
60
|
# @return [Boolean] true if the key existed
|
|
72
61
|
def delete(key)
|
|
73
|
-
@mutex.synchronize
|
|
74
|
-
removed = @data.key?(key)
|
|
75
|
-
remove_entry(key)
|
|
76
|
-
removed
|
|
77
|
-
end
|
|
62
|
+
@mutex.synchronize { @data.key?(key).tap { remove_entry(key) } }
|
|
78
63
|
end
|
|
79
64
|
|
|
80
65
|
# Invalidate all entries with a given tag.
|
|
@@ -90,14 +75,9 @@ module Philiprehberger
|
|
|
90
75
|
end
|
|
91
76
|
end
|
|
92
77
|
|
|
93
|
-
# Clear all entries.
|
|
94
|
-
#
|
|
95
78
|
# @return [void]
|
|
96
79
|
def clear
|
|
97
|
-
@mutex.synchronize
|
|
98
|
-
@data.clear
|
|
99
|
-
@order.clear
|
|
100
|
-
end
|
|
80
|
+
@mutex.synchronize { @data.clear && @order.clear }
|
|
101
81
|
end
|
|
102
82
|
|
|
103
83
|
# @return [Integer] number of entries (including expired ones not yet evicted)
|
|
@@ -105,15 +85,39 @@ module Philiprehberger
|
|
|
105
85
|
@mutex.synchronize { @data.size }
|
|
106
86
|
end
|
|
107
87
|
|
|
108
|
-
# @param key [String]
|
|
109
88
|
# @return [Boolean] true if the key exists and is not expired
|
|
110
89
|
def key?(key)
|
|
111
90
|
@mutex.synchronize do
|
|
112
91
|
entry = @data[key]
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
entry ? !entry.expired? : false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @return [Array<String>] list of valid keys
|
|
97
|
+
def keys
|
|
98
|
+
@mutex.synchronize { @data.reject { |_, entry| entry.expired? }.keys }
|
|
99
|
+
end
|
|
115
100
|
|
|
116
|
-
|
|
101
|
+
# @param key [String]
|
|
102
|
+
def [](key) = get(key)
|
|
103
|
+
|
|
104
|
+
# @param key [String]
|
|
105
|
+
# @param value the value to cache
|
|
106
|
+
def []=(key, value)
|
|
107
|
+
set(key, value)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @return [Hash] stats with :size, :hits, :misses, :evictions
|
|
111
|
+
def stats
|
|
112
|
+
@mutex.synchronize { { size: @data.size, hits: @hits, misses: @misses, evictions: @evictions } }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @return [Integer] number of entries removed
|
|
116
|
+
def prune
|
|
117
|
+
@mutex.synchronize do
|
|
118
|
+
expired_keys = @data.select { |_, entry| entry.expired? }.keys
|
|
119
|
+
expired_keys.each { |k| remove_entry(k) }
|
|
120
|
+
expired_keys.size
|
|
117
121
|
end
|
|
118
122
|
end
|
|
119
123
|
|
|
@@ -124,9 +128,31 @@ module Philiprehberger
|
|
|
124
128
|
@order.push(key)
|
|
125
129
|
end
|
|
126
130
|
|
|
131
|
+
def fetch_entry(key)
|
|
132
|
+
entry = @data[key]
|
|
133
|
+
return record_miss unless entry
|
|
134
|
+
|
|
135
|
+
if entry.expired?
|
|
136
|
+
remove_entry(key)
|
|
137
|
+
return record_miss
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
@hits += 1
|
|
141
|
+
touch(key)
|
|
142
|
+
entry.value
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def record_miss
|
|
146
|
+
@misses += 1
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
|
|
127
150
|
def evict
|
|
128
151
|
oldest = @order.first
|
|
129
|
-
|
|
152
|
+
return unless oldest
|
|
153
|
+
|
|
154
|
+
remove_entry(oldest)
|
|
155
|
+
@evictions += 1
|
|
130
156
|
end
|
|
131
157
|
|
|
132
158
|
def remove_entry(key)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-cache_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A lightweight, thread-safe in-memory LRU cache with TTL expiration and
|
|
14
14
|
tag-based bulk invalidation for Ruby applications.
|