Class: ToggleFleet::Client

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

Overview

A self-contained client. Use ToggleFleet.* for the process-wide singleton, or build your own (e.g. to talk to two environments at once): ToggleFleet::Client.new(config).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Client

Returns a new instance of Client.



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

def initialize(config)
  @config  = config
  @groups  = {}            # name => predicate proc
  @flags   = {}            # flag key => state hash
  @etag    = nil
  @loaded  = false
  @mutex   = Mutex.new
  @poller  = nil
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



46
47
48
# File 'lib/togglefleet.rb', line 46

def config
  @config
end

Instance Method Details

#all(actor: nil, groups: nil) ⇒ Object

Snapshot every known flag for an actor — handy for bootstrapping a JS client.



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

def all(actor: nil, groups: nil)
  ensure_loaded
  keys = @mutex.synchronize { @flags.keys }
  keys.each_with_object({}) { |k, h| h[k] = enabled?(k, actor: actor, groups: groups) }
end

#enabled?(flag, actor: nil, groups: nil) ⇒ Boolean

The whole point: evaluate locally, no network call here.

Returns:

  • (Boolean)


82
83
84
85
86
87
88
89
90
91
# File 'lib/togglefleet.rb', line 82

def enabled?(flag, actor: nil, groups: nil)
  ensure_loaded
  state  = @mutex.synchronize { @flags[flag.to_s] }
  result = state ? evaluate(state, actor, groups) : @config.default
  @config.on_evaluation&.call(flag.to_s, actor, result)
  result
rescue StandardError => e
  log("enabled?(#{flag}) error: #{e.class}: #{e.message}")
  @config.default
end

#register_group(name, &block) ⇒ Object

Register a group predicate. Group membership is decided in YOUR code, so a flag enabled for :admins turns on for any actor where the block returns true.

Raises:

  • (ArgumentError)


60
61
62
63
64
# File 'lib/togglefleet.rb', line 60

def register_group(name, &block)
  raise ArgumentError, "register_group needs a block" unless block
  @mutex.synchronize { @groups[name.to_s] = block }
  self
end

#startObject

Pull the config once and start the background refresh thread. Idempotent.



67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/togglefleet.rb', line 67

def start
  sync
  @mutex.synchronize do
    @poller ||= Thread.new do
      loop do
        sleep(@config.refresh_interval)
        begin; sync; rescue StandardError => e; log("refresh failed: #{e.class}: #{e.message}"); end
      end
    end
    @poller.name = "togglefleet-refresh" if @poller.respond_to?(:name=)
  end
  self
end

#syncObject

Force a refresh now (returns true if the config changed).



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/togglefleet.rb', line 101

def sync
  uri = URI.join(@config.url + "/", "v1/config")
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == "https"
  http.open_timeout = @config.open_timeout
  http.read_timeout = @config.read_timeout
  req = Net::HTTP::Get.new(uri)
  req["Authorization"] = "Bearer #{@config.sdk_key}"
  req["If-None-Match"] = @etag if @etag
  req["User-Agent"]    = "togglefleet-ruby/#{VERSION}"
  res = http.request(req)

  case res
  when Net::HTTPNotModified
    false
  when Net::HTTPSuccess
    flags = JSON.parse(res.body).fetch("flags", {})
    @mutex.synchronize { @flags = flags; @etag = res["ETag"]; @loaded = true }
    true
  when Net::HTTPUnauthorized
    raise Error, "invalid SDK key (401) — check config.sdk_key"
  else
    raise Error, "config fetch failed: HTTP #{res.code}"
  end
end