sin_lru_redux 2.0.1 → 2.2.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.
@@ -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