Class: Featurevisor::Instance

Inherits:
Object
  • Object
show all
Defined in:
lib/featurevisor/instance.rb

Overview

Instance class for managing feature flag evaluations

Constant Summary collapse

EMPTY_DATAFILE =

Empty datafile template

{
  schemaVersion: "2",
  revision: "unknown",
  segments: {},
  features: {}
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Instance

Initialize a new Featurevisor instance

Options Hash (options):

  • :datafile (Hash, String)

    Datafile content or JSON string

  • :context (Hash)

    Initial context

  • :log_level (String)

    Log level

  • :logger (Logger)

    Logger instance

  • :sticky (Hash)

    Sticky features

  • :hooks (Array<Hook>)

    Array of hooks



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/featurevisor/instance.rb', line 26

def initialize(options = {})
  # from options
  @context = options[:context] || {}
  @logger = options[:logger] || Featurevisor.create_logger(level: options[:log_level] || "info")
  @hooks_manager = Featurevisor::Hooks::HooksManager.new(
    hooks: (options[:hooks] || []).map { |hook_data| Featurevisor::Hooks::Hook.new(hook_data) },
    logger: @logger
  )
  @emitter = Featurevisor::Emitter.new
  @sticky = options[:sticky] || {}

  # datafile
  @datafile_reader = Featurevisor::DatafileReader.new(
    datafile: EMPTY_DATAFILE,
    logger: @logger
  )

  if options[:datafile]
    @datafile_reader = Featurevisor::DatafileReader.new(
      datafile: options[:datafile].is_a?(String) ? JSON.parse(options[:datafile], symbolize_names: true) : options[:datafile],
      logger: @logger
    )
  end

  @logger.info("Featurevisor SDK initialized")
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



8
9
10
# File 'lib/featurevisor/instance.rb', line 8

def context
  @context
end

#datafile_readerObject (readonly)

Returns the value of attribute datafile_reader.



8
9
10
# File 'lib/featurevisor/instance.rb', line 8

def datafile_reader
  @datafile_reader
end

#emitterObject (readonly)

Returns the value of attribute emitter.



8
9
10
# File 'lib/featurevisor/instance.rb', line 8

def emitter
  @emitter
end

#hooks_managerObject (readonly)

Returns the value of attribute hooks_manager.



8
9
10
# File 'lib/featurevisor/instance.rb', line 8

def hooks_manager
  @hooks_manager
end

#loggerObject (readonly)

Returns the value of attribute logger.



8
9
10
# File 'lib/featurevisor/instance.rb', line 8

def logger
  @logger
end

#stickyObject (readonly)

Returns the value of attribute sticky.



8
9
10
# File 'lib/featurevisor/instance.rb', line 8

def sticky
  @sticky
end

Instance Method Details

#add_hook(hook) ⇒ Proc?

Add a hook



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

def add_hook(hook)
  @hooks_manager.add(hook)
end

#closeObject

Close the instance



128
129
130
# File 'lib/featurevisor/instance.rb', line 128

def close
  @emitter.clear_all
end

#evaluate_flag(feature_key, context = {}, options = {}) ⇒ Hash

Evaluate a flag



184
185
186
187
188
189
190
191
# File 'lib/featurevisor/instance.rb', line 184

def evaluate_flag(feature_key, context = {}, options = {})
  Featurevisor::Evaluate.evaluate_with_hooks(
    get_evaluation_dependencies(context, options).merge(
      type: "flag",
      feature_key: feature_key
    )
  )
end

#evaluate_variable(feature_key, variable_key, context = {}, options = {}) ⇒ Hash

Evaluate a variable



250
251
252
253
254
255
256
257
258
# File 'lib/featurevisor/instance.rb', line 250

def evaluate_variable(feature_key, variable_key, context = {}, options = {})
  Featurevisor::Evaluate.evaluate_with_hooks(
    get_evaluation_dependencies(context, options).merge(
      type: "variable",
      feature_key: feature_key,
      variable_key: variable_key
    )
  )
end

#evaluate_variation(feature_key, context = {}, options = {}) ⇒ Hash

Evaluate a variation



213
214
215
216
217
218
219
220
# File 'lib/featurevisor/instance.rb', line 213

def evaluate_variation(feature_key, context = {}, options = {})
  Featurevisor::Evaluate.evaluate_with_hooks(
    get_evaluation_dependencies(context, options).merge(
      type: "variation",
      feature_key: feature_key
    )
  )
end

#get_all_evaluations(context = {}, feature_keys = [], options = {}) ⇒ Hash

Get all evaluations



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/featurevisor/instance.rb', line 369

def get_all_evaluations(context = {}, feature_keys = [], options = {})
  result = {}

          keys = feature_keys.size > 0 ? feature_keys : @datafile_reader.get_feature_keys

  keys.each do |feature_key|
    # Convert symbol keys to strings for evaluation functions
    feature_key_str = feature_key.to_s

    # isEnabled
    evaluated_feature = {
      enabled: is_enabled(feature_key_str, context, options)
    }

    # variation
    if @datafile_reader.has_variations?(feature_key_str)
      variation = get_variation(feature_key_str, context, options)
      evaluated_feature[:variation] = variation if variation
    end

    # variables
    variable_keys = @datafile_reader.get_variable_keys(feature_key_str)
    if variable_keys.size > 0
      evaluated_feature[:variables] = {}

      variable_keys.each do |variable_key|
        evaluated_feature[:variables][variable_key] = get_variable(
          feature_key_str,
          variable_key,
          context,
          options
        )
      end
    end

    result[feature_key] = evaluated_feature
  end

  result
end

#get_context(context = nil) ⇒ Hash

Get context



156
157
158
159
160
161
162
163
164
165
# File 'lib/featurevisor/instance.rb', line 156

def get_context(context = nil)
  if context
    {
      **@context,
      **context
    }
  else
    @context
  end
end

#get_feature(feature_key) ⇒ Hash?

Get a feature by key



108
109
110
# File 'lib/featurevisor/instance.rb', line 108

def get_feature(feature_key)
  @datafile_reader.get_feature(feature_key)
end

#get_revisionString

Get the revision



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

def get_revision
  @datafile_reader.get_revision
end

#get_variable(feature_key, variable_key, context = {}, options = {}) ⇒ Object?

Get variable value



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/featurevisor/instance.rb', line 266

def get_variable(feature_key, variable_key, context = {}, options = {})
  begin
    evaluation = evaluate_variable(feature_key, variable_key, context, options)

    if !evaluation[:variable_value].nil?
      if evaluation[:variable_schema] &&
         evaluation[:variable_schema][:type] == "json" &&
         evaluation[:variable_value].is_a?(String)
        JSON.parse(evaluation[:variable_value], symbolize_names: true)
      else
        evaluation[:variable_value]
      end
    else
      nil
    end
  rescue => e
    @logger.error("getVariable", { feature_key: feature_key, variable_key: variable_key, error: e })
    nil
  end
end

#get_variable_array(feature_key, variable_key, context = {}, options = {}) ⇒ Array?

Get variable as array



337
338
339
340
# File 'lib/featurevisor/instance.rb', line 337

def get_variable_array(feature_key, variable_key, context = {}, options = {})
  variable_value = get_variable(feature_key, variable_key, context, options)
  get_value_by_type(variable_value, "array")
end

#get_variable_boolean(feature_key, variable_key, context = {}, options = {}) ⇒ Boolean?

Get variable as boolean



293
294
295
296
# File 'lib/featurevisor/instance.rb', line 293

def get_variable_boolean(feature_key, variable_key, context = {}, options = {})
  variable_value = get_variable(feature_key, variable_key, context, options)
  get_value_by_type(variable_value, "boolean")
end

#get_variable_double(feature_key, variable_key, context = {}, options = {}) ⇒ Float?

Get variable as double



326
327
328
329
# File 'lib/featurevisor/instance.rb', line 326

def get_variable_double(feature_key, variable_key, context = {}, options = {})
  variable_value = get_variable(feature_key, variable_key, context, options)
  get_value_by_type(variable_value, "double")
end

#get_variable_integer(feature_key, variable_key, context = {}, options = {}) ⇒ Integer?

Get variable as integer



315
316
317
318
# File 'lib/featurevisor/instance.rb', line 315

def get_variable_integer(feature_key, variable_key, context = {}, options = {})
  variable_value = get_variable(feature_key, variable_key, context, options)
  get_value_by_type(variable_value, "integer")
end

#get_variable_json(feature_key, variable_key, context = {}, options = {}) ⇒ Object?

Get variable as JSON



359
360
361
362
# File 'lib/featurevisor/instance.rb', line 359

def get_variable_json(feature_key, variable_key, context = {}, options = {})
  variable_value = get_variable(feature_key, variable_key, context, options)
  get_value_by_type(variable_value, "json")
end

#get_variable_object(feature_key, variable_key, context = {}, options = {}) ⇒ Hash?

Get variable as object



348
349
350
351
# File 'lib/featurevisor/instance.rb', line 348

def get_variable_object(feature_key, variable_key, context = {}, options = {})
  variable_value = get_variable(feature_key, variable_key, context, options)
  get_value_by_type(variable_value, "object")
end

#get_variable_string(feature_key, variable_key, context = {}, options = {}) ⇒ String?

Get variable as string



304
305
306
307
# File 'lib/featurevisor/instance.rb', line 304

def get_variable_string(feature_key, variable_key, context = {}, options = {})
  variable_value = get_variable(feature_key, variable_key, context, options)
  get_value_by_type(variable_value, "string")
end

#get_variation(feature_key, context = {}, options = {}) ⇒ String?

Get variation value



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/featurevisor/instance.rb', line 227

def get_variation(feature_key, context = {}, options = {})
  begin
    evaluation = evaluate_variation(feature_key, context, options)

    if evaluation[:variation_value]
      evaluation[:variation_value]
    elsif evaluation[:variation]
      evaluation[:variation][:value]
    else
      nil
    end
  rescue => e
    @logger.error("getVariation", { feature_key: feature_key, error: e })
    nil
  end
end

#is_enabled(feature_key, context = {}, options = {}) ⇒ Boolean

Check if a feature is enabled



198
199
200
201
202
203
204
205
206
# File 'lib/featurevisor/instance.rb', line 198

def is_enabled(feature_key, context = {}, options = {})
  begin
    evaluation = evaluate_flag(feature_key, context, options)
    evaluation[:enabled] == true
  rescue => e
    @logger.error("isEnabled", { feature_key: feature_key, error: e })
    false
  end
end

#on(event_name, callback) ⇒ Proc

Subscribe to an event



123
124
125
# File 'lib/featurevisor/instance.rb', line 123

def on(event_name, callback)
  @emitter.on(event_name, callback)
end

#set_context(context, replace = false) ⇒ Object

Set context



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/featurevisor/instance.rb', line 135

def set_context(context, replace = false)
  if replace
    @context = context
  else
    @context = { **@context, **context }
  end

  @emitter.trigger("context_set", {
    context: @context,
    replaced: replace
  })

  @logger.debug(replace ? "context replaced" : "context updated", {
    context: @context,
    replaced: replace
  })
end

#set_datafile(datafile) ⇒ Object

Set the datafile



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/featurevisor/instance.rb', line 61

def set_datafile(datafile)
  begin
    new_datafile_reader = Featurevisor::DatafileReader.new(
      datafile: datafile.is_a?(String) ? JSON.parse(datafile, symbolize_names: true) : datafile,
      logger: @logger
    )

    details = Featurevisor::Events.get_params_for_datafile_set_event(@datafile_reader, new_datafile_reader)
    @datafile_reader = new_datafile_reader

    @logger.info("datafile set", details)
    @emitter.trigger("datafile_set", details)
  rescue => e
    @logger.error("could not parse datafile", { error: e })
  end
end

#set_log_level(level) ⇒ Object

Set the log level



55
56
57
# File 'lib/featurevisor/instance.rb', line 55

def set_log_level(level)
  @logger.set_level(level)
end

#set_sticky(sticky, replace = false) ⇒ Object

Set sticky features



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/featurevisor/instance.rb', line 81

def set_sticky(sticky, replace = false)
  previous_sticky_features = @sticky || {}

  if replace
    @sticky = sticky
  else
    @sticky = {
      **@sticky,
      **sticky
    }
  end

  params = Featurevisor::Events.get_params_for_sticky_set_event(previous_sticky_features, @sticky, replace)

  @logger.info("sticky features set", params)
  @emitter.trigger("sticky_set", params)
end

#spawn(context = {}, options = {}) ⇒ ChildInstance

Spawn a child instance



171
172
173
174
175
176
177
# File 'lib/featurevisor/instance.rb', line 171

def spawn(context = {}, options = {})
  Featurevisor::ChildInstance.new(
    parent: self,
    context: get_context(context),
    sticky: options[:sticky]
  )
end