philiprehberger-cache_kit 0.1.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 969b99417559919665d91aea64820dd9373d787020b4d966b2443d0099864da0
4
- data.tar.gz: '09e639031c7c4b3864e45dfe35ece4e29d109d062c4b0ebca424f06269f5b14b'
3
+ metadata.gz: b52d4fb6a336dbcb73a7fa0ff6c2586d175f8b8562e482f06f4e45ae5a0ffc30
4
+ data.tar.gz: b5d3cc503b17c9946d0ee3458e42b16d59bdaf6a01adf93c173210f0828abfb3
5
5
  SHA512:
6
- metadata.gz: f15036e4a4f6cd77fd5e9f6af3c8f668eb4dee397415bbdeeda94d8acac24e7e7f81cfc6cc06721b4621ea4f1f043b0f468d1c44bef745ec14b8c01600b545f9
7
- data.tar.gz: 771c1d2c89759a190c242c1cf6b12fca3673050d8ddca0e0cdee746d7daa73be7fe81b945dc945f69a7eae43aef3e991d32c4cb0c51d4304f79faa27af6ad0ba
6
+ metadata.gz: dba97258e96462833062a4df553b7c9f267547fb97c79da41db696e6f4723ca0bf85cbe9215e2b674f8d8c8c89f00977bd3e7901266a7d4e8d46e6d1593420e3
7
+ data.tar.gz: 48625bfcb842d9eff02c0be6dd784860cfed993e3597880101dddc984d16584e00ea368a2a579de8be2eb9d1793eafe60315fe196931017e402922d17c4c8048
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] - 2026-03-13
11
+
12
+ ### Fixed
13
+ - Fix SyntaxError: setter method `[]=` cannot use endless method definition
14
+
15
+ ## [0.2.0] - 2026-03-13
16
+
17
+ ### Added
18
+ - `keys` method returns all non-expired cache keys
19
+ - `[]` and `[]=` hash-like accessors for get/set
20
+ - `stats` method returns size, hits, misses, and eviction counts
21
+ - `prune` method removes all expired entries and returns count removed
22
+ - Hit/miss/eviction tracking counters (thread-safe)
23
+
10
24
  ## [0.1.0] - 2026-03-10
11
25
 
12
26
  ### Added
data/README.md CHANGED
@@ -68,6 +68,13 @@ cache.set("post:1", data3, tags: ["posts"])
68
68
  cache.invalidate_tag("users") # removes user:1 and user:2, keeps post:1
69
69
  ```
70
70
 
71
+ ### Hash-like Access
72
+
73
+ ```ruby
74
+ cache["user:1"] = { name: "Alice" }
75
+ cache["user:1"] # => { name: "Alice" }
76
+ ```
77
+
71
78
  ### LRU Eviction
72
79
 
73
80
  ```ruby
@@ -82,6 +89,27 @@ cache.get("a") # => nil
82
89
  cache.get("d") # => 4
83
90
  ```
84
91
 
92
+ ### Pruning Expired Entries
93
+
94
+ ```ruby
95
+ cache.set("a", 1, ttl: 1)
96
+ cache.set("b", 2, ttl: 1)
97
+ cache.set("c", 3)
98
+ # ... after TTL expires ...
99
+ cache.prune # => 2 (removed expired entries)
100
+ ```
101
+
102
+ ### Stats
103
+
104
+ ```ruby
105
+ cache.set("a", 1)
106
+ cache.get("a") # hit
107
+ cache.get("missing") # miss
108
+
109
+ cache.stats
110
+ # => { size: 1, hits: 1, misses: 1, evictions: 0 }
111
+ ```
112
+
85
113
  ## API
86
114
 
87
115
  | Method | Description |
@@ -95,6 +123,11 @@ cache.get("d") # => 4
95
123
  | `Store#clear` | Remove all entries |
96
124
  | `Store#size` | Number of entries |
97
125
  | `Store#key?(key)` | Check if a key exists and is not expired |
126
+ | `Store#keys` | Returns all non-expired keys |
127
+ | `Store#[](key)` | Hash-like read (alias for `get`) |
128
+ | `Store#[]=(key, value)` | Hash-like write (alias for `set` without TTL/tags) |
129
+ | `Store#stats` | Returns `{ size:, hits:, misses:, evictions: }` |
130
+ | `Store#prune` | Remove all expired entries, returns count removed |
98
131
 
99
132
  ## Development
100
133
 
@@ -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 do
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 do
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 do
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
- return false unless entry
114
- return false if entry.expired?
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
- true
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
- remove_entry(oldest) if oldest
152
+ return unless oldest
153
+
154
+ remove_entry(oldest)
155
+ @evictions += 1
130
156
  end
131
157
 
132
158
  def remove_entry(key)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module CacheKit
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.1"
6
6
  end
7
7
  end
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.1.0
4
+ version: 0.2.1
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-10 00:00:00.000000000 Z
11
+ date: 2026-03-13 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.