Skip to content

Commit

Permalink
Add cache_store adapter for caching via ActiveSupport::CacheStore
Browse files Browse the repository at this point in the history
* Helpful adapter for Rails users that want to use their configured
cache.
  • Loading branch information
AlexWheeler committed Jun 8, 2017
1 parent 75ec873 commit 522b60a
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 0 deletions.
103 changes: 103 additions & 0 deletions lib/flipper/adapters/cache_store.rb
@@ -0,0 +1,103 @@
module Flipper
module Adapters
# Public: Adapter that wraps another adapter with the ability to cache
# adapter calls in ActiveSupport::CacheStore caches.
#
class CacheStore
include ::Flipper::Adapter

Version = 'v1'.freeze
Namespace = "flipper/#{Version}".freeze
FeaturesKey = "#{Namespace}/features".freeze

# Private
def self.key_for(key)
"#{Namespace}/feature/#{key}"
end

# Internal
attr_reader :cache

# Public: The name of the adapter.
attr_reader :name

# Public
def initialize(adapter, cache, options = nil)
@adapter = adapter
@name = :cache_store
@cache = cache
@options = options
end

# Public
def features
@cache.fetch(FeaturesKey, @options) do
@adapter.features
end
end

# Public
def add(feature)
result = @adapter.add(feature)
@cache.delete(FeaturesKey, @options)
result
end

## Public
def remove(feature)
result = @adapter.remove(feature)
@cache.delete(FeaturesKey, @options)
@cache.delete(key_for(feature.key), @options)
result
end

## Public
def clear(feature)
result = @adapter.clear(feature)
@cache.delete(key_for(feature.key), @options)
result
end

## Public
def get(feature)
@cache.fetch(key_for(feature.key), @options) do
@adapter.get(feature)
end
end

def get_multi(features)
keys = features.map { |feature| key_for(feature.key) }
result = @cache.read_multi(keys)
uncached_features = features.reject { |feature| result[feature.key] }
if uncached_features.any?
response = @adapter.get_multi(uncached_features)
response.each do |key, value|
@cache.write(key_for(key), value, @options)
result[key] = value
end
end
result
end

## Public
def enable(feature, gate, thing)
result = @adapter.enable(feature, gate, thing)
@cache.delete(key_for(feature.key), @options)
result
end

## Public
def disable(feature, gate, thing)
result = @adapter.disable(feature, gate, thing)
@cache.delete(key_for(feature.key), @options)
result
end

private

def key_for(key)
self.class.key_for(key)
end
end
end
end
50 changes: 50 additions & 0 deletions spec/flipper/adapters/cache_store_spec.rb
@@ -0,0 +1,50 @@
require 'helper'
require 'active_support/cache'
require 'flipper/adapters/memory'
require 'flipper/adapters/cache_store'
require 'flipper/spec/shared_adapter_specs'

RSpec.describe Flipper::Adapters::CacheStore do
let(:memory_adapter) { Flipper::Adapters::Memory.new }
let(:cache) { ActiveSupport::Cache::MemoryStore.new }
let(:adapter) { described_class.new(memory_adapter, cache) }
let(:flipper) { Flipper.new(adapter) }

subject { adapter }

it_should_behave_like 'a flipper adapter'

describe '#remove' do
it 'expires feature' do
feature = flipper[:stats]
adapter.get(feature)
adapter.remove(feature)
expect(cache.read(described_class.key_for(feature))).to be(nil)
end
end

describe '#get_multi' do
it 'warms uncached features' do
stats = flipper[:stats]
search = flipper[:search]
other = flipper[:other]
stats.enable
search.enable

adapter.get(stats)
expect(cache.read(described_class.key_for(search))).to be(nil)
expect(cache.read(described_class.key_for(other))).to be(nil)

adapter.get_multi([stats, search, other])

expect(cache.read(described_class.key_for(search))[:boolean]).to eq('true')
expect(cache.read(described_class.key_for(other))[:boolean]).to be(nil)
end
end

describe '#name' do
it 'is cache_store' do
expect(subject.name).to be(:cache_store)
end
end
end

0 comments on commit 522b60a

Please sign in to comment.