flipper-redis 1.2.2 → 1.3.0.pre

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: e55b034a67c36b705521502c717a6a702e20fe7339571700e7a9250f49f21781
4
- data.tar.gz: 95633728475c7a39bd88fd59b17ba4924dbbf9c98e2f7a2b0f196722302b1b19
3
+ metadata.gz: 59430891951625789c71f32a02e5980d2cc652325b440c67567d18ef558f1ef4
4
+ data.tar.gz: c8b13da6dab4000d45de813dd552ff1ec3e8781fed5e4271b846f0237efd60e3
5
5
  SHA512:
6
- metadata.gz: 4cf728abd260addff1b057c68b0ea401458bc53fe8aa1ec4247d322dd1f1083fdbec76d20a4d3fc3bd123c4babde353d5a5be1d66ff07be1881e5430e794d21f
7
- data.tar.gz: af6843a3bb222f418ae9917bde9b7d4d9d5f37bcb88371950ad834a7f6d557c0bf7ee8779bcfba8ee57fe6f6144bb1ab928ecbac04fdd3d7290dd70c441f28d0
6
+ metadata.gz: 79249f1638b67670fb93f725659e58bb0231e02199300024350f3ede831e7861ba7596f4eb257c7689a87724f259a07d08c6f89dadfb643202f00944b474b65d
7
+ data.tar.gz: 7bbc58f6f5ab4caa2e1f7cd8b111bad1187beaad54d55b5c76e75b5add26df75ed5ccb6853e698df4affa6e159eb24484e6daf68d8599b10a125c39c9b56906d
@@ -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.pre'.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.pre
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-03-14 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.pre
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.pre
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.pre
74
74
  post_install_message:
75
75
  rdoc_options: []
76
76
  require_paths: