flipper-cache-store 0.11.0.beta7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/docs/cache_store/README.md +99 -0
- data/lib/flipper/adapters/cache_store.rb +104 -0
- data/lib/flipper/version.rb +3 -0
- data/spec/flipper/adapters/cache_store_spec.rb +50 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e05826a8bee645823d83c2fc1497001a21061cb3
|
4
|
+
data.tar.gz: 4b57d8c399fe0025e17ba36dc668f76fa78b77a1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6b90ec6e64f7aeb62fef75403f52ba9c823ee3d67b9a46cdfd74a2430b2f2effba63dc343392c91348b7338dbbcb3cbd5db8bda479a2f2181224c8f0731e8c4c
|
7
|
+
data.tar.gz: b7b502a2905d855a4a79d060e12b96e60ebc282494ede5aa5281953f13e3b6c9181cbe52bb43964fe3e58bcad283ed42376dd749e451e94f945531c1b70f9cd9
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Flipper CacheStore
|
2
|
+
|
3
|
+
A [CacheStore](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html) adapter for [Flipper](https://github.com/jnunemaker/flipper).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'flipper-cache-store'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself with:
|
16
|
+
|
17
|
+
$ gem install flipper-cache-store
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'active_support/cache'
|
23
|
+
require 'flipper/adapters/memory'
|
24
|
+
require 'flipper/adapters/cache_store'
|
25
|
+
|
26
|
+
memory_adapter = Flipper::Adapters::Memory.new
|
27
|
+
cache = ActiveSupport::Cache::MemoryStore.new
|
28
|
+
adapter = Flipper::Adapters::CacheStore.new(memory_adapter, cache, expires_in: 5.minutes)
|
29
|
+
flipper = Flipper.new(adapter)
|
30
|
+
```
|
31
|
+
Setting `expires_in` is optional and will set an expiration time on Flipper cache keys. If specified, all flipper keys will use this `expires_in` over the `expires_in` passed to your ActiveSupport cache constructor.
|
32
|
+
|
33
|
+
## Internals
|
34
|
+
|
35
|
+
Each feature is stored in the underlying cache store.
|
36
|
+
|
37
|
+
This is an example using `ActiveSupport::Cache::MemoryStore` with the [Flipper memory adapter](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb).
|
38
|
+
|
39
|
+
Each key is namespaced under `flipper/v1/feature/`
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'active_support/cache'
|
43
|
+
require 'flipper/adapters/memory'
|
44
|
+
require 'flipper/adapters/cache_store'
|
45
|
+
|
46
|
+
memory_adapter = Flipper::Adapters::Memory.new
|
47
|
+
cache = ActiveSupport::Cache::MemoryStore.new
|
48
|
+
adapter = Flipper::Adapters::CacheStore.new(memory_adapter, cache)
|
49
|
+
flipper = Flipper.new(adapter)
|
50
|
+
|
51
|
+
# Register a few groups.
|
52
|
+
Flipper.register(:admins) { |thing| thing.admin? }
|
53
|
+
Flipper.register(:early_access) { |thing| thing.early_access? }
|
54
|
+
|
55
|
+
# Create a user class that has flipper_id instance method.
|
56
|
+
User = Struct.new(:flipper_id)
|
57
|
+
|
58
|
+
flipper[:stats].enable
|
59
|
+
flipper[:stats].enable_group :admins
|
60
|
+
flipper[:stats].enable_group :early_access
|
61
|
+
flipper[:stats].enable_actor User.new('25')
|
62
|
+
flipper[:stats].enable_actor User.new('90')
|
63
|
+
flipper[:stats].enable_actor User.new('180')
|
64
|
+
flipper[:stats].enable_percentage_of_time 15
|
65
|
+
flipper[:stats].enable_percentage_of_actors 45
|
66
|
+
flipper[:search].enable
|
67
|
+
|
68
|
+
# reading all feature keys
|
69
|
+
pp cache.read("flipper/v1/features")
|
70
|
+
#<Set: {"stats", "search"}>
|
71
|
+
|
72
|
+
# reading a single feature
|
73
|
+
pp cache.read("flipper/v1/feature/stats")
|
74
|
+
{
|
75
|
+
:boolean=>"true",
|
76
|
+
:groups=>#<Set: {"admins", "early_access"}>,
|
77
|
+
:actors=>#<Set: {"25", "90", "180"}>,
|
78
|
+
:percentage_of_actors=>"45",
|
79
|
+
:percentage_of_time=>"15"
|
80
|
+
}
|
81
|
+
|
82
|
+
# flipper get of feature
|
83
|
+
pp adapter.get(flipper[:stats])
|
84
|
+
{
|
85
|
+
:boolean=>"true",
|
86
|
+
:groups=>#<Set: {"admins", "early_access"}>,
|
87
|
+
:actors=>#<Set: {"25", "90", "180"}>,
|
88
|
+
:percentage_of_actors=>"45",
|
89
|
+
:percentage_of_time=>"15"
|
90
|
+
}
|
91
|
+
```
|
92
|
+
|
93
|
+
## Contributing
|
94
|
+
|
95
|
+
1. Fork it
|
96
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
97
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
98
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
99
|
+
5. Create new Pull Request
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Adapters
|
3
|
+
# Public: Adapter that wraps another adapter with the ability to cache
|
4
|
+
# adapter calls in ActiveSupport::CacheStore caches.
|
5
|
+
#
|
6
|
+
class CacheStore
|
7
|
+
include ::Flipper::Adapter
|
8
|
+
|
9
|
+
Version = 'v1'.freeze
|
10
|
+
Namespace = "flipper/#{Version}".freeze
|
11
|
+
FeaturesKey = "#{Namespace}/features".freeze
|
12
|
+
|
13
|
+
# Private
|
14
|
+
def self.key_for(key)
|
15
|
+
"#{Namespace}/feature/#{key}"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Internal
|
19
|
+
attr_reader :cache
|
20
|
+
|
21
|
+
# Public: The name of the adapter.
|
22
|
+
attr_reader :name
|
23
|
+
|
24
|
+
# Public
|
25
|
+
def initialize(adapter, cache, expires_in: nil)
|
26
|
+
@adapter = adapter
|
27
|
+
@name = :cache_store
|
28
|
+
@cache = cache
|
29
|
+
@write_options = {}
|
30
|
+
@write_options.merge!(expires_in: expires_in) if expires_in
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public
|
34
|
+
def features
|
35
|
+
@cache.fetch(FeaturesKey, @write_options) do
|
36
|
+
@adapter.features
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public
|
41
|
+
def add(feature)
|
42
|
+
result = @adapter.add(feature)
|
43
|
+
@cache.delete(FeaturesKey)
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
## Public
|
48
|
+
def remove(feature)
|
49
|
+
result = @adapter.remove(feature)
|
50
|
+
@cache.delete(FeaturesKey)
|
51
|
+
@cache.delete(key_for(feature.key))
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
## Public
|
56
|
+
def clear(feature)
|
57
|
+
result = @adapter.clear(feature)
|
58
|
+
@cache.delete(key_for(feature.key))
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
## Public
|
63
|
+
def get(feature)
|
64
|
+
@cache.fetch(key_for(feature.key), @write_options) do
|
65
|
+
@adapter.get(feature)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_multi(features)
|
70
|
+
keys = features.map { |feature| key_for(feature.key) }
|
71
|
+
result = @cache.read_multi(keys)
|
72
|
+
uncached_features = features.reject { |feature| result[feature.key] }
|
73
|
+
if uncached_features.any?
|
74
|
+
response = @adapter.get_multi(uncached_features)
|
75
|
+
response.each do |key, value|
|
76
|
+
@cache.write(key_for(key), value, @write_options)
|
77
|
+
result[key] = value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
83
|
+
## Public
|
84
|
+
def enable(feature, gate, thing)
|
85
|
+
result = @adapter.enable(feature, gate, thing)
|
86
|
+
@cache.delete(key_for(feature.key))
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
## Public
|
91
|
+
def disable(feature, gate, thing)
|
92
|
+
result = @adapter.disable(feature, gate, thing)
|
93
|
+
@cache.delete(key_for(feature.key))
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def key_for(key)
|
100
|
+
self.class.key_for(key)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'active_support/cache'
|
3
|
+
require 'flipper/adapters/memory'
|
4
|
+
require 'flipper/adapters/cache_store'
|
5
|
+
require 'flipper/spec/shared_adapter_specs'
|
6
|
+
|
7
|
+
RSpec.describe Flipper::Adapters::CacheStore do
|
8
|
+
let(:memory_adapter) { Flipper::Adapters::Memory.new }
|
9
|
+
let(:cache) { ActiveSupport::Cache::MemoryStore.new }
|
10
|
+
let(:adapter) { described_class.new(memory_adapter, cache) }
|
11
|
+
let(:flipper) { Flipper.new(adapter) }
|
12
|
+
|
13
|
+
subject { adapter }
|
14
|
+
|
15
|
+
it_should_behave_like 'a flipper adapter'
|
16
|
+
|
17
|
+
describe '#remove' do
|
18
|
+
it 'expires feature' do
|
19
|
+
feature = flipper[:stats]
|
20
|
+
adapter.get(feature)
|
21
|
+
adapter.remove(feature)
|
22
|
+
expect(cache.read(described_class.key_for(feature))).to be(nil)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#get_multi' do
|
27
|
+
it 'warms uncached features' do
|
28
|
+
stats = flipper[:stats]
|
29
|
+
search = flipper[:search]
|
30
|
+
other = flipper[:other]
|
31
|
+
stats.enable
|
32
|
+
search.enable
|
33
|
+
|
34
|
+
adapter.get(stats)
|
35
|
+
expect(cache.read(described_class.key_for(search))).to be(nil)
|
36
|
+
expect(cache.read(described_class.key_for(other))).to be(nil)
|
37
|
+
|
38
|
+
adapter.get_multi([stats, search, other])
|
39
|
+
|
40
|
+
expect(cache.read(described_class.key_for(search))[:boolean]).to eq('true')
|
41
|
+
expect(cache.read(described_class.key_for(other))[:boolean]).to be(nil)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#name' do
|
46
|
+
it 'is cache_store' do
|
47
|
+
expect(subject.name).to be(:cache_store)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flipper-cache-store
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.11.0.beta7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Nunemaker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-07-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: flipper
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.11.0.beta7
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.11.0.beta7
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '6'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '3.2'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '6'
|
47
|
+
description: ActiveSupport::Cache::Store adapter for Flipper
|
48
|
+
email:
|
49
|
+
- nunemaker@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- docs/cache_store/README.md
|
55
|
+
- lib/flipper/adapters/cache_store.rb
|
56
|
+
- lib/flipper/version.rb
|
57
|
+
- spec/flipper/adapters/cache_store_spec.rb
|
58
|
+
homepage: https://github.com/jnunemaker/flipper
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.3.1
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.5.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: ActiveSupport::Cache::Store adapter for Flipper
|
82
|
+
test_files:
|
83
|
+
- spec/flipper/adapters/cache_store_spec.rb
|