> For the complete documentation index, see [llms.txt](https://developer.harness.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.harness.io/feature-management-experimentation/new-to-fme/sdks-and-customer-deployed-components/server-side-sdks/ruby-sdk.md).

# Ruby SDK

This guide provides detailed information about our Ruby SDK. All of our SDKs are open source. Go to our [Ruby SDK GitHub repository](https://github.com/splitio/ruby-client) to learn more.

{% hint style="info" %}
**RULE-BASED SEGMENTS SUPPORT**

Rule-based segments are supported in SDK versions 8.6.0 and above. No changes are required to your SDK implementation, but updating to a supported version is required to ensure compatibility.

Older SDK versions will return the control treatment for flags using rule-based segments and log an impression with a special label for unsupported targeting rules.
{% endhint %}

### Initialization <a href="#initialization" id="initialization"></a>

#### 1. Import the SDK into your project <a href="#id-1-import-the-sdk-into-your-project" id="id-1-import-the-sdk-into-your-project"></a>

{% tabs %}
{% tab title="Ruby" %}

```ruby
gem install splitclient-rb -v '~> 8.11.1'
```

{% endtab %}

{% tab title="JRuby" %}

```ruby
gem install splitclient-rb -v '~> 8.11.1'
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
**IF USING SYNCHRONIZER WITH REDIS - SYNCHRONIZER 2.X REQUIRED AFTER SDK VERSION 3.X**

Since version 2.0.0 of the split-synchronizer, we use a more efficient scheme to store impressions in Redis. This approach is faster and easier on your Redis instances, since it yields better throughput of impressions to the backend. If you use this SDK with the Synchronizer in Redis or Proxy mode, you need the newest versions of our Split Synchronizer. We recommend that once you're using SDK versions compatible with Split-Sync 2.0 on all your applications pointing to the redis instance maintained by the Split-Sync, you disable backwards compatibility. This is as easy as changing a parameter to `true` on the JSON config or an environment variable to `on` if you're using the docker image.
{% endhint %}

#### 2. Instantiate the SDK and create a new SDK factory client <a href="#id-2-instantiate-the-sdk-and-create-a-new-sdk-factory-client" id="id-2-instantiate-the-sdk-and-create-a-new-sdk-factory-client"></a>

When the SDK is instantiated, it starts background tasks to update an in-memory cache with small amounts of data fetched from Harness servers. This process can take up to a few hundred milliseconds, depending on the size of data. If the SDK is asked to evaluate which treatment to show to a customer for a specific feature flag while its in this intermediate state, it may not have the data necessary to run the evaluation. In this case, the SDK does not fail, rather, it returns [the control treatment](/feature-management-experimentation/use-fme/feature-management/setup/control-treatment.md).

To make sure the SDK is properly loaded before asking it for a treatment, block it until the SDK is ready. You can do this by using the `block_until_ready` method of the SDK factory client (or Manager) as part of the instantiation process of the SDK as shown below. Do this as a part of the startup sequence of your application.

We recommend instantiating the SDK factory once as a singleton and reusing it throughout your application.

Configure the SDK with the SDK key for the FME environment that you would like to access. In legacy Split (app.split.io) the SDK key is found on your Admin settings page, in the API keys section. Select a server-side SDK API key. See [API keys](/feature-management-experimentation/management-and-administration/api-keys.md) to learn more.

```ruby
require 'splitclient-rb'

split_factory = SplitIoClient::SplitFactory.new('YOUR_SDK_KEY')
split_client = split_factory.client

begin
  split_client.block_until_ready
rescue SplitIoClient::SDKBlockerTimeoutExpiredException
  puts 'SDK is not ready. Decide whether to continue or abort execution'
end
```

#### Configure the SDK for use with Rails <a href="#configure-the-sdk-for-use-with-rails" id="configure-the-sdk-for-use-with-rails"></a>

Our SDK is compatible with Ruby on Rails. There are a few extra steps for the initialization. You can configure the SDK to work with Rails with the code snippet below.

```ruby
split_factory = SplitIoClient::SplitFactory.new('YOUR_SDK_KEY')
Rails.configuration.split_client = split_factory.client
```

To access the SDK factory client in your controllers, use the code snippet below:

```ruby
Rails.application.config.split_client
```

Now you can start asking the SDK to evaluate treatments for your customers.

#### SDK Server Compatibility <a href="#sdk-server-compatibility" id="sdk-server-compatibility"></a>

The Ruby SDK has been tested as a standalone app using the following web servers:

* Puma
* Passenger
* Unicorn

For other setups, contact <support@split.io>.

**Unicorn and Puma in cluster mode**

**Note:** This is only applicable when using "memory storage".

During the start of your application, the SDK spawns multiple threads. Each thread has an infinite loop inside, which is used to fetch feature flags/segments or send impressions/events to the FME service continuously. When using Unicorn or Puma in cluster mode (i.e. with `workers` > 0) the application server will spawn multiple child processes, but they won't recreate the threads that existed in the parent process. So, if your application is running in Unicorn or Puma in cluster mode you need to make two small extra steps.

For both servers, you need to have the following line in your `config/initializers/splitclient.rb`:

```ruby
Rails.configuration.split_factory = factory
```

Find below the specific setup for each one:

**Unicorn**

If you’re using Unicorn in cluster mode, you’ll need to include these lines in your Unicorn config (likely `config/unicorn.rb`):

```ruby
before_fork do |server, worker|
  ## keep your existing before_fork code if any
  Rails.configuration.split_factory.stop!
end
after_fork do |server, worker|
  ## keep your existing after_fork code if any
  Rails.configuration.split_factory.resume!
end
```

**Puma**

If using Puma in cluster mode, add these lines to your Puma config (likely `config/puma.rb`):

```ruby
before_fork do
  ## keep your existing before_fork code if any
  Rails.configuration.split_factory.stop!
end
on_worker_boot do
  ## keep your existing on_worker_boot code if any
  Rails.configuration.split_factory.resume!
end
```

By doing the above, the SDK recreates the threads for each new worker and prevents the master process (that doesn't handle requests) from needlessly querying the service.

{% hint style="danger" %}
**SERVER SPAWNING METHOD**

If you are running NGINX with `thread_spawn_method = 'smart'`, use our Redis integration with the [Split Synchronizer](/feature-management-experimentation/new-to-fme/sdks-and-customer-deployed-components/customer-deployed-components/split-synchronizer.md) or contact <support@split.io> for alternatives to run FME.
{% endhint %}

### Use the SDK <a href="#use-the-sdk" id="use-the-sdk"></a>

#### Basic use <a href="#basic-use" id="basic-use"></a>

After you instantiate the SDK factory client, you can start using the `get_Treatment` method of the SDK factory client to decide what version of your features your customers are served. The method requires the `FEATURE_FLAG_NAME` attribute that you want to ask for a treatment and a unique `KEY` attribute that corresponds to the end user that you want to serve the feature to.

From there, you simply need to use an if-else-if block as shown below and insert the code for the different treatments that you defined in Harness FME. Remember the final else branch in your code to handle the client returning [the control treatment](/feature-management-experimentation/use-fme/feature-management/setup/control-treatment.md).

```ruby
## The key here represents the ID of the user, account, etc. you're trying to evaluate a treatment for <a href="#the-key-here-represents-the-id-of-the-user-account-etc-youre-trying-to-evaluate-a-treatment-for" id="the-key-here-represents-the-id-of-the-user-account-etc-youre-trying-to-evaluate-a-treatment-for"></a>
treatment = split_client.get_treatment('KEY', 'FEATURE_FLAG_NAME');

if treatment == 'on'
  ## insert code here to show on treatment
elsif treatment == 'off'
  ## insert code here to show off treatment
else
  ## insert your control treatment code here
end
```

#### Attribute syntax <a href="#attribute-syntax" id="attribute-syntax"></a>

To [target based on custom attributes](/feature-management-experimentation/use-fme/feature-management/targeting/target-with-custom-attributes.md), the SDK's `get_treatment` method needs to be passed an attribute map at runtime.

In the example below, we are rolling out a feature flag to users. The provided attributes `plan_type`, `registered_date`, `permissions`, `paying_customer`, and `deal_size` are passed to the `get_treatment` call. These attributes are compared and evaluated against the attributes used in the rollout plan as defined in Harness FME to decide whether to show the `on` or `off` treatment to this account.

The `get_treatment` method supports five types of attributes: strings, numbers, dates, booleans, and sets. The proper data type and syntax for each are:

* **Strings:** Use type String.
* **Numbers:** Use type `long` or `int`.
* **Dates:** Express the value as `seconds since epoch` and as objects of class `DateTime`.
* **Booleans:** Use type Boolean.
* **Sets:** Use type `List<string>`.

```ruby
attributes = {}
attributes[:deal_size] = 10000
attributes[:registered_date] = Time.now.to_i ## any time as seconds since epoch
attributes[:plan_type] = "growth"
attributes[:permissions] = ['read', 'write']
attributes[:paying_customer] = true

treatment = split_client.get_treatment("KEY",
                                       "FEATURE_FLAG_NAME",
                                       attributes)

if treatment == 'on'
    ## insert on code here
 elsif treatment == 'off'
    ## insert off code here
 else
    ## insert control code here
end
```

#### Multiple evaluations at once <a href="#multiple-evaluations-at-once" id="multiple-evaluations-at-once"></a>

In some instances, you may want to evaluate treatments for multiple feature flag at once. Use the different variations of `get_treatments` method from the SDK factory client to do this.

* `get_treatments`': Pass a list of the feature flag names you want treatments for.
* `get_treatments_by_flag_set`: Evaluate all flags that are part of the provided set name and are cached on the SDK instance.
* `get_treatments_by_flag_sets`: Evaluate all flags that are part of the provided set names and are cached on the SDK instance.

{% tabs %}
{% tab title="get\_treatments" %}

```ruby
attributes = {}
split_client.get_treatments('key', ['FEATURE_FLAG_NAME_1', 'FEATURE_FLAG_NAME_2'], attributes)
```

{% endtab %}

{% tab title="getTreatments\_by\_flag\_set" %}

```ruby
attributes = {}
treatments = split.get_treatments_by_flag_set('key', 'backend', attributes)
```

{% endtab %}

{% tab title="getTreatments\_by\_flag\_sets" %}

```ruby
attributes = {}
treatments = split.get_treatments_by_flag_sets('key', ['backend', 'server_side'], attributes)
```

{% endtab %}
{% endtabs %}

You can also use the [Split Manager](#manager) if you want to get all of your treatments at once.

#### Get treatments with configurations <a href="#get-treatments-with-configurations" id="get-treatments-with-configurations"></a>

To [leverage dynamic configurations with your treatments](/feature-management-experimentation/use-fme/feature-management/setup/dynamic-configurations.md), you should use the `get_treatment_with_config` method.

This method will return an object containing the treatment and associated configuration.

The config element will be a stringified version of the configuration JSON defined in Harness FME. If there are no configs defined for a treatment, the SDK returns `None` for the config parameter.

This method takes the exact same set of arguments as the standard `get_treatment` method. See below for examples on proper usage:

```ruby
result = client.get_treatment_with_config('key', 'new_boxes', attributes)
configs = JSON.parse(result[:config])
treatment = result[:treatment]
```

If you need to get multiple evaluations at once, you can also use the `get_treatments_with_config` methods. These methods take the exact same arguments as the [get\_treatments](#multiple-evaluations-at-once) methods but return a mapping of feature flag names to SplitResult objects instead of strings. Example usage below.

{% tabs %}
{% tab title="get\_treatments\_with\_config" %}

```ruby

feature_flag_names = ['FEATURE_FLAG_NAME_1', 'FEATURE_FLAG_NAME_2']
feature_flag_results = client.get_treatments_with_config('KEY', feature_flag_names)

 ## feature_flag_results will have the following format:
 ## {
 ##   'FEATURE_FLAG_NAME_1': ('on', '{"color": "red"}'),
 ##   'FEATURE_FLAG_NAME_2': ('v2', '{"copy": "better copy"}')
 ## }
```

{% endtab %}

{% tab title="get\_treatments\_with\_config\_by\_flag\_set" %}

```ruby
attributes = {}
result = split.get_treatments_with_config_by_flag_set('key', 'backend', attributes)
result.each do |feature_flag, treatment_with_config|
 configs = JSON.parse(treatment_with_config[:config])
 treatment = treatment_with_config[:treatment]
 puts "Feature: #{feature_flag}, Treatment: #{treatment}, Config: #{configs}"
end
```

{% endtab %}

{% tab title="get\_treatments\_with\_config\_by\_flag\_sets" %}

```ruby
attributes = {}
result = split.get_treatments_with_config_by_flag_sets('key', ['backend', 'server_side'], attributes)
result.each do |feature_flag, treatment_with_config|
 configs = JSON.parse(treatment_with_config[:config])
 treatment = treatment_with_config[:treatment]
 puts "Feature: #{feature_flag}, Treatment: #{treatment}, Config: #{configs}"
end
```

{% endtab %}
{% endtabs %}

If a flag cannot be evaluated, the SDK returns the fallback treatment value (default `"control"` unless overridden globally or per flag). For more information, see [Fallback treatments](/feature-management-experimentation/use-fme/feature-management/setup/fallback-treatment.md).

#### Append properties to impressions <a href="#append-properties-to-impressions" id="append-properties-to-impressions"></a>

[Impressions](/feature-management-experimentation/use-fme/feature-management/monitoring-and-analysis/impressions.md) are generated by the SDK each time a `getTreatment` method is called. These impressions are periodically sent back to Harness servers for feature monitoring and experimentation.

You can append properties to an impression by passing an object of key-value pairs to the `getTreatment` method. These properties are then included in the impression sent by the SDK and can provide useful context to the impression data.

Three types of properties are supported: strings, numbers, and booleans.

```ruby
# Define impression properties <a href="#define-impression-properties" id="define-impression-properties"></a>
evaluation_option = SplitIoClient::Engine::Models::EvaluationOptions.new = ({
  :userType => 'premium',  # string
  :loginCount => 42,       # number
  :isAdmin => true         # boolean
}

# Get treatment with properties <a href="#get-treatment-with-properties" id="get-treatment-with-properties"></a>
treatment = split_client.get_treatment('KEY', 'FEATURE_FLAG_NAME', {}, evaluation_option)

if treatment == 'on'
  # Show ON treatment
elsif treatment == 'off'
  # Show OFF treatment
else
  # Control treatment
end
```

#### Shutdown <a href="#shutdown" id="shutdown"></a>

Call the `.destroy` method before letting a process using the SDK exit, as this method gracefully shuts down the SDK by stopping all background threads, clearing caches, closing connections, and flushing the remaining unpublished impressions.

```ruby
client.destroy
```

**Note: Within multi-threaded setups like using Ruby with Rails and Puma, to fully destroy the factory, you need to set it to nil as follows: `Rails.configuration.split_factory = nil`**

{% hint style="warning" %}
**IMPORTANT!**

A call to the `destroy()` method also destroys the factory object. When creating new client instance, first create a new factory instance.
{% endhint %}

### Track <a href="#track" id="track"></a>

Use the `track` method to record any actions your customers perform. Each action is known as an `event` and corresponds to an `event type`. Calling `track` through one of our SDKs or via the API is the first step to and allows you to measure the impact of your feature flags on your users’ actions and metrics.

[Learn more](/feature-management-experimentation/use-fme/release-monitoring/events/index.md) about using track events in feature flags.

In the examples below, you can see that the `.track()` method can take up to four arguments. The proper data type and syntax for each are:

* **key:** The `key` variable used in the `get_treatment` call and firing this track event. The expected data type is **String**.
* **TRAFFIC\_TYPE:** The traffic type of the key in the track call. The expected data type is **String**. You can only pass values that match the names of [traffic types](/feature-management-experimentation/management-and-administration/traffic-types.md) that you have defined in Harness FME.
* **EVENT\_TYPE:** The event type that this event should correspond to. The expected data type is **String**. Full requirements on this argument are:
  * Contains 63 characters or fewer.
  * Starts with a letter or number.
  * Contains only letters, numbers, hyphen, underscore, or period.
  * This is the regular expression we use to validate the value: `[a-zA-Z0-9][-_\.a-zA-Z0-9]{0,62}`
* **VALUE:** (Optional) The value used in creating the metric. This field can be sent in as nil or 0 if you intend to only use the count function when creating a metric. The expected data type is **Integer** or **Float**.
* **PROPERTIES:** (Optional) A Hash of key value pairs that can be used to filter your metrics. Learn more about event property capture in the [Events](/feature-management-experimentation/use-fme/release-monitoring/events/index.md#event-properties) guide. FME currently supports three types of properties: strings, numbers, and booleans.

The `track` method returns a boolean value of `true` or `false` to indicate whether or not the SDK successfully queued the event to be sent back to Harness servers on the next event post. The SDK returns `false` if the current queue size is equal to the config set by `events_queue_size` or if an incorrect input to the `track` method is provided.

In the case that a bad input has been provided, refer to the [Events](/feature-management-experimentation/use-fme/release-monitoring/events/index.md) guide for more information about our SDK's expected behavior.

```ruby
## If you would like to send an event without a value <a href="#if-you-would-like-to-send-an-event-without-a-value" id="if-you-would-like-to-send-an-event-without-a-value"></a>
track_event = split_client.track('KEY', 'TRAFFIC_TYPE', 'EVENT_TYPE')

## Example <a href="#example" id="example"></a>
track_event = split_client.track('john@doe.com', 'user', 'page_load_time')

## If you would like to associate a value to an event <a href="#if-you-would-like-to-associate-a-value-to-an-event" id="if-you-would-like-to-associate-a-value-to-an-event"></a>
track_event = split_client.track('KEY', 'TRAFFIC_TYPE', 'EVENT_TYPE', VALUE)

## Example <a href="#example" id="example"></a>
track_event = split_client.track('john@doe.com', 'user', 'page_load_time', 83.334)

## If you would like to associate just properties to an event <a href="#if-you-would-like-to-associate-just-properties-to-an-event" id="if-you-would-like-to-associate-just-properties-to-an-event"></a>
track_event = split_client.track('KEY', 'TRAFFIC_TYPE', 'EVENT_TYPE', nil, { PROPERTIES })

## If you would like to associate a value and properties to an event <a href="#if-you-would-like-to-associate-a-value-and-properties-to-an-event" id="if-you-would-like-to-associate-a-value-and-properties-to-an-event"></a>
track_event = split_client.track('KEY', 'TRAFFIC_TYPE', 'EVENT_TYPE', VALUE, { PROPERTIES })

## Example <a href="#example" id="example"></a>
properties = {
  package: 'premium',
  admin: true,
  discount: 50
}

track_event = split_client.track('john@doe.com', 'user', 'page_load_time', nil, properties)
```

### Configuration <a href="#configuration" id="configuration"></a>

The SDK has a number of knobs for configuring performance. Each knob is tuned to a reasonable default. However, you can override the value while instantiating the SDK. The parameters available for configuration are described below:

| **Configuration**                                      | **Description**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | **Default value**           |
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| block\_until\_ready                                    | Maximum time (in seconds) the SDK blocks waiting for readiness during initialization. If the SDK does not become ready within this period, it raises a `SDKBlockerTimeoutExpiredException`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | 15s                         |
| logger                                                 | The log implementation to use for warnings and errors from the SDK.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | Logs to STDOUT              |
| debug\_enabled                                         | Enabled verbose mode.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | false                       |
| transport\_debug\_enabled                              | Super verbose mode that prints network payloads among others.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | false                       |
| connection\_timeout                                    | HTTP client connection timeout (in seconds).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | 5s                          |
| read\_timeout                                          | HTTP socket read timeout (in seconds).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 5s                          |
| features\_refresh\_rate                                | The SDK polls Harness servers for changes to feature flags at this period (in seconds).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | 60s                         |
| segments\_refresh\_rate                                | The SDK polls Harness servers for changes to segments at this period (in seconds).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | 60s                         |
| telemetry\_refresh\_rate                               | The SDK caches diagnostic data that it periodically sends to Harness servers. This configuration controls how frequently this data is sent back to Harness servers (in seconds).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | 3600s                       |
| impressions\_refresh\_rate                             | How often impressions are sent out (in seconds).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | 300s                        |
| events\_push\_rate                                     | How often events are sent out (in seconds).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | 60s                         |
| cache\_adapter                                         | Where to store feature flags and impressions: `:memory` or `:redis`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `:memory`                   |
| redis\_url                                             | Redis URL or hash with configuration for SDK to connect to. See <http://www.rubydoc.info/github/redis/redis-rb/Redis%3Ainitialize>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | 'redis\://127.0.0.1:6379/0' |
| mode                                                   | Whether the SDK is running in `standalone mode` using memory storage or `consumer mode` using an external storage. See Redis integration.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `:standalone`               |
| redis\_namespace                                       | Prefix to add to elements in Redis cache when having to share Redis with other applications.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | `"SPLITIO/ruby-#{VERSION}"` |
| labels\_enabled                                        | Disable labels from being sent to the Harness servers. Labels may contain sensitive information.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | true                        |
| impressions\_queue\_size                               | The size of the impressions queue in case of `cache_adapter == :memory`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | 5000                        |
| events\_queue\_size                                    | The size of the events queue in case of `cache_adapter == :memory`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | 500                         |
| impressions\_bulk\_size                                | Max number of impressions to be sent to the backend on each post.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | impressions\_queue\_size    |
| ip\_addresses\_enabled                                 | Flag to disable IP addresses and host name from being sent to the Harness servers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | true                        |
| streaming\_enabled                                     | Boolean flag to enable the streaming service as default synchronization mechanism. In the event of an issue with streaming, the SDK will fallback to the polling mechanism. If false, the SDK will poll for changes as usual without attempting to use streaming.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | true                        |
| impressions\_mode                                      | Defines how impressions are queued on the SDK. Supported modes are OPTIMIZED(`:optimized`), NONE(`:none`), and DEBUG(`:debug`). In OPTIMIZED mode, only unique impressions are queued and posted to Harness; this is the recommended mode for experimentation use cases. In NONE mode, no impression is tracked in Harness FME and only minimum viable data to support usage stats is, so never use this mode if you are experimenting with that instance impressions. Use NONE when you want to optimize for feature flagging only use cases and reduce impressions network and storage load. In DEBUG mode, all impressions are queued and sent to Harness; this is useful for validations. Use DEBUG mode when you want every impression to be logged in Harness when trying to debug your SDK setup. This setting does not impact the impression listener which receives all generated impressions locally. | `:optimized`                |
| flag\_sets\_filter                                     | This setting allows the SDK to only synchronize the feature flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | nil                         |
| To set each of these parameters, use the syntax below: |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |                             |

```ruby
options = {connection_timeout: 10,
           read_timeout: 5,
           impressions_refresh_rate: 360,
           logger: Logger.new('logfile.log')}

split_factory = SplitIoClient::SplitFactory.new('YOUR_SDK_KEY', options)
split_client = split_factory.client
```

### Sharing state: Redis integration <a href="#sharing-state-redis-integration" id="sharing-state-redis-integration"></a>

**Configuring this Redis integration section is optional for most setups. Read the information below to determine if it might be useful for your project.**

By default, the SDK factory client stores the state it needs to compute treatments (rollout plans, segments, and so on) in memory. As a result, it is easy to get set up with FME by instantiating a client and starting to use it. Configuring this Redis integration section is optional for most setups.

This simplicity hides one important detail that is worth exploring. Because each SDK factory client downloads and stores state separately, a change in a feature flag is picked up by every client on its own schedule. Thus, if a customer issues back-to-back requests that are served by two different machines behind a load balancer, the customer can see different treatments for the same feature flag because one SDK factory client may not have picked up the latest change. This drift in clients is natural and usually ignorable as long as each client sets an aggressive value for `features_refresh_rate` and `segments_refresh_rate`. You can learn more about setting these rates in the [Configuration section](#configuration).

However, if your application requires a total guarantee that SDK clients across your entire infrastructure pick up a change in a feature flag at the exact same time, the only way to ensure that is to externalize the state of the SDK factory client in a data store hosted on your infrastructure.

We currently support Redis for this external data store.

To use the Ruby SDK with Redis, set up the Split Synchronizer and instantiate the SDK in consumer mode.

**Split Synchronizer**

Follow the steps in our [Split Synchronizer](/feature-management-experimentation/new-to-fme/sdks-and-customer-deployed-components/customer-deployed-components/split-synchronizer.md) document to get everything set to sync data to your Redis cache. After you do that, you can set up the SDK in consumer mode.

**Consumer Mode**

In consumer mode, a client can be embedded in your application code and respond to calls to `get_treatment` by retrieving state from the data store (Redis in this case).

Here is how to configure and get treatments for a SDK factory client in consumer mode.

```ruby
options = {
  ## Other options here
  cache_adapter: :redis,
  mode: :consumer,
  redis_url: 'redis://127.0.0.1:6379/0'
}

split_factory = SplitIoClient::SplitFactory.new('YOUR_SDK_KEY', options)
split_client = split_factory.client
```

#### Configure Redis using Sentinel <a href="#configure-redis-using-sentinel" id="configure-redis-using-sentinel"></a>

Use the syntax below to configure Redis using Sentinel:

```ruby
SENTINELS = [{host: '127.0.0.1', port: 26380},
             {host: '127.0.0.1', port: 26381}]

redis_connection = {
  url: 'redis://mymaster',
  sentinels: SENTINELS,
  role: :master
}

options = {
  ## Other options here
  redis_url: redis_connection
}

split_factory = SplitIoClient::SplitFactory.new('YOUR_SDK_KEY', options)
split_client = split_factory.client
```

### Localhost mode <a href="#localhost-mode" id="localhost-mode"></a>

Features start their life on one developer's machine. A developer should be able to put code behind feature flags on their development machine without the SDK requiring network connectivity. To achieve this, the SDK can be started in **localhost** mode (aka off-the-grid mode). In this mode, the SDK neither polls nor updates Harness servers. Instead, it uses an in-memory data structure to determine what treatments to show to the logged in customer for each of the features.

To use the SDK in localhost mode, replace the SDK Key with "localhost", as shown in the example below:

```ruby
require 'splitclient-rb'
split_file = File.expand_path(File.join(File.dirname(__FILE__), '../test_data/local_treatments/split.yaml'))

split_client = split_factory = SplitIoClient::SplitFactory.new('localhost', split_file: split_file).client
```

In this mode, the SDK loads a `.yaml` file containing a simple depiction of a feature flag. eg:

```yaml
- single_key_feature:
    treatment: 'on'
    keys: 'john_doe'
    config: {'desc': 'this applies only to ON and only for john_doe. The rest will receive OFF'}
- single_key_feature:
    treatment: 'off'
    keys:
    config: {'desc': 'this applies only to OFF treatment'}
```

In the example given, a call to `get_treatment` passing `john_doe` as the key renders the `on` treatment, while as any other key receives `off`. Note that configs can be added to test the `_with_config` versions of `get_treatment` and `get_treatments`. Also, you can set multiple keys for the same treatment using an array:

```yaml
- multiple_keys_feature:
    treatment: 'on'
    keys: ['john_doe', 'jane_doe']
    config: {'desc': 'this applies only to ON and only for john_doe and jane_doe. The rest will receive OFF'}
```

Any feature that is not provided in the `split_file` markup map returns [the control treatment](/feature-management-experimentation/use-fme/feature-management/setup/control-treatment.md) if the SDK is asked to evaluate them.

By default, changes in the file are not automatically picked up without restarting the client. To have the client automatically pick up changes to the file, specify `reload_rate` as the interval in seconds at which changes are picked up. Here is an example of specifying both `split_file` and `reload_rate`.

```ruby
factory = SplitIoClient::SplitFactoryBuilder.build('localhost', split_file: '/where/to-look-for/<file_name>', reload_rate: 3)
```

### Manager <a href="#manager" id="manager"></a>

Use the Split Manager to get a list of feature flags available to the SDK factory client. To instantiate a Manager in your code base, use the same factory that you used for your client.

```ruby
## Reusing the split_factory created originally. <a href="#reusing-the-splitfactory-created-originally" id="reusing-the-splitfactory-created-originally"></a>
split_manager = split_factory.manager
```

The `SplitView` object referenced above has the following structure.

```ruby
## returns a List of SplitViews or empty. <a href="#returns-a-list-of-splitviews-or-empty" id="returns-a-list-of-splitviews-or-empty"></a>
list_of_feature_flags = split_manager.splits

## Array of String representing feature flag names <a href="#array-of-string-representing-feature-flag-names" id="array-of-string-representing-feature-flag-names"></a>
list_of_feature_flag_names = split_manager.split_names

## returns a SplitView of the 'name' specified or empty. <a href="#returns-a-splitview-of-the-name-specified-or-empty" id="returns-a-splitview-of-the-name-specified-or-empty"></a>
feature_flag = split_manager.split(name)
```

The `feature_flag` object referenced above has the following structure.

```ruby
{
 :name=>"new_reporting",
 :traffic_type_name=>"user",
 :killed=>false,
 :treatments=>["v1", "v2", "v3"],
 :change_number=>1469134003507,
 :configs=>{:on=>"{\"size\":15,\"test\":20}"},
 :default_treatment=>"off",
 :sets>=["backend"],
 :impressions_disabled=>false
}
```

### Listener <a href="#listener" id="listener"></a>

FME SDKs send impression data back to Harness servers periodically when evaluating feature flags. To send this information to a location of your choice, define and attach an *impression listener*.

The SDK sends the generated impressions to the impression listener right away. However, to avoid blocking the caller thread, use the second parameter to specify the size of the queue acting as a buffer. Refer to the followoing snippet:

If the impression listener is slow at processing the incoming data, the queue fills up and any subsequent impressions are dropped.

```ruby
class MyImpressionListener
  def log(impression)
    Logger.new($stdout).info(impression)
  end
end

options = {
  ## other options
  impression_listener: MyImpressionListener.new ## do remember to initialize your class here
  ## other options
}

factory = SplitIoClient::SplitFactoryBuilder.build(sdk_key, options)
```

### Logging <a href="#logging" id="logging"></a>

Our Ruby SDK makes use of Ruby’s stdlib `Logger` class to log errors/events. The default option is shown below:

```ruby
Logger.new($stdout)
```

You can configure the following options in the config file.

```ruby
{
  ## ...
  ## you can specify your own Logger class instance here:
  logger: Logger.new('logfile.log'),
  ## to enable more verbose logging, including more debug information (false is the default) use:
  debug_enabled: true,
  ## to log transport data (mostly http requests, false is the default) use:
  transport_debug_enabled: true
  ## ...
}
```

### Proxy <a href="#proxy" id="proxy"></a>

Ruby SDK respects the `HTTP_PROXY` environment variable. To use a proxy, assign a proxy address to that variable.

```ruby
http_proxy=http://username:password@hostname:port
```

### Configure fallback treatments <a href="#configure-fallback-treatments" id="configure-fallback-treatments"></a>

Fallback treatments let you define a treatment value (and optional configuration) to be returned when a flag cannot be evaluated. By default, the SDK returns `control`, but you can override this globally or for individual flags at the SDK level.

This is useful when you want to:

* Maintain a predictable user experience during outages or evaluation failures (avoid unexpected `control` in production)
* Protect critical user flows by returning a safe, stable treatment (for example, forcing `off` during an incident)
* Customize behavior per flag so each evaluation inherits appropriate safe defaults if something goes wrong

#### Global fallback treatment <a href="#global-fallback-treatment" id="global-fallback-treatment"></a>

Set a global fallback treatment when initializing the SDK factory. This value is returned whenever any flag cannot be evaluated.

```ruby
// Initialize SDK with global fallback treatment
fallback_config = SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(SplitIoClient::Engine::Models::FallbackTreatment.new("control_fallback_ruby", '{"my_feature": "control"}'))

options = {block_until_ready: 5,
  fallback_treatments: fallback_config
}
split_factory = SplitIoClient::SplitFactoryBuilder.build("SDK API KEY", options)
```

#### Flag-level fallback treatment <a href="#flag-level-fallback-treatment" id="flag-level-fallback-treatment"></a>

You can set a fallback treatment per flag in the SDK options. When a flag evaluation fails, the SDK returns the corresponding fallback treatment defined for that flag. This flag-level fallback always takes precedence over the global fallback treatment, so if both are defined, the SDK will return the flag-level value when that flag cannot be evaluated.

```ruby
fallback_config = SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(nil, {:flag_1 => SplitIoClient::Engine::Models::FallbackTreatment.new(
	"flag_1_FALLBACK", 
	'{"my_feature": "collection control"}')
})

options = {block_until_ready: 5,
  fallback_treatments: fallback_config
}
split_factory = SplitIoClient::SplitFactoryBuilder.build("SDK API KEY", options)
split_client = split_factory.client

treatment = split_client.get_treatment_with_config("ruby", "flag_1")
```

For more information, see [Fallback treatments](/feature-management-experimentation/use-fme/feature-management/setup/fallback-treatment.md).

## Advanced use cases <a href="#advanced-use-cases" id="advanced-use-cases"></a>

This section describes advanced use cases and features provided by the SDK.

#### Subscribe to events <a href="#subscribe-to-events" id="subscribe-to-events"></a>

{% hint style="info" %}
**SUPPORTED SDK VERSIONS**

SDK events and event metadata are supported in the Ruby SDK version 8.11.0 or later.
{% endhint %}

You can listen for four different events from the SDK.

* `SDK_READY`. This event fires once the SDK is ready to evaluate treatments using the most up-to-date version of your rollout plan, downloaded from Harness servers.
* `SDK_UPDATE`. This event fires whenever your rollout plan is changed. Listen for this event to refresh your app whenever a feature flag or segment is changed in Harness FME.

These events provide hooks to run custom logic whenever the SDK state changes.

```ruby
require 'splitclient-rb'

def ready_callback(metadata)
    puts "SDK is ready."
end

def update_callback(metadata)
    puts "UPDATE CALLBACK"
    puts metadata.type
    puts metadata.names
end

split_factory = SplitIoClient::SplitFactoryBuilder.build('API KEY', {block_until_ready: 100})

split_client = split_factory.client
split_client.register(SplitIoClient::Engine::Models::SdkEvent::SDK_READY, method(:ready_callback))
split_client.register(SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE, method(:update_callback))
```

**Include metadata**

`metadata` provides additional context for events:

* `SDK_READY`: No metadata is included.
* `SDK_UPDATE`: Includes the type (SdkEventType.FLAG\_UPDATE or SdkEventType.SEGMENTS\_UPDATE) and names (list of impacted flags; empty for segment-only updates).

### Troubleshooting <a href="#troubleshooting" id="troubleshooting"></a>

#### SSL Certificate Error: OpenSSL::SSL::SSLError on macOS <a href="#ssl-certificate-error-opensslsslsslerror-on-macos" id="ssl-certificate-error-opensslsslsslerror-on-macos"></a>

I am seeing the following certificate error: `OpenSSL::SSL::SSLError`. On OSX, if you see an SSL issue that looks similar to the example below, refer to [this post](https://toadle.me/2015/04/16/fixing-failing-ssl-verification-with-rvm.html) for troubleshooting.

```ruby
OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
```

#### Error: uninitialized constant caused by Process::RLIMIT\_NOFILE in lib/net/http/persistent.rb <a href="#error-uninitialized-constant-caused-by-processrlimitnofile-in-libnethttppersistentrb" id="error-uninitialized-constant-caused-by-processrlimitnofile-in-libnethttppersistentrb"></a>

When using Ruby SDK on Windows, initializing the SDK factory object causes the following error:

```
uninitialized constant error caused by 'Process::RLIMIT_NOFILE' in lib/net/http/persistent.rb
```

This issue is related to the `net-http-persistent` 3.0 library on Windows OS. This library is a dependency installed automatically with the SDK gem.

Downgrade `net-http-persistent` to version 2.9.4, which is compatible with the Ruby SDK on Windows, by running:

```bash
gem uninstall net-http-persistent
gem install net-http-persistent -v '2.9.4'
```

#### Upgrading Ruby SDK from 4.x to 5.x and Above <a href="#upgrading-ruby-sdk-from-4x-to-5x-and-above" id="upgrading-ruby-sdk-from-4x-to-5x-and-above"></a>

The Ruby SDK uses a hashing algorithm to divide users across treatments for feature flags (e.g., a 50/50 split between "on" and "off"). Historically, Split has used two hashing algorithms:

* **Legacy Hash (Algorithm 1)**: A simple and fast implementation, but it produces uneven user distributions when user counts are below 100.
* **Murmur Hash (Algorithm 2)**: An industry-standard hashing algorithm that is both fast and provides even distributions regardless of user count.

In Ruby SDK versions 4.x and below, feature flags intended to use the Murmur Hash were incorrectly using the Legacy Hash. This caused inconsistent treatment assignments when compared with other language SDKs.

Starting from Ruby SDK version 5.0.0 and above, this issue is fixed and the SDK correctly uses the Murmur Hash.

When upgrading, if feature flags are in a ramping phase (i.e., partially rolled out), users may experience treatment shifts due to the change in hashing algorithm.

* Ideally, upgrade the Ruby SDK when all experiments are at 100% distribution to avoid user treatment shifts.
* If this is not possible, consider creating new versions of the active feature flags. This resets metric calculations and can lead to users receiving different treatments, but users won’t be excluded from metrics as no treatment changes are recorded within the same flag version.

#### Why do CLOSE\_WAIT TCP connections in Puma not go down as expected? <a href="#why-do-closewait-tcp-connections-in-puma-not-go-down-as-expected" id="why-do-closewait-tcp-connections-in-puma-not-go-down-as-expected"></a>

When using the Ruby SDK with Puma or Unicorn in cluster mode (multiple workers, single thread each), you may notice an increasing number of CLOSE\_WAIT TCP connections when the SDK sends treatment events. Running the following command will confirm this:

```bash
lsof -l | grep CLOSE_WAIT | wc -l
```

If no SDK treatment calls are made, the number of `CLOSE_WAIT` connections does not decrease as expected.

The SDK threads may not be terminating properly, leaving client connections hanging and waiting for the server’s final ACK signal.

Puma spawns a new process for each group of incoming requests. To ensure all SDK threads are terminated before Puma closes the process, add the following to your `config/puma.rb` file:

```ruby
before_fork do
  $split_factory.instance_variable_get(:@config).threads.each { |_, t| t.exit }
end
```

This cleanly shuts down SDK threads, helping close the TCP connections properly.
