class Molasses::Client
Public Class Methods
new(api_key, opts = {})
click to toggle source
# File lib/molasses.rb, line 10 def initialize(api_key, opts = {}) @api_key = api_key @send_events = opts[:auto_send_events] || false @base_url = opts[:base_url] || "https://sdk.molasses.app/v1" @logger = opts[:logger] || get_default_logger @conn = Faraday.new( url: @base_url, headers: { "Content-Type" => "application/json", "Authorization" => "Bearer #{@api_key}", }, ) @feature_cache = {} @initialized = {} fetch_features @timer = Workers::PeriodicTimer.new(15) do fetch_features end end
Public Instance Methods
experiment_started(key, user = nil, additional_details = {})
click to toggle source
# File lib/molasses.rb, line 60 def experiment_started(key, user = nil, additional_details = {}) if !@initialized || user == nil || !user.include?("id") return false end unless @feature_cache.include?(key) @logger.info "Warning - feature flag #{key} not set in environment" return false end feature = @feature_cache[key] result = is_active(feature, user) send_event({ "event" => "experiment_started", "tags" => user.include?("params") ? user["params"].merge(additional_details) : additional_details, "userId" => user["id"], "featureId" => feature["id"], "featureName" => key, "testType" => result ? "experiment" : "control", }) end
experiment_success(key, user = nil, additional_details = {})
click to toggle source
# File lib/molasses.rb, line 80 def experiment_success(key, user = nil, additional_details = {}) if !@initialized || user == nil || !user.include?("id") return false end unless @feature_cache.include?(key) @logger.info "Warning - feature flag #{key} not set in environment" return false end feature = @feature_cache[key] result = is_active(feature, user) send_event({ "event" => "experiment_success", "tags" => user.include?("params") ? user["params"].merge(additional_details) : additional_details, "userId" => user["id"], "featureId" => feature["id"], "featureName" => key, "testType" => result ? "experiment" : "control", }) end
is_active(key, user = {})
click to toggle source
# File lib/molasses.rb, line 31 def is_active(key, user = {}) unless @initialized return false end if @feature_cache.include?(key) feature = @feature_cache[key] result = user_active(feature, user) if @send_events && user && user.include?("id") send_event({ "event" => "experiment_started", "tags" => user["params"], "userId" => user["id"], "featureId" => feature["id"], "featureName" => key, "testType" => result ? "experiment" : "control", }) end return result else @logger.info "Warning - feature flag #{key} not set in environment" return false end end
stop()
click to toggle source
# File lib/molasses.rb, line 56 def stop @timer.cancel end
track(key, user = nil, additional_details = {})
click to toggle source
# File lib/molasses.rb, line 100 def track(key, user = nil, additional_details = {}) if user == nil || !user.include?("id") return false end send_event({ "event" => key, "tags" => user.include?("params") ? user["params"].merge(additional_details) : additional_details, "userId" => user["id"], }) end
Private Instance Methods
fetch_features()
click to toggle source
# File lib/molasses.rb, line 255 def fetch_features() response = @conn.get("features") if response.status == 200 data = JSON.parse(response.body) if data.include?("data") and data["data"].include?("features") features = data["data"]["features"] for feature in features @feature_cache[feature["key"]] = feature end unless @initialized @logger.info "Molasses - connected and initialized" end @initialized = true end else @logger.info "Molasses - #{response.status} - #{response.body}" end end
get_default_logger()
click to toggle source
# File lib/molasses.rb, line 274 def get_default_logger if defined?(Rails) && Rails.respond_to?(:logger) Rails.logger else log = ::Logger.new(STDOUT) log.level = ::Logger::WARN log end end
in_segment(user, segment_map)
click to toggle source
# File lib/molasses.rb, line 152 def in_segment(user, segment_map) user_constraints = segment_map["userConstraints"] constraints_length = user_constraints.length constraints_to_be_met = segment_map["constraint"] == "any" ? 1 : constraints_length constraints_met = 0 for constraint in user_constraints param = constraint["userParam"] param_exists = user["params"].include?(param) user_value = nil if param_exists user_value = user["params"][param] end if param == "id" param_exists = true user_value = user["id"] end if meets_constraint(user_value, param_exists, constraint) constraints_met = constraints_met + 1 end end return constraints_met >= constraints_to_be_met end
meets_constraint(user_value, param_exists, constraint)
click to toggle source
# File lib/molasses.rb, line 202 def meets_constraint(user_value, param_exists, constraint) operator = constraint["operator"] if param_exists == false return false end constraint_value = constraint["values"] case constraint["userParamType"] when "number" user_value = parse_number(user_value) constraint_value = parse_number(constraint_value) when "boolean" user_value = parse_bool(user_value) constraint_value = parse_bool(constraint_value) when "semver" user_value = Semantic::Version.new user_value constraint_value = Semantic::Version.new constraint_value else user_value = user_value.to_s end case operator when "in" list_values = constraint_value.split(",") return list_values.include? user_value when "nin" list_values = constraint_value.split(",") return !list_values.include?(user_value) when "lt" return user_value < constraint_value when "lte" return user_value <= constraint_value when "gt" return user_value > constraint_value when "gte" return user_value >= constraint_value when "equals" return user_value == constraint_value when "doesNotEqual" return user_value != constraint_value when "contains" return constraint_value.include?(user_value) when "doesNotContain" return !constraint_value.include?(user_value) else return false end end
parse_bool(user_value)
click to toggle source
# File lib/molasses.rb, line 191 def parse_bool(user_value) case user_value when Numeric return user_value == 1 when TrueClass, FalseClass return user_value when String return user_value == "true" end end
parse_number(user_value)
click to toggle source
# File lib/molasses.rb, line 178 def parse_number(user_value) case user_value when Numeric return user_value when TrueClass return 1 when FalseClass return 0 when String return user_value.to_f end end
send_event(event_options)
click to toggle source
# File lib/molasses.rb, line 251 def send_event(event_options) @conn.post("analytics", event_options.to_json) end
user_active(feature, user)
click to toggle source
# File lib/molasses.rb, line 113 def user_active(feature, user) unless feature.include?("active") return false end unless user && user.include?("id") return true end segment_map = {} for feature_segment in feature["segments"] segment_type = feature_segment["segmentType"] segment_map[segment_type] = feature_segment end if segment_map.include?("alwaysControl") and in_segment(user, segment_map["alwaysControl"]) return false end if segment_map.include?("alwaysExperiment") and in_segment(user, segment_map["alwaysExperiment"]) return true end return user_percentage(user["id"], segment_map["everyoneElse"]["percentage"]) end
user_percentage(id = "", percentage = 0)
click to toggle source
# File lib/molasses.rb, line 138 def user_percentage(id = "", percentage = 0) if percentage == 100 return true end if percentage == 0 return false end c = Zlib.crc32(id) v = (c % 100).abs return v < percentage end