Module: SafeMemoize

Includes:
InstanceMethods
Defined in:
lib/safe_memoize.rb,
lib/safe_memoize/rails.rb,
lib/safe_memoize/version.rb,
lib/safe_memoize/extension.rb,
lib/safe_memoize/lru_methods.rb,
lib/safe_memoize/stores/base.rb,
lib/safe_memoize/stores/redis.rb,
lib/safe_memoize/class_methods.rb,
lib/safe_memoize/configuration.rb,
lib/safe_memoize/hooks_methods.rb,
lib/safe_memoize/stores/memory.rb,
lib/safe_memoize/public_methods.rb,
lib/safe_memoize/adapters/statsd.rb,
lib/safe_memoize/release_tooling.rb,
lib/safe_memoize/instance_methods.rb,
lib/safe_memoize/rails/middleware.rb,
lib/safe_memoize/custom_key_methods.rb,
lib/safe_memoize/inspection_methods.rb,
lib/safe_memoize/stores/rails_cache.rb,
lib/safe_memoize/cache_store_methods.rb,
lib/safe_memoize/fiber_local_methods.rb,
lib/safe_memoize/cache_record_methods.rb,
lib/safe_memoize/rails/request_scoped.rb,
lib/safe_memoize/cache_metrics_methods.rb,
lib/safe_memoize/ractor_shared_methods.rb,
lib/safe_memoize/adapters/opentelemetry.rb,
lib/safe_memoize/public_metrics_methods.rb,
lib/safe_memoize/adapters/concurrent_ruby.rb,
lib/safe_memoize/public_custom_key_methods.rb

Overview

Thread-safe memoization for Ruby that correctly handles +nil+ and +false+ values.

Prepend this module into any class, then call ClassMethods#memoize to wrap instance methods with a per-instance cache backed by a +Mutex+.

Examples:

Basic usage

class UserService
  prepend SafeMemoize

  def current_user
    User.find_by(session_id: session_id)
  end
  memoize :current_user
end

With TTL and LRU cap

class ApiClient
  prepend SafeMemoize

  def fetch(id)
    http_get("/items/#{id}")
  end
  memoize :fetch, ttl: 60, max_size: 500
end

See Also:

Defined Under Namespace

Modules: Adapters, CacheMetricsMethods, CacheRecordMethods, CacheStoreMethods, ClassMethods, CustomKeyMethods, Extension, FiberLocalMethods, HooksMethods, InspectionMethods, InstanceMethods, LruMethods, PublicCustomKeyMethods, PublicMethods, PublicMetricsMethods, RactorSharedMethods, Rails, ReleaseTooling, Stores Classes: Configuration, Error

Constant Summary collapse

UNSET =
Object.new.freeze
SHARED_CACHE_REGISTRY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

{}
SHARED_CACHE_MUTEX =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Mutex.new
EXTENSION_REGISTRY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

{}
EXTENSION_MUTEX =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Mutex.new
VERSION =

The current gem version string.

"1.4.0"

Constants included from FiberLocalMethods

FiberLocalMethods::FIBER_STORE_KEY

Constants included from HooksMethods

HooksMethods::NOTIFICATION_EVENT_NAMES

Class Method Summary collapse

Methods included from FiberLocalMethods

#fiber_local_memoized?, #reset_all_fiber_memos, #reset_fiber_memo

Methods included from PublicCustomKeyMethods

#clear_custom_keys, #memoize_with_custom_key

Methods included from PublicMetricsMethods

#cache_hit_rate, #cache_metrics_reset, #cache_miss_rate, #cache_stats, #cache_stats_for

Methods included from PublicMethods

#clear_memo_hooks, #dump_memo, #load_memo, #memo_age, #memo_count, #memo_inspect, #memo_keys, #memo_preload, #memo_refresh, #memo_stale?, #memo_touch, #memo_ttl_remaining, #memo_values, #memoized?, #on_memo_evict, #on_memo_expire, #on_memo_hit, #on_memo_miss, #on_memo_store, #reset_all_memos, #reset_memo, #warm_memo

Class Method Details

.clear_shared_cache(name) ⇒ void

This method returns an undefined value.

Clears all entries in the named shared cache without removing it from the registry. A no-op when no cache is registered under +name+.

Parameters:

  • name (String)


155
156
157
# File 'lib/safe_memoize.rb', line 155

def self.clear_shared_cache(name)
  SHARED_CACHE_MUTEX.synchronize { SHARED_CACHE_REGISTRY[name]&.clear }
end

.configurationConfiguration

Returns the global Configuration instance, creating it on first access.

Returns:



95
96
97
# File 'lib/safe_memoize.rb', line 95

def self.configuration
  @configuration ||= Configuration.new
end

.configure {|config| ... } ⇒ void

This method returns an undefined value.

Yields the global Configuration object for mutation.

Examples:

SafeMemoize.configure do |c|
  c.default_ttl = 300
end

Yields:

Yield Parameters:



88
89
90
# File 'lib/safe_memoize.rb', line 88

def self.configure
  yield configuration
end

.deprecate(subject, message:, horizon:) ⇒ void

This method returns an undefined value.

Emits a structured deprecation warning through the configured handler.

Parameters:

  • subject (String)

    short identifier of the deprecated symbol

  • message (String)

    migration instructions

  • horizon (String)

    version when the symbol will be removed (e.g. +"v2.0.0"+)



114
115
116
117
118
# File 'lib/safe_memoize.rb', line 114

def self.deprecate(subject, message:, horizon:)
  text = "[SafeMemoize] #{subject} is deprecated and will be removed in #{horizon}. #{message}"
  handler = configuration.on_deprecation
  handler ? handler.call(text) : warn(text)
end

.dispatch_extension_events(event_type, klass, method_name, cache_key, record) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Dispatches a cache lifecycle event to all registered extensions that respond to +dispatch_cache_event+.



240
241
242
243
244
245
# File 'lib/safe_memoize.rb', line 240

def self.dispatch_extension_events(event_type, klass, method_name, cache_key, record)
  exts = EXTENSION_MUTEX.synchronize { EXTENSION_REGISTRY.values.dup }
  exts.each do |ext|
    ext.dispatch_cache_event(event_type, klass, method_name, cache_key, record) if ext.respond_to?(:dispatch_cache_event)
  end
end

.drop_shared_cache(name) ⇒ Stores::Base?

Removes the named shared cache from the registry entirely. Subsequent +shared_cache(name)+ calls will create a fresh store.

Parameters:

  • name (String)

Returns:

  • (Stores::Base, nil)

    the removed store, or +nil+ if not present



164
165
166
# File 'lib/safe_memoize.rb', line 164

def self.drop_shared_cache(name)
  SHARED_CACHE_MUTEX.synchronize { SHARED_CACHE_REGISTRY.delete(name) }
end

.extension_for_option(option_name) ⇒ Object?

Returns the first registered extension that declares it handles +option_name+, or +nil+ if none does.

Parameters:

  • option_name (Symbol)

Returns:

  • (Object, nil)


227
228
229
230
231
232
233
234
# File 'lib/safe_memoize.rb', line 227

def self.extension_for_option(option_name)
  sym = option_name.to_sym
  EXTENSION_MUTEX.synchronize do
    EXTENSION_REGISTRY.values.find do |ext|
      ext.respond_to?(:handled_options) && ext.handled_options.include?(sym)
    end
  end
end

.extensionsHash{Symbol => Object}

Returns a snapshot of all registered extensions as a +Hash+.

Returns:

  • (Hash{Symbol => Object})


209
210
211
# File 'lib/safe_memoize.rb', line 209

def self.extensions
  EXTENSION_MUTEX.synchronize { EXTENSION_REGISTRY.dup }
end

.prepended(base) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



74
75
76
# File 'lib/safe_memoize.rb', line 74

def self.prepended(base)
  base.extend(ClassMethods)
end

.register_extension(name, extension) ⇒ Object

Registers an extension under +name+.

An extension is any Ruby object that optionally responds to the Extension interface: SafeMemoize::Extension#handled_options, SafeMemoize::Extension#process_memoize_option, and SafeMemoize::Extension#dispatch_cache_event. Use Extension as a mixin to get a convenient DSL for defining these.

Parameters:

  • name (Symbol, String)

    a unique identifier for this extension

  • extension (Object)

    the extension object or module

Returns:

  • (Object)

    the registered extension



194
195
196
# File 'lib/safe_memoize.rb', line 194

def self.register_extension(name, extension)
  EXTENSION_MUTEX.synchronize { EXTENSION_REGISTRY[name.to_sym] = extension }
end

.register_shared_cache(name, store) ⇒ Stores::Base

Registers a custom store under +name+, replacing any existing entry.

Must be called before any class that references +name+ via +shared_cache:+ is loaded, because the store is captured at +memoize+ definition time.

Parameters:

Returns:

Raises:



143
144
145
146
147
148
# File 'lib/safe_memoize.rb', line 143

def self.register_shared_cache(name, store)
  unless store.is_a?(Stores::Base)
    raise ArgumentError, "store must be a SafeMemoize::Stores::Base instance (got #{store.class})"
  end
  SHARED_CACHE_MUTEX.synchronize { SHARED_CACHE_REGISTRY[name] = store }
end

.reset_configuration!Configuration

Resets the global configuration to all defaults.

Useful in test suites to prevent configuration leaking between examples.

Returns:



104
105
106
# File 'lib/safe_memoize.rb', line 104

def self.reset_configuration!
  @configuration = Configuration.new
end

.reset_extensions!void

This method returns an undefined value.

Removes all registered extensions.

Useful in test suite +after+ hooks to prevent state leaking between examples.



218
219
220
# File 'lib/safe_memoize.rb', line 218

def self.reset_extensions!
  EXTENSION_MUTEX.synchronize { EXTENSION_REGISTRY.clear }
end

.reset_shared_caches!void

This method returns an undefined value.

Removes all named shared caches from the registry.

Useful in test suite +after+ hooks to prevent state leaking between examples.



180
181
182
# File 'lib/safe_memoize.rb', line 180

def self.reset_shared_caches!
  SHARED_CACHE_MUTEX.synchronize { SHARED_CACHE_REGISTRY.clear }
end

.shared_cache(name) ⇒ Stores::Base

Returns the named shared cache store, creating a new in-process SafeMemoize::Stores::Memory instance if one has not been registered under +name+.

Use register_shared_cache to supply a custom adapter (e.g. Redis) before any class that references the same name is loaded.

Parameters:

  • name (String)

    the logical cache name

Returns:



128
129
130
131
132
# File 'lib/safe_memoize.rb', line 128

def self.shared_cache(name)
  SHARED_CACHE_MUTEX.synchronize do
    SHARED_CACHE_REGISTRY[name] ||= Stores::Memory.new
  end
end

.shared_cachesHash{String => Stores::Base}

Returns a snapshot of the current registry as a plain +Hash+.

Returns:



171
172
173
# File 'lib/safe_memoize.rb', line 171

def self.shared_caches
  SHARED_CACHE_MUTEX.synchronize { SHARED_CACHE_REGISTRY.dup }
end

.unregister_extension(name) ⇒ Object?

Removes an extension from the registry.

Parameters:

  • name (Symbol, String)

Returns:

  • (Object, nil)

    the removed extension, or +nil+ if not present



202
203
204
# File 'lib/safe_memoize.rb', line 202

def self.unregister_extension(name)
  EXTENSION_MUTEX.synchronize { EXTENSION_REGISTRY.delete(name.to_sym) }
end