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_featuresObject



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

Returns:

  • (Boolean)


179
180
181
182
183
184
# File 'lib/abstract_feature_branch.rb', line 179

def cacheable?
  # TODO Make thread-safe
  value = downcase_keys(cacheable)[application_environment]
  value = (application_environment != 'development') if value.nil?
  value
end

.clear_store_featuresObject

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

.configurationObject



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_environmentsObject



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_overridesObject



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

.featuresObject



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_featuresObject

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_featuresObject



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/abstract_feature_branch.rb', line 146

def load_application_features
  if @load_application_features.nil?
    MUTEX[:load_application_features].synchronize do
      if @load_application_features.nil?
        AbstractFeatureBranch.load_redis_overrides
        AbstractFeatureBranch.load_environment_variable_overrides
        AbstractFeatureBranch.load_features
        AbstractFeatureBranch.load_local_features
        AbstractFeatureBranch.load_environment_features(application_environment)
        AbstractFeatureBranch.load_redis_scoped_features
        @unload_application_features = nil
        @load_application_features = true
      end
    end
  end
end

.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_overridesObject



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_featuresObject



86
87
88
89
# File 'lib/abstract_feature_branch.rb', line 86

def load_features
  @features = {}
  load_specific_features(@features, '.yml')
end

.load_local_featuresObject



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_overridesObject



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_featuresObject



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_featuresObject



75
76
77
# File 'lib/abstract_feature_branch.rb', line 75

def local_features
  memoize_thread_safe(:@local_features, :load_local_features)
end

.redis_overridesObject



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_featuresObject



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

Returns:

  • (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



256
257
258
259
260
261
# File 'lib/abstract_feature_branch.rb', line 256

def scopes_for_feature(feature)
  normalized_feature_name = feature.to_s.downcase
  AbstractFeatureBranch.
    feature_store.
    smembers("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}#{normalized_feature_name}")
end

.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



246
247
248
249
250
251
252
253
254
# File 'lib/abstract_feature_branch.rb', line 246

def toggled_features_for_scope(scope)
  AbstractFeatureBranch.feature_store.keys.select do |key|
    key.start_with?(AbstractFeatureBranch::ENV_FEATURE_PREFIX)
  end.map do |key|
    feature = key.sub(AbstractFeatureBranch::ENV_FEATURE_PREFIX, '')
  end.select do |feature|
    scopes_for_feature(feature).include?(scope.to_s)
  end
end

.unload_application_featuresObject



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