flipper-active_support_cache_store 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 528dc13fd274c8753ac7df38a790bfe405fec333d614967cbc9925144e4c985e
4
- data.tar.gz: e3f7b3b9d27ecceea9ef6fd28afe0f492e36d46df5da560caef458bc3c7b38d7
3
+ metadata.gz: 447a24782b327c966b7808c983d2284e0e94b0b0086a72f7e409f308f171f25f
4
+ data.tar.gz: 2d8e915c4b82a4b5e5754956e022e5d0d052a6365b1a98c39d27aa6c47c7446e
5
5
  SHA512:
6
- metadata.gz: 5cb25bc61e007c9cb9d8676d725fbe5d2a781218dafe522e2d980a5370a7a5147d2a75c5f85190b11b287d2c183b4b23882c30af1353cf4034609ae8a998483c
7
- data.tar.gz: c7154a8b1d0b8c68abbe5f2d90d1ae8effd0835407d4fcba420fd137c0d526f9525e2b3ad9441505a6b74e8048fe8ff2108df8d08ef8ba8d9ecfa4a0e68c130e
6
+ metadata.gz: 0c36c62284cf5e96acee11e2b0ce7dd21a4d44a814aabccbfcf5850afc6c86139690d08da23aab047ba1c172bcf6702f998ac9d3cd627b39e3a6ebf689142d95
7
+ data.tar.gz: 637b581a2481cf7e16b8cde460819b5544df3cff1a5e3241012e4178fc9e36196c9c38694fc7e04ede465d981bb5aa6380227abdbe91b82d738d664cb987392c
@@ -1,146 +1,79 @@
1
1
  require 'flipper'
2
+ require 'flipper/adapters/cache_base'
2
3
  require 'active_support/notifications'
3
4
 
4
5
  module Flipper
5
6
  module Adapters
6
7
  # Public: Adapter that wraps another adapter with the ability to cache
7
8
  # adapter calls in ActiveSupport::ActiveSupportCacheStore caches.
8
- #
9
- class ActiveSupportCacheStore
10
- include ::Flipper::Adapter
11
-
12
- # Internal
13
- attr_reader :cache
14
-
15
- # Public
16
- def initialize(adapter, cache, expires_in: nil, write_through: false)
17
- @adapter = adapter
18
- @cache = cache
19
- @write_options = {}
20
- @write_options[:expires_in] = expires_in if expires_in
9
+ class ActiveSupportCacheStore < CacheBase
10
+ def initialize(adapter, cache, ttl = nil, expires_in: :none_provided, write_through: false, prefix: nil)
11
+ if expires_in == :none_provided
12
+ ttl ||= nil
13
+ else
14
+ warn "DEPRECATION WARNING: The `expires_in` kwarg is deprecated for " +
15
+ "Flipper::Adapters::ActiveSupportCacheStore and will be removed " +
16
+ "in the next major version. Please pass in expires in as third " +
17
+ "argument instead."
18
+ ttl = expires_in
19
+ end
20
+ super(adapter, cache, ttl, prefix: prefix)
21
21
  @write_through = write_through
22
-
23
- @cache_version = 'v1'.freeze
24
- @namespace = "flipper/#{@cache_version}".freeze
25
- @features_key = "#{@namespace}/features".freeze
26
- @get_all_key = "#{@namespace}/get_all".freeze
27
- end
28
-
29
- # Public
30
- def features
31
- read_feature_keys
32
22
  end
33
23
 
34
- # Public
35
- def add(feature)
36
- result = @adapter.add(feature)
37
- @cache.delete(@features_key)
38
- result
39
- end
40
-
41
- ## Public
42
24
  def remove(feature)
43
- result = @adapter.remove(feature)
44
- @cache.delete(@features_key)
45
-
46
25
  if @write_through
47
- @cache.write(key_for(feature.key), default_config, @write_options)
48
- else
49
- @cache.delete(key_for(feature.key))
50
- end
51
-
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
- read_many_features(features)
71
- end
72
-
73
- def get_all
74
- if @cache.write(@get_all_key, Time.now.to_i, @write_options.merge(unless_exist: true))
75
- response = @adapter.get_all
76
- response.each do |key, value|
77
- @cache.write(key_for(key), value, @write_options)
78
- end
79
- @cache.write(@features_key, response.keys.to_set, @write_options)
80
- response
26
+ result = @adapter.remove(feature)
27
+ expire_features_cache
28
+ cache_write feature_cache_key(feature.key), default_config
29
+ result
81
30
  else
82
- features = read_feature_keys.map { |key| Flipper::Feature.new(key, self) }
83
- read_many_features(features)
31
+ super
84
32
  end
85
33
  end
86
34
 
87
- ## Public
88
35
  def enable(feature, gate, thing)
89
- result = @adapter.enable(feature, gate, thing)
90
-
91
36
  if @write_through
92
- @cache.write(key_for(feature.key), @adapter.get(feature), @write_options)
37
+ result = @adapter.enable(feature, gate, thing)
38
+ cache_write feature_cache_key(feature.key), @adapter.get(feature)
39
+ result
93
40
  else
94
- @cache.delete(key_for(feature.key))
41
+ super
95
42
  end
96
-
97
- result
98
43
  end
99
44
 
100
- ## Public
101
45
  def disable(feature, gate, thing)
102
- result = @adapter.disable(feature, gate, thing)
103
-
104
46
  if @write_through
105
- @cache.write(key_for(feature.key), @adapter.get(feature), @write_options)
47
+ result = @adapter.disable(feature, gate, thing)
48
+ cache_write feature_cache_key(feature.key), @adapter.get(feature)
49
+ result
106
50
  else
107
- @cache.delete(key_for(feature.key))
51
+ super
108
52
  end
109
-
110
- result
111
53
  end
112
54
 
113
55
  private
114
56
 
115
- def key_for(key)
116
- "#{@namespace}/feature/#{key}"
57
+ def cache_fetch(key, &block)
58
+ @cache.fetch(key, write_options, &block)
117
59
  end
118
60
 
119
- # Internal: Returns an array of the known feature keys.
120
- def read_feature_keys
121
- @cache.fetch(@features_key, @write_options) { @adapter.features }
61
+ def cache_read_multi(keys)
62
+ @cache.read_multi(*keys)
122
63
  end
123
64
 
124
- # Internal: Given an array of features, attempts to read through cache in
125
- # as few network calls as possible.
126
- def read_many_features(features)
127
- keys = features.map { |feature| key_for(feature.key) }
128
- cache_result = @cache.read_multi(*keys)
129
- uncached_features = features.reject { |feature| cache_result[key_for(feature)] }
65
+ def cache_write(key, value)
66
+ @cache.write(key, value, write_options)
67
+ end
130
68
 
131
- if uncached_features.any?
132
- response = @adapter.get_multi(uncached_features)
133
- response.each do |key, value|
134
- @cache.write(key_for(key), value, @write_options)
135
- cache_result[key_for(key)] = value
136
- end
137
- end
69
+ def cache_delete(key)
70
+ @cache.delete(key)
71
+ end
138
72
 
139
- result = {}
140
- features.each do |feature|
141
- result[feature.key] = cache_result[key_for(feature.key)]
142
- end
143
- result
73
+ def write_options
74
+ write_options = {}
75
+ write_options[:expires_in] = @ttl if @ttl
76
+ write_options
144
77
  end
145
78
  end
146
79
  end
@@ -1,5 +1,5 @@
1
1
  module Flipper
2
- VERSION = '1.2.2'.freeze
2
+ VERSION = '1.3.0'.freeze
3
3
 
4
4
  REQUIRED_RUBY_VERSION = '2.6'.freeze
5
5
  NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
@@ -8,7 +8,7 @@ RSpec.describe Flipper::Adapters::ActiveSupportCacheStore do
8
8
  end
9
9
  let(:cache) { ActiveSupport::Cache::MemoryStore.new }
10
10
  let(:write_through) { false }
11
- let(:adapter) { described_class.new(memory_adapter, cache, expires_in: 10.seconds, write_through: write_through) }
11
+ let(:adapter) { described_class.new(memory_adapter, cache, 10, write_through: write_through) }
12
12
  let(:flipper) { Flipper.new(adapter) }
13
13
 
14
14
  subject { adapter }
@@ -19,6 +19,92 @@ RSpec.describe Flipper::Adapters::ActiveSupportCacheStore do
19
19
 
20
20
  it_should_behave_like 'a flipper adapter'
21
21
 
22
+ it "knows ttl" do
23
+ expect(adapter.ttl).to eq(10)
24
+ end
25
+
26
+ it "knows ttl when only expires_in provided" do
27
+ silence do
28
+ adapter = described_class.new(memory_adapter, cache, expires_in: 10)
29
+ expect(adapter.ttl).to eq(10)
30
+ end
31
+ end
32
+
33
+ it "knows ttl when ttl and expires_in are provided" do
34
+ silence do
35
+ adapter = described_class.new(memory_adapter, cache, 200, expires_in: 10)
36
+ expect(adapter.ttl).to eq(10)
37
+ end
38
+ end
39
+
40
+ it "knows default when no ttl or expires_in provided" do
41
+ adapter = described_class.new(memory_adapter, cache)
42
+ expect(adapter.ttl).to be(nil)
43
+ end
44
+
45
+ it "knows features_cache_key" do
46
+ expect(adapter.features_cache_key).to eq("flipper/v1/features")
47
+ end
48
+
49
+ it "can expire features cache" do
50
+ # cache the features
51
+ adapter.features
52
+ expect(cache.read("flipper/v1/features")).not_to be(nil)
53
+
54
+ # expire cache
55
+ adapter.expire_features_cache
56
+ expect(cache.read("flipper/v1/features")).to be(nil)
57
+ end
58
+
59
+ it "can expire feature cache" do
60
+ # cache the features
61
+ adapter.get(flipper[:stats])
62
+ expect(cache.read("flipper/v1/feature/stats")).not_to be(nil)
63
+
64
+ # expire cache
65
+ adapter.expire_feature_cache("stats")
66
+ expect(cache.read("flipper/v1/feature/stats")).to be(nil)
67
+ end
68
+
69
+ it "can generate feature cache key" do
70
+ expect(adapter.feature_cache_key("stats")).to eq("flipper/v1/feature/stats")
71
+ end
72
+
73
+ context "when using a prefix" do
74
+ let(:adapter) { described_class.new(memory_adapter, cache, 10, prefix: "foo/") }
75
+ it_should_behave_like 'a flipper adapter'
76
+
77
+ it "knows features_cache_key" do
78
+ expect(adapter.features_cache_key).to eq("foo/flipper/v1/features")
79
+ end
80
+
81
+ it "can generate feature cache key" do
82
+ expect(adapter.feature_cache_key("stats")).to eq("foo/flipper/v1/feature/stats")
83
+ end
84
+
85
+ it "uses the prefix for all keys" do
86
+ # check individual feature get cached with prefix
87
+ adapter.get(flipper[:stats])
88
+ expect(cache.read("foo/flipper/v1/feature/stats")).not_to be(nil)
89
+
90
+ # check individual feature expired with prefix
91
+ adapter.remove(flipper[:stats])
92
+ expect(cache.read("foo/flipper/v1/feature/stats")).to be(nil)
93
+
94
+ # enable some stuff
95
+ flipper.enable_percentage_of_actors(:search, 10)
96
+ flipper.enable(:stats)
97
+
98
+ # populate the cache
99
+ adapter.get_all
100
+
101
+ # verify cached with prefix
102
+ expect(cache.read("foo/flipper/v1/features")).to eq(Set["stats", "search"])
103
+ expect(cache.read("foo/flipper/v1/feature/search")[:percentage_of_actors]).to eq("10")
104
+ expect(cache.read("foo/flipper/v1/feature/stats")[:boolean]).to eq("true")
105
+ end
106
+ end
107
+
22
108
  describe '#remove' do
23
109
  let(:feature) { flipper[:stats] }
24
110
 
@@ -130,17 +216,19 @@ RSpec.describe Flipper::Adapters::ActiveSupportCacheStore do
130
216
  adapter.get_all
131
217
  expect(cache.read("flipper/v1/feature/#{stats.key}")[:boolean]).to eq('true')
132
218
  expect(cache.read("flipper/v1/feature/#{search.key}")[:boolean]).to be(nil)
133
- expect(cache.read("flipper/v1/get_all")).to be_within(2).of(Time.now.to_i)
219
+ expect(cache.read("flipper/v1/features")).to eq(Set["stats", "search"])
134
220
  end
135
221
 
136
222
  it 'returns same result when already cached' do
137
223
  expect(adapter.get_all).to eq(adapter.get_all)
138
224
  end
139
225
 
140
- it 'only invokes one call to wrapped adapter' do
226
+ it 'only invokes two calls to wrapped adapter (for features set and gate data for each feature in set)' do
141
227
  memory_adapter.reset
142
228
  5.times { adapter.get_all }
143
- expect(memory_adapter.count(:get_all)).to eq(1)
229
+ expect(memory_adapter.count(:features)).to eq(1)
230
+ expect(memory_adapter.count(:get_multi)).to eq(1)
231
+ expect(memory_adapter.count).to eq(2)
144
232
  end
145
233
  end
146
234
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-active_support_cache_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-19 00:00:00.000000000 Z
11
+ date: 2024-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: flipper
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.2.2
19
+ version: 1.3.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.2.2
26
+ version: 1.3.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -63,7 +63,7 @@ metadata:
63
63
  homepage_uri: https://www.flippercloud.io
64
64
  source_code_uri: https://github.com/flippercloud/flipper
65
65
  bug_tracker_uri: https://github.com/flippercloud/flipper/issues
66
- changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.2.2
66
+ changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.0
67
67
  post_install_message:
68
68
  rdoc_options: []
69
69
  require_paths: