> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trevosdk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Ruby

> Server-side assignment and tracking for Rails, Sidekiq, and scripts.

`trevosdk` runs experiments and records conversions from your Ruby backend. Zero
dependencies, Ruby 3.2+, and assignment is byte-identical to every other Trevo SDK — the
same user gets the same variant in the browser and on the backend.

```bash theme={null}
gem install trevosdk
```

Or in your Gemfile: `gem "trevosdk"`.

## Create a client

You need a **server** key: `tsk_secret_…`, from Settings → SDK keys with platform `Server`.
It is shown once. Never put it in client code.

```ruby theme={null}
# config/initializers/trevosdk.rb (Rails) or anywhere at boot
Trevosdk.init(ENV.fetch("TREVO_SECRET_KEY"))
```

`Trevosdk.init` sets up a process-wide singleton reachable as `Trevosdk.client` — the common
one-client-per-app setup. Config refresh (60-second poll) and event delivery run on
background threads; events flush at process exit, or deterministically via `flush` / `close`.
For multiple clients, use `Trevosdk::Client.new(secret_key, **options)` directly.

Assignment needs config, so wait for the first fetch once at boot:

```ruby theme={null}
Trevosdk.client.ready(2)   # true once config loaded; false on timeout
```

Without this, requests in the first moments after a deploy fall back to control.

## Read a variant

```ruby theme={null}
variant = Trevosdk.client.get_variant("checkout-cta", user_id: current_user.id)
if variant == "treatment"
  # alternate experience
end
```

`get_variant` reads an immutable in-memory config snapshot — no network call, nothing that
blocks — so it is safe anywhere: Rails controllers, Sidekiq jobs, rake tasks. The singleton
is safe across Puma threads.

Each call records an **exposure** (deduplicated per identity and variant within a 10-minute
window). To read without recording one, pass `track_exposure: false`.

## Identity is passed per call

A server holds no ambient user state, so every call takes the identity explicitly:

```ruby theme={null}
variant = Trevosdk.client.get_variant(
  "pricing-algo-v2",
  user_id: current_user&.id,
  anonymous_id: cookies[:trevo_id]
)
```

**Send both ids whenever you have them.** `user_id:` wins for bucketing when present; an
event carrying both is what links a user's anonymous browsing to their conversions. With no
identity at all, `get_variant` returns `"control"` and records nothing.

**Bucket with the identity the browser is using at that moment** — the user id when
identified, otherwise the `trevo_id` cookie. Anything else assigns the same person different
variants on the client and the server.

## Track conversions

```ruby theme={null}
Trevosdk.client.track("subscription_started", user_id: current_user.id, properties: {plan: "pro"})
```

Payment providers replay webhooks, and a conversion counted twice inflates whichever variant
that user was in. Pass a stable `insert_id:` derived from the thing that happened, and Trevo
records the event once no matter how many deliveries arrive:

```ruby theme={null}
Trevosdk.client.track(
  "subscription_started",
  user_id: user.id,
  properties: {plan: "pro"},
  insert_id: subscription.id
)
```

## Linking identities without a cookie

Webhooks and background jobs have no cookie to read. State the link directly:

```ruby theme={null}
Trevosdk.client.alias(anonymous_id, user_id)
```

Idempotent and safe to retry. An `anonymous_id` belongs to one user — pointing it at a
different user raises and changes nothing.

## Runtime notes

* **Rails** — initialize in `config/initializers/trevosdk.rb` and call `Trevosdk.client`
  from anywhere.
* **Forking servers (Puma workers, Sidekiq, Spring)** — a client built before the fork keeps
  working in the child. Background threads do not survive a fork, so the SDK detects the new
  pid and restarts them on the first `get_variant` or `track`; an explicit `on_worker_boot`
  re-init is optional.
* **Short-lived scripts and jobs** — call `Trevosdk.client.flush` before exiting so the
  batch is delivered inside the run (an `at_exit` flush also runs).

## Local development

Pin variants without touching Trevo:

```bash theme={null}
TREVO_FORCE_VARIANTS=checkout-cta:treatment,pricing-v2:control
```

Or in code with `force_variants: {"checkout-cta" => "treatment"}`. Forced reads never record
an exposure, so local testing never pollutes results. The SDK's own spec suite deletes the
variable around every example, so exporting it cannot turn a conformance run red.

Experiment keys may be Strings or Symbols — `get_variant(:"checkout-cta", ...)` and
`get_variant("checkout-cta", ...)` resolve the same experiment.

## Errors

Everything raised or passed to `on_error:` is typed, so you can branch on what happened:
`Trevosdk::ConfigError`, `Trevosdk::DeliveryError` (carries `#status`),
`Trevosdk::SerializationError`, `Trevosdk::QueueFullError` and
`Trevosdk::EventRejectedError`, all under `Trevosdk::Error` (itself a `RuntimeError`).

## Options

| Option | Default | Notes |
| - | - | - |
| `secret_key` | — | Required. `tsk_secret_…` (positional) |
| `bootstrap_config:` | — | An array of experiment configs to assign against before the first fetch resolves |
| `poll_interval_s:` | `60` | Config refresh cadence |
| `flush_interval_s:` | `5` | Background flush cadence; short-lived processes should call `flush` instead |
| `max_batch_size:` | `50` | Capped at 500 by the server |
| `force_variants:` | — | Pin variants locally; also read from `TREVO_FORCE_VARIANTS` |
| `transport:` | stdlib `Net::HTTP` | Override for tests or proxies |
| `on_error:` | log warning | Background failures surface here |

## API summary

| Method | Purpose |
| - | - |
| `ready(timeout_s = nil)` | Block until initial config loads; returns a boolean |
| `get_variant(key, user_id:, anonymous_id:, track_exposure:)` | Deterministic assignment |
| `track(name, user_id:, anonymous_id:, properties:, insert_id:)` | Queue a conversion |
| `alias(anonymous_id, user_id)` | Link an anonymous journey to a user |
| `flush` | Send everything queued now |
| `close` | Stop background threads and flush once |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.