This is a Ruby gem that adds transparent caching to ActiveRecord support/lookup tables. It intercepts find_by queries and belongs_to associations to cache small, rarely-changing reference tables (statuses, types, categories) without code changes.
Core principle: Cache entries keyed by unique attribute combinations, auto-invalidated on record changes via after_commit callbacks.
- lib/support_table_cache.rb: Main module with
cache_byDSL, cache configuration, and invalidation logic - lib/support_table_cache/find_by_override.rb: Prepends to model class to intercept
find_bycalls - lib/support_table_cache/relation_override.rb: Prepends to
ActiveRecord::Relationto handle scoped queries (e.g.,where(group: 'x').find_by(name: 'y')) - lib/support_table_cache/associations.rb: Extends
belongs_towithcache_belongs_toto cache foreign key lookups - lib/support_table_cache/memory_cache.rb: In-process cache implementation (use
support_table_cache = :memory)
See ARCHITECTURE.md for detailed flow diagrams showing cache key generation, invalidation, and association caching sequences.
Models use cache_by to declare unique keys that can be cached. Support composite keys and case-insensitivity:
cache_by :name, case_sensitive: false
cache_by [:group, :code]
cache_by :name, where: {deleted_at: nil} # For default scopesCache keys are [ClassName, {attr1: val1, attr2: val2}] arrays with sorted attribute names. Case-insensitive values are downcased before keying.
Uses prepend to wrap ActiveRecord methods (find_by) rather than monkey-patching. This allows super to call original behavior on cache misses or when caching disabled.
- Multi-version testing: Uses Appraisal gem to test against ActiveRecord 5.0-8.0 (see Appraisals)
- Run tests:
bundle exec rspec(default rake task) orbundle exec appraisal rspecfor all versions - Test setup: In-memory SQLite database created in spec/spec_helper.rb with test tables
- Test isolation: Tests wrapped with
SupportTableCache.testing!in RSpecconfig.beforeto prevent cache pollution
Use standardrb for linting. Run standardrb --fix before committing. CI enforces this on ActiveRecord 8.0 matrix entry.
- Include
SupportTableCachein model class - Call
cache_bywith unique key attributes - Optionally set
self.support_table_cache_ttl = 5.minutes - For associations: include
SupportTableCache::Associationsin parent model, thencache_belongs_to :association_name
Automatic via after_commit callback that clears all cache key variations (both old and new attribute values on updates). No manual invalidation needed unless using in-memory cache across processes.
- Use
fetch_byinstead offind_byto raise error if query won't hit cache - Disable caching in block:
Model.disable_cache { ... }or globallySupportTableCache.disable { ... } - Check if caching enabled: inspect
support_table_cache_by_attributesclass attribute
- Running specs locally:
bundle exec rspec(uses Ruby 3.3+ and ActiveRecord 8.0 from Gemfile) - Testing specific AR version:
bundle exec appraisal activerecord_7 rspec - Generating all gemfiles:
bundle exec appraisal generate - Lint before commit:
standardrb --fix - Release: Only from
mainbranch (enforced byRakefilepre-release check)
- Target models: Only for small tables (few hundred rows max)
- Unique keys only:
cache_byattributes must define unique constraints - No runtime scopes: Cannot use
cache_belongs_towith scoped associations (checked at configuration time) - In-memory cache caveat: Per-process, not invalidated across processes—only use for truly static data or with TTL