Ruby

View as Markdown

The Unleash Ruby SDK lets you evaluate feature flags in Ruby applications. It connects to Unleash or Unleash Edge to fetch flag configurations and evaluates them locally against an Unleash context.

You can use this SDK with Unleash Enterprise or Unleash Open Source.

For an overview of how Unleash SDKs work, including offline behavior, feature compatibility across SDKs, and default refresh and metrics intervals, refer to the SDK overview.

Requirements

  • Ruby (MRI) 2.7 or later
  • JRuby 9.4 or later

Installation

Add this line to your application’s Gemfile:

1gem 'unleash', '~> 6.4.1'

And then execute:

$bundle

Or install it yourself as:

$gem install unleash

Configuration

It is required to configure:

  • app_name with the name of the running application
  • url of your Unleash server
  • custom_http_headers with {'Authorization': '<YOUR_API_TOKEN>'} when using Unleash v4+

It is highly recommended to configure:

  • instance_id parameter with a unique identifier for the running instance
1Unleash.configure do |config|
2 config.app_name = 'my_ruby_app'
3 config.url = '<YOUR_UNLEASH_URL>/api'
4 config.custom_http_headers = {'Authorization': '<YOUR_API_TOKEN>'}
5end

or instantiate the client with the valid configuration:

1UNLEASH = Unleash::Client.new(url: '<YOUR_UNLEASH_URL>/api', app_name: 'my_ruby_app', custom_http_headers: {'Authorization': '<YOUR_API_TOKEN>'})

Dynamic custom HTTP headers

If you need custom HTTP headers that change during the lifetime of the client, you can pass custom_http_headers as a Proc.

1Unleash.configure do |config|
2 config.app_name = 'my_ruby_app'
3 config.url = '<YOUR_UNLEASH_URL>/api'
4 config.custom_http_headers = proc do
5 {
6 'Authorization': '<YOUR_API_TOKEN>',
7 'X-Client-Request-Time': Time.now.iso8601
8 }
9 end
10end

Configuration options

ArgumentDescriptionRequired?TypeDefault value
urlUnleash server URL.YStringN/A
app_nameName of your program.YStringN/A
instance_idIdentifier for the running instance of your program—set this to be able trace where metrics are being collected from.NStringrandom UUID
environmentUnleash context option, for example, prod or dev. Not yet in use. Not the same as the SDK’s Unleash environment.NStringdefault
project_nameName of the project to retrieve feature flags from. If not set, all feature flags will be retrieved.NStringnil
refresh_intervalHow often the Unleash client should check with the server for configuration changes.NInteger15
metrics_intervalHow often the Unleash client should send metrics to server.NInteger60
disable_clientDisables all communication with the Unleash server, effectively taking it offline. If set, is_enabled? always answer with the default_value and configuration validation is skipped. Will also forcefully set disable_metrics to true. Defeats the entire purpose of using Unleash, except when running tests.NBooleanfalse
disable_metricsDisables sending metrics to Unleash server. If the disable_client option is set to true, then this option will also be set to true, regardless of the value provided.NBooleanfalse
custom_http_headersCustom headers to send to Unleash. As of Unleash v4.0.0, the Authorization header is required. For example: {'Authorization': '<YOUR_API_TOKEN>'}.NHash/Proc
timeoutHow long to wait for the connection to be established or wait in reading state (open_timeout/read_timeout)NInteger30
retry_limitHow many consecutive failures in connecting to the Unleash server are allowed before giving up. The default is to retry indefinitely.NFloat::INFINITY5
backup_fileFilename to store the last known state from the Unleash server. It is best to not change this from the default.NStringDir.tmpdir + "/unleash-#{app_name}-repo.json
loggerSpecify a custom Logger class to handle logs for the Unleash client.NClassLogger.new(STDOUT)
log_levelChange the log level for the Logger class. Constant from Logger::Severity.NConstantLogger::WARN
bootstrap_configBootstrap config for loading data on startup—useful for loading large states on startup without (or before) hitting the network.NUnleash::Bootstrap::Configurationnil
strategiesStrategies manager that holds all strategies and allows to add custom strategies.NUnleash::StrategiesUnleash::Strategies.new

For a more in-depth look, please see lib/unleash/configuration.rb.

Environment VariableDescription
UNLEASH_BOOTSTRAP_FILEFile to read bootstrap data from
UNLEASH_BOOTSTRAP_URLURL to read bootstrap data from

Usage in a plain Ruby application

1require 'unleash'
2require 'unleash/context'
3
4@unleash = Unleash::Client.new(app_name: 'my_ruby_app', url: '<YOUR_UNLEASH_URL>/api', custom_http_headers: { 'Authorization': '<YOUR_API_TOKEN>' })
5
6feature_name = "AwesomeFeature"
7unleash_context = Unleash::Context.new
8unleash_context.user_id = 123
9
10if @unleash.is_enabled?(feature_name, unleash_context)
11 puts " #{feature_name} is enabled according to unleash"
12else
13 puts " #{feature_name} is disabled according to unleash"
14end
15
16if @unleash.is_disabled?(feature_name, unleash_context)
17 puts " #{feature_name} is disabled according to unleash"
18else
19 puts " #{feature_name} is enabled according to unleash"
20end

Usage in a Rails application

Add initializer

The initializer setup varies depending on whether you’re using a standard setup, Puma in clustered mode, Phusion Passenger, or Sidekiq.

Standard Rails applications

Put in config/initializers/unleash.rb:

1Unleash.configure do |config|
2 config.app_name = Rails.application.class.module_parent_name
3 config.url = '<YOUR_UNLEASH_URL>'
4 # config.instance_id = "#{Socket.gethostname}"
5 config.logger = Rails.logger
6 config.custom_http_headers = {'Authorization': '<YOUR_API_TOKEN>'}
7end
8
9UNLEASH = Unleash::Client.new
10
11# Or if preferred:
12# Rails.configuration.unleash = Unleash::Client.new

For config.instance_id use a string with a unique identification for the running instance. For example, it could be the hostname if you only run one App per host, or the docker container ID, if you are running in Docker. If not set, the client will generate a unique UUID for each execution.

To have it available in the rails console command as well, also add to the file above:

1Rails.application.console do
2 UNLEASH = Unleash::Client.new
3 # or
4 # Rails.configuration.unleash = Unleash::Client.new
5end

Puma in clustered mode

When using Puma with multiple workers configured in puma.rb:

1workers ENV.fetch("WEB_CONCURRENCY") { 2 }
With preload_app!

Then you may keep the client configuration still in config/initializers/unleash.rb:

1Unleash.configure do |config|
2 config.app_name = Rails.application.class.parent.to_s
3 config.url = '<YOUR_UNLEASH_URL>/api'
4 config.custom_http_headers = {'Authorization': '<YOUR_API_TOKEN>'}
5end

But you must ensure that the Unleash client is instantiated only after the process is forked. This is done by creating the client inside the on_worker_boot code block in puma.rb as below:

1#...
2preload_app!
3#...
4
5on_worker_boot do
6 # ...
7
8 ::UNLEASH = Unleash::Client.new
9end
10
11on_worker_shutdown do
12 ::UNLEASH.shutdown
13end
Without preload_app!

By not using preload_app!:

  • The Rails constant will not be available.
  • Phased restarts will be possible.

You need to ensure that in puma.rb:

  • The Unleash SDK is loaded with require 'unleash' explicitly, as it will not be pre-loaded.
  • All parameters are set explicitly in the on_worker_boot block, as config/initializers/unleash.rb is not read.
  • There are no references to Rails constant, as that is not yet available.

Example for puma.rb:

1require 'unleash'
2
3#...
4# no preload_app!
5
6on_worker_boot do
7 # ...
8
9 ::UNLEASH = Unleash::Client.new(
10 app_name: 'my_rails_app',
11 url: '<YOUR_UNLEASH_URL>/api',
12 custom_http_headers: {'Authorization': '<YOUR_API_TOKEN>'},
13 )
14end
15
16on_worker_shutdown do
17 ::UNLEASH.shutdown
18end

Note that we also added shutdown hooks in on_worker_shutdown, to ensure a clean shutdown.

Phusion Passenger

When using Phusion Passenger, the Unleash client needs to be configured and instantiated inside the PhusionPassenger.on_event(:starting_worker_process) code block due to smart spawning:

The initializer in config/initializers/unleash.rb should look like:

1PhusionPassenger.on_event(:starting_worker_process) do |forked|
2 if forked
3 Unleash.configure do |config|
4 config.app_name = Rails.application.class.parent.to_s
5 # config.instance_id = "#{Socket.gethostname}"
6 config.logger = Rails.logger
7 config.url = '<YOUR_UNLEASH_URL>/api'
8 config.custom_http_headers = {'Authorization': '<YOUR_API_TOKEN>'}
9 end
10
11 UNLEASH = Unleash::Client.new
12 end
13end

Sidekiq

When using Sidekiq, the code block for Unleash.configure must be set beforehand. For example in config/initializers/unleash.rb.

1Sidekiq.configure_server do |config|
2 config.on(:startup) do
3 UNLEASH = Unleash::Client.new
4 end
5
6 config.on(:shutdown) do
7 UNLEASH.shutdown
8 end
9end

Unleash context

Add the following method and callback in the application controller to have @unleash_context set for all requests:

Add in app/controllers/application_controller.rb:

1 before_action :set_unleash_context
2
3 private
4 def set_unleash_context
5 @unleash_context = Unleash::Context.new(
6 session_id: session.id,
7 remote_address: request.remote_ip,
8 user_id: session[:user_id]
9 )
10 end

Alternatively, you can add this method only to the controllers that use Unleash.

Example usage

Then wherever in your application that you need a feature toggle, you can use:

1if UNLEASH.is_enabled? "AwesomeFeature", @unleash_context
2 puts "AwesomeFeature is enabled"
3end

or if client is set in Rails.configuration.unleash:

1if Rails.configuration.unleash.is_enabled? "AwesomeFeature", @unleash_context
2 puts "AwesomeFeature is enabled"
3end

If you don’t want to check a feature is disabled with unless, you can also use is_disabled?:

1# so instead of:
2unless UNLEASH.is_enabled? "AwesomeFeature", @unleash_context
3 puts "AwesomeFeature is disabled"
4end
5
6# it might be more intelligible:
7if UNLEASH.is_disabled? "AwesomeFeature", @unleash_context
8 puts "AwesomeFeature is disabled"
9end

If the feature is not found in the server, it will by default return false. However, you can override that by setting the default return value to true:

1if UNLEASH.is_enabled? "AwesomeFeature", @unleash_context, true
2 puts "AwesomeFeature is enabled by default"
3end
4# or
5if UNLEASH.is_disabled? "AwesomeFeature", @unleash_context, true
6 puts "AwesomeFeature is disabled by default"
7end

Another possibility is to send a block, Lambda or Proc to evaluate the default value:

1net_check_proc = proc do |feature_name, context|
2 context.remote_address.starts_with?("10.0.0.")
3end
4
5if UNLEASH.is_enabled?("AwesomeFeature", @unleash_context, &net_check_proc)
6 puts "AwesomeFeature is enabled by default if you are in the 10.0.0.* network."
7end

or

1awesomeness = 10
2@unleash_context.properties[:coolness] = 10
3
4if UNLEASH.is_enabled?("AwesomeFeature", @unleash_context) { |feat, ctx| awesomeness >= 6 && ctx.properties[:coolness] >= 8 }
5 puts "AwesomeFeature is enabled by default if both the user has a high enough coolness and the application has a high enough awesomeness"
6end

Note:

  • The block/lambda/proc can use the feature name and context as arguments.
  • The client will evaluate the fallback function once per call of is_enabled(). Please keep this in mind when creating your fallback function.
  • The returned value of the block should be a boolean. However, the client will coerce the result to a boolean via !!.
  • If both a default_value and fallback_function are supplied, the client will define the default value by ORing the default value and the output of the fallback function.

Alternatively by using if_enabled (or if_disabled) you can send a code block to be executed as a parameter:

1UNLEASH.if_enabled "AwesomeFeature", @unleash_context, true do
2 puts "AwesomeFeature is enabled by default"
3end

Note: if_enabled (and if_disabled) only support default_value, but not fallback_function.

Check variants

If no flag is found in the server, use the fallback variant.

1fallback_variant = Unleash::Variant.new(name: 'default', enabled: true, payload: {"color" => "blue"})
2variant = UNLEASH.get_variant "ColorVariants", @unleash_context, fallback_variant
3
4puts "variant color is: #{variant.payload.fetch('color')}"

Bootstrap

Bootstrap configuration allows the client to be initialized with a predefined set of toggle states. Bootstrapping can be configured by providing a bootstrap configuration when initializing the client.

1@unleash = Unleash::Client.new(
2 url: '<YOUR_UNLEASH_URL>/api',
3 app_name: 'my_ruby_app',
4 custom_http_headers: { 'Authorization': '<YOUR_API_TOKEN>' },
5 bootstrap_config: Unleash::Bootstrap::Configuration.new({
6 url: "<YOUR_UNLEASH_URL>/api/client/features",
7 url_headers: {'Authorization': '<YOUR_API_TOKEN>'}
8 })
9)

The Bootstrap::Configuration initializer takes a hash with one of the following options specified:

  • file_path - An absolute or relative path to a file containing a JSON string of the response body from the Unleash server. This can also be set through the UNLEASH_BOOTSTRAP_FILE environment variable.
  • url - A url pointing to an Unleash server’s features endpoint, the code sample above is illustrative. This can also be set through the UNLEASH_BOOTSTRAP_URL environment variable.
  • url_headers - Headers for the GET HTTP request to the url above. Only used if the url parameter is also set. If this option isn’t set then the bootstrapper will use the same url headers as the Unleash client.
  • data - A raw JSON string as returned by the Unleash server.
  • block - A lambda containing custom logic if you need it, an example is provided below.

You should only specify one type of bootstrapping since only one will be invoked and the others will be ignored. The order of preference is as follows:

  • Select a data bootstrapper if it exists.
  • If no data bootstrapper exists, select the block bootstrapper.
  • If no block bootstrapper exists, select the file bootstrapper from either parameters or the specified environment variable.
  • If no file bootstrapper exists, then check for a URL bootstrapper from either the parameters or the specified environment variable.

Example usage:

First, save the toggles locally:

$curl -H 'Authorization: <YOUR_API_TOKEN>' -XGET '<YOUR_UNLEASH_URL>/api' > ./default-toggles.json

Then use them on startup:

1custom_boostrapper = lambda {
2 File.read('./default-toggles.json')
3}
4
5@unleash = Unleash::Client.new(
6 app_name: 'my_ruby_app',
7 url: '<YOUR_UNLEASH_URL>/api',
8 custom_http_headers: { 'Authorization': '<YOUR_API_TOKEN>' },
9 bootstrap_config: Unleash::Bootstrap::Configuration.new({
10 block: custom_boostrapper
11 })
12)

This example could be easily achieved with a file bootstrapper, this is just to illustrate the usage of custom bootstrapping. Be aware that the client initializer will block until bootstrapping is complete.

Client methods

Method nameDescriptionReturn type
is_enabled?Checks if a feature toggle is enabled or notBoolean
enabled?A more idiomatic Ruby alias for the is_enabled? methodBoolean
if_enabledRuns a code block, if a feature is enabledyield
is_disabled?Checks if feature toggle is enabled or notBoolean
disabled?A more idiomatic Ruby alias for the is_disabled? methodBoolean
if_disabledRuns a code block, if a feature is disabledyield
get_variantGets variant for a given featureUnleash::Variant
shutdownSaves metrics to disk, flushes metrics to server, and then kills ToggleFetcher and MetricsReporter threads—a safe shutdown, not generally needed in long-running applications, like web applicationsnil
shutdown!Kills ToggleFetcher and MetricsReporter threads immediatelynil

For the full method signatures, see client.rb.

Built-in activation strategies

This client comes with all the required strategies out of the box:

  • ApplicationHostnameStrategy
  • DefaultStrategy
  • FlexibleRolloutStrategy
  • GradualRolloutRandomStrategy
  • GradualRolloutSessionIdStrategy
  • GradualRolloutUserIdStrategy
  • RemoteAddressStrategy
  • UserWithIdStrategy

Custom strategies

You can add custom activation strategies using configuration. In order for the strategy to work correctly it should support two methods name and is_enabled?.

1class MyCustomStrategy
2 def name
3 'myCustomStrategy'
4 end
5
6 def is_enabled?(params = {}, context = nil)
7 true
8 end
9end
10
11Unleash.configure do |config|
12 config.strategies.add(MyCustomStrategy.new)
13end

Check out our guide for more information on how to build and scale feature flag systems.

Migrating to v6

If you use custom strategies or override built-in ones, read the complete v6 migration guide before upgrading.