flipper-dalli 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: 46bffba42a4bebe087a33ed757147ef0abe92bad5bc8417cad5eebd55c083e0b
4
- data.tar.gz: 585bcfdef7495be45d2004c5a57123a0c8841f872cb7802796acbf90b1325aea
3
+ metadata.gz: 960ecc222c7e5c33b7837cca5d48f2ae5039cc050301977d3b14dfd94d9c6081
4
+ data.tar.gz: e759cb6f88b00a59265e9d7084046afa6e1f37b5321ab65f12ecdc9403490733
5
5
  SHA512:
6
- metadata.gz: a99d7deea1e125024a759a91857ce5ed564e68952e1096cd79e288b372d592adebb7f6530119fee3c26d7e266489d58057430b16a7762b252151323a0d79bc19
7
- data.tar.gz: 42874c56819a0ce8ad2061910ae0c11121a9a1173dff6203b5cff45939c7627c574a65fcca17876c2b81b29148fe58d3b4fc4ce04c34fba22927033ce501090d
6
+ metadata.gz: 0c0c0a121101433f3cdac3cf3d58921fd03bfecc406cd642d00b527be8b359a657216afd37c72b0873e80d40cb070786c83e20ce6abcf036448cece9d3b8a788
7
+ data.tar.gz: 1cf7e3aec7f903bd74e4b1b55e931ca09acb35e7c1eb64114f6ee52d1698a4efb8f632f5953e5956a07d07e00af40e08328599902fba742e2ecee8f7085e862d
@@ -1,127 +1,32 @@
1
1
  require 'dalli'
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
8
  # adapter calls in Memcached using the Dalli gem.
8
- class Dalli
9
- include ::Flipper::Adapter
10
-
11
- # Internal
12
- attr_reader :cache
13
-
14
- # Public: The ttl for all cached data.
15
- attr_reader :ttl
16
-
17
- # Public
18
- def initialize(adapter, cache, ttl = 0)
19
- @adapter = adapter
20
- @cache = cache
21
- @ttl = ttl
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
- end
33
-
34
- # Public
35
- def add(feature)
36
- result = @adapter.add(feature)
37
- @cache.delete(@features_key)
38
- result
39
- end
40
-
41
- # Public
42
- def remove(feature)
43
- result = @adapter.remove(feature)
44
- @cache.delete(@features_key)
45
- @cache.delete(key_for(feature.key))
46
- result
47
- end
48
-
49
- # Public
50
- def clear(feature)
51
- result = @adapter.clear(feature)
52
- @cache.delete(key_for(feature.key))
53
- result
54
- end
55
-
56
- # Public
57
- def get(feature)
58
- @cache.fetch(key_for(feature.key), @ttl) do
59
- @adapter.get(feature)
60
- end
61
- end
62
-
63
- def get_multi(features)
64
- read_many_features(features)
65
- end
66
-
67
- def get_all
68
- if @cache.add(@get_all_key, Time.now.to_i, @ttl)
69
- response = @adapter.get_all
70
- response.each do |key, value|
71
- @cache.set(key_for(key), value, @ttl)
72
- end
73
- @cache.set(@features_key, response.keys.to_set, @ttl)
74
- response
75
- else
76
- features = read_feature_keys.map { |key| Flipper::Feature.new(key, self) }
77
- read_many_features(features)
78
- end
79
- end
80
-
81
- # Public
82
- def enable(feature, gate, thing)
83
- result = @adapter.enable(feature, gate, thing)
84
- @cache.delete(key_for(feature.key))
85
- result
86
- end
87
-
88
- # Public
89
- def disable(feature, gate, thing)
90
- result = @adapter.disable(feature, gate, thing)
91
- @cache.delete(key_for(feature.key))
92
- result
9
+ class Dalli < CacheBase
10
+ def initialize(adapter, cache, ttl = 0, prefix: nil)
11
+ super
93
12
  end
94
13
 
95
14
  private
96
15
 
97
- def key_for(key)
98
- "#{@namespace}/feature/#{key}"
16
+ def cache_fetch(key, &block)
17
+ @cache.fetch(key, @ttl, &block)
99
18
  end
100
19
 
101
- def read_feature_keys
102
- @cache.fetch(@features_key, @ttl) { @adapter.features }
20
+ def cache_read_multi(keys)
21
+ @cache.get_multi(keys)
103
22
  end
104
23
 
105
- # Internal: Given an array of features, attempts to read through cache in
106
- # as few network calls as possible.
107
- def read_many_features(features)
108
- keys = features.map { |feature| key_for(feature.key) }
109
- cache_result = @cache.get_multi(keys)
110
- uncached_features = features.reject { |feature| cache_result[key_for(feature.key)] }
111
-
112
- if uncached_features.any?
113
- response = @adapter.get_multi(uncached_features)
114
- response.each do |key, value|
115
- @cache.set(key_for(key), value, @ttl)
116
- cache_result[key_for(key)] = value
117
- end
118
- end
24
+ def cache_write(key, value)
25
+ @cache.set(key, value, @ttl)
26
+ end
119
27
 
120
- result = {}
121
- features.each do |feature|
122
- result[feature.key] = cache_result[key_for(feature.key)]
123
- end
124
- result
28
+ def cache_delete(key)
29
+ @cache.delete(key)
125
30
  end
126
31
  end
127
32
  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
@@ -7,7 +7,7 @@ RSpec.describe Flipper::Adapters::Dalli do
7
7
  Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
8
8
  end
9
9
  let(:cache) { Dalli::Client.new(ENV['MEMCACHED_URL']) }
10
- let(:adapter) { described_class.new(memory_adapter, cache) }
10
+ let(:adapter) { described_class.new(memory_adapter, cache, 10) }
11
11
  let(:flipper) { Flipper.new(adapter) }
12
12
 
13
13
  subject { adapter }
@@ -21,6 +21,72 @@ RSpec.describe Flipper::Adapters::Dalli do
21
21
 
22
22
  it_should_behave_like 'a flipper adapter'
23
23
 
24
+ it "knows ttl" do
25
+ expect(adapter.ttl).to eq(10)
26
+ end
27
+
28
+ it "knows features_cache_key" do
29
+ expect(adapter.features_cache_key).to eq("flipper/v1/features")
30
+ end
31
+
32
+ it "can expire features cache" do
33
+ # cache the features
34
+ adapter.features
35
+ expect(cache.get("flipper/v1/features")).not_to be(nil)
36
+
37
+ # expire cache
38
+ adapter.expire_features_cache
39
+ expect(cache.get("flipper/v1/features")).to be(nil)
40
+ end
41
+
42
+ it "can expire feature cache" do
43
+ # cache the features
44
+ adapter.get(flipper[:stats])
45
+ expect(cache.get("flipper/v1/feature/stats")).not_to be(nil)
46
+
47
+ # expire cache
48
+ adapter.expire_feature_cache("stats")
49
+ expect(cache.get("flipper/v1/feature/stats")).to be(nil)
50
+ end
51
+
52
+ it "can generate feature cache key" do
53
+ expect(adapter.feature_cache_key("stats")).to eq("flipper/v1/feature/stats")
54
+ end
55
+
56
+ context "when using a prefix" do
57
+ let(:adapter) { described_class.new(memory_adapter, cache, 0, prefix: "foo/") }
58
+ it_should_behave_like 'a flipper adapter'
59
+
60
+ it "knows features_cache_key" do
61
+ expect(adapter.features_cache_key).to eq("foo/flipper/v1/features")
62
+ end
63
+
64
+ it "can generate feature cache key" do
65
+ expect(adapter.feature_cache_key("stats")).to eq("foo/flipper/v1/feature/stats")
66
+ end
67
+
68
+ it "uses the prefix for all keys" do
69
+ # check individual feature get cached with prefix
70
+ adapter.get(flipper[:stats])
71
+ expect(cache.get("foo/flipper/v1/feature/stats")).not_to be(nil)
72
+
73
+ # check individual feature expired with prefix
74
+ adapter.remove(flipper[:stats])
75
+ expect(cache.get("foo/flipper/v1/feature/stats")).to be(nil)
76
+
77
+ # enable some stuff
78
+ flipper.enable_percentage_of_actors(:search, 10)
79
+ flipper.enable(:stats)
80
+
81
+ # populate the cache
82
+ adapter.get_all
83
+
84
+ # verify cached with prefix
85
+ expect(cache.get("foo/flipper/v1/feature/search")[:percentage_of_actors]).to eq("10")
86
+ expect(cache.get("foo/flipper/v1/feature/stats")[:boolean]).to eq("true")
87
+ end
88
+ end
89
+
24
90
  describe '#remove' do
25
91
  it 'expires feature' do
26
92
  feature = flipper[:stats]
@@ -68,17 +134,19 @@ RSpec.describe Flipper::Adapters::Dalli do
68
134
  adapter.get_all
69
135
  expect(cache.get("flipper/v1/feature/#{stats.key}")[:boolean]).to eq('true')
70
136
  expect(cache.get("flipper/v1/feature/#{search.key}")[:boolean]).to be(nil)
71
- expect(cache.get("flipper/v1/get_all")).to be_within(2).of(Time.now.to_i)
137
+ expect(cache.get("flipper/v1/features")).to eq(Set["stats", "search"])
72
138
  end
73
139
 
74
140
  it 'returns same result when already cached' do
75
141
  expect(adapter.get_all).to eq(adapter.get_all)
76
142
  end
77
143
 
78
- it 'only invokes one call to wrapped adapter' do
144
+ it 'only invokes two calls to wrapped adapter (for features set and gate data for each feature in set)' do
79
145
  memory_adapter.reset
80
146
  5.times { adapter.get_all }
81
- expect(memory_adapter.count(:get_all)).to eq(1)
147
+ expect(memory_adapter.count(:features)).to eq(1)
148
+ expect(memory_adapter.count(:get_multi)).to eq(1)
149
+ expect(memory_adapter.count).to eq(2)
82
150
  end
83
151
  end
84
152
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-dalli
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: dalli
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -64,7 +64,7 @@ metadata:
64
64
  homepage_uri: https://www.flippercloud.io
65
65
  source_code_uri: https://github.com/flippercloud/flipper
66
66
  bug_tracker_uri: https://github.com/flippercloud/flipper/issues
67
- changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.2.2
67
+ changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.0
68
68
  post_install_message:
69
69
  rdoc_options: []
70
70
  require_paths: