Skip to content

Commit f7f6cba

Browse files
agrbergclaude
andcommitted
Support per-class cache adapter configuration
Previously all classes shared a single global Cacheable.cache_adapter. Now each class that includes Cacheable can set its own adapter via MyClass.cache_adapter=, falling back to the global adapter when not configured. Changes: - Extend including classes with CacheAdapter - CacheAdapter#cache_adapter falls back to Cacheable.cache_adapter when no class-level adapter is set - Generated methods resolve adapter from the class instead of referencing Cacheable directly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8c488bf commit f7f6cba

5 files changed

Lines changed: 60 additions & 6 deletions

File tree

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,26 @@ If your cache backend supports options, you can pass them as the `cache_options:
254254
cacheable :with_options, cache_options: {expires_in: 3_600}
255255
```
256256

257+
### Per-Class Cache Adapter
258+
259+
By default, all classes use the global adapter set via `Cacheable.cache_adapter`. If you need a specific class to use a different cache backend, you can set one directly on the class:
260+
261+
```ruby
262+
class FrequentlyAccessedModel
263+
include Cacheable
264+
265+
self.cache_adapter = MyFasterCache.new
266+
267+
cacheable :expensive_lookup
268+
269+
def expensive_lookup
270+
# ...
271+
end
272+
end
273+
```
274+
275+
The class-level adapter takes precedence over the global adapter. Classes without their own adapter fall back to `Cacheable.cache_adapter` as usual.
276+
257277
### Flexible Options
258278

259279
You can use the same options with multiple cache methods or limit them only to specific methods:

lib/cacheable.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module Cacheable
3232
extend CacheAdapter
3333

3434
def self.included(base)
35+
base.extend(Cacheable::CacheAdapter)
3536
base.extend(Cacheable::MethodGenerator)
3637

3738
interceptor_name = base.send(:method_interceptor_module_name)

lib/cacheable/cache_adapter.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ module CacheAdapter
77

88
def self.extended(base)
99
base.instance_variable_set(:@_cache_adapter, nil)
10-
base.cache_adapter = DEFAULT_ADAPTER
10+
base.cache_adapter = DEFAULT_ADAPTER if base == Cacheable
1111
end
1212

1313
def cache_adapter
14-
@_cache_adapter
14+
@_cache_adapter || (self == Cacheable ? nil : Cacheable.cache_adapter)
1515
end
1616

1717
def cache_adapter=(name_or_adapter)

lib/cacheable/method_generator.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def method_interceptor_module_name
1515
"#{class_name}Cacher"
1616
end
1717

18-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
18+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
1919
def create_cacheable_methods(original_method_name, opts = {})
2020
method_names = create_method_names(original_method_name)
2121
key_format_proc = opts[:key_format] || default_key_format
@@ -28,15 +28,17 @@ def create_cacheable_methods(original_method_name, opts = {})
2828
end
2929

3030
define_method(method_names[:clear_cache_method_name]) do |*args, **kwargs|
31-
Cacheable.cache_adapter.delete(__send__(method_names[:key_format_method_name], *args, **kwargs))
31+
adapter = (is_a?(Module) ? singleton_class : self.class).cache_adapter
32+
adapter.delete(__send__(method_names[:key_format_method_name], *args, **kwargs))
3233
end
3334

3435
define_method(method_names[:without_cache_method_name]) do |*args, **kwargs, &block|
3536
method(original_method_name).super_method.call(*args, **kwargs, &block)
3637
end
3738

3839
define_method(method_names[:with_cache_method_name]) do |*args, **kwargs, &block|
39-
Cacheable.cache_adapter.fetch(__send__(method_names[:key_format_method_name], *args, **kwargs), opts[:cache_options]) do # rubocop:disable Lint/UselessDefaultValueArgument -- not Hash#fetch; second arg is cache options (e.g. expires_in) passed to the adapter
40+
adapter = (is_a?(Module) ? singleton_class : self.class).cache_adapter
41+
adapter.fetch(__send__(method_names[:key_format_method_name], *args, **kwargs), opts[:cache_options]) do # rubocop:disable Lint/UselessDefaultValueArgument -- not Hash#fetch; second arg is cache options (e.g. expires_in) passed to the adapter
4042
__send__(method_names[:without_cache_method_name], *args, **kwargs, &block)
4143
end
4244
end
@@ -50,7 +52,7 @@ def create_cacheable_methods(original_method_name, opts = {})
5052
end
5153
end
5254
end
53-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
55+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
5456

5557
def default_key_format
5658
warned = false

spec/cacheable/cacheable_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,37 @@ def cache_control_method
558558
end
559559
end
560560

561+
describe 'per-class cache adapter' do
562+
it 'falls back to the global adapter by default' do
563+
expect(cacheable_class.cache_adapter).to eq(described_class.cache_adapter)
564+
end
565+
566+
it 'allows setting a class-specific adapter' do
567+
class_adapter = Cacheable::CacheAdapters::MemoryAdapter.new
568+
cacheable_class.cache_adapter = class_adapter
569+
570+
expect(cacheable_class.cache_adapter).to eq(class_adapter)
571+
expect(cacheable_class.cache_adapter).not_to eq(described_class.cache_adapter)
572+
end
573+
574+
it 'uses the class adapter for caching when set' do
575+
class_adapter = Cacheable::CacheAdapters::MemoryAdapter.new
576+
cacheable_class.cache_adapter = class_adapter
577+
578+
cacheable_object.send(cacheable_method)
579+
expect(class_adapter.exist?([cacheable_method])).to be true
580+
expect(described_class.cache_adapter.exist?([cacheable_method])).to be false
581+
end
582+
583+
it 'does not affect other classes' do
584+
other_class = Class.new.tap { |klass| klass.class_exec(&class_definition) }
585+
class_adapter = Cacheable::CacheAdapters::MemoryAdapter.new
586+
cacheable_class.cache_adapter = class_adapter
587+
588+
expect(other_class.cache_adapter).to eq(described_class.cache_adapter)
589+
end
590+
end
591+
561592
it 'passes `cache_options` to the cache client' do
562593
cache_options = {expires_in: 3_600}
563594
cache_method_with_cache_options = :cache_method_with_cache_options

0 commit comments

Comments
 (0)