zache 0.13.1 → 0.14.0
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/.0pdd.yml +3 -0
- data/.github/workflows/actionlint.yml +25 -0
- data/.github/workflows/codecov.yml +13 -8
- data/.github/workflows/copyrights.yml +15 -0
- data/.github/workflows/license.yml +42 -0
- data/.github/workflows/markdown-lint.yml +23 -0
- data/.github/workflows/pdd.yml +8 -4
- data/.github/workflows/rake.yml +10 -6
- data/.github/workflows/reuse.yml +19 -0
- data/.github/workflows/xcop.yml +6 -2
- data/.github/workflows/yamllint.yml +19 -0
- data/.gitignore +6 -3
- data/.rubocop.yml +21 -6
- data/.rultor.yml +6 -1
- data/Gemfile +16 -27
- data/Gemfile.lock +115 -0
- data/LICENSE.txt +1 -1
- data/LICENSES/MIT.txt +21 -0
- data/README.md +55 -26
- data/REUSE.toml +31 -0
- data/Rakefile +19 -34
- data/lib/zache.rb +123 -34
- data/logo.svg +1 -1
- data/test/test__helper.rb +24 -21
- data/test/test_zache.rb +91 -82
- data/zache.gemspec +5 -24
- metadata +13 -7
data/README.md
CHANGED
@@ -1,66 +1,95 @@
|
|
1
|
-
|
1
|
+
# In Memory Cache for Ruby
|
2
2
|
|
3
3
|
[](https://www.elegantobjects.org)
|
4
|
-
[](https://www.rultor.com/p/yegor256/zache)
|
5
5
|
[](https://www.jetbrains.com/ruby/)
|
6
6
|
|
7
7
|
[](https://github.com/yegor256/zache/actions/workflows/rake.yml)
|
8
|
-
[](
|
8
|
+
[](https://badge.fury.io/rb/zache)
|
9
9
|
[](https://codeclimate.com/github/yegor256/zache/maintainability)
|
10
|
-
[](https://rubydoc.info/github/yegor256/zache/master/frames)
|
11
11
|
[](https://github.com/yegor256/zache/blob/master/LICENSE.txt)
|
12
12
|
[](https://codecov.io/github/yegor256/zache?branch=master)
|
13
13
|
[](https://hitsofcode.com/view/github/yegor256/zache)
|
14
14
|
|
15
|
-
|
15
|
+
This is a simple Ruby gem for in-memory caching.
|
16
16
|
Read [this blog post](https://www.yegor256.com/2019/02/05/zache.html)
|
17
|
-
to understand what Zache is for.
|
17
|
+
to understand what Zache is designed for.
|
18
18
|
|
19
19
|
First, install it:
|
20
20
|
|
21
21
|
```bash
|
22
|
-
|
22
|
+
gem install zache
|
23
23
|
```
|
24
24
|
|
25
|
-
Then, use it like this
|
25
|
+
Then, use it like this:
|
26
26
|
|
27
27
|
```ruby
|
28
28
|
require 'zache'
|
29
29
|
zache = Zache.new
|
30
30
|
# Expires in 5 minutes
|
31
|
-
v = zache.get(:count, lifetime: 5 * 60) {
|
31
|
+
v = zache.get(:count, lifetime: 5 * 60) { expensive_calculation() }
|
32
32
|
```
|
33
33
|
|
34
|
+
If you omit the `lifetime` parameter, the key will never expire.
|
35
|
+
|
34
36
|
By default `Zache` is thread-safe. It locks the entire cache on each
|
35
|
-
`get` call. You turn that off by using `sync` argument:
|
37
|
+
`get` call. You can turn that off by using the `sync` argument:
|
36
38
|
|
37
39
|
```ruby
|
38
40
|
zache = Zache.new(sync: false)
|
39
|
-
v = zache.get(:count) {
|
41
|
+
v = zache.get(:count) { expensive_calculation() }
|
40
42
|
```
|
41
43
|
|
42
|
-
You may use "dirty" mode, which will return
|
43
|
-
calculation is
|
44
|
-
expired
|
45
|
-
|
46
|
-
receive
|
47
|
-
|
44
|
+
You may use "dirty" mode, which will return an expired value while
|
45
|
+
calculation is in progress. For example, if you have a value in the cache that's
|
46
|
+
expired, and you call `get` with a long-running block, the thread waits.
|
47
|
+
If another thread calls `get` again, that second thread won't wait, but will
|
48
|
+
receive the expired value from the cache. This is a very convenient mode for situations
|
49
|
+
where absolute data accuracy is less important than performance:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
zache = Zache.new(dirty: true)
|
53
|
+
# Or enable dirty mode for a specific get call
|
54
|
+
value = zache.get(:key, dirty: true) { expensive_calculation() }
|
55
|
+
```
|
56
|
+
|
57
|
+
The entire API is documented
|
58
|
+
[here](https://www.rubydoc.info/github/yegor256/zache/master/Zache).
|
59
|
+
Here are some additional useful methods:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# Check if a key exists
|
63
|
+
zache.exists?(:key)
|
48
64
|
|
49
|
-
|
50
|
-
(
|
65
|
+
# Remove a key
|
66
|
+
zache.remove(:key)
|
51
67
|
|
52
|
-
|
68
|
+
# Remove all keys
|
69
|
+
zache.remove_all
|
70
|
+
|
71
|
+
# Remove keys that match a condition
|
72
|
+
zache.remove_by { |key| key.to_s.start_with?('temp_') }
|
73
|
+
|
74
|
+
# Clean up expired keys
|
75
|
+
zache.clean
|
76
|
+
|
77
|
+
# Check if cache is empty
|
78
|
+
zache.empty?
|
79
|
+
```
|
53
80
|
|
54
81
|
## How to contribute
|
55
82
|
|
56
|
-
Read
|
57
|
-
|
58
|
-
|
83
|
+
Read
|
84
|
+
[these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
|
85
|
+
Make sure your build is green before you contribute
|
86
|
+
your pull request. You will need to have
|
87
|
+
[Ruby](https://www.ruby-lang.org/en/) 2.3+ and
|
59
88
|
[Bundler](https://bundler.io/) installed. Then:
|
60
89
|
|
61
|
-
```
|
62
|
-
|
63
|
-
|
90
|
+
```bash
|
91
|
+
bundle update
|
92
|
+
bundle exec rake
|
64
93
|
```
|
65
94
|
|
66
95
|
If it's clean and you don't see any error messages, submit your pull request.
|
data/REUSE.toml
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
version = 1
|
5
|
+
[[annotations]]
|
6
|
+
path = [
|
7
|
+
"**/*.csv",
|
8
|
+
"**/*.jpg",
|
9
|
+
"**/*.json",
|
10
|
+
"**/*.md",
|
11
|
+
"**/*.pdf",
|
12
|
+
"**/*.png",
|
13
|
+
"**/*.svg",
|
14
|
+
"**/*.txt",
|
15
|
+
"**/*.vm",
|
16
|
+
"**/.DS_Store",
|
17
|
+
"**/.gitignore",
|
18
|
+
"**/.pdd",
|
19
|
+
"**/CNAME",
|
20
|
+
"**/Gemfile.lock",
|
21
|
+
".DS_Store",
|
22
|
+
".gitattributes",
|
23
|
+
".gitignore",
|
24
|
+
".pdd",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"README.md",
|
27
|
+
"renovate.json",
|
28
|
+
]
|
29
|
+
precedence = "override"
|
30
|
+
SPDX-FileCopyrightText = "Copyright (c) 2018-2025 Yegor Bugayenko"
|
31
|
+
SPDX-License-Identifier = "MIT"
|
data/Rakefile
CHANGED
@@ -1,25 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2018-
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
22
5
|
|
6
|
+
require 'os'
|
7
|
+
require 'qbash'
|
23
8
|
require 'rubygems'
|
24
9
|
require 'rake'
|
25
10
|
require 'rake/clean'
|
@@ -34,7 +19,7 @@ def version
|
|
34
19
|
Gem::Specification.load(Dir['*.gemspec'].first).version
|
35
20
|
end
|
36
21
|
|
37
|
-
task default: %i[clean test rubocop
|
22
|
+
task default: %i[clean test picks rubocop yard]
|
38
23
|
|
39
24
|
require 'rake/testtask'
|
40
25
|
desc 'Run all unit tests'
|
@@ -44,24 +29,24 @@ Rake::TestTask.new(:test) do |test|
|
|
44
29
|
test.verbose = false
|
45
30
|
end
|
46
31
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
32
|
+
desc 'Run them via Ruby, one by one'
|
33
|
+
task :picks do
|
34
|
+
next if OS.windows?
|
35
|
+
%w[test lib].each do |d|
|
36
|
+
Dir["#{d}/**/*.rb"].each do |f|
|
37
|
+
qbash("bundle exec ruby #{Shellwords.escape(f)}", log: $stdout, env: { 'PICKS' => 'yes' })
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'yard'
|
43
|
+
desc 'Build Yard documentation'
|
44
|
+
YARD::Rake::YardocTask.new do |t|
|
45
|
+
t.files = ['lib/**/*.rb']
|
52
46
|
end
|
53
47
|
|
54
48
|
require 'rubocop/rake_task'
|
55
49
|
desc 'Run RuboCop on all directories'
|
56
50
|
RuboCop::RakeTask.new(:rubocop) do |task|
|
57
51
|
task.fail_on_error = true
|
58
|
-
task.requires << 'rubocop-rspec'
|
59
|
-
end
|
60
|
-
|
61
|
-
task :copyright do
|
62
|
-
sh "grep -q -r '2018-#{Date.today.strftime('%Y')}' \
|
63
|
-
--include '*.rb' \
|
64
|
-
--include '*.txt' \
|
65
|
-
--include 'Rakefile' \
|
66
|
-
."
|
67
52
|
end
|
data/lib/zache.rb
CHANGED
@@ -1,26 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# (
|
4
|
-
#
|
5
|
-
# Copyright (c) 2018-2023 Yegor Bugayenko
|
6
|
-
#
|
7
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
9
|
-
# in the Software without restriction, including without limitation the rights
|
10
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
-
# copies of the Software, and to permit persons to whom the Software is
|
12
|
-
# furnished to do so, subject to the following conditions:
|
13
|
-
#
|
14
|
-
# The above copyright notice and this permission notice shall be included in all
|
15
|
-
# copies or substantial portions of the Software.
|
16
|
-
#
|
17
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
24
5
|
|
25
6
|
# It is a very simple thread-safe in-memory cache with an ability to expire
|
26
7
|
# keys automatically, when their lifetime is over. Use it like this:
|
@@ -34,35 +15,60 @@
|
|
34
15
|
# {README}[https://github.com/yegor256/zache/blob/master/README.md] file.
|
35
16
|
#
|
36
17
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
37
|
-
# Copyright:: Copyright (c) 2018-
|
18
|
+
# Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko
|
38
19
|
# License:: MIT
|
39
20
|
class Zache
|
40
21
|
# Fake implementation that doesn't cache anything, but behaves like it
|
41
22
|
# does. It implements all methods of the original class, but doesn't do
|
42
23
|
# any caching. This is very useful for testing.
|
43
24
|
class Fake
|
25
|
+
# Returns a fixed size of 1.
|
26
|
+
# @return [Integer] Always returns 1
|
44
27
|
def size
|
45
28
|
1
|
46
29
|
end
|
47
30
|
|
31
|
+
# Always returns the result of the block, never caches.
|
32
|
+
# @param [Object] key Ignored
|
33
|
+
# @param [Hash] opts Ignored
|
34
|
+
# @yield Block that provides the value
|
35
|
+
# @return [Object] The result of the block
|
48
36
|
def get(*)
|
49
37
|
yield
|
50
38
|
end
|
51
39
|
|
40
|
+
# Always returns true regardless of the key.
|
41
|
+
# @param [Object] key Ignored
|
42
|
+
# @param [Hash] opts Ignored
|
43
|
+
# @return [Boolean] Always returns true
|
52
44
|
def exists?(*)
|
53
45
|
true
|
54
46
|
end
|
55
47
|
|
48
|
+
# Always returns false.
|
49
|
+
# @return [Boolean] Always returns false
|
56
50
|
def locked?
|
57
51
|
false
|
58
52
|
end
|
59
53
|
|
54
|
+
# No-op method that ignores the input.
|
55
|
+
# @param [Object] key Ignored
|
56
|
+
# @param [Object] value Ignored
|
57
|
+
# @param [Hash] opts Ignored
|
58
|
+
# @return [nil] Always returns nil
|
60
59
|
def put(*); end
|
61
60
|
|
61
|
+
# No-op method that ignores the key.
|
62
|
+
# @param [Object] _key Ignored
|
63
|
+
# @return [nil] Always returns nil
|
62
64
|
def remove(_key); end
|
63
65
|
|
66
|
+
# No-op method.
|
67
|
+
# @return [nil] Always returns nil
|
64
68
|
def remove_all; end
|
65
69
|
|
70
|
+
# No-op method.
|
71
|
+
# @return [nil] Always returns nil
|
66
72
|
def clean; end
|
67
73
|
end
|
68
74
|
|
@@ -73,7 +79,11 @@ class Zache
|
|
73
79
|
# unless you really know what you are doing.
|
74
80
|
#
|
75
81
|
# If the <tt>dirty</tt> argument is set to <tt>true</tt>, a previously
|
76
|
-
# calculated result will be returned if it exists
|
82
|
+
# calculated result will be returned if it exists, even if it is already expired.
|
83
|
+
#
|
84
|
+
# @param sync [Boolean] Whether the hash is thread-safe
|
85
|
+
# @param dirty [Boolean] Whether to return expired values
|
86
|
+
# @return [Zache] A new instance of the cache
|
77
87
|
def initialize(sync: true, dirty: false)
|
78
88
|
@hash = {}
|
79
89
|
@sync = sync
|
@@ -82,6 +92,8 @@ class Zache
|
|
82
92
|
end
|
83
93
|
|
84
94
|
# Total number of keys currently in cache.
|
95
|
+
#
|
96
|
+
# @return [Integer] Number of keys in the cache
|
85
97
|
def size
|
86
98
|
@hash.size
|
87
99
|
end
|
@@ -90,17 +102,39 @@ class Zache
|
|
90
102
|
#
|
91
103
|
# If the value is not
|
92
104
|
# found in the cache, it will be calculated via the provided block. If
|
93
|
-
# the block is not given, an exception will be raised
|
94
|
-
# is set to <tt>true</tt>). The lifetime
|
105
|
+
# the block is not given and the key doesn't exist or is expired, an exception will be raised
|
106
|
+
# (unless <tt>dirty</tt> is set to <tt>true</tt>). The lifetime
|
95
107
|
# must be in seconds. The default lifetime is huge, which means that the
|
96
108
|
# key will never be expired.
|
97
109
|
#
|
98
110
|
# If the <tt>dirty</tt> argument is set to <tt>true</tt>, a previously
|
99
|
-
# calculated result will be returned if it exists
|
100
|
-
|
111
|
+
# calculated result will be returned if it exists, even if it is already expired.
|
112
|
+
#
|
113
|
+
# @param key [Object] The key to retrieve from the cache
|
114
|
+
# @param lifetime [Integer] Time in seconds until the key expires
|
115
|
+
# @param dirty [Boolean] Whether to return expired values
|
116
|
+
# @param eager [Boolean] Whether to return placeholder while working?
|
117
|
+
# @param placeholder [Object] The placeholder to return in eager mode
|
118
|
+
# @yield Block to calculate the value if not in cache
|
119
|
+
# @yieldreturn [Object] The value to cache
|
120
|
+
# @return [Object] The cached value
|
121
|
+
def get(key, lifetime: 2**32, dirty: false, placeholder: nil, eager: false, &block)
|
101
122
|
if block_given?
|
102
123
|
return @hash[key][:value] if (dirty || @dirty) && locked? && expired?(key) && @hash.key?(key)
|
103
|
-
|
124
|
+
if eager
|
125
|
+
return @hash[key][:value] if @hash.key?(key)
|
126
|
+
put(key, placeholder, lifetime: 0)
|
127
|
+
Thread.new do
|
128
|
+
synchronized do
|
129
|
+
calc(key, lifetime, &block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
placeholder
|
133
|
+
else
|
134
|
+
synchronized do
|
135
|
+
calc(key, lifetime, &block)
|
136
|
+
end
|
137
|
+
end
|
104
138
|
else
|
105
139
|
rec = @hash[key]
|
106
140
|
if expired?(key)
|
@@ -114,8 +148,12 @@ class Zache
|
|
114
148
|
end
|
115
149
|
|
116
150
|
# Checks whether the value exists in the cache by the provided key. Returns
|
117
|
-
# TRUE if the value is here. If the key is already expired in the
|
151
|
+
# TRUE if the value is here. If the key is already expired in the cache,
|
118
152
|
# it will be removed by this method and the result will be FALSE.
|
153
|
+
#
|
154
|
+
# @param key [Object] The key to check in the cache
|
155
|
+
# @param dirty [Boolean] Whether to consider expired values as existing
|
156
|
+
# @return [Boolean] True if the key exists and is not expired (unless dirty is true)
|
119
157
|
def exists?(key, dirty: false)
|
120
158
|
rec = @hash[key]
|
121
159
|
if expired?(key) && !dirty && !@dirty
|
@@ -127,6 +165,9 @@ class Zache
|
|
127
165
|
|
128
166
|
# Checks whether the key exists in the cache and is expired. If the
|
129
167
|
# key is absent FALSE is returned.
|
168
|
+
#
|
169
|
+
# @param key [Object] The key to check in the cache
|
170
|
+
# @return [Boolean] True if the key exists and is expired
|
130
171
|
def expired?(key)
|
131
172
|
rec = @hash[key]
|
132
173
|
!rec.nil? && rec[:start] < Time.now - rec[:lifetime]
|
@@ -134,17 +175,27 @@ class Zache
|
|
134
175
|
|
135
176
|
# Returns the modification time of the key, if it exists.
|
136
177
|
# If not, current time is returned.
|
178
|
+
#
|
179
|
+
# @param key [Object] The key to get the modification time for
|
180
|
+
# @return [Time] The modification time of the key or current time if key doesn't exist
|
137
181
|
def mtime(key)
|
138
182
|
rec = @hash[key]
|
139
183
|
rec.nil? ? Time.now : rec[:start]
|
140
184
|
end
|
141
185
|
|
142
186
|
# Is cache currently locked doing something?
|
187
|
+
#
|
188
|
+
# @return [Boolean] True if the cache is locked
|
143
189
|
def locked?
|
144
190
|
@mutex.locked?
|
145
191
|
end
|
146
192
|
|
147
193
|
# Put a value into the cache.
|
194
|
+
#
|
195
|
+
# @param key [Object] The key to store the value under
|
196
|
+
# @param value [Object] The value to store in the cache
|
197
|
+
# @param lifetime [Integer] Time in seconds until the key expires (default: never expires)
|
198
|
+
# @return [Object] The value stored
|
148
199
|
def put(key, value, lifetime: 2**32)
|
149
200
|
synchronized do
|
150
201
|
@hash[key] = {
|
@@ -155,33 +206,67 @@ class Zache
|
|
155
206
|
end
|
156
207
|
end
|
157
208
|
|
158
|
-
# Removes the value from the
|
209
|
+
# Removes the value from the cache, by the provided key. If the key is absent
|
159
210
|
# and the block is provided, the block will be called.
|
211
|
+
#
|
212
|
+
# @param key [Object] The key to remove from the cache
|
213
|
+
# @yield Block to call if the key is not found
|
214
|
+
# @return [Object] The removed value or the result of the block
|
160
215
|
def remove(key)
|
161
216
|
synchronized { @hash.delete(key) { yield if block_given? } }
|
162
217
|
end
|
163
218
|
|
164
219
|
# Remove all keys from the cache.
|
220
|
+
#
|
221
|
+
# @return [Hash] Empty hash
|
165
222
|
def remove_all
|
166
223
|
synchronized { @hash = {} }
|
167
224
|
end
|
168
225
|
|
169
226
|
# Remove all keys that match the block.
|
227
|
+
#
|
228
|
+
# @yield [key] Block that should return true for keys to be removed
|
229
|
+
# @yieldparam key [Object] The cache key to evaluate
|
230
|
+
# @return [Integer] Number of keys removed
|
170
231
|
def remove_by
|
171
232
|
synchronized do
|
233
|
+
count = 0
|
172
234
|
@hash.each_key do |k|
|
173
|
-
|
235
|
+
if yield(k)
|
236
|
+
@hash.delete(k)
|
237
|
+
count += 1
|
238
|
+
end
|
174
239
|
end
|
240
|
+
count
|
175
241
|
end
|
176
242
|
end
|
177
243
|
|
178
|
-
# Remove keys that are expired.
|
244
|
+
# Remove keys that are expired. This cleans up the cache by removing all keys
|
245
|
+
# where the lifetime has been exceeded.
|
246
|
+
#
|
247
|
+
# @return [Integer] Number of keys removed
|
179
248
|
def clean
|
180
|
-
synchronized
|
249
|
+
synchronized do
|
250
|
+
size_before = @hash.size
|
251
|
+
@hash.delete_if { |key, _value| expired?(key) }
|
252
|
+
size_before - @hash.size
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Returns TRUE if the cache is empty, FALSE otherwise.
|
257
|
+
#
|
258
|
+
# @return [Boolean] True if the cache is empty
|
259
|
+
def empty?
|
260
|
+
@hash.empty?
|
181
261
|
end
|
182
262
|
|
183
263
|
private
|
184
264
|
|
265
|
+
# Calculates or retrieves a cached value for the given key.
|
266
|
+
# @param key [Object] The key to store the value under
|
267
|
+
# @param lifetime [Integer] Time in seconds until the key expires
|
268
|
+
# @yield Block that provides the value if not cached
|
269
|
+
# @return [Object] The cached or newly calculated value
|
185
270
|
def calc(key, lifetime)
|
186
271
|
rec = @hash[key]
|
187
272
|
rec = nil if expired?(key)
|
@@ -195,11 +280,15 @@ class Zache
|
|
195
280
|
@hash[key][:value]
|
196
281
|
end
|
197
282
|
|
283
|
+
# Executes a block within a synchronized context if sync is enabled.
|
284
|
+
# @param block [Proc] The block to execute
|
285
|
+
# @yield The block to execute in a synchronized context
|
286
|
+
# @return [Object] The result of the block
|
198
287
|
def synchronized(&block)
|
199
288
|
if @sync
|
200
289
|
@mutex.synchronize(&block)
|
201
290
|
else
|
202
|
-
|
291
|
+
yield
|
203
292
|
end
|
204
293
|
end
|
205
294
|
end
|
data/logo.svg
CHANGED
data/test/test__helper.rb
CHANGED
@@ -1,28 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2018-
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
22
5
|
|
23
6
|
$stdout.sync = true
|
24
7
|
|
25
8
|
require 'simplecov'
|
26
|
-
|
9
|
+
require 'simplecov-cobertura'
|
10
|
+
unless SimpleCov.running || ENV['PICKS']
|
11
|
+
SimpleCov.command_name('test')
|
12
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
|
13
|
+
[
|
14
|
+
SimpleCov::Formatter::HTMLFormatter,
|
15
|
+
SimpleCov::Formatter::CoberturaFormatter
|
16
|
+
]
|
17
|
+
)
|
18
|
+
SimpleCov.minimum_coverage 90
|
19
|
+
SimpleCov.minimum_coverage_by_file 90
|
20
|
+
SimpleCov.start do
|
21
|
+
add_filter 'test/'
|
22
|
+
add_filter 'vendor/'
|
23
|
+
add_filter 'target/'
|
24
|
+
track_files 'lib/**/*.rb'
|
25
|
+
track_files '*.rb'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
27
29
|
require 'minitest/autorun'
|
28
|
-
|
30
|
+
require 'minitest/reporters'
|
31
|
+
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
|