arturo 2.5.0 → 2.5.1
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 +4 -4
- data/README.md +15 -0
- data/lib/arturo.rb +9 -1
- data/lib/arturo/feature.rb +1 -1
- data/lib/arturo/feature_caching.rb +92 -9
- data/lib/arturo/null_logger.rb +8 -0
- data/lib/arturo/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bbc3960e60444e79f01132e5ce7aa3a52ec0a95e45218bf69a7d215b5e4b1af8
|
4
|
+
data.tar.gz: 1b999b93f95b7f4a388b50fad8f029727fccf7cf1a599b24ca1b15c2b293f000
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6d4d6fc203bc4b40a01170a19b0a155b929f137af63c0fef84cf98f0a543ac1a4cea34c23d042c3872611621910ad0a6481570f78e0f012abeba041c815abaa
|
7
|
+
data.tar.gz: d73e30ce3fb43583ca4d17aba91a24d9fb796117e8c1b3cad57d8afdf284a4904ec0b95b4a05b371c7e9ef1a201d54ad6fa5e4425da6b406ea5ce49d6680e828
|
data/README.md
CHANGED
@@ -102,6 +102,7 @@ rake db:migrate
|
|
102
102
|
Open up the newly-generated `config/initializers/arturo_initializer.rb`.
|
103
103
|
There are configuration options for the following:
|
104
104
|
|
105
|
+
* logging capabilities (see [logging](#logging))
|
105
106
|
* the method that determines whether a user has permission to manage features
|
106
107
|
(see [admin permissions](#adminpermissions))
|
107
108
|
* the method that returns the object that has features
|
@@ -126,6 +127,15 @@ work with you on support for your favorite framework.
|
|
126
127
|
|
127
128
|
## Deep-Dive
|
128
129
|
|
130
|
+
### <span id='logging'>Logging</span>
|
131
|
+
|
132
|
+
You can provide a logger in order to inspect Arturo usage.
|
133
|
+
A potential implementation for Rails would be:
|
134
|
+
|
135
|
+
```Ruby
|
136
|
+
Arturo.logger = Rails.logger
|
137
|
+
```
|
138
|
+
|
129
139
|
### <span id='adminpermissions'>Admin Permissions</span>
|
130
140
|
|
131
141
|
`Arturo::FeatureManagement#may_manage_features?` is a method that is run in
|
@@ -314,6 +324,11 @@ Arturo::Feature.warm_cache!
|
|
314
324
|
|
315
325
|
This will pre-fetch all `Feature`s and put them in the cache.
|
316
326
|
|
327
|
+
To use the current cache state when you can't fetch updates from origin:
|
328
|
+
|
329
|
+
```Ruby
|
330
|
+
Arturo::Feature.extend_cache_on_failure = true
|
331
|
+
```
|
317
332
|
|
318
333
|
The following is the **intended** support for integration with view caching:
|
319
334
|
|
data/lib/arturo.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Arturo
|
3
|
-
|
3
|
+
require 'arturo/null_logger'
|
4
4
|
require 'arturo/special_handling'
|
5
5
|
require 'arturo/feature_availability'
|
6
6
|
require 'arturo/feature_management'
|
@@ -16,5 +16,13 @@ module Arturo
|
|
16
16
|
f = self::Feature.to_feature(feature_name)
|
17
17
|
f && f.enabled_for?(recipient)
|
18
18
|
end
|
19
|
+
|
20
|
+
def logger=(logger)
|
21
|
+
@logger = logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger
|
25
|
+
@logger || NullLogger.new
|
26
|
+
end
|
19
27
|
end
|
20
28
|
end
|
data/lib/arturo/feature.rb
CHANGED
@@ -15,7 +15,7 @@ module Arturo
|
|
15
15
|
attr_readonly :symbol
|
16
16
|
|
17
17
|
validates_presence_of :symbol, :deployment_percentage
|
18
|
-
validates_uniqueness_of :symbol, :allow_blank => true
|
18
|
+
validates_uniqueness_of :symbol, :allow_blank => true, :case_sensitive => false
|
19
19
|
validates_numericality_of :deployment_percentage,
|
20
20
|
:only_integer => true,
|
21
21
|
:allow_blank => true,
|
@@ -37,15 +37,21 @@ module Arturo
|
|
37
37
|
class << base
|
38
38
|
prepend PrependMethods
|
39
39
|
attr_accessor :cache_ttl, :feature_cache, :feature_caching_strategy
|
40
|
+
attr_writer :extend_cache_on_failure
|
40
41
|
end
|
41
42
|
base.send(:after_save) do |f|
|
42
43
|
f.class.feature_caching_strategy.expire(f.class.feature_cache, f.symbol.to_sym) if f.class.caches_features?
|
43
44
|
end
|
44
45
|
base.cache_ttl = 0
|
46
|
+
base.extend_cache_on_failure = false
|
45
47
|
base.feature_cache = Arturo::FeatureCaching::Cache.new
|
46
48
|
base.feature_caching_strategy = AllStrategy
|
47
49
|
end
|
48
50
|
|
51
|
+
def extend_cache_on_failure?
|
52
|
+
!!@extend_cache_on_failure
|
53
|
+
end
|
54
|
+
|
49
55
|
def caches_features?
|
50
56
|
self.cache_ttl.to_i > 0
|
51
57
|
end
|
@@ -56,13 +62,20 @@ module Arturo
|
|
56
62
|
|
57
63
|
class AllStrategy
|
58
64
|
class << self
|
65
|
+
##
|
66
|
+
# @param cache [Arturo::Cache] cache backend
|
67
|
+
# @param symbol [Symbol] arturo identifier
|
68
|
+
# @return [Arturo::Feature, Arturo::NoSuchFeature]
|
69
|
+
#
|
59
70
|
def fetch(cache, symbol, &block)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
71
|
+
existing_features = cache.read("arturo.all")
|
72
|
+
|
73
|
+
features = if cache_is_current?(cache, existing_features)
|
74
|
+
existing_features
|
75
|
+
else
|
76
|
+
arturos_from_origin(fallback: existing_features).tap do |updated_features|
|
77
|
+
update_and_extend_cache!(cache, updated_features)
|
78
|
+
end
|
66
79
|
end
|
67
80
|
|
68
81
|
features[symbol] || Arturo::NoSuchFeature.new(symbol)
|
@@ -74,15 +87,85 @@ module Arturo
|
|
74
87
|
|
75
88
|
private
|
76
89
|
|
90
|
+
##
|
91
|
+
# @param fallback [Hash] features to use on database failure
|
92
|
+
# @return [Hash] updated features from origin or fallback
|
93
|
+
# @raise [ActiveRecord::ActiveRecordError] on database failure
|
94
|
+
# without cache extension option
|
95
|
+
#
|
96
|
+
def arturos_from_origin(fallback:)
|
97
|
+
Hash[Arturo::Feature.all.map { |f| [f.symbol.to_sym, f] }]
|
98
|
+
rescue ActiveRecord::ActiveRecordError
|
99
|
+
raise unless Arturo::Feature.extend_cache_on_failure?
|
100
|
+
|
101
|
+
if fallback.blank?
|
102
|
+
log_empty_cache
|
103
|
+
raise
|
104
|
+
else
|
105
|
+
log_stale_cache
|
106
|
+
fallback
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# @return [Boolean] whether the current cache has to be updated from origin
|
112
|
+
# @raise [ActiveRecord::ActiveRecordError] on database failure
|
113
|
+
# without cache extension option
|
114
|
+
#
|
77
115
|
def cache_is_current?(cache, features)
|
78
116
|
return unless features
|
79
117
|
return true if cache.read("arturo.current")
|
80
|
-
|
118
|
+
|
119
|
+
begin
|
120
|
+
return false if origin_changed?(features)
|
121
|
+
rescue ActiveRecord::ActiveRecordError
|
122
|
+
raise unless Arturo::Feature.extend_cache_on_failure?
|
123
|
+
|
124
|
+
if features.blank?
|
125
|
+
log_empty_cache
|
126
|
+
raise
|
127
|
+
else
|
128
|
+
log_stale_cache
|
129
|
+
update_and_extend_cache!(cache, features)
|
130
|
+
end
|
131
|
+
|
132
|
+
return true
|
133
|
+
end
|
81
134
|
mark_as_current!(cache)
|
82
135
|
end
|
83
136
|
|
137
|
+
def formatted_log(namespace, msg)
|
138
|
+
"[Arturo][#{namespace}] #{msg}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def log_empty_cache
|
142
|
+
Arturo.logger.error(formatted_log('extend_cache_on_failure', 'Fallback cache is empty'))
|
143
|
+
end
|
144
|
+
|
145
|
+
def log_stale_cache
|
146
|
+
Arturo.logger.warn(formatted_log('extend_cache_on_failure', 'Falling back to stale cache'))
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# @return [True]
|
151
|
+
#
|
84
152
|
def mark_as_current!(cache)
|
85
|
-
cache.write("arturo.current", true, :
|
153
|
+
cache.write("arturo.current", true, expires_in: Arturo::Feature.cache_ttl)
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# The Arturo origin might return a big payload, so checking for the latest
|
158
|
+
# update is a cheaper operation.
|
159
|
+
#
|
160
|
+
# @return [Boolean] if origin has been updated since the last cache update.
|
161
|
+
#
|
162
|
+
def origin_changed?(features)
|
163
|
+
features.values.map(&:updated_at).compact.max != Arturo::Feature.maximum(:updated_at)
|
164
|
+
end
|
165
|
+
|
166
|
+
def update_and_extend_cache!(cache, features)
|
167
|
+
mark_as_current!(cache)
|
168
|
+
cache.write("arturo.all", features, expires_in: Arturo::Feature.cache_ttl * 10)
|
86
169
|
end
|
87
170
|
end
|
88
171
|
end
|
@@ -92,7 +175,7 @@ module Arturo
|
|
92
175
|
if feature = cache.read("arturo.#{symbol}")
|
93
176
|
feature
|
94
177
|
else
|
95
|
-
cache.write("arturo.#{symbol}", yield || Arturo::NoSuchFeature.new(symbol), :
|
178
|
+
cache.write("arturo.#{symbol}", yield || Arturo::NoSuchFeature.new(symbol), expires_in: Arturo::Feature.cache_ttl)
|
96
179
|
end
|
97
180
|
end
|
98
181
|
|
data/lib/arturo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arturo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.5.
|
4
|
+
version: 2.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James A. Rosen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-03-
|
11
|
+
date: 2020-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -168,6 +168,7 @@ files:
|
|
168
168
|
- lib/arturo/feature_params_support.rb
|
169
169
|
- lib/arturo/middleware.rb
|
170
170
|
- lib/arturo/no_such_feature.rb
|
171
|
+
- lib/arturo/null_logger.rb
|
171
172
|
- lib/arturo/special_handling.rb
|
172
173
|
- lib/arturo/test_support.rb
|
173
174
|
- lib/arturo/version.rb
|