Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning from v1.0.0 onwards. Prior 0.x releases may include breaking changes between minor versions.
Unreleased
[1.6.0] - 2026-06-02
Added
SafeMemoize::Stores::CircuitBreaker— aStores::Basewrapper that protects any external store adapter with a three-state circuit breaker (:closed→:open→:half_open). When the wrapped store raises onreadorwrite, the error is swallowed: reads returnMISS(triggering the per-instance in-process fallback cache) and writes are no-ops (the return value is unaffected). After a configurable number of consecutive failures (error_threshold:, default 5) the circuit opens and all store calls are bypassed until a probe interval elapses (probe_interval:, default 30 s), at which point a single probe request is let through; success closes the circuit, failure resets the timer. Any successful call in:closedstate resets the consecutive error counter, so transient blips do not accumulate toward the threshold.state— returns:closed,:open, or:half_openopen?—truewhen the circuit is not fully closederror_count— current consecutive error countreset!— manually close the circuit and clear the counterwrapped_store,error_threshold,probe_interval— readers
circuit_breaker:option onmemoize— syntactic sugar that auto-wraps the configuredstore:adapter in aCircuitBreaker. Passtrueto use defaults, or aHashwith:error_thresholdand/or:probe_intervalkeys to customise. RaisesArgumentErrorif no store is configured. Does not double-wrap a store that is already aCircuitBreaker. Accepted bysafe_memoize_optionsas a class-wide default.
[1.5.0] - 2026-06-02
Added
group:option onmemoize— assigns a method to a named invalidation group (memoize :find, group: :database). Groups are stored on the class and survive re-memoization; a method can belong to at most one group at a time (re-memoizing with a different group moves it). Accepts any non-empty Symbol or String. Can be set as a class default viasafe_memoize_options group: :my_group.reset_memo_group(group_name)instance method — clears all per-instance cached entries for every method in the named group in a single call; each evicted entry fires the:on_evicthook. A no-op for unknown groups.reset_shared_memo_group(group_name)class method — the shared-cache equivalent ofreset_memo_group; clears all shared-cache entries for every method in the group that was memoized withshared: true.memo_group_methods(group_name)instance method — returns the array of method names belonging to the given group on the instance's class (empty array for unknown groups).memo_groupsinstance method — returns all group names registered on the instance's class.safe_memo_group_methods(group_name)class method — class-level equivalent ofmemo_group_methods.safe_memo_groupsclass method — class-level equivalent ofmemo_groups.
[1.4.0] - 2026-06-02
Added
safe_memoize_options(**opts)class-level macro — sets default options for every subsequentmemoizecall on the class. Per-call options take precedence; class defaults take precedence over globalSafeMemoize.configuredefaults. Accepts allmemoizeoptions except mode-switch options (shared:,fiber_local:,ractor_safe:,shared_cache:), which must be specified per call. Call with no arguments to clear class-level defaults.copy_on_read: trueoption onmemoize— returns adup(ordeep_dupwhen available, e.g. ActiveRecord objects) of the cached value on every read, preventing callers from mutating shared cached state.niland frozen values are returned as-is. Works across all cache paths (per-instance, LRU, shared, fiber-local, and external store). Incompatible withractor_safe:(ractor-safe values are always frozen; use that guarantee instead). Can be set as a class default viasafe_memoize_options copy_on_read: true.
[1.3.0] - 2026-05-28
Added
SafeMemoize::Extension— mixin for building SafeMemoize extensions. Extend it in any module to get a DSL for declaring custommemoizeoptions and global lifecycle event handlers without monkey-patching SafeMemoize internals.handles_option(name, &processor)— declares a custom keyword argument thatmemoizewill accept; the processor block is called at definition time with(value, method_name, all_extension_options)and must return aHashof standard memoize options to inject (e.g.{cache_bust: ...},{ttl: 60},{namespace: "v2"}).on_cache_event(*event_types, &handler)— registers a global lifecycle handler that fires after every matching event (:on_hit,:on_miss,:on_store,:on_expire,:on_evict) across all memoized methods on all classes; handler receives(klass, method_name, cache_key, record); runs on the main Ractor only.- Duck-type compatible — any object responding to
handled_options,process_memoize_option, anddispatch_cache_eventworks withoutextend SafeMemoize::Extension.
SafeMemoize.register_extension(name, extension)— registers an extension under a symbolic name.SafeMemoize.unregister_extension(name)— removes an extension.SafeMemoize.extensions— returns a snapshot of the registry.SafeMemoize.reset_extensions!— clears the registry (test teardown).SafeMemoize.extension_for_option(option_name)— returns the registered extension that handles the named option, ornil.memoizenow accepts**extension_optionsfor any unknown keyword argument; each key is validated against registered extensions at call time and raisesArgumentErrorif no extension claims it, preserving the existing strict-options behaviour for typos.cache_bust: callableoption onmemoize— automatic cache invalidation driven by a version token. A callable (Proc, lambda, or Symbol naming an instance method) is invoked on the instance at every cache lookup; the returned token is folded into the cache key alongside the normal arguments. When the token changes (e.g. an ActiveRecordupdated_atadvances after asave), the old key no longer matches any entry — the method body is recomputed and stored under the new key without any explicitreset_memocall. Accepts a zero-argument callable invoked viainstance_exec(giving access toself, instance variables, and methods) or aSymbolnaming an instance method. Returns any comparable value as the token: aTime,Integer,String,Array, etc. Old token entries accumulate as stale; pair withttl:or a store adapter's eviction to bound memory. Incompatible withkey:. Composes withnamespace:,ttl:,if:,unless:, andshared_cache:.shared_cache: "name"option onmemoize— routes all reads and writes through a globally-registered namedStores::Baseinstance, enabling cross-class cache sharing. Any number of unrelated classes can share the same backing store by referencing the same name. The store is resolved atmemoizedefinition time viaSafeMemoize.shared_cache("name"), which auto-creates aStores::Memoryinstance on first access; supply a custom adapter (Redis, RailsCache, etc.) by callingSafeMemoize.register_shared_cache("name", store)before any class that references the name is loaded. Incompatible withshared:,store:,fiber_local:,ractor_safe:, andmax_size:; composes naturally withnamespace:,ttl:,if:,unless:, andkey:.SafeMemoize.shared_cache(name)— returns theStores::Baseinstance for the given name, creating a newStores::Memoryif none is registered.SafeMemoize.register_shared_cache(name, store)— registers a customStores::Baseinstance under a name; must be called before any class that uses that name viashared_cache:is loaded.SafeMemoize.clear_shared_cache(name)— callsclearon the named store, evicting all entries. No-op for unregistered names.SafeMemoize.drop_shared_cache(name)— removes the named store from the registry; subsequentshared_cache(name)calls will auto-create a newMemorystore.SafeMemoize.shared_caches— returns a dup of the current registry as aHash{String => Stores::Base}.SafeMemoize.reset_shared_caches!— clears the entire registry; useful in test-suiteafterhooks to prevent state leaking between examples.namespace:option onmemoize— a String prefix scoped to a single method; prepended to the cache key's first element so that entries with different namespaces never collide, even when sharing the same store or the same per-instance hash. Must be a non-empty string without:. Useful for versioning one method independently of its peers..safe_memoize_namespace/.safe_memoize_namespace=— class-level namespace attribute; applies to everymemoizecall on the class that does not specify its ownnamespace:option. Takes precedence over the globalSafeMemoize::Configuration#namespace.SafeMemoize::Configuration#namespace— global namespace prefix applied to everymemoizecall site that has no per-method or class-level namespace set. Set viaSafeMemoize.configure { |c| c.namespace = "v1" }. Useful for versioned deployments and multi-tenant setups. Cleared byreset_configuration!.Resolution priority: per-method
namespace:> class.safe_memoize_namespace> globalConfiguration#namespace.All introspection methods (
memoized?,memo_count,memo_keys,memo_values,reset_memo,reset_all_memos,dump_memo,cache_stats_for,cache_metrics_reset, shared-cache equivalents, etc.) accept the bare method name regardless of which namespace tier is active; the:methodfield in projections always returns the bare method name.Ractor-safe: namespace resolution uses
instance_variable_get(read-only) so worker Ractors can callcompute_cache_keywithout triggering unshareable class-level ivar initialization.
[1.2.0] - 2026-05-27
Added
SafeMemoize::Adapters::ConcurrentRuby— optional store adapter backed byconcurrent-ruby; usesConcurrent::Mapas the backing hash andConcurrent::ReentrantReadWriteLockto allow multiple readers to proceed in parallel while writers still get exclusive access; reduces lock contention on hot read paths compared to the defaultMutex-backedStores::Memory;concurrent-rubyis a soft dependency — aLoadErrorwith an actionable message is raised at instantiation if the gem is not available.safe_memoize_store=/.safe_memoize_store— class-level attribute for setting a default store on an individual class without touching the globalSafeMemoize.configuredefault; takes precedence overSafeMemoize.configuration.default_storebut is overridden by a per-methodstore:argument; accepts anySafeMemoize::Stores::Baseinstance ornil; raisesArgumentErrorfor invalid valuesractor_safe: trueoption onmemoize— replaces theMutex-backed class-level shared cache with a supervisorRactorthat owns the mutable cache hash; all reads and writes are serialised through message passing so the cache is safe to use from multiple Ractors; requiresshared: true; cached values are deep-frozen viaRactor.make_shareable; the memoize wrapper Proc is frozen withRactor.make_shareablebefore being passed todefine_methodso that classes usingractor_safe: truecan be passed directly into worker Ractors; incompatible withif:,unless:,max_size:,ttl_refresh:,key:, andstore:(raisesArgumentError);ttl:is supported.reset_ractor_memo(method_name, *args, **kwargs)— class method to clear one or all entries from the Ractor-safe shared cache for a given method.reset_all_ractor_memos— class method to clear the entire Ractor-safe shared cache for this class.ractor_memoized?(method_name, *args, **kwargs)— returnstrueif a live entry exists in the Ractor-safe shared cache for the given call signature.ractor_memo_count(method_name = nil)— returns the number of live entries in the Ractor-safe shared cache; scoped to one method when a name is givenfiber_local: trueoption onmemoize— stores results inFiber[:__safe_memoize__]rather than instance variables, giving each fiber its own isolated cache that is automatically discarded when the fiber terminates; noMutexis acquired because fibers are cooperative; a per-fiber ownership sentinel ensures inherited storage from parent fibers is replaced with a fresh isolated store on first write; supports all standard options (ttl:,ttl_refresh:,max_size:,if:,unless:,key:); incompatible withshared:andstore:(raisesArgumentError)#fiber_local_memoized?(method_name, *args, **kwargs)— returnstrueif the given call is currently cached in the current fiber's store#reset_fiber_memo(method_name, *args, **kwargs)— clears one or all fiber-local cached entries for a method in the current fiber#reset_all_fiber_memos— clears all fiber-local cached entries for this instance in the current fiber
Fixed
call_memo_hooksno longer raisesRactor::IsolationErrorwhen called from a worker Ractor —SafeMemoize.configuration(a module-level ivar) is now accessed only from the main Ractor;ActiveSupport::NotificationsandStatsDdispatch are silently skipped from worker Ractors; hook-error handling falls back towarnrather than reading the configuration handler- CI coverage ordering — ractor specs now run last (after all other specs) so that Ractor background threads cannot disrupt Ruby's Coverage counters while collecting coverage for non-Ractor code; previously only store specs were ordered first
- Codecov reporting accuracy — switched SimpleCov output from
.resultset.json(internal format, misread by Codecov as ~85%) tocoverage/coverage.jsonviasimplecov_json_formatter; CI now uploads the correct file - CI coverage ordering —
bundle exec rspecran files alphabetically, causingractor_spec.rbto execute beforespec/stores/, disrupting Ruby's Coverage counters and dropping reported coverage to ~96%; CI now usesbundle exec rake spec, which enforces the store-first ordering already documented in the Rakefile
[1.1.0] - 2026-05-22
Added
SafeMemoize::Stores::Base— abstract adapter base class defining the cache store contract:read(key),write(key, value, expires_in: nil),delete(key),clear,keys, andexist?(key); a frozenMISSsentinel onBasedistinguishes cache misses from cachednilorfalsevalues;exist?has a default implementation that delegates toreadSafeMemoize::Stores::Memory— built-in in-process store that wraps a plainHashbehind aMutex; supports per-entry TTL viaexpires_in:with lazy expiry on read; serves as both the default store and the reference implementation for custom adaptersConfiguration#default_store— set viaSafeMemoize.configure { |c| c.default_store = MyStore.new }to route everymemoizecall that has no explicitstore:through the given adapter; methods usingmax_size:orshared:are incompatible and fall back silently to the per-instance hash; an invalid value raisesArgumentErroratmemoizetime; cleared byreset_configuration!SafeMemoize::Stores::RailsCache— opt-in adapter (require "safe_memoize/stores/rails_cache") wrapping anyActiveSupport::Cache::Store(includingRails.cache); values are wrapped in a sentinel envelope so cachednil/falseare distinguished from a cache miss; TTL forwarded asexpires_in:for native store expiry;clearusesdelete_matchedscoped to the namespace;keysreturns[](AS::Cache has no enumeration API)SafeMemoize::Stores::Redis— opt-in adapter (require "safe_memoize/stores/redis") backed by any Redis-compatible client responding to#get,#set,#del, and#scan_each; values and keys are serialized with Marshal +pack("m0"); TTL is forwarded asPX(milliseconds, rounded up) for sub-second precision;clearusesSCANto avoid blocking; all entries are namespaced (default:"safe_memoize") so multiple stores or applications can share one Redis instancestore:option onmemoize— accepts anyStores::Basesubclass instance; routes all reads and writes through the adapter'sread/writeinterface; the store is shared across all instances of the class;ttl:is forwarded asexpires_in:towrite,ttl_refresh:re-writes on every hit, andif:/unless:conditional storage is enforced at the SafeMemoize layer; raisesArgumentErrorif combined withmax_size:(LRU belongs in the adapter) orshared:
Changed
- Test suite achieves 100% line coverage —
spec_helpernow requires opt-in store adapters (Stores::Redis,Stores::RailsCache) afterSimpleCov.startso Coverage tracks them;Rakefilerunsspec/stores/before other specs to prevent Ruby 3.4 Coverage counter disruption from Ractor/concurrency tests;version.rbexcluded from coverage reporting store:type guard inClassMethods#memoizecollapsed to an inline guard clause so Ruby's Coverage module counts the raise correctly- Hook-error isolation tests (
concurrency_spec,hooks_spec) now configureon_hook_error = ->(*) {}to silence expected stderr warnings rather than leaking them into test output; StatsD error-resilience test asserts on the emitted warning withexpect { }.to output(...).to_stderr
[1.0.0] - 2026-05-22
Added
- Ractor compatibility audit —
spec/ractor_spec.rbdocuments the specific failure modes (non-shareable closures indefine_methodblocks,Ractor::IsolationErroronSafeMemoize.configuration); README section explains the limitation and the Thread-based workaround - Semantic versioning guarantee — README
## Public API and versioning guaranteesection enumerates every public constant, method, option key, andConfigurationattribute covered by semver from v1.0.0 onwards; opt-in extensions (SafeMemoize::Rails,SafeMemoize::Adapters::*) are explicitly called out as not yet covered until their owning milestone ships - Full API reference — YARD documentation added to all public methods, classes, and modules;
SafeMemoize::Adapters::StatsDandSafeMemoize::Adapters::OpenTelemetryfully documented with usage examples; internal modules marked@api private;.yardoptsandrake doctask added;gem "yard"added as a development dependency - Deprecation sweep — pre-v1.0.0 API consistency audit:
memoized?,memo_ttl_remaining,memo_touch,memo_age,memo_stale?now usecompute_cache_keyinstead ofsafe_memo_cache_keyso they correctly resolve entries stored with a custom key (instance-levelmemoize_with_custom_keyor class-levelkey:);memo_matcher_for(used byreset_memoandmemo_refresh) receives the same fix;SafeMemoize::Erroradded to the public API guarantee table and to RBS + Sorbet signatures; RBS and.rbiwarm_memoblock annotation corrected back to mandatory (was incorrectly marked optional in v0.9.0 signatures) - Ruby version policy — README
## Ruby version supportsection formalises the supported version window (Ruby ≥ 3.3; current stable plus two previous non-EOL minors), the cadence for dropping EOL versions (minor release only, never a patch), and a history table of dropped versions; CI matrix documents covered versions with their EOL dates - Complete RBS + Sorbet signatures —
sig/safe_memoize.rbscorrected:SafeMemoize::Adapters::StatsDadded;memo_count,memo_keys,memo_valuesfixed from rest-arg to proper optional single arg;clear_memo_hooksandclear_custom_keysoptional-arg annotations corrected;warm_memoblock marked optional; newrbi/safe_memoize.rbiships Sorbet stubs covering the full public API, allConfigurationattributes, adapters, and opt-in Rails helpers - Upgrade guide —
UPGRADING.mddocuments every breaking change introduced across the 0.x series, with before/after code examples and migration steps for each; covers Ruby 3.2 removal, TTL clock change,memo_keys/memo_valuesshape change,memoizedefinition-time raise, argument mutation fix, hook exception isolation, and the two custom-key introspection fixes landing in v1.0.0
[0.9.0] - 2026-05-22
Added
ActiveSupport::Notificationsintegration — opt-in viaSafeMemoize.configure { |c| c.active_support_notifications = true }; emitscache_hit.safe_memoize,cache_miss.safe_memoize,cache_evict.safe_memoize,cache_expire.safe_memoize, andcache_store.safe_memoizeevents; each payload includes:method,:key, and:class; zero overhead when ActiveSupport is not loadedSafeMemoize::Adapters::StatsD— thin optional adapter that routes lifecycle events to any StatsD client viaSafeMemoize.configure { |c| c.statsd_client = my_client }; emitssafe_memoize.hit,safe_memoize.miss,safe_memoize.evict,safe_memoize.expire, andsafe_memoize.storewithmethod:andclass:tags; client errors are rescued and warned rather than raised- Formal benchmark suite (
benchmarks/benchmark.rb) — six scenarios covering zero-arg cache hit/miss, with-argument hit, fast vs locked path, shared vs instance cache, and concurrent throughput under 8-thread contention; optional comparisons againstmemeryandmemo_wise; run withbundle exec ruby benchmarks/benchmark.rb - Concurrency stress test suite (
spec/concurrency_spec.rb) — 18 barrier-synchronized examples hammering the fast path, locked path, and shared cache under 30 concurrent threads; covers exactly-once computation, LRU size invariant, hook count integrity, metric accuracy, TTL pruning, and deadlock detection (10-second timeout per run) SafeMemoize::Adapters::OpenTelemetry— optional adapter that wraps each cache-miss computation in an OpenTelemetry span; configure viaSafeMemoize.configure { |c| c.opentelemetry_tracer = OpenTelemetry.tracer_provider.tracer("safe_memoize") }; span name is"safe_memoize.compute"with attributessafe_memoize.method,safe_memoize.class, andsafe_memoize.cache_hit; falls back to untraced execution when the tracer is absent or does not respond toin_spanSafeMemoize::Rails— opt-in request-scope helpers (require "safe_memoize/rails"):SafeMemoize::Rails::RequestScopedconcern auto-registersafter_action :reset_all_memosin controllers and exposesreset_request_memoselsewhere;SafeMemoize::Rails::MiddlewareRack middleware resets all thread-tracked instances (SafeMemoize::Rails.track(self)) at the end of each request even on error
[0.8.0] - 2026-05-21
Added
- Raise
ArgumentErrorat definition time whenmemoizeis called on a method that does not exist on the class — previously the error only surfaced at runtime whensuperhad nothing to call - Key serialization safety: argument arrays, hashes, and strings are deep-frozen into an independent copy when the cache key is built, so callers that mutate their arguments after a call can no longer corrupt or miss the cached entry
memo_inspect— single-entry deep-inspection helper returning all metadata for one cached call in one mutex-held read:cached,value,hits,misses,ttl_remaining,age,custom_key, andlru_position; returnsnilwhen the entry is not cached- Deprecation infrastructure:
SafeMemoize.deprecate(subject, message:, horizon:)emits a structured[SafeMemoize]warning to stderr by default; configurable viaSafeMemoize.configure { |c| c.on_deprecation = ->(msg) { ... } }to raise, log, or collect warnings memoize_all only:— symmetric counterpart toexcept:; explicitly lists the methods to memoize and skips all others; raisesArgumentErrorwhen bothonly:andexcept:are given- Hook error isolation: exceptions raised inside lifecycle hooks no longer propagate to the caller; by default a
[SafeMemoize] Hook error in <type>: <message>warning is emitted to stderr; configurable viaSafeMemoize.configure { |c| c.on_hook_error = ->(error, hook_type, cache_key) { ... } }to raise, log, or silence
0.7.0 - 2026-05-18
Added
memo_preloadto batch-warm multiple cache entries in one call —obj.memo_preload(:find, [1], [2], [3])calls the memoized method for each arg set, caches all results, and returns them in input orderon_memo_storehook that fires whenever a value is written to the cache (miss,warm_memo, orload_memo); completes the full lifecycle hook set alongsideon_hit,on_miss,on_expire, andon_evictSafeMemoize.configurefor global default options —default_ttlanddefault_max_sizeapply to all subsequently memoized methods; per-call options override the global defaultsSafeMemoize.reset_configuration!to restore all global defaults tonilmemo_touchto reset the expiry clock on a cached entry without recomputing — accepts an optionalttl:override; returnstrueon success,falseif the entry is not cached or already expiredshared_memo_ageclass method to inspect how long ago a shared entry was cachedshared_memo_stale?class method to check whether a shared entry's TTL has elapsedkey:option onmemoizefor class-level cache key generation — calls whose key block returns the same value share one cache entry; instance-levelmemoize_with_custom_keystill takes prioritymemo_refreshto force-recompute a cached entry and store the new value in one callmemo_ageto return how many seconds ago an entry was cached (nilif not cached or expired)memo_stale?to check whether a cached entry exists but its TTL has elapsed
Changed
cache_metrics_resetnow accepts an optional method name to clear stats for a single method only; calling without arguments still clears all metricsshared:support inmemoize_allis now tested and documented (was already functional via**optionspassthrough)- RBS type signatures updated for all new methods and the
Configurationclass
0.6.3 - 2026-05-18
Changed
- Upgrade
softprops/action-gh-releasefrom v2 to v3 to resolve Node.js 20 deprecation warning in release workflow
0.6.2 - 2026-05-18
Added
- 100% line coverage across all lib files — added tests for edge cases in
CacheRecordMethods,CacheStoreMethods,InspectionMethods, andReleaseTooling; added SimpleCov filter to exclude/specfrom coverage reporting
0.6.1 - 2026-05-17
Changed
- Refactored
cache_stats/cache_stats_forto share aggregation logic via private helpers
Fixed
memo_keysandmemo_valuesshowedargs: custom_key, kwargs: nilfor methods usingmemoize_with_custom_key— now correctly surfaces ascustom_key:
0.6.0 - 2026-05-17
Added
ttl:option onwarm_memoso warmed entries can be given an expirymax_size:support forshared: truememoization (class-level LRU eviction)ttl_refresh: trueoption onmemoizefor sliding window TTL — resets the expiry clock on every cache hit so the entry only expires after a full TTL of inactivityinclude_protected:andinclude_private:options onmemoize_allmemo_ttl_remainingfor TTL introspection — returns seconds until expiry,nilfor no TTL,0for uncached or expired
Fixed
- TTL clock started at
memoizedefinition time instead of at first method call - Metrics key silently dropped kwargs, causing methods that differ only in kwargs to share a metrics bucket
- Stale LRU references remained in the order list after expired entries were pruned
0.5.0 - 2026-05-17
Removed
- Support for Ruby 3.2 (EOL); minimum required version is now Ruby 3.3
0.4.0 - 2026-05-17
Added
warm_memo,dump_memo, andload_memofor cache warm-up and persistence — pre-populate entries without calling the method, export live entries as a plain hash, and restore from a snapshotshared: trueoption onmemoizeto store results on the class instead of per-instance — includesreset_shared_memo,reset_all_shared_memos,shared_memoized?, andshared_memo_count; supportsttl:,if:, andunless:memoize_allto memoize every public method defined on the class in one call — accepts allmemoizeoptions plusexcept:to skip specific methodson_memo_misshook that fires on every cache miss, completing the full lifecycle hook set
0.3.0 - 2026-05-15
Added
on_memo_hithook that fires on every cache hit- Conditional memoization via
if:andunless:predicates onmemoize— uncached calls recompute on every invocation until the condition is satisfied; composes withttl:,max_size:, and hooks - LRU cache size limit via
max_size:onmemoize— evicts the least-recently-used entry when the limit is reached; cache hits promote entries; fireson_evict; thread-safe
0.2.0 - 2026-05-14
Added
- Optional TTL expiration for memoized entries
on_memo_expireandon_memo_evictlifecycle hooks;clear_memo_hooksto remove registered hooks- Cache metrics:
cache_stats,cache_stats_for,cache_hit_rate,cache_miss_rate, andcache_metrics_reset - Custom cache key generation via
memoize_with_custom_keyandclear_custom_keys
0.1.2 - 2026-05-13
Added
- Method visibility preservation (public, protected, private) for memoized methods
- Targeted
reset_memo— clear one cached argument combination or all entries for a method memoized?helper to check whether a specific call is cachedmemo_count,memo_keys, andmemo_valueshelpers for cache introspection
0.1.1 - 2026-05-13
Added
- Automated release tooling (
bin/release) and GitHub Actions workflow for RubyGems publishing and GitHub releases
0.1.0 - 2026-02-26
Added
- Initial release