sin_lru_redux 2.0.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,56 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class LruRedux::Cache
4
+ attr_reader :max_size, :ignore_nil
5
+
4
6
  def initialize(*args)
5
7
  max_size, ignore_nil, _ = args
6
8
 
9
+ max_size ||= 1000
7
10
  ignore_nil ||= false
8
11
 
9
- raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
10
- raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
12
+ validate_max_size!(max_size)
13
+ validate_ignore_nil!(ignore_nil)
11
14
 
12
15
  @max_size = max_size
13
16
  @ignore_nil = ignore_nil
14
17
  @data = {}
15
18
  end
16
19
 
17
- def max_size=(max_size)
18
- max_size ||= @max_size
19
-
20
- raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
21
-
22
- @max_size = max_size
20
+ def max_size=(new_max_size)
21
+ validate_max_size!(new_max_size)
23
22
 
24
- @data.shift while @data.size > @max_size
23
+ @max_size = new_max_size
24
+ evict_excess
25
25
  end
26
26
 
27
27
  def ttl=(_)
28
28
  nil
29
29
  end
30
30
 
31
- def ignore_nil=(ignore_nil)
32
- ignore_nil ||= @ignore_nil
33
- raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
31
+ def ignore_nil=(new_ignore_nil)
32
+ validate_ignore_nil!(new_ignore_nil)
34
33
 
35
- @ignore_nil = ignore_nil
34
+ @ignore_nil = new_ignore_nil
35
+ evict_nil
36
36
  end
37
37
 
38
38
  def getset(key)
39
- found = true
40
- value = @data.delete(key) { found = false }
41
- if found
39
+ key_found = true
40
+ value = @data.delete(key) { key_found = false }
41
+
42
+ if key_found
42
43
  @data[key] = value
43
44
  else
44
- result = @data[key] = yield
45
- @data.shift if @data.length > @max_size
45
+ result = yield
46
+ store_item(key, result)
46
47
  result
47
48
  end
48
49
  end
49
50
 
50
51
  def fetch(key)
51
- found = true
52
- value = @data.delete(key) { found = false }
53
- if found
52
+ key_found = true
53
+ value = @data.delete(key) { key_found = false }
54
+
55
+ if key_found
54
56
  @data[key] = value
55
57
  else
56
58
  yield if block_given? # rubocop:disable Style/IfInsideElse
@@ -58,46 +60,39 @@ class LruRedux::Cache
58
60
  end
59
61
 
60
62
  def [](key)
61
- found = true
62
- value = @data.delete(key) { found = false }
63
- @data[key] = value if found
63
+ key_found = true
64
+ value = @data.delete(key) { key_found = false }
65
+ return unless key_found
66
+
67
+ @data[key] = value
64
68
  end
65
69
 
66
70
  def []=(key, val)
67
- @data.delete(key)
68
- @data[key] = val
69
- @data.shift if @data.length > @max_size
70
- val # rubocop:disable Lint/Void
71
+ store_item(key, val)
71
72
  end
72
73
 
73
74
  def each(&block)
74
- array = @data.to_a
75
- array.reverse!.each(&block)
75
+ @data.to_a.reverse_each(&block)
76
76
  end
77
-
78
- # used further up the chain, non thread safe each
77
+ # Used further up the chain, non thread safe each
79
78
  alias_method :each_unsafe, :each
80
79
 
81
80
  def to_a
82
- array = @data.to_a
83
- array.reverse!
81
+ @data.to_a.reverse
84
82
  end
85
83
 
86
84
  def values
87
- vals = @data.values
88
- vals.reverse!
85
+ @data.values.reverse
89
86
  end
90
87
 
91
88
  def delete(key)
92
89
  @data.delete(key)
93
90
  end
94
-
95
91
  alias_method :evict, :delete
96
92
 
97
93
  def key?(key)
98
94
  @data.key?(key)
99
95
  end
100
-
101
96
  alias_method :has_key?, :key?
102
97
 
103
98
  def clear
@@ -108,24 +103,59 @@ class LruRedux::Cache
108
103
  @data.size
109
104
  end
110
105
 
111
- protected
106
+ private
112
107
 
113
- # for cache validation only, ensures all is sound
108
+ # For cache validation only, ensure all is valid
114
109
  def valid?
115
110
  true
116
111
  end
117
112
 
118
- private
113
+ def validate_max_size!(max_size)
114
+ unless max_size.is_a?(Numeric)
115
+ raise ArgumentError.new(<<~ERROR)
116
+ Invalid max_size: #{max_size.inspect}
117
+ max_size must be a number.
118
+ ERROR
119
+ end
120
+ return if max_size >= 1
121
+
122
+ raise ArgumentError.new(<<~ERROR)
123
+ Invalid max_size: #{max_size.inspect}
124
+ max_size must be greater than or equal to 1.
125
+ ERROR
126
+ end
119
127
 
120
- def valid_max_size?(max_size)
121
- return true if max_size.is_a?(Integer) && max_size >= 1
128
+ def validate_ignore_nil!(ignore_nil)
129
+ return if [true, false].include?(ignore_nil)
122
130
 
123
- false
131
+ raise ArgumentError.new(<<~ERROR)
132
+ Invalid ignore_nil: #{ignore_nil.inspect}
133
+ ignore_nil must be a boolean value.
134
+ ERROR
135
+ end
136
+
137
+ def evict_excess
138
+ @data.shift while @data.size > @max_size
124
139
  end
125
140
 
126
- def valid_ignore_nil?(ignore_nil)
127
- return true if [true, false].include?(ignore_nil)
141
+ if RUBY_VERSION >= '2.6.0'
142
+ def evict_nil
143
+ return unless @ignore_nil
128
144
 
129
- false
145
+ @data.compact!
146
+ end
147
+ else
148
+ def evict_nil
149
+ return unless @ignore_nil
150
+
151
+ @data.reject! { |_key, value| value.nil? }
152
+ end
153
+ end
154
+
155
+ def store_item(key, val)
156
+ @data.delete(key)
157
+ @data[key] = val if !val.nil? || !@ignore_nil
158
+ evict_excess
159
+ val
130
160
  end
131
161
  end
@@ -8,12 +8,13 @@ module LruRedux
8
8
  def initialize(*args)
9
9
  max_size, ttl, ignore_nil = args
10
10
 
11
+ max_size ||= 1000
11
12
  ttl ||= :none
12
13
  ignore_nil ||= false
13
14
 
14
- raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
15
- raise ArgumentError.new(:ttl) unless valid_ttl?(ttl)
16
- raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
15
+ validate_max_size!(max_size)
16
+ validate_ttl!(ttl)
17
+ validate_ignore_nil!(ignore_nil)
17
18
 
18
19
  @max_size = max_size
19
20
  @ttl = ttl
@@ -22,64 +23,54 @@ module LruRedux
22
23
  @data_ttl = {}
23
24
  end
24
25
 
25
- def max_size=(max_size)
26
- max_size ||= @max_size
26
+ def max_size=(new_max_size)
27
+ validate_max_size!(new_max_size)
27
28
 
28
- raise ArgumentError.new(:max_size) unless valid_max_size?(max_size)
29
-
30
- @max_size = max_size
31
-
32
- resize
29
+ @max_size = new_max_size
30
+ evict_expired
31
+ evict_excess
33
32
  end
34
33
 
35
- def ttl=(ttl)
36
- ttl ||= @ttl
37
- raise ArgumentError.new(:ttl) unless valid_ttl?(ttl)
38
-
39
- @ttl = ttl
34
+ def ttl=(new_ttl)
35
+ validate_ttl!(new_ttl)
40
36
 
41
- ttl_evict
37
+ @ttl = new_ttl
38
+ evict_expired
42
39
  end
43
40
 
44
- def ignore_nil=(ignore_nil)
45
- ignore_nil ||= @ignore_nil
46
- raise ArgumentError.new(:ignore_nil) unless valid_ignore_nil?(ignore_nil)
41
+ def ignore_nil=(new_ignore_nil)
42
+ validate_ignore_nil!(new_ignore_nil)
47
43
 
48
- @ignore_nil = ignore_nil
44
+ @ignore_nil = new_ignore_nil
45
+ evict_nil
49
46
  end
50
47
 
51
48
  def getset(key)
52
- ttl_evict
49
+ evict_expired
53
50
 
54
- found = true
55
- value = @data_lru.delete(key) { found = false }
56
- if found
51
+ key_found = true
52
+ value = @data_lru.delete(key) { key_found = false }
53
+
54
+ if key_found
55
+ @data_ttl.delete(key)
56
+ @data_ttl[key] = Time.now.to_f
57
57
  @data_lru[key] = value
58
58
  else
59
59
  result = yield
60
-
61
- if !result.nil? || !@ignore_nil
62
- @data_lru[key] = result
63
- @data_ttl[key] = Time.now.to_f
64
-
65
- if @data_lru.size > @max_size
66
- key, _ = @data_lru.first
67
-
68
- @data_ttl.delete(key)
69
- @data_lru.delete(key)
70
- end
71
- end
72
-
60
+ store_item(key, result)
73
61
  result
74
62
  end
75
63
  end
76
64
 
77
65
  def fetch(key)
78
- ttl_evict
66
+ evict_expired
67
+
68
+ key_found = true
69
+ value = @data_lru.delete(key) { key_found = false }
79
70
 
80
- found = true
81
- value = @data_lru.delete(key) { found = false }
82
- if found
71
+ if key_found
72
+ @data_ttl.delete(key)
73
+ @data_ttl[key] = Time.now.to_f
83
74
  @data_lru[key] = value
84
75
  else
85
76
  yield if block_given? # rubocop:disable Style/IfInsideElse
@@ -87,71 +78,55 @@ module LruRedux
87
78
  end
88
79
 
89
80
  def [](key)
90
- ttl_evict
81
+ evict_expired
91
82
 
92
- found = true
93
- value = @data_lru.delete(key) { found = false }
94
- @data_lru[key] = value if found
95
- end
96
-
97
- def []=(key, val)
98
- ttl_evict
83
+ key_found = true
84
+ value = @data_lru.delete(key) { key_found = false }
85
+ return unless key_found
99
86
 
100
- @data_lru.delete(key)
101
87
  @data_ttl.delete(key)
102
-
103
- @data_lru[key] = val
104
88
  @data_ttl[key] = Time.now.to_f
89
+ @data_lru[key] = value
90
+ end
105
91
 
106
- if @data_lru.size > @max_size
107
- key, _ = @data_lru.first
108
-
109
- @data_ttl.delete(key)
110
- @data_lru.delete(key)
111
- end
92
+ def []=(key, val)
93
+ evict_expired
112
94
 
113
- val # rubocop:disable Lint/Void
95
+ store_item(key, val)
114
96
  end
115
97
 
116
98
  def each(&block)
117
- ttl_evict
99
+ evict_expired
118
100
 
119
- array = @data_lru.to_a
120
- array.reverse!.each(&block)
101
+ @data_lru.to_a.reverse_each(&block)
121
102
  end
122
-
123
- # used further up the chain, non thread safe each
124
103
  alias_method :each_unsafe, :each
125
104
 
126
105
  def to_a
127
- ttl_evict
106
+ evict_expired
128
107
 
129
- array = @data_lru.to_a
130
- array.reverse!
108
+ @data_lru.to_a.reverse
131
109
  end
132
110
 
133
111
  def values
134
- ttl_evict
112
+ evict_expired
135
113
 
136
- vals = @data_lru.values
137
- vals.reverse!
114
+ @data_lru.values.reverse
138
115
  end
139
116
 
140
117
  def delete(key)
141
- ttl_evict
118
+ evict_expired
142
119
 
143
120
  @data_ttl.delete(key)
144
121
  @data_lru.delete(key)
145
122
  end
146
-
147
123
  alias_method :evict, :delete
148
124
 
149
125
  def key?(key)
150
- ttl_evict
126
+ evict_expired
151
127
 
152
128
  @data_lru.key?(key)
153
129
  end
154
-
155
130
  alias_method :has_key?, :key?
156
131
 
157
132
  def clear
@@ -159,65 +134,105 @@ module LruRedux
159
134
  @data_lru.clear
160
135
  end
161
136
 
162
- def expire
163
- ttl_evict
164
- end
165
-
166
137
  def count
167
138
  @data_lru.size
168
139
  end
169
140
 
170
- protected
141
+ def expire
142
+ evict_expired
143
+ end
144
+
145
+ private
171
146
 
172
- # for cache validation only, ensures all is sound
147
+ # For cache validation only, ensure all is valid
173
148
  def valid?
174
149
  @data_lru.size == @data_ttl.size
175
150
  end
176
151
 
177
- def ttl_evict
178
- return if @ttl == :none
152
+ def validate_max_size!(max_size)
153
+ unless max_size.is_a?(Numeric)
154
+ raise ArgumentError.new(<<~ERROR)
155
+ Invalid max_size: #{max_size.inspect}
156
+ max_size must be a number.
157
+ ERROR
158
+ end
159
+ return if max_size >= 1
179
160
 
180
- ttl_horizon = Time.now.to_f - @ttl
181
- key, time = @data_ttl.first
161
+ raise ArgumentError.new(<<~ERROR)
162
+ Invalid max_size: #{max_size.inspect}
163
+ max_size must be greater than or equal to 1.
164
+ ERROR
165
+ end
182
166
 
183
- until time.nil? || time > ttl_horizon
184
- @data_ttl.delete(key)
185
- @data_lru.delete(key)
167
+ def validate_ttl!(ttl)
168
+ return if ttl == :none
186
169
 
187
- key, time = @data_ttl.first
170
+ unless ttl.is_a?(Numeric)
171
+ raise ArgumentError.new(<<~ERROR)
172
+ Invalid ttl: #{ttl.inspect}
173
+ ttl must be a number.
174
+ ERROR
188
175
  end
176
+ return if ttl >= 0
177
+
178
+ raise ArgumentError.new(<<~ERROR)
179
+ Invalid ttl: #{ttl.inspect}
180
+ ttl must be greater than or equal to 0.
181
+ ERROR
189
182
  end
190
183
 
191
- def resize
192
- ttl_evict
184
+ def validate_ignore_nil!(ignore_nil)
185
+ return if [true, false].include?(ignore_nil)
186
+
187
+ raise ArgumentError.new("Invalid ignore_nil: #{ignore_nil.inspect}")
188
+ end
193
189
 
194
- while @data_lru.size > @max_size
195
- key, _ = @data_lru.first
190
+ def evict_expired
191
+ return if @ttl == :none
196
192
 
197
- @data_ttl.delete(key)
193
+ expiration_threshold = Time.now.to_f - @ttl
194
+ key, time = @data_ttl.first
195
+ until time.nil? || time > expiration_threshold
198
196
  @data_lru.delete(key)
197
+ @data_ttl.delete(key)
198
+ key, time = @data_ttl.first
199
199
  end
200
200
  end
201
201
 
202
- private
202
+ def evict_excess
203
+ evict_least_recently_used while @data_lru.size > @max_size
204
+ end
203
205
 
204
- def valid_max_size?(max_size)
205
- return true if max_size.is_a?(Integer) && max_size >= 1
206
+ def evict_least_recently_used
207
+ return if @data_lru.empty?
206
208
 
207
- false
209
+ oldest_key, _value = @data_lru.first
210
+ @data_lru.delete(oldest_key)
211
+ @data_ttl.delete(oldest_key)
208
212
  end
209
213
 
210
- def valid_ttl?(ttl)
211
- return true if ttl == :none
212
- return true if ttl.is_a?(Numeric) && ttl >= 0
214
+ def evict_nil
215
+ return unless @ignore_nil
213
216
 
214
- false
217
+ @data_lru.reject! do |key, value|
218
+ if value.nil?
219
+ @data_ttl.delete(key)
220
+ true
221
+ else
222
+ false
223
+ end
224
+ end
215
225
  end
216
226
 
217
- def valid_ignore_nil?(ignore_nil)
218
- return true if [true, false].include?(ignore_nil)
219
-
220
- false
227
+ def store_item(key, value)
228
+ @data_lru.delete(key)
229
+ @data_ttl.delete(key)
230
+ if !value.nil? || !@ignore_nil
231
+ @data_lru[key] = value
232
+ @data_ttl[key] = Time.now.to_f
233
+ end
234
+ evict_excess
235
+ value
221
236
  end
222
237
  end
223
238
  end
@@ -107,6 +107,8 @@ module LruRedux
107
107
  end
108
108
  end
109
109
 
110
+ private
111
+
110
112
  def valid?
111
113
  synchronize do
112
114
  super
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LruRedux
4
- VERSION = '2.0.1'
4
+ VERSION = '2.2.0'
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sin_lru_redux
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahiro
8
- bindir: bin
8
+ bindir: exe
9
9
  cert_chain: []
10
- date: 2024-12-28 00:00:00.000000000 Z
10
+ date: 2024-12-31 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: |
13
13
  Efficient and thread-safe LRU cache.
@@ -18,17 +18,12 @@ executables: []
18
18
  extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
- - ".github/workflows/lint.yml"
22
- - ".github/workflows/test.yml"
23
- - ".gitignore"
24
21
  - ".rubocop.yml"
25
- - Gemfile
26
- - Guardfile
22
+ - CHANGELOG.md
23
+ - CODE_OF_CONDUCT.md
27
24
  - LICENSE.txt
28
25
  - README.md
29
26
  - Rakefile
30
- - bench/bench.rb
31
- - bench/bench_ttl.rb
32
27
  - lib/lru_redux.rb
33
28
  - lib/lru_redux/cache.rb
34
29
  - lib/lru_redux/thread_safe_cache.rb
@@ -38,20 +33,15 @@ files:
38
33
  - lib/lru_redux/util.rb
39
34
  - lib/lru_redux/util/safe_sync.rb
40
35
  - lib/lru_redux/version.rb
41
- - sin_lru_redux.gemspec
42
- - test/cache_test.rb
43
- - test/thread_safe_cache_test.rb
44
- - test/ttl/cache_test.rb
45
- - test/ttl/thread_safe_cache_test.rb
46
- homepage: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.0.1
36
+ homepage: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.2.0
47
37
  licenses:
48
38
  - MIT
49
39
  metadata:
50
- homepage_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.0.1
51
- source_code_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.0.1
52
- changelog_uri: https://github.com/cadenza-tech/sin_lru_redux/blob/2.0.1#changelog
40
+ homepage_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.2.0
41
+ source_code_uri: https://github.com/cadenza-tech/sin_lru_redux/tree/v2.2.0
42
+ changelog_uri: https://github.com/cadenza-tech/sin_lru_redux/blob/v2.2.0/CHANGELOG.md
53
43
  bug_tracker_uri: https://github.com/cadenza-tech/sin_lru_redux/issues
54
- documentation_uri: https://rubydoc.info/gems/sin_lru_redux/2.0.1
44
+ documentation_uri: https://rubydoc.info/gems/sin_lru_redux/2.2.0
55
45
  rubygems_mfa_required: 'true'
56
46
  rdoc_options: []
57
47
  require_paths:
@@ -1,19 +0,0 @@
1
- name: Lint
2
- on:
3
- pull_request:
4
- paths:
5
- - '**/*.rb'
6
- permissions:
7
- contents: read
8
- jobs:
9
- lint:
10
- runs-on: ubuntu-latest
11
- steps:
12
- - uses: actions/checkout@v4
13
- - name: Set up Ruby
14
- uses: ruby/setup-ruby@v1
15
- with:
16
- ruby-version: 3.4
17
- bundler-cache: true
18
- - name: RuboCop
19
- run: bundle exec rake rubocop
@@ -1,21 +0,0 @@
1
- name: Test
2
- on:
3
- pull_request:
4
- permissions:
5
- contents: read
6
- jobs:
7
- test:
8
- runs-on: ubuntu-latest
9
- strategy:
10
- fail-fast: false
11
- matrix:
12
- ruby-version: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, 3.4]
13
- steps:
14
- - uses: actions/checkout@v4
15
- - name: Set up Ruby
16
- uses: ruby/setup-ruby@v1
17
- with:
18
- ruby-version: ${{ matrix.ruby-version }}
19
- bundler-cache: true
20
- - name: Run tests
21
- run: bundle exec rake test
data/.gitignore DELETED
@@ -1,19 +0,0 @@
1
- *.gem
2
- *.rbc
3
- *.swp
4
- .bundle
5
- .config
6
- .vscode
7
- .yardoc
8
- _yardoc
9
- coverage
10
- doc/
11
- Gemfile.lock
12
- InstalledFiles
13
- lib/bundler/man
14
- pkg
15
- rdoc
16
- spec/reports
17
- test/tmp
18
- test/version_tmp
19
- tmp