Class: Lyra::Projections::CachedProjection
- Inherits:
-
Object
- Object
- Lyra::Projections::CachedProjection
- Defined in:
- lib/lyra/projections/cached_projection.rb
Overview
Cached projection layer using Solid Cache (or any Rails.cache backend).
In disabled projection mode, this caches reconstructed model state from events. Provides fast reads while keeping events as the source of truth.
Cache Strategy:
- Individual records: cached by model class + id
- Collections: cached by query fingerprint
- Invalidation: on event publish for affected streams
Usage:
CachedProjection.find(User, 123)
CachedProjection.where(User, status: "active")
CachedProjection.invalidate(User, 123)
Constant Summary collapse
- CACHE_VERSION =
Cache configuration
1- DEFAULT_EXPIRES_IN =
1.hour
Class Method Summary collapse
-
.all(model_class) ⇒ Array<Hash>
Get all records (cached, use with caution on large datasets).
-
.count(model_class, conditions = {}) ⇒ Integer
Count records (cached).
-
.exists?(model_class, id) ⇒ Boolean
Check if a record exists (cached).
-
.find(model_class, id, force: false) ⇒ Hash?
Find a single record by ID (cached).
-
.find_by(model_class, attributes) ⇒ Hash?
Find a record by attributes (cached).
-
.invalidate(model_class, id) ⇒ Object
Invalidate cache for a specific record.
-
.invalidate_all(model_class) ⇒ Object
Invalidate all caches for a model class.
-
.warm(model_class, id) ⇒ Object
Warm the cache for a record (call after event is stored).
-
.where(model_class, conditions) ⇒ Array<Hash>
Query records with conditions (cached).
Class Method Details
.all(model_class) ⇒ Array<Hash>
Get all records (cached, use with caution on large datasets)
70 71 72 73 74 75 76 |
# File 'lib/lyra/projections/cached_projection.rb', line 70 def all(model_class) cache_key = collection_cache_key(model_class, :all) cache_fetch(cache_key, expires_in: 5.minutes) do build_all_from_events(model_class) end end |
.count(model_class, conditions = {}) ⇒ Integer
Count records (cached)
96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/lyra/projections/cached_projection.rb', line 96 def count(model_class, conditions = {}) if conditions.empty? cache_key = collection_cache_key(model_class, :count) else cache_key = query_cache_key(model_class, :count, conditions) end cache_fetch(cache_key, expires_in: 1.minute) do build_count_from_events(model_class, conditions) end end |
.exists?(model_class, id) ⇒ Boolean
Check if a record exists (cached)
113 114 115 116 |
# File 'lib/lyra/projections/cached_projection.rb', line 113 def exists?(model_class, id) # Use find - if it returns data, it exists find(model_class, id).present? end |
.find(model_class, id, force: false) ⇒ Hash?
Find a single record by ID (cached)
32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/lyra/projections/cached_projection.rb', line 32 def find(model_class, id, force: false) cache_key = record_cache_key(model_class, id) if force result = build_from_events(model_class, id) cache_write(cache_key, result) if result result else cache_fetch(cache_key) do build_from_events(model_class, id) end end end |
.find_by(model_class, attributes) ⇒ Hash?
Find a record by attributes (cached)
51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/lyra/projections/cached_projection.rb', line 51 def find_by(model_class, attributes) # Optimization: if looking up by :id only, use the fast find() path # which uses single-stream lookup instead of scanning all events if attributes.size == 1 && (attributes.key?(:id) || attributes.key?("id")) id = attributes[:id] || attributes["id"] return find(model_class, id) end cache_key = query_cache_key(model_class, :find_by, attributes) cache_fetch(cache_key, expires_in: 5.minutes) do build_find_by_from_events(model_class, attributes) end end |
.invalidate(model_class, id) ⇒ Object
Invalidate cache for a specific record
122 123 124 125 126 127 128 |
# File 'lib/lyra/projections/cached_projection.rb', line 122 def invalidate(model_class, id) # Invalidate individual record cache cache_delete(record_cache_key(model_class, id)) # Invalidate collection caches (they may contain this record) invalidate_collection_caches(model_class) end |
.invalidate_all(model_class) ⇒ Object
Invalidate all caches for a model class
133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/lyra/projections/cached_projection.rb', line 133 def invalidate_all(model_class) # Use cache key prefix deletion if available prefix = "#{cache_namespace}/#{model_class.name}/" if cache_store.respond_to?(:delete_matched) cache_store.delete_matched("#{prefix}*") else # Fallback: invalidate known collection keys invalidate_collection_caches(model_class) end end |
.warm(model_class, id) ⇒ Object
Warm the cache for a record (call after event is stored)
Also invalidates collection caches (all, count) to ensure accurate counts after creates/updates.
152 153 154 155 156 157 158 159 |
# File 'lib/lyra/projections/cached_projection.rb', line 152 def warm(model_class, id) # Rebuild the individual record cache find(model_class, id, force: true) # Invalidate collection caches so count/all queries are rebuilt # This ensures assert_difference "Model.count" works correctly invalidate_collection_caches(model_class) end |
.where(model_class, conditions) ⇒ Array<Hash>
Query records with conditions (cached)
83 84 85 86 87 88 89 |
# File 'lib/lyra/projections/cached_projection.rb', line 83 def where(model_class, conditions) cache_key = query_cache_key(model_class, :where, conditions) cache_fetch(cache_key, expires_in: 5.minutes) do build_where_from_events(model_class, conditions) end end |