flipper-redis 1.2.2 → 1.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e55b034a67c36b705521502c717a6a702e20fe7339571700e7a9250f49f21781
4
- data.tar.gz: 95633728475c7a39bd88fd59b17ba4924dbbf9c98e2f7a2b0f196722302b1b19
3
+ metadata.gz: 8469e0a68d8a08cc42f6e8a50ca2b3121495f3200510a9ebe71e3c4a2304a2af
4
+ data.tar.gz: bca16917e5dd42cfdbae647ff87c83f5420f12855c61930247fb6b50cecda34e
5
5
  SHA512:
6
- metadata.gz: 4cf728abd260addff1b057c68b0ea401458bc53fe8aa1ec4247d322dd1f1083fdbec76d20a4d3fc3bd123c4babde353d5a5be1d66ff07be1881e5430e794d21f
7
- data.tar.gz: af6843a3bb222f418ae9917bde9b7d4d9d5f37bcb88371950ad834a7f6d557c0bf7ee8779bcfba8ee57fe6f6144bb1ab928ecbac04fdd3d7290dd70c441f28d0
6
+ metadata.gz: d765d8c9434676e1ed307ffbae3e9aa4652712c91208d72ddbdd3b8ca1114af8bf7aa0590821ed55b51c103ef9f2248e63461f592071fe56897662bc010c6ab4
7
+ data.tar.gz: 1e43b7077112e805baf33f8ad6df5528b009b1d23bc3477d43d249cc1537fa907daf66835eed3cfb24933590001cd433ad6b5d695c260878956775c29367c7a4
@@ -1,147 +1,45 @@
1
1
  require 'redis'
2
2
  require 'flipper'
3
+ require 'flipper/adapters/cache_base'
3
4
 
4
5
  module Flipper
5
6
  module Adapters
6
7
  # Public: Adapter that wraps another adapter with the ability to cache
7
- # adapter calls in Redis
8
- class RedisCache
9
- include ::Flipper::Adapter
10
-
11
- # Internal
12
- attr_reader :cache
13
-
14
- # Public
15
- def initialize(adapter, cache, ttl = 3600)
16
- @adapter = adapter
17
- @cache = cache
18
- @ttl = ttl
19
-
20
- @version = 'v1'.freeze
21
- @namespace = "flipper/#{@version}".freeze
22
- @features_key = "#{@namespace}/features".freeze
23
- @get_all_key = "#{@namespace}/get_all".freeze
24
- end
25
-
26
- # Public
27
- def features
28
- read_feature_keys
29
- end
30
-
31
- # Public
32
- def add(feature)
33
- result = @adapter.add(feature)
34
- @cache.del(@features_key)
35
- result
36
- end
37
-
38
- # Public
39
- def remove(feature)
40
- result = @adapter.remove(feature)
41
- @cache.del(@features_key)
42
- @cache.del(key_for(feature.key))
43
- result
44
- end
45
-
46
- # Public
47
- def clear(feature)
48
- result = @adapter.clear(feature)
49
- @cache.del(key_for(feature.key))
50
- result
51
- end
52
-
53
- # Public
54
- def get(feature)
55
- fetch(key_for(feature.key)) do
56
- @adapter.get(feature)
57
- end
58
- end
59
-
60
- def get_multi(features)
61
- read_many_features(features)
62
- end
63
-
64
- def get_all
65
- if @cache.setnx(@get_all_key, Time.now.to_i)
66
- @cache.expire(@get_all_key, @ttl)
67
- response = @adapter.get_all
68
- response.each do |key, value|
69
- set_with_ttl key_for(key), value
70
- end
71
- set_with_ttl @features_key, response.keys.to_set
72
- response
73
- else
74
- features = read_feature_keys.map { |key| Flipper::Feature.new(key, self) }
75
- read_many_features(features)
76
- end
77
- end
78
-
79
- # Public
80
- def enable(feature, gate, thing)
81
- result = @adapter.enable(feature, gate, thing)
82
- @cache.del(key_for(feature.key))
83
- result
84
- end
85
-
86
- # Public
87
- def disable(feature, gate, thing)
88
- result = @adapter.disable(feature, gate, thing)
89
- @cache.del(key_for(feature.key))
90
- result
8
+ # adapter calls in Redis.
9
+ class RedisCache < CacheBase
10
+ def initialize(adapter, cache, ttl = 3600, prefix: nil)
11
+ super
91
12
  end
92
13
 
93
14
  private
94
15
 
95
- def key_for(key)
96
- "#{@namespace}/feature/#{key}"
97
- end
98
-
99
- def read_feature_keys
100
- fetch(@features_key) { @adapter.features }
101
- end
102
-
103
- def read_many_features(features)
104
- keys = features.map(&:key)
105
- cache_result = Hash[keys.zip(multi_cache_get(keys))]
106
- uncached_features = features.reject { |feature| cache_result[feature.key] }
107
-
108
- if uncached_features.any?
109
- response = @adapter.get_multi(uncached_features)
110
- response.each do |key, value|
111
- set_with_ttl(key_for(key), value)
112
- cache_result[key] = value
113
- end
114
- end
115
-
116
- result = {}
117
- features.each do |feature|
118
- result[feature.key] = cache_result[feature.key]
119
- end
120
- result
121
- end
122
-
123
- def fetch(cache_key)
124
- cached = @cache.get(cache_key)
16
+ def cache_fetch(key, &block)
17
+ cached = @cache.get(key)
125
18
  if cached
126
19
  Marshal.load(cached)
127
20
  else
128
21
  to_cache = yield
129
- set_with_ttl(cache_key, to_cache)
22
+ cache_write key, to_cache
130
23
  to_cache
131
24
  end
132
25
  end
133
26
 
134
- def set_with_ttl(key, value)
135
- @cache.setex(key, @ttl, Marshal.dump(value))
136
- end
27
+ def cache_read_multi(keys)
28
+ return {} if keys.empty?
137
29
 
138
- def multi_cache_get(keys)
139
- return [] if keys.empty?
140
-
141
- cache_keys = keys.map { |key| key_for(key) }
142
- @cache.mget(*cache_keys).map do |value|
30
+ values = @cache.mget(*keys).map do |value|
143
31
  value ? Marshal.load(value) : nil
144
32
  end
33
+
34
+ Hash[keys.zip(values)]
35
+ end
36
+
37
+ def cache_write(key, value)
38
+ @cache.setex(key, @ttl, Marshal.dump(value))
39
+ end
40
+
41
+ def cache_delete(key)
42
+ @cache.del(key)
145
43
  end
146
44
  end
147
45
  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
@@ -11,7 +11,7 @@ RSpec.describe Flipper::Adapters::RedisCache do
11
11
  let(:memory_adapter) do
12
12
  Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
13
13
  end
14
- let(:adapter) { described_class.new(memory_adapter, client) }
14
+ let(:adapter) { described_class.new(memory_adapter, client, 10) }
15
15
  let(:flipper) { Flipper.new(adapter) }
16
16
 
17
17
  subject { adapter }
@@ -24,6 +24,73 @@ RSpec.describe Flipper::Adapters::RedisCache do
24
24
 
25
25
  it_should_behave_like 'a flipper adapter'
26
26
 
27
+ it "knows ttl" do
28
+ expect(adapter.ttl).to eq(10)
29
+ end
30
+
31
+ it "knows features_cache_key" do
32
+ expect(adapter.features_cache_key).to eq("flipper/v1/features")
33
+ end
34
+
35
+ it "can expire features cache" do
36
+ # cache the features
37
+ adapter.features
38
+ expect(client.get("flipper/v1/features")).not_to be(nil)
39
+
40
+ # expire cache
41
+ adapter.expire_features_cache
42
+ expect(client.get("flipper/v1/features")).to be(nil)
43
+ end
44
+
45
+ it "can expire feature cache" do
46
+ # cache the features
47
+ adapter.get(flipper[:stats])
48
+ expect(client.get("flipper/v1/feature/stats")).not_to be(nil)
49
+
50
+ # expire cache
51
+ adapter.expire_feature_cache("stats")
52
+ expect(client.get("flipper/v1/feature/stats")).to be(nil)
53
+ end
54
+
55
+ it "can generate feature cache key" do
56
+ expect(adapter.feature_cache_key("stats")).to eq("flipper/v1/feature/stats")
57
+ end
58
+
59
+ context "when using a prefix" do
60
+ let(:adapter) { described_class.new(memory_adapter, client, 3600, prefix: "foo/") }
61
+ it_should_behave_like 'a flipper adapter'
62
+
63
+ it "knows features_cache_key" do
64
+ expect(adapter.features_cache_key).to eq("foo/flipper/v1/features")
65
+ end
66
+
67
+ it "can generate feature cache key" do
68
+ expect(adapter.feature_cache_key("stats")).to eq("foo/flipper/v1/feature/stats")
69
+ end
70
+
71
+ it "uses the prefix for all keys" do
72
+ # check individual feature get cached with prefix
73
+ adapter.get(flipper[:stats])
74
+ expect(Marshal.load(client.get("foo/flipper/v1/feature/stats"))).not_to be(nil)
75
+
76
+ # check individual feature expired with prefix
77
+ adapter.remove(flipper[:stats])
78
+ expect(client.get("foo/flipper/v1/feature/stats")).to be(nil)
79
+
80
+ # enable some stuff
81
+ flipper.enable_percentage_of_actors(:search, 10)
82
+ flipper.enable(:stats)
83
+
84
+ # populate the cache
85
+ adapter.get_all
86
+
87
+ # verify cached with prefix
88
+ expect(Marshal.load(client.get("foo/flipper/v1/features"))).to eq(Set["stats", "search"])
89
+ expect(Marshal.load(client.get("foo/flipper/v1/feature/search"))[:percentage_of_actors]).to eq("10")
90
+ expect(Marshal.load(client.get("foo/flipper/v1/feature/stats"))[:boolean]).to eq("true")
91
+ end
92
+ end
93
+
27
94
  describe '#remove' do
28
95
  it 'expires feature' do
29
96
  feature = flipper[:stats]
@@ -82,17 +149,19 @@ RSpec.describe Flipper::Adapters::RedisCache do
82
149
  adapter.get_all
83
150
  expect(Marshal.load(client.get("flipper/v1/feature/#{stats.key}"))[:boolean]).to eq('true')
84
151
  expect(Marshal.load(client.get("flipper/v1/feature/#{search.key}"))[:boolean]).to be(nil)
85
- expect(client.get("flipper/v1/get_all").to_i).to be_within(2).of(Time.now.to_i)
152
+ expect(Marshal.load(client.get("flipper/v1/features"))).to eq(Set["stats", "search"])
86
153
  end
87
154
 
88
155
  it 'returns same result when already cached' do
89
156
  expect(adapter.get_all).to eq(adapter.get_all)
90
157
  end
91
158
 
92
- it 'only invokes one call to wrapped adapter' do
159
+ it 'only invokes two calls to wrapped adapter (for features set and gate data for each feature in set)' do
93
160
  memory_adapter.reset
94
161
  5.times { adapter.get_all }
95
- expect(memory_adapter.count(:get_all)).to eq(1)
162
+ expect(memory_adapter.count(:features)).to eq(1)
163
+ expect(memory_adapter.count(:get_multi)).to eq(1)
164
+ expect(memory_adapter.count).to eq(2)
96
165
  end
97
166
  end
98
167
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-redis
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: redis
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -70,7 +70,7 @@ metadata:
70
70
  homepage_uri: https://www.flippercloud.io
71
71
  source_code_uri: https://github.com/flippercloud/flipper
72
72
  bug_tracker_uri: https://github.com/flippercloud/flipper/issues
73
- changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.2.2
73
+ changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.0
74
74
  post_install_message:
75
75
  rdoc_options: []
76
76
  require_paths: