Module: AbstractFeatureBranch
- Extended by:
- Memoizable, Forwardable
- Defined in:
- lib/abstract_feature_branch.rb,
lib/abstract_feature_branch/memoizable.rb,
lib/abstract_feature_branch/configuration.rb,
lib/generators/abstract_feature_branch/context_generator.rb,
lib/generators/abstract_feature_branch/install_generator.rb,
lib/abstract_feature_branch/redis/connection_pool_to_redis_adapter.rb
Defined Under Namespace
Modules: FileBeautifier, Generators, Memoizable, Redis
Classes: Configuration
Constant Summary
collapse
- ENV_FEATURE_PREFIX =
"abstract_feature_branch_"
- REDIS_HKEY =
"abstract_feature_branch"
- VALUE_SCOPED =
'scoped'
- SCOPED_SPECIAL_VALUES =
[VALUE_SCOPED, 'per_user', 'per-user', 'per user']
- MUTEX =
{
'@configuration': Mutex.new,
'@redis_overrides': Mutex.new,
'@environment_variable_overrides': Mutex.new,
'@local_features': Mutex.new,
'@features': Mutex.new,
'@environment_features': Mutex.new,
'@redis_scoped_features': Mutex.new,
'environment_features': Mutex.new,
'load_application_features': Mutex.new,
'unload_application_features': Mutex.new,
}
Class Method Summary
collapse
Class Method Details
.application_features ⇒ Object
142
143
144
145
|
# File 'lib/abstract_feature_branch.rb', line 142
def application_features
unload_application_features if !cacheable?
environment_features(application_environment)
end
|
.cacheable? ⇒ Boolean
179
180
181
182
183
184
|
# File 'lib/abstract_feature_branch.rb', line 179
def cacheable?
value = downcase_keys(cacheable)[application_environment]
value = (application_environment != 'development') if value.nil?
value
end
|
.clear_store_features ⇒ Object
Gets features array (all features) from storage (e.g. Redis client)
228
229
230
231
232
233
|
# File 'lib/abstract_feature_branch.rb', line 228
def clear_store_features
raise 'Feature storage (e.g. Redis) is not setup!' if feature_store.nil?
feature_store.hkeys(REDIS_HKEY).each do |feature|
feature_store.hdel(REDIS_HKEY, feature)
end
end
|
.configuration ⇒ Object
48
49
50
|
# File 'lib/abstract_feature_branch.rb', line 48
def configuration
memoize_thread_safe(:@configuration) { Configuration.new }
end
|
.delete_store_feature(feature) ⇒ Object
Gets feature value (true or false) from storage (e.g. Redis client)
215
216
217
218
219
|
# File 'lib/abstract_feature_branch.rb', line 215
def delete_store_feature(feature)
raise 'Feature storage (e.g. Redis) is not setup!' if feature_store.nil?
feature = feature.to_s
feature_store.hdel(REDIS_HKEY, feature)
end
|
.environment_features(environment) ⇒ Object
performance optimization via caching of feature values resolved through environment variable overrides and local features
92
93
94
95
96
97
98
99
100
101
102
|
# File 'lib/abstract_feature_branch.rb', line 92
def environment_features(environment)
if environment_features_for_all_environments[environment].nil?
MUTEX[:environment_features].synchronize do
if environment_features_for_all_environments[environment].nil?
environment_features_for_all_environments[environment] = load_environment_features(environment)
end
@unload_application_features = nil
end
end
environment_features_for_all_environments[environment]
end
|
.environment_features_for_all_environments ⇒ Object
113
114
115
|
# File 'lib/abstract_feature_branch.rb', line 113
def environment_features_for_all_environments
memoize_thread_safe(:@environment_features) { {} }
end
|
.environment_variable_overrides ⇒ Object
68
69
70
|
# File 'lib/abstract_feature_branch.rb', line 68
def environment_variable_overrides
memoize_thread_safe(:@environment_variable_overrides, :load_environment_variable_overrides)
end
|
83
84
85
|
# File 'lib/abstract_feature_branch.rb', line 83
def features
memoize_thread_safe(:@features, :load_features)
end
|
.get_store_feature(feature) ⇒ Object
Gets feature value (true or false) from storage (e.g. Redis client)
201
202
203
204
205
206
207
208
209
210
211
212
|
# File 'lib/abstract_feature_branch.rb', line 201
def get_store_feature(feature)
raise 'Feature storage (e.g. Redis) is not setup!' if feature_store.nil?
feature = feature.to_s
value = feature_store.hget(REDIS_HKEY, feature)
if value.nil?
matching_feature = get_store_features.find { |store_feature| store_feature.downcase == feature.downcase }
value = feature_store.hget(REDIS_HKEY, matching_feature) if matching_feature
end
return nil if value.nil?
return VALUE_SCOPED if scoped_value?(value)
value.to_s.downcase == 'true'
end
|
.get_store_features ⇒ Object
Gets features array (all features) from storage (e.g. Redis client)
222
223
224
225
|
# File 'lib/abstract_feature_branch.rb', line 222
def get_store_features
raise 'Feature storage (e.g. Redis) is not setup!' if feature_store.nil?
feature_store.hkeys(REDIS_HKEY)
end
|
.load_application_features ⇒ Object
.load_environment_features(environment) ⇒ Object
103
104
105
106
107
108
109
110
111
|
# File 'lib/abstract_feature_branch.rb', line 103
def load_environment_features(environment)
@environment_features ||= {}
features[environment] ||= {}
local_features[environment] ||= {}
@environment_features[environment] = features[environment].
merge(local_features[environment]).
merge(environment_variable_overrides).
merge(redis_overrides)
end
|
.load_environment_variable_overrides ⇒ Object
71
72
73
|
# File 'lib/abstract_feature_branch.rb', line 71
def load_environment_variable_overrides
@environment_variable_overrides = featureize_keys(downcase_keys(booleanize_values(select_feature_keys(ENV))))
end
|
.load_features ⇒ Object
86
87
88
89
|
# File 'lib/abstract_feature_branch.rb', line 86
def load_features
@features = {}
load_specific_features(@features, '.yml')
end
|
.load_local_features ⇒ Object
78
79
80
81
|
# File 'lib/abstract_feature_branch.rb', line 78
def load_local_features
@local_features = {}
load_specific_features(@local_features, '.local.yml')
end
|
.load_redis_overrides ⇒ Object
55
56
57
58
59
60
61
62
63
64
65
66
|
# File 'lib/abstract_feature_branch.rb', line 55
def load_redis_overrides
return (@redis_overrides = {}) if feature_store.nil?
redis_feature_hash = get_store_features.inject({}) do |output, feature|
output.merge(feature => get_store_feature(feature))
end
@redis_overrides = downcase_keys(redis_feature_hash)
rescue Exception => error
AbstractFeatureBranch.logger.error "AbstractFeatureBranch encounter an error in loading Redis Overrides!\n\nError:#{error.full_message}\n\n"
@redis_overrides = {}
end
|
.load_redis_scoped_features ⇒ Object
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
# File 'lib/abstract_feature_branch.rb', line 120
def load_redis_scoped_features
@redis_scoped_features = {}
return @redis_scoped_features if AbstractFeatureBranch.configuration.feature_store_live_fetching?
@environment_features.each do |environment, features|
features.each do |feature, value|
if SCOPED_SPECIAL_VALUES.include?(value.to_s.downcase)
normalized_feature_name = feature.to_s.downcase
@redis_scoped_features[normalized_feature_name] ||= []
begin
@redis_scoped_features[normalized_feature_name] += scopes_for_feature(normalized_feature_name)
rescue Exception => error
AbstractFeatureBranch.logger.error "AbstractFeatureBranch encountered an error in retrieving Per-User values for feature \"#{normalized_feature_name}\"! Defaulting to no values...\n\nError: #{error.full_message}\n\n"
nil
end
end
end
end
@redis_scoped_features
end
|
.local_features ⇒ Object
75
76
77
|
# File 'lib/abstract_feature_branch.rb', line 75
def local_features
memoize_thread_safe(:@local_features, :load_local_features)
end
|
.redis_overrides ⇒ Object
52
53
54
|
# File 'lib/abstract_feature_branch.rb', line 52
def redis_overrides
memoize_thread_safe(:@redis_overrides, :load_redis_overrides)
end
|
.redis_scoped_features ⇒ Object
117
118
119
|
# File 'lib/abstract_feature_branch.rb', line 117
def redis_scoped_features
memoize_thread_safe(:@redis_scoped_features, :load_redis_scoped_features)
end
|
.scoped_value?(value) ⇒ Boolean
186
187
188
|
# File 'lib/abstract_feature_branch.rb', line 186
def scoped_value?(value)
SCOPED_SPECIAL_VALUES.include?(value.to_s.downcase)
end
|
.scopes_for_feature(feature) ⇒ Object
.set_store_feature(feature, value) ⇒ Object
Sets feature value (true or false) in storage (e.g. Redis client)
191
192
193
194
195
196
197
198
|
# File 'lib/abstract_feature_branch.rb', line 191
def set_store_feature(feature, value)
raise 'Feature storage (e.g. Redis) is not setup!' if feature_store.nil?
feature = feature.to_s
return delete_store_feature(feature) if value.nil?
value = 'true' if value == true
value = 'false' if value == false
feature_store.hset(REDIS_HKEY, feature, value)
end
|
.toggle_features_for_scope(scope, features) ⇒ Object
Also known as:
toggle_features_for_user
235
236
237
238
239
240
241
242
243
|
# File 'lib/abstract_feature_branch.rb', line 235
def toggle_features_for_scope(scope, features)
features.each do |name, value|
if value
feature_store.sadd("#{ENV_FEATURE_PREFIX}#{name.to_s.downcase}", scope)
else
feature_store.srem("#{ENV_FEATURE_PREFIX}#{name.to_s.downcase}", scope)
end
end
end
|
.toggled_features_for_scope(scope) ⇒ Object
.unload_application_features ⇒ Object
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
# File 'lib/abstract_feature_branch.rb', line 162
def unload_application_features
if @unload_application_features.nil?
MUTEX[:unload_application_features].synchronize do
if @unload_application_features.nil?
@redis_overrides = nil
@environment_variable_overrides = nil
@features = nil
@local_features = nil
@environment_features = nil
@redis_scoped_features = nil
@load_application_features = nil
@unload_application_features = true
end
end
end
end
|