safe_memoize 0.9.0 → 1.0.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.
@@ -25,10 +25,13 @@ module SafeMemoize
25
25
  # end
26
26
  # end
27
27
  module RequestScoped
28
+ # @api private
28
29
  def self.included(base)
29
30
  base.after_action :reset_all_memos if base.respond_to?(:after_action)
30
31
  end
31
32
 
33
+ # Resets all memoized values on this instance. Delegates to {PublicMethods#reset_all_memos}.
34
+ # @return [void]
32
35
  def reset_request_memos
33
36
  reset_all_memos
34
37
  end
@@ -3,6 +3,7 @@
3
3
  require "date"
4
4
 
5
5
  module SafeMemoize
6
+ # @api private
6
7
  module ReleaseTooling
7
8
  module_function
8
9
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SafeMemoize
4
- VERSION = "0.9.0"
4
+ # The current gem version string.
5
+ VERSION = "1.0.0"
5
6
  end
data/lib/safe_memoize.rb CHANGED
@@ -17,27 +17,81 @@ require_relative "safe_memoize/public_custom_key_methods"
17
17
  require_relative "safe_memoize/lru_methods"
18
18
  require_relative "safe_memoize/instance_methods"
19
19
 
20
+ # Thread-safe memoization for Ruby that correctly handles +nil+ and +false+ values.
21
+ #
22
+ # Prepend this module into any class, then call {ClassMethods#memoize} to wrap
23
+ # instance methods with a per-instance cache backed by a +Mutex+.
24
+ #
25
+ # @example Basic usage
26
+ # class UserService
27
+ # prepend SafeMemoize
28
+ #
29
+ # def current_user
30
+ # User.find_by(session_id: session_id)
31
+ # end
32
+ # memoize :current_user
33
+ # end
34
+ #
35
+ # @example With TTL and LRU cap
36
+ # class ApiClient
37
+ # prepend SafeMemoize
38
+ #
39
+ # def fetch(id)
40
+ # http_get("/items/#{id}")
41
+ # end
42
+ # memoize :fetch, ttl: 60, max_size: 500
43
+ # end
44
+ #
45
+ # @see ClassMethods#memoize
46
+ # @see https://github.com/eclectic-coding/safe_memoize README
20
47
  module SafeMemoize
48
+ # Base class for all SafeMemoize-specific exceptions.
49
+ # Rescue this to catch any error raised by the library itself.
21
50
  class Error < StandardError; end
22
51
 
23
52
  include InstanceMethods
24
53
 
54
+ # @api private
25
55
  def self.prepended(base)
26
56
  base.extend(ClassMethods)
27
57
  end
28
58
 
59
+ # Yields the global {Configuration} object for mutation.
60
+ #
61
+ # @example
62
+ # SafeMemoize.configure do |c|
63
+ # c.default_ttl = 300
64
+ # end
65
+ #
66
+ # @yield [config] The current {Configuration} instance.
67
+ # @yieldparam config [Configuration]
68
+ # @return [void]
29
69
  def self.configure
30
70
  yield configuration
31
71
  end
32
72
 
73
+ # Returns the global {Configuration} instance, creating it on first access.
74
+ #
75
+ # @return [Configuration]
33
76
  def self.configuration
34
77
  @configuration ||= Configuration.new
35
78
  end
36
79
 
80
+ # Resets the global configuration to all defaults.
81
+ #
82
+ # Useful in test suites to prevent configuration leaking between examples.
83
+ #
84
+ # @return [Configuration] the new blank configuration
37
85
  def self.reset_configuration!
38
86
  @configuration = Configuration.new
39
87
  end
40
88
 
89
+ # Emits a structured deprecation warning through the configured handler.
90
+ #
91
+ # @param subject [String] short identifier of the deprecated symbol
92
+ # @param message [String] migration instructions
93
+ # @param horizon [String] version when the symbol will be removed (e.g. +"v2.0.0"+)
94
+ # @return [void]
41
95
  def self.deprecate(subject, message:, horizon:)
42
96
  text = "[SafeMemoize] #{subject} is deprecated and will be removed in #{horizon}. #{message}"
43
97
  handler = configuration.on_deprecation
@@ -0,0 +1,245 @@
1
+ # typed: true
2
+
3
+ # Sorbet type stubs for the safe_memoize gem.
4
+ #
5
+ # These stubs cover every symbol listed in the public API guarantee
6
+ # (see README "Public API and versioning guarantee"). Opt-in extensions
7
+ # (SafeMemoize::Rails, SafeMemoize::Adapters::*) are included as well,
8
+ # but they are not part of the v1.0.0 semver guarantee.
9
+ #
10
+ # Usage: add `require "safe_memoize"` to your Sorbet ignore list if you
11
+ # use tapioca, or copy this file into sorbet/rbi/shims/ for manual setups.
12
+
13
+ module SafeMemoize
14
+ VERSION = T.let(T.unsafe(nil), String)
15
+
16
+ class Error < StandardError; end
17
+
18
+ sig { params(base: T::Class[T.anything]).void }
19
+ def self.prepended(base); end
20
+
21
+ sig { params(blk: T.proc.params(config: SafeMemoize::Configuration).void).void }
22
+ def self.configure(&blk); end
23
+
24
+ sig { returns(SafeMemoize::Configuration) }
25
+ def self.configuration; end
26
+
27
+ sig { returns(SafeMemoize::Configuration) }
28
+ def self.reset_configuration!; end
29
+
30
+ sig { params(subject: String, message: String, horizon: String).void }
31
+ def self.deprecate(subject, message:, horizon:); end
32
+
33
+ class Configuration
34
+ sig { returns(T.nilable(Numeric)) }
35
+ attr_accessor :default_ttl
36
+
37
+ sig { returns(T.nilable(Integer)) }
38
+ attr_accessor :default_max_size
39
+
40
+ sig { returns(T.nilable(T.proc.params(message: String).void)) }
41
+ attr_accessor :on_deprecation
42
+
43
+ sig { returns(T.nilable(T.proc.params(error: Exception, hook_type: Symbol, cache_key: T.untyped).void)) }
44
+ attr_accessor :on_hook_error
45
+
46
+ sig { returns(T::Boolean) }
47
+ attr_accessor :active_support_notifications
48
+
49
+ sig { returns(T.untyped) }
50
+ attr_accessor :statsd_client
51
+
52
+ sig { returns(T.untyped) }
53
+ attr_accessor :opentelemetry_tracer
54
+
55
+ sig { void }
56
+ def initialize; end
57
+ end
58
+
59
+ module ClassMethods
60
+ sig do
61
+ params(
62
+ method_name: T.any(Symbol, String),
63
+ ttl: T.nilable(Numeric),
64
+ max_size: T.nilable(Integer),
65
+ ttl_refresh: T::Boolean,
66
+ if: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
67
+ unless: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
68
+ shared: T::Boolean,
69
+ key: T.nilable(T.proc.params(args: T.untyped).returns(T.untyped))
70
+ ).void
71
+ end
72
+ def memoize(method_name, ttl: nil, max_size: nil, ttl_refresh: false, if: nil, unless: nil, shared: false, key: nil); end
73
+
74
+ sig do
75
+ params(
76
+ except: T::Array[T.any(Symbol, String)],
77
+ only: T::Array[T.any(Symbol, String)],
78
+ include_protected: T::Boolean,
79
+ include_private: T::Boolean,
80
+ ttl: T.nilable(Numeric),
81
+ max_size: T.nilable(Integer),
82
+ ttl_refresh: T::Boolean,
83
+ if: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
84
+ unless: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
85
+ shared: T::Boolean,
86
+ key: T.nilable(T.proc.params(args: T.untyped).returns(T.untyped))
87
+ ).void
88
+ end
89
+ def memoize_all(except: [], only: [], include_protected: false, include_private: false, ttl: nil, max_size: nil, ttl_refresh: false, if: nil, unless: nil, shared: false, key: nil); end
90
+
91
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).void }
92
+ def reset_shared_memo(method_name, *args, **kwargs); end
93
+
94
+ sig { void }
95
+ def reset_all_shared_memos; end
96
+
97
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T::Boolean) }
98
+ def shared_memoized?(method_name, *args, **kwargs); end
99
+
100
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(Integer) }
101
+ def shared_memo_count(method_name = nil); end
102
+
103
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.nilable(Float)) }
104
+ def shared_memo_age(method_name, *args, **kwargs); end
105
+
106
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T::Boolean) }
107
+ def shared_memo_stale?(method_name, *args, **kwargs); end
108
+ end
109
+
110
+ module PublicMethods
111
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped, blk: T.nilable(T.proc.returns(T.untyped))).returns(T::Boolean) }
112
+ def memoized?(method_name, *args, **kwargs, &blk); end
113
+
114
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.nilable(T.any(Float, Integer))) }
115
+ def memo_ttl_remaining(method_name, *args, **kwargs); end
116
+
117
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(Integer) }
118
+ def memo_count(method_name = nil); end
119
+
120
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(T::Array[T.untyped]) }
121
+ def memo_keys(method_name = nil); end
122
+
123
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(T::Array[T.untyped]) }
124
+ def memo_values(method_name = nil); end
125
+
126
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
127
+ def on_memo_expire(&blk); end
128
+
129
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
130
+ def on_memo_evict(&blk); end
131
+
132
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
133
+ def on_memo_hit(&blk); end
134
+
135
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
136
+ def on_memo_miss(&blk); end
137
+
138
+ sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
139
+ def on_memo_store(&blk); end
140
+
141
+ sig { params(hook_type: T.nilable(Symbol)).void }
142
+ def clear_memo_hooks(hook_type = nil); end
143
+
144
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, ttl: T.nilable(Numeric), kwargs: T.untyped, blk: T.proc.returns(T.untyped)).returns(T.untyped) }
145
+ def warm_memo(method_name, *args, ttl: nil, **kwargs, &blk); end
146
+
147
+ sig { params(method_name: T.any(Symbol, String), arg_sets: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
148
+ def memo_preload(method_name, *arg_sets); end
149
+
150
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(T::Hash[T.untyped, T.untyped]) }
151
+ def dump_memo(method_name = nil); end
152
+
153
+ sig { params(snapshot: T::Hash[T.untyped, T.untyped]).void }
154
+ def load_memo(snapshot); end
155
+
156
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, ttl: T.nilable(Numeric), kwargs: T.untyped).returns(T::Boolean) }
157
+ def memo_touch(method_name, *args, ttl: nil, **kwargs); end
158
+
159
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.untyped) }
160
+ def memo_refresh(method_name, *args, **kwargs); end
161
+
162
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.nilable(Float)) }
163
+ def memo_age(method_name, *args, **kwargs); end
164
+
165
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T::Boolean) }
166
+ def memo_stale?(method_name, *args, **kwargs); end
167
+
168
+ sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).void }
169
+ def reset_memo(method_name, *args, **kwargs); end
170
+
171
+ sig { void }
172
+ def reset_all_memos; end
173
+
174
+ sig do
175
+ params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped)
176
+ .returns(T.nilable(T::Hash[Symbol, T.untyped]))
177
+ end
178
+ def memo_inspect(method_name, *args, **kwargs); end
179
+ end
180
+
181
+ module PublicMetricsMethods
182
+ sig { returns(T::Hash[Symbol, T.untyped]) }
183
+ def cache_stats; end
184
+
185
+ sig { params(method_name: T.any(Symbol, String)).returns(T::Hash[Symbol, T.untyped]) }
186
+ def cache_stats_for(method_name); end
187
+
188
+ sig { returns(Float) }
189
+ def cache_hit_rate; end
190
+
191
+ sig { returns(Float) }
192
+ def cache_miss_rate; end
193
+
194
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).void }
195
+ def cache_metrics_reset(method_name = nil); end
196
+ end
197
+
198
+ module PublicCustomKeyMethods
199
+ sig { params(method_name: T.any(Symbol, String), blk: T.proc.params(args: T.untyped).returns(T.untyped)).void }
200
+ def memoize_with_custom_key(method_name, &blk); end
201
+
202
+ sig { params(method_name: T.nilable(T.any(Symbol, String))).void }
203
+ def clear_custom_keys(method_name = nil); end
204
+ end
205
+
206
+ module Adapters
207
+ module StatsD
208
+ METRIC_NAMES = T.let(T.unsafe(nil), T::Hash[Symbol, String])
209
+
210
+ sig { params(client: T.untyped, hook_type: Symbol, cache_key: T.untyped, class_name: T.nilable(String)).void }
211
+ def self.dispatch(client, hook_type, cache_key, class_name); end
212
+ end
213
+
214
+ module OpenTelemetry
215
+ SPAN_NAME = T.let(T.unsafe(nil), String)
216
+
217
+ sig { params(tracer: T.untyped, method_name: T.any(Symbol, String), class_name: T.nilable(String), blk: T.proc.returns(T.untyped)).returns(T.untyped) }
218
+ def self.trace(tracer, method_name, class_name, &blk); end
219
+ end
220
+ end
221
+
222
+ module Rails
223
+ sig { params(instance: T.untyped).void }
224
+ def self.track(instance); end
225
+
226
+ sig { void }
227
+ def self.reset_tracked!; end
228
+
229
+ module RequestScoped
230
+ sig { params(base: Module).void }
231
+ def self.included(base); end
232
+
233
+ sig { void }
234
+ def reset_request_memos; end
235
+ end
236
+
237
+ class Middleware
238
+ sig { params(app: T.untyped).void }
239
+ def initialize(app); end
240
+
241
+ sig { params(env: T.untyped).returns(T.untyped) }
242
+ def call(env); end
243
+ end
244
+ end
245
+ end
data/sig/safe_memoize.rbs CHANGED
@@ -1,5 +1,9 @@
1
1
  module SafeMemoize
2
2
  VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
3
7
  include InstanceMethods
4
8
 
5
9
  type default_memo_key = [Symbol, Array[untyped], Hash[Symbol, untyped]]
@@ -56,15 +60,15 @@ module SafeMemoize
56
60
 
57
61
  def memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> bool
58
62
  def memo_ttl_remaining: (Symbol | String method_name, *untyped args, **untyped kwargs) -> (Float | Integer | nil)
59
- def memo_count: (*untyped method_name) -> Integer
60
- def memo_keys: (*untyped method_name) -> Array[untyped]
61
- def memo_values: (*untyped method_name) -> Array[untyped]
63
+ def memo_count: (?(Symbol | String) method_name) -> Integer
64
+ def memo_keys: (?(Symbol | String) method_name) -> Array[untyped]
65
+ def memo_values: (?(Symbol | String) method_name) -> Array[untyped]
62
66
  def on_memo_expire: { (memo_key cache_key, memo_record record) -> untyped } -> void
63
67
  def on_memo_evict: { (memo_key cache_key, memo_record record) -> untyped } -> void
64
68
  def on_memo_hit: { (memo_key cache_key, memo_record record) -> untyped } -> void
65
69
  def on_memo_miss: { (memo_key cache_key, memo_record record) -> untyped } -> void
66
70
  def on_memo_store: { (memo_key cache_key, memo_record record) -> untyped } -> void
67
- def clear_memo_hooks: (Symbol? hook_type) -> void
71
+ def clear_memo_hooks: (?Symbol hook_type) -> void
68
72
  def warm_memo: (Symbol | String method_name, *untyped args, ?ttl: Numeric?, **untyped kwargs) { () -> untyped } -> untyped
69
73
  def memo_preload: (Symbol | String method_name, *Array[untyped] arg_sets) -> Array[untyped]
70
74
  def dump_memo: (?Symbol | String method_name) -> Hash[memo_key, untyped]
@@ -167,7 +171,7 @@ module SafeMemoize
167
171
 
168
172
  module PublicCustomKeyMethods
169
173
  def memoize_with_custom_key: (Symbol | String method_name) { (*untyped args, **untyped kwargs) -> untyped } -> void
170
- def clear_custom_keys: (Symbol | String? method_name) -> void
174
+ def clear_custom_keys: (?(Symbol | String) method_name) -> void
171
175
  end
172
176
 
173
177
  module LruMethods
@@ -196,6 +200,12 @@ module SafeMemoize
196
200
  end
197
201
 
198
202
  module Adapters
203
+ module StatsD
204
+ METRIC_NAMES: Hash[Symbol, String]
205
+
206
+ def self.dispatch: (untyped client, Symbol hook_type, memo_key cache_key, String? class_name) -> void
207
+ end
208
+
199
209
  module OpenTelemetry
200
210
  SPAN_NAME: String
201
211
  def self.trace: (untyped tracer, Symbol | String method_name, String? class_name) { () -> untyped } -> untyped
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safe_memoize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -41,11 +41,13 @@ extra_rdoc_files: []
41
41
  files:
42
42
  - ".github/workflows/ci.yml"
43
43
  - ".github/workflows/release.yml"
44
+ - ".yardopts"
44
45
  - CHANGELOG.md
45
46
  - LICENSE.txt
46
47
  - README.md
47
48
  - ROADMAP.md
48
49
  - Rakefile
50
+ - UPGRADING.md
49
51
  - benchmarks/README.md
50
52
  - benchmarks/benchmark.rb
51
53
  - codecov.yml
@@ -70,6 +72,7 @@ files:
70
72
  - lib/safe_memoize/rails/request_scoped.rb
71
73
  - lib/safe_memoize/release_tooling.rb
72
74
  - lib/safe_memoize/version.rb
75
+ - rbi/safe_memoize.rbi
73
76
  - sig/safe_memoize.rbs
74
77
  homepage: https://github.com/eclectic-coding/safe_memoize
75
78
  licenses: