Module: Shipeasy

Defined in:
lib/shipeasy/config.rb,
lib/shipeasy-sdk.rb,
lib/shipeasy/client.rb,
lib/shipeasy/engine.rb,
lib/shipeasy/sdk/see.rb,
lib/shipeasy/sdk/eval.rb,
lib/shipeasy/sdk/anon_id.rb,
lib/shipeasy/sdk/murmur3.rb,
lib/shipeasy/sdk/railtie.rb,
lib/shipeasy/sdk/version.rb,
lib/shipeasy/i18n/railtie.rb,
lib/shipeasy/sdk/telemetry.rb,
lib/shipeasy/sdk/openfeature.rb,
lib/shipeasy/sdk/sticky_store.rb,
lib/shipeasy/i18n/view_helpers.rb,
lib/shipeasy/i18n/label_fetcher.rb,
lib/shipeasy/sdk/rack_middleware.rb

Overview

Single configuration object for the Shipeasy gem.

Covers both subsystems:

- SDK / experimentation (api_key, base_url) — drives Engine
- i18n / string manager (public_key, profile, cdn_base_url, ...) — drives
  the Rails view helpers and label fetcher

Usage:

Shipeasy.configure do |c|
  c.api_key    = ENV["SHIPEASY_SERVER_KEY"]
  c.public_key = ENV["SHIPEASY_CLIENT_KEY"]
  c.profile    = "default"
end

Anything not set falls back to the defaults below. The same Shipeasy.config is read by Engine and the Rails helpers, so there is one place to point environment variables at.

Defined Under Namespace

Modules: I18n, OpenFeature, SDK Classes: Client, Configuration, Engine, Error

Class Method Summary collapse

Class Method Details

.attributes_transformObject

The resolved attributes transform (callable). Default = identity, so a user object that is already the attribute hash is used verbatim.



84
85
86
87
88
89
90
91
92
93
# File 'lib/shipeasy/config.rb', line 84

def attributes_transform
  transform = config.attributes
  if transform.nil?
    ->(user) { user }
  elsif transform.respond_to?(:call)
    transform
  else
    raise Error, "Shipeasy.configure { |c| c.attributes = … } must be a callable (e.g. a lambda)"
  end
end

.configObject



57
58
59
# File 'lib/shipeasy/config.rb', line 57

def config
  @config ||= Configuration.new
end

.configure {|config| ... } ⇒ Object

Configure the gem once at boot. In addition to populating the shared Configuration, this builds and registers the ONE global Shipeasy::Engine (first-config-wins) from the api_key/base_url and kicks off its one-shot fetch (fire-and-forget) so ‘Shipeasy::Client.new(user).get_flag(…)` resolves against real rules with no explicit init call.

Shipeasy.configure do |c|
  c.api_key    = ENV["SHIPEASY_SERVER_KEY"]
  c.attributes = ->(u) { { "user_id" => u.id, "plan" => u.plan } }
end

Shipeasy::Client.new(current_user).get_flag("new_checkout")

Long-running servers that also want the background poll can call ‘Shipeasy.engine.init` after configure.

Yields:



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

def configure
  yield config
  register_engine!(config) if config.api_key
  config
end

.engineObject

The single global engine registered by configure, or nil if configure has not run (or ran without an api_key). Shipeasy::Client reads this.



97
98
99
100
101
102
103
104
105
106
# File 'lib/shipeasy/config.rb', line 97

def engine
  pid = Process.pid
  if @engine && @engine_pid != pid
    # Post-fork: the parent's poll thread didn't survive. Rebuild lazily
    # from the stored config in this child process.
    @engine = nil
    register_engine!(config) if config.api_key
  end
  @engine
end

.flagsObject

Lazy, fork-safe singleton Engine. The first call from each process spawns a fresh client + poll thread — including post-fork workers under Puma’s preload_app!. Callers can ‘Shipeasy.flags.get_flag(…)` straight from a controller without holding a constant or worrying about `before_worker_boot` hooks.

Initializers stay minimal:

# config/initializers/shipeasy.rb
Shipeasy.configure { |c| c.api_key = ENV["SHIPEASY_SERVER_KEY"] }

The first request that touches ‘Shipeasy.flags.*` triggers init(). For serverless / Lambda where you want a single fetch with no thread, build the engine explicitly: `Shipeasy::Engine.new(…).init_once`.

NOTE: this remains a separate, polling engine from the one configure() registers (Shipeasy.engine). New code should prefer the Shipeasy.configure + Shipeasy::Client.new(user) front door; ‘Shipeasy.flags` is retained for the legacy `Shipeasy.flags.get_flag(name, user)` style.



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/shipeasy/config.rb', line 155

def flags
  pid = Process.pid
  if @flags && @flags_pid != pid
    # Post-fork: parent's poll thread didn't survive. Don't destroy
    # @flags (its mutex/state is invalid in this child anyway); just
    # rebuild from scratch.
    @flags = nil
  end
  @flags ||= begin
    @flags_pid = pid
    client = Engine.new(
      api_key:  config.api_key,
      base_url: config.base_url,
    )
    client.init
    client
  end
end

.register_engine!(cfg) ⇒ Object

Build + register the one global engine (first-config-wins). Fires the one-shot fetch fire-and-forget. Idempotent within a process.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/shipeasy/config.rb', line 110

def register_engine!(cfg)
  return @engine if @engine && @engine_pid == Process.pid
  @engine_pid = Process.pid
  engine = Engine.new(api_key: cfg.api_key, base_url: cfg.base_url)
  @engine = engine
  # Capture +engine+ in the closure (not the @engine ivar, which a concurrent
  # reset/reconfigure could nil out before the thread runs).
  Thread.new do
    engine.init_once
  rescue => e
    warn "[shipeasy] configure() one-shot fetch failed: #{e.message}"
  end
  engine
end

.reset_config!Object

Reset the config back to defaults — primarily for tests.



126
127
128
129
130
131
132
133
134
# File 'lib/shipeasy/config.rb', line 126

def reset_config!
  @config = nil
  @flags_pid = nil
  @flags&.destroy
  @flags = nil
  @engine&.destroy
  @engine = nil
  @engine_pid = nil
end