Class: Rollout

Inherits:
Object
  • Object
show all
Includes:
Observable
Defined in:
lib/rollout.rb,
lib/rollout/feature.rb,
lib/rollout/logging.rb,
lib/rollout/version.rb,
lib/rollout/feature_state.rb

Defined Under Namespace

Modules: Logging Classes: Feature, FeatureState

Constant Summary collapse

RAND_BASE =
(2**32 - 1) / 100.0
VERSION =
'3.1.0'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(adapter:, **options) ⇒ Rollout

Returns a new instance of Rollout.



18
19
20
21
22
23
24
# File 'lib/rollout.rb', line 18

def initialize(adapter:, **options)
  @adapter = adapter
  @options = options
  @groups  = { all: ->(_user) { true } }

  extend(Logging) if options[:logging]
end

Instance Attribute Details

#adapter ⇒ Object (readonly)

Returns the value of attribute adapter.



16
17
18
# File 'lib/rollout.rb', line 16

def adapter
  @adapter
end

#options ⇒ Object (readonly)

Returns the value of attribute options.



16
17
18
# File 'lib/rollout.rb', line 16

def options
  @options
end

Instance Method Details

#activate(feature) ⇒ Object



30
31
32
33
34
# File 'lib/rollout.rb', line 30

def activate(feature)
  with_feature(feature) do |f|
    f.percentage = 100
  end
end

#activate_group(feature, group) ⇒ Object



58
59
60
61
62
# File 'lib/rollout.rb', line 58

def activate_group(feature, group)
  with_feature(feature) do |f|
    f.add_group(group)
  end
end

#activate_percentage(feature, percentage) ⇒ Object



119
120
121
122
123
# File 'lib/rollout.rb', line 119

def activate_percentage(feature, percentage)
  with_feature(feature) do |f|
    f.percentage = percentage
  end
end

#activate_user(feature, user) ⇒ Object



70
71
72
73
74
# File 'lib/rollout.rb', line 70

def activate_user(feature, user)
  with_feature(feature) do |f|
    f.add_user(user)
  end
end

#activate_users(feature, users) ⇒ Object



82
83
84
85
86
# File 'lib/rollout.rb', line 82

def activate_users(feature, users)
  with_feature(feature) do |f|
    users.each { |user| f.add_user(user) }
  end
end

#active?(feature, user = nil) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
108
# File 'lib/rollout.rb', line 105

def active?(feature, user = nil)
  feature = get(feature)
  feature.active?(user)
end

#active_features(user = nil) ⇒ Object



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

def active_features(user = nil)
  multi_get(*features).select do |f|
    f.active?(user)
  end.map(&:name)
end

#active_in_group?(group, user) ⇒ Boolean

Returns:

  • (Boolean)


131
132
133
134
# File 'lib/rollout.rb', line 131

def active_in_group?(group, user)
  f = @groups[group.to_sym]
  f&.call(user)
end

#clear! ⇒ Object



181
182
183
184
185
186
187
# File 'lib/rollout.rb', line 181

def clear!
  features.each do |feature|
    with_feature(feature, &:clear)
    @adapter.delete_feature(feature)
  end
  @adapter.clear_features
end

#clear_feature_data(feature) ⇒ Object



151
152
153
154
155
# File 'lib/rollout.rb', line 151

def clear_feature_data(feature)
  with_feature(feature) do |f|
    f.data = {}
  end
end

#deactivate(feature) ⇒ Object



36
37
38
# File 'lib/rollout.rb', line 36

def deactivate(feature)
  with_feature(feature, &:clear)
end

#deactivate_group(feature, group) ⇒ Object



64
65
66
67
68
# File 'lib/rollout.rb', line 64

def deactivate_group(feature, group)
  with_feature(feature) do |f|
    f.remove_group(group)
  end
end

#deactivate_percentage(feature) ⇒ Object



125
126
127
128
129
# File 'lib/rollout.rb', line 125

def deactivate_percentage(feature)
  with_feature(feature) do |f|
    f.percentage = 0
  end
end

#deactivate_user(feature, user) ⇒ Object



76
77
78
79
80
# File 'lib/rollout.rb', line 76

def deactivate_user(feature, user)
  with_feature(feature) do |f|
    f.remove_user(user)
  end
end

#deactivate_users(feature, users) ⇒ Object



88
89
90
91
92
# File 'lib/rollout.rb', line 88

def deactivate_users(feature, users)
  with_feature(feature) do |f|
    users.each { |user| f.remove_user(user) }
  end
end

#define_group(group, &block) ⇒ Object



101
102
103
# File 'lib/rollout.rb', line 101

def define_group(group, &block)
  @groups[group.to_sym] = block
end

#delete(feature) ⇒ Object



40
41
42
43
44
45
46
# File 'lib/rollout.rb', line 40

def delete(feature)
  @adapter.delete_feature(feature)

  if respond_to?(:logging)
    logging.delete(feature)
  end
end

#exists?(feature) ⇒ Boolean

Returns:

  • (Boolean)


189
190
191
# File 'lib/rollout.rb', line 189

def exists?(feature)
  @adapter.feature_exists?(feature)
end

#feature_states(user = nil) ⇒ Object



169
170
171
172
173
# File 'lib/rollout.rb', line 169

def feature_states(user = nil)
  multi_get(*features).each_with_object({}) do |f, hash|
    hash[f.name] = f.active?(user)
  end
end

#features ⇒ Object



165
166
167
# File 'lib/rollout.rb', line 165

def features
  @adapter.feature_names.map(&:to_sym)
end

#get(feature) ⇒ Object



136
137
138
139
140
141
142
143
# File 'lib/rollout.rb', line 136

def get(feature)
  Feature.new(
    state: @adapter.fetch_feature(feature),
    rollout: self,
    options: @options,
    name: feature,
  )
end

#groups ⇒ Object



26
27
28
# File 'lib/rollout.rb', line 26

def groups
  @groups.keys
end

#inactive?(feature, user = nil) ⇒ Boolean

Returns:

  • (Boolean)


115
116
117
# File 'lib/rollout.rb', line 115

def inactive?(feature, user = nil)
  !active?(feature, user)
end

#multi_get(*features) ⇒ Object



157
158
159
160
161
162
163
# File 'lib/rollout.rb', line 157

def multi_get(*features)
  return [] if features.empty?

  @adapter.fetch_features(features).zip(features).map do |state, name|
    Feature.new(state: state, rollout: self, options: @options, name: name)
  end
end

#set(feature, desired_state) ⇒ Object



48
49
50
51
52
53
54
55
56
# File 'lib/rollout.rb', line 48

def set(feature, desired_state)
  with_feature(feature) do |f|
    if desired_state
      f.percentage = 100
    else
      f.clear
    end
  end
end

#set_feature_data(feature, data) ⇒ Object



145
146
147
148
149
# File 'lib/rollout.rb', line 145

def set_feature_data(feature, data)
  with_feature(feature) do |f|
    f.data.merge!(data) if data.is_a? Hash
  end
end

#set_users(feature, users) ⇒ Object



94
95
96
97
98
99
# File 'lib/rollout.rb', line 94

def set_users(feature, users)
  with_feature(feature) do |f|
    f.users = []
    users.each { |user| f.add_user(user) }
  end
end

#user_in_active_users?(feature, user = nil) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
113
# File 'lib/rollout.rb', line 110

def user_in_active_users?(feature, user = nil)
  feature = get(feature)
  feature.user_in_active_users?(user)
end

#with_feature(feature) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/rollout.rb', line 193

def with_feature(feature)
  mutated = nil
  before = nil
  capture_logging = logging_capture?
  notify = count_observers > 0
  snapshot = notify || capture_logging

  @adapter.mutate_feature(feature) do |current_state|
    mutated = Feature.new(
      state: current_state,
      rollout: self,
      options: @options,
      name: feature,
    )
    before = mutated.deep_clone if snapshot
    yield mutated

    state = mutated.to_feature_state
    mutated = Feature.new(
      state: state,
      rollout: self,
      options: @options,
      name: feature,
    )
    event = capture_logging ? logging.event_for(before, mutated) : nil
    result = { state: state, event: event }
    if event
      result[:history_length] = logging.history_length
      result[:global] = logging.global
    end
    result
  end

  if notify
    changed
    notify_observers(:update, before, mutated)
  end

  mutated
end