Class: SimpleFeatureFlags::RedisStorage

Inherits:
BaseStorage show all
Defined in:
lib/simple_feature_flags/redis_storage.rb

Overview

Stores feature flags in Redis.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(redis, file) ⇒ RedisStorage

: ((::Redis | ::Redis::Namespace) redis, String file) -> void



21
22
23
24
25
26
27
# File 'lib/simple_feature_flags/redis_storage.rb', line 21

def initialize(redis, file)
  @file = file
  @redis = redis
  @mandatory_flags = []

  import_flags_from_file
end

Instance Attribute Details

#fileObject (readonly)

: String



11
12
13
# File 'lib/simple_feature_flags/redis_storage.rb', line 11

def file
  @file
end

#mandatory_flagsObject (readonly)

: Array



15
16
17
# File 'lib/simple_feature_flags/redis_storage.rb', line 15

def mandatory_flags
  @mandatory_flags
end

#redisObject (readonly)

: (::Redis | ::Redis::Namespace)



18
19
20
# File 'lib/simple_feature_flags/redis_storage.rb', line 18

def redis
  @redis
end

Instance Method Details

#activate(feature) ⇒ Object Also known as: activate_globally

Activates the given flag. Returns ‘false` if it does not exist. : ((Symbol | String) feature) -> bool



202
203
204
205
206
207
208
# File 'lib/simple_feature_flags/redis_storage.rb', line 202

def activate(feature)
  return false unless exists?(feature)

  redis.hset(feature.to_s, 'active', 'globally')

  true
end

#activate_for(feature, *objects, object_id_method: CONFIG.default_id_method) ⇒ Object

Activates the given flag for the given objects. Returns ‘false` if it does not exist. : ((Symbol | String) feature, *Object objects, ?object_id_method: Symbol) -> void



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/simple_feature_flags/redis_storage.rb', line 250

def activate_for(feature, *objects, object_id_method: CONFIG.default_id_method)
  return false unless exists?(feature)

  to_activate_hash = objects_to_hash(objects, object_id_method: object_id_method)
  active_objects_hash = active_objects(feature)

  to_activate_hash.each do |klass, ids|
    (active_objects_hash[klass] = ids) && next unless active_objects_hash[klass]

    active_objects_hash[klass]&.concat(ids)&.uniq!&.sort! # rubocop:disable Style/SafeNavigationChainLength
  end

  redis.hset(feature.to_s, 'active_for_objects', active_objects_hash.to_json)

  true
end

#activate_for!(feature, *objects, object_id_method: CONFIG.default_id_method) ⇒ Object

Activates the given flag for the given objects and sets the flag as partially active. Returns ‘false` if it does not exist. : ((Symbol | String) feature, *Object objects, ?object_id_method: Symbol) -> void



271
272
273
274
275
# File 'lib/simple_feature_flags/redis_storage.rb', line 271

def activate_for!(feature, *objects, object_id_method: CONFIG.default_id_method)
  return false unless T.unsafe(self).activate_for(feature, *objects, object_id_method: object_id_method)

  activate_partially(feature)
end

#activate_partially(feature) ⇒ Object

Activates the given flag partially. Returns ‘false` if it does not exist. : ((Symbol | String) feature) -> bool



228
229
230
231
232
233
234
# File 'lib/simple_feature_flags/redis_storage.rb', line 228

def activate_partially(feature)
  return false unless exists?(feature)

  redis.hset(feature.to_s, 'active', 'partially')

  true
end

#active(feature) ⇒ Object

Checks whether the flag is active. Returns ‘true`, `false`, `:globally` or `:partially` : ((Symbol | String) feature) -> (Symbol | bool)



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/simple_feature_flags/redis_storage.rb', line 32

def active(feature)
  case redis.hget(feature.to_s, 'active')
  when 'globally'
    :globally
  when 'partially'
    :partially
  when 'true', true
    true
  else
    false
  end
end

#active?(feature) ⇒ Boolean

Checks whether the flag is active. : ((Symbol | String) feature) -> bool

Returns:

  • (Boolean)


48
49
50
51
52
# File 'lib/simple_feature_flags/redis_storage.rb', line 48

def active?(feature)
  return true if active(feature)

  false
end

#active_for?(feature, object, object_id_method: CONFIG.default_id_method) ⇒ Boolean

Checks whether the flag is active for the given object. : ((Symbol | String) feature, Object object, ?object_id_method: Symbol) -> bool

Returns:

  • (Boolean)


92
93
94
95
96
97
98
99
100
101
102
# File 'lib/simple_feature_flags/redis_storage.rb', line 92

def active_for?(feature, object, object_id_method: CONFIG.default_id_method)
  return false unless active?(feature)
  return true if active_globally?(feature)

  active_objects_hash = active_objects(feature)
  active_ids = active_objects_hash[object.class.to_s]

  return false unless active_ids

  active_ids.include? object.public_send(object_id_method)
end

#active_globally?(feature) ⇒ Boolean

Checks whether the flag is active globally, for every object. : ((Symbol | String) feature) -> bool

Returns:

  • (Boolean)


64
65
66
# File 'lib/simple_feature_flags/redis_storage.rb', line 64

def active_globally?(feature)
  ACTIVE_GLOBALLY.include? redis.hget(feature.to_s, 'active')
end

#active_objects(feature) ⇒ Object

Returns a hash of Objects that the given flag is turned on for. The keys are class/model names, values are arrays of IDs of instances/records.

looks like this:

{ "Page" => [25, 89], "Book" => [152] }

: ((Symbol | String) feature) -> Hash[String, Array]



324
325
326
327
328
# File 'lib/simple_feature_flags/redis_storage.rb', line 324

def active_objects(feature)
  ::JSON.parse(redis.hget(feature.to_s, 'active_for_objects').to_s)
rescue ::JSON::ParserError
  {}
end

#active_partially?(feature) ⇒ Boolean

Checks whether the flag is active partially, only for certain objects. : ((Symbol | String) feature) -> bool

Returns:

  • (Boolean)


78
79
80
# File 'lib/simple_feature_flags/redis_storage.rb', line 78

def active_partially?(feature)
  ACTIVE_PARTIALLY.include? redis.hget(feature.to_s, 'active')
end

#add(feature, description = '', active = 'false') ⇒ Object

Adds the given feature flag. : ((Symbol | String) feature, ?String description, ?(String | Symbol | bool)? active) -> Hash[String, top]?



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/simple_feature_flags/redis_storage.rb', line 372

def add(feature, description = '', active = 'false')
  return if exists?(feature)

  active = if ACTIVE_GLOBALLY.include?(active)
             'globally'
           elsif ACTIVE_PARTIALLY.include?(active)
             'partially'
           else
             'false'
           end

  hash = {
    'name'        => feature.to_s,
    'active'      => active,
    'description' => description,
  }

  redis.hset(feature.to_s, hash)
  hash
end

#allObject

Returns the data of all feature flags. : -> Array[Hash[String, top]]



409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/simple_feature_flags/redis_storage.rb', line 409

def all
  keys = []
  hashes = []
  redis.scan_each(match: '*') do |key|
    next if keys.include?(key)

    keys << key
    hashes << get(key)
  end

  hashes
end

#deactivate(feature) ⇒ Object

Deactivates the given flag globally. Does not reset the list of objects that this flag has been turned on for. Returns ‘false` if it does not exist. : ((Symbol | String) feature) -> bool



296
297
298
299
300
301
302
# File 'lib/simple_feature_flags/redis_storage.rb', line 296

def deactivate(feature)
  return false unless exists?(feature)

  redis.hset(feature.to_s, 'active', 'false')

  true
end

#deactivate!(feature) ⇒ Object

Deactivates the given flag for all objects. Resets the list of objects that this flag has been turned on for. Returns ‘false` if it does not exist. : ((Symbol | String) feature) -> bool



282
283
284
285
286
287
288
289
# File 'lib/simple_feature_flags/redis_storage.rb', line 282

def deactivate!(feature)
  return false unless exists?(feature)

  redis.hset(feature.to_s, 'active', 'false')
  redis.hset(feature.to_s, 'active_for_objects', '')

  true
end

#deactivate_for(feature, *objects, object_id_method: CONFIG.default_id_method) ⇒ Object

Deactivates the given flag for the given objects. Returns ‘false` if it does not exist. : ((Symbol | String) feature, *Object objects, ?object_id_method: Symbol) -> void



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/simple_feature_flags/redis_storage.rb', line 333

def deactivate_for(feature, *objects, object_id_method: CONFIG.default_id_method)
  return false unless exists?(feature)

  active_objects_hash = active_objects(feature)

  objects_to_deactivate_hash = objects_to_hash(objects, object_id_method: object_id_method)

  objects_to_deactivate_hash.each do |klass, ids_to_remove|
    active_ids = active_objects_hash[klass]
    next unless active_ids

    active_ids.reject! { |id| ids_to_remove.include? id }
  end

  redis.hset(feature.to_s, 'active_for_objects', active_objects_hash.to_json)

  true
end

#description(feature) ⇒ Object

Returns the description of the flag if it exists. : ((Symbol | String) feature) -> String?



123
124
125
# File 'lib/simple_feature_flags/redis_storage.rb', line 123

def description(feature)
  redis.hget(feature.to_s, 'description')
end

#do_activate(feature, &block) ⇒ Object Also known as: do_activate_globally

: [R] ((Symbol | String) feature) { -> R } -> R



212
213
214
215
216
217
218
219
# File 'lib/simple_feature_flags/redis_storage.rb', line 212

def do_activate(feature, &block)
  feature = feature.to_s
  prev_value = redis.hget(feature, 'active')
  activate(feature)
  block.call
ensure
  redis.hset(feature, 'active', prev_value)
end

#do_activate_partially(feature, &block) ⇒ Object

: [R] ((Symbol | String) feature) { -> R } -> R



238
239
240
241
242
243
244
245
# File 'lib/simple_feature_flags/redis_storage.rb', line 238

def do_activate_partially(feature, &block)
  feature = feature.to_s
  prev_value = redis.hget(feature, 'active')
  activate_partially(feature)
  block.call
ensure
  redis.hset(feature, 'active', prev_value)
end

#do_deactivate(feature, &block) ⇒ Object

: [R] ((Symbol | String) feature) { -> R } -> R



306
307
308
309
310
311
312
313
# File 'lib/simple_feature_flags/redis_storage.rb', line 306

def do_deactivate(feature, &block)
  feature = feature.to_s
  prev_value = redis.hget(feature, 'active')
  deactivate(feature)
  block.call
ensure
  redis.hset(feature, 'active', prev_value)
end

#exists?(feature) ⇒ Boolean

Checks whether the flag exists. : ((Symbol | String) feature) -> bool

Returns:

  • (Boolean)


114
115
116
117
118
# File 'lib/simple_feature_flags/redis_storage.rb', line 114

def exists?(feature)
  return false if [nil, ''].include? redis.hget(feature.to_s, 'name')

  true
end

#get(feature) ⇒ Object

Returns the data of the flag in a hash. : ((Symbol | String) feature) -> Hash[String, top]?



355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/simple_feature_flags/redis_storage.rb', line 355

def get(feature)
  return unless exists?(feature)

  hash = redis.hgetall(feature.to_s)
  hash['mandatory'] = mandatory_flags.include?(feature.to_s)
  hash['active_for_objects'] = begin
    ::JSON.parse(hash['active_for_objects'])
  rescue StandardError
    {}
  end

  hash
end

#inactive?(feature) ⇒ Boolean

Checks whether the flag is inactive. : ((Symbol | String) feature) -> bool

Returns:

  • (Boolean)


57
58
59
# File 'lib/simple_feature_flags/redis_storage.rb', line 57

def inactive?(feature)
  !active?(feature)
end

#inactive_for?(feature, object, object_id_method: CONFIG.default_id_method) ⇒ Boolean

Checks whether the flag is inactive for the given object. : ((Symbol | String) feature, Object object, ?object_id_method: Symbol) -> bool

Returns:

  • (Boolean)


107
108
109
# File 'lib/simple_feature_flags/redis_storage.rb', line 107

def inactive_for?(feature, object, object_id_method: CONFIG.default_id_method)
  !active_for?(feature, object, object_id_method: object_id_method)
end

#inactive_globally?(feature) ⇒ Boolean

Checks whether the flag is inactive globally, for every object. : ((Symbol | String) feature) -> bool

Returns:

  • (Boolean)


71
72
73
# File 'lib/simple_feature_flags/redis_storage.rb', line 71

def inactive_globally?(feature)
  !active_globally?(feature)
end

#inactive_partially?(feature) ⇒ Boolean

Checks whether the flag is inactive partially, only for certain objects. : ((Symbol | String) feature) -> bool

Returns:

  • (Boolean)


85
86
87
# File 'lib/simple_feature_flags/redis_storage.rb', line 85

def inactive_partially?(feature)
  !active_partially?(feature)
end

#namespaced_redisObject

: -> Redis::Namespace?



423
424
425
426
427
428
# File 'lib/simple_feature_flags/redis_storage.rb', line 423

def namespaced_redis
  r = redis
  return unless r.is_a?(Redis::Namespace)

  r
end

#remove(feature) ⇒ Object

Removes the given feature flag. Returns its data or nil if it does not exist. : ((Symbol | String) feature) -> Hash[String, top]?



397
398
399
400
401
402
403
404
# File 'lib/simple_feature_flags/redis_storage.rb', line 397

def remove(feature)
  return unless exists?(feature)

  removed = get(feature)
  redis.del(feature.to_s)

  removed
end

#when_active(feature, &block) ⇒ Object

Calls the given block if the flag is active. : ((Symbol | String) feature) { -> void } -> void



130
131
132
133
134
# File 'lib/simple_feature_flags/redis_storage.rb', line 130

def when_active(feature, &block)
  return unless active?(feature)

  block.call
end

#when_active_for(feature, object, object_id_method: CONFIG.default_id_method, &block) ⇒ Object

Calls the given block if the flag is active for the given object. : ((Symbol | String) feature, Object object, ?object_id_method: Symbol) { -> void } -> void



184
185
186
187
188
# File 'lib/simple_feature_flags/redis_storage.rb', line 184

def when_active_for(feature, object, object_id_method: CONFIG.default_id_method, &block)
  return unless active_for?(feature, object, object_id_method: object_id_method)

  block.call
end

#when_active_globally(feature, &block) ⇒ Object

Calls the given block if the flag is active globally. : ((Symbol | String) feature) { -> void } -> void



148
149
150
151
152
# File 'lib/simple_feature_flags/redis_storage.rb', line 148

def when_active_globally(feature, &block)
  return unless active_globally?(feature)

  block.call
end

#when_active_partially(feature, &block) ⇒ Object

Calls the given block if the flag is active partially. : ((Symbol | String) feature) { -> void } -> void



166
167
168
169
170
# File 'lib/simple_feature_flags/redis_storage.rb', line 166

def when_active_partially(feature, &block)
  return unless active_partially?(feature)

  block.call
end

#when_inactive(feature, &block) ⇒ Object

Calls the given block if the flag is inactive. : ((Symbol | String) feature) { -> void } -> void



139
140
141
142
143
# File 'lib/simple_feature_flags/redis_storage.rb', line 139

def when_inactive(feature, &block)
  return unless inactive?(feature)

  block.call
end

#when_inactive_for(feature, object, object_id_method: CONFIG.default_id_method, &block) ⇒ Object

Calls the given block if the flag is inactive for the given object. : ((Symbol | String) feature, Object object, ?object_id_method: Symbol) { -> void } -> void



193
194
195
196
197
# File 'lib/simple_feature_flags/redis_storage.rb', line 193

def when_inactive_for(feature, object, object_id_method: CONFIG.default_id_method, &block)
  return unless inactive_for?(feature, object, object_id_method: object_id_method)

  block.call
end

#when_inactive_globally(feature, &block) ⇒ Object

Calls the given block if the flag is inactive globally. : ((Symbol | String) feature) { -> void } -> void



157
158
159
160
161
# File 'lib/simple_feature_flags/redis_storage.rb', line 157

def when_inactive_globally(feature, &block)
  return unless inactive_globally?(feature)

  block.call
end

#when_inactive_partially(feature, &block) ⇒ Object

Calls the given block if the flag is inactive partially. : ((Symbol | String) feature) { -> void } -> void



175
176
177
178
179
# File 'lib/simple_feature_flags/redis_storage.rb', line 175

def when_inactive_partially(feature, &block)
  return unless inactive_partially?(feature)

  block.call
end