Skip to main content

Documentation Index

Fetch the complete documentation index at: https://growthbook-preview.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Ruby SDK Resources v1.3.0 growthbook-rubyRubyGemsRails example appGet help on Slack

Requirements

The Ruby SDK requires Ruby version 2.5.0 or higher.

Installation

Install the gem:
gem install growthbook

Quick start

require 'growthbook'  

# Fetch features from a GrowthBook instance  
# You should cache this in Redis or similar in production  
features_repository = Growthbook::FeatureRepository.new(  
  endpoint: 'https://cdn.growthbook.io/api/features/MY_API_KEY',  
  decryption_key: nil  
)  
features = features_repository.fetch  

# Create a context for the current user/request  
gb = Growthbook::Context.new(  
  features: features,  
  # User attributes for targeting / variation assignment  
  attributes: {  
    id: '123',  
    country: 'US'  
  }  
)  

# Use a boolean feature flag  
if gb.on? :my_feature_key  
  puts 'My feature is on!'  
end  

# Get the value of a multivariate feature with a fallback  
btn_color = gb.feature_value(:signup_btn_color, 'pink')

Tracking

Track experiment impressions

When a feature’s value is determined by an experiment (A/B test), you typically want to track that assignment event for later analysis. There are two ways to do this. First is by accessing all impressions at the end of a request:
gb.impressions.each do |key, result|  
  puts "Assigned variation #{result.variation_id} in experiment #{key}"  
end
Second is by using a listener to get alerted in realtime as users are put into experiments:
class MyImpressionListener  
  def on_experiment_viewed(experiment, result)  
    puts "Assigned variation #{result.variation_id} in experiment #{experiment.key}"  
  end  
end  

gb.listener = MyImpressionListener.new

Track feature usage

GrowthBook can fire a callback whenever a feature is evaluated for a user. This can be useful to update 3rd party tools like NewRelic or DataDog. Provide a receiver that can receive def on_feature_usage: (String _feature_key, FeatureResult _result) -> void. There’s a convenience class FeatureUsageCallback with a method you can override but you can provide your own.
class MyFeatureUsageCallback < FeatureUsageCallback  
  def on_feature_usage(feature_key, feature_result)  
    puts "on_feature_usage_called with key: #{feature_key} and result #{feature_result}"  
  end  
end  

on_feature_usage = MyFeatureUsageCallback.new  

# you can pass it into the context  
gb = Growthbook::Context.new({  
  attributes: {  
    id: 'user-abc123'  
  },  
  features: feature_repository.fetch || {},  
  on_feature_usage: on_feature_usage,  
})  

# or assign it afterwards  
gb.on_feature_usage = on_feature_usage

Using with Rails

You can use the provided Growthbook::FeatureRepository class along with the Rails cache to fetch features periodically within your usage limits. Here is a controller concern you can use:
require 'growthbook'  

module GrowthbookSdk  
  def growthbook  
    @growthbook ||= Growthbook::Context.new(  
      features: growthbook_features_json,  
      attributes: {},  
    )  
  end  

  # use this as a before_action on your controller  
  def init_feature_flags  
    return if current_user.nil?  

    # TODO: Change this to get your user attributes as a hash in a way that works for your app  
    growthbook.attributes = current_user.as_json  
  end  

  private  

  def growthbook_features_json  
    Rails.cache.fetch("growthbook_features", expires_in: 1.hour) do  
      puts "🌎 Fetching GrowthBook features from the network"  

      repo = Growthbook::FeatureRepository.new(  
        endpoint: 'https://cdn.growthbook.io/api/features/java_NsrWldWd5bxQJZftGsWKl7R2yD2LtAK8C8EUYh9L8',  
        decryption_key: nil,  
      )  

      repo.fetch || {}  
    end  
  end  
end
And in your ApplicationController:
class ApplicationController < ActionController::API  
  include Authentication # your own auth strategy  
  include GrowthbookSdk # the controller concern code above  

  before_action :authenticate!  
  before_action :init_feature_flags # call this once you have a user from which to get attributes  
end
The above code exposes the following methods on your application controller:
  • growthbook: an instance of the GrowthBook SDK for the request
  • init_feature_flags: a method intended to be used as a before_action hook, e.g. before_action :init_feature_flags
It assumes you have a method current_user that returns the currently-authenticated user, and that it responds to as_json to return a hash of the targeting attributes. How this works:
  1. With each request, the init_feature_flags method is called. This creates a new instance of Growthbook::Context
  2. When creating the context for the first time, features are fetched and cached in the Rails cache. Subsequent calls use the cached version until the cache expires.
  3. Developers can call methods on growthbook in their controllers to use the GrowthBook SDK, e.g. growthbook.on?(:dark_mode).
You can see the Rails example linked in the Code examples below.

Dev and QA helpers

For dev/QA it’s often useful to force specific feature values.
# These take precedence over everything else when determining a feature's value  
gb.forced_features = {  
  my_feature: true,  
  other_feature: "new value"  
}  

# Will always be true  
gb.is_on?(:my_feature)  

# Will always be "new value"  
gb.feature_value(:other_feature)
For more predictability during QA, you can also globally disable all random assignment in experiments from running:
gb.enabled = false

Sticky Bucketing

Available starting in version 1.3.0 By default GrowthBook does not persist assigned experiment variations for a user. We rely on deterministic hashing to ensure that the same user attributes always map to the same experiment variation. However, there are cases where this isn’t good enough. For example, if you change targeting conditions in the middle of an experiment, users may stop being shown a variation even if they were previously bucketed into it. Sticky Bucketing is a solution to these issues. You can provide a Sticky Bucket Service to the GrowthBook instance to persist previously seen variations and ensure that the user experience remains consistent for your users. A sample InMemoryStickyBucketService implementation is provided for reference, but in production you will definitely want to implement your own version using a database, cookies, or similar for persistence. Sticky Bucket documents contain three fields
  • attributeName - The name of the attribute used to identify the user (e.g. id, cookie_id, etc.)
  • attributeValue - The value of the attribute (e.g. 123)
  • assignments - A hash of persisted experiment assignments. For example: {"exp1__0":"control"}
The attributeName/attributeValue combo is the primary key. Here’s an example implementation using a theoretical db object:
require 'growthbook'  

class MyStickyBucketService < Growthbook::StickyBucketService  
  def get_assignments(attribute_name, attribute_value)  
    db.find({  
      attributeName: attribute_name,  
      attributeValue: attribute_value  
    })  
  end  

  def save_assignments(doc)  
    # Insert new record if not exists, otherwise update  
    db.upsert({  
        attributeName: doc["attributeName"],  
        attributeValue: doc["attributeValue"]  
    }, {  
      "$set": {  
        assignments: doc["assignments"]  
      }  
    })  
  end  
end  

# Pass in an instance of this service to your GrowthBook constructor  
gb = Growthbook::Context.new(  
  sticky_bucket_service: MyStickyBucketService.new  
)

Inline experiments

It’s also possible to directly run an experiment directly in code without going through a feature flag.
# Simple 50/50 experiment  
result = gb.run(Growthbook::InlineExperiment.new(  
  key: "my-experiment-key",  
  variations: ["red", "green"]  
))  

# Whether or not the user was included in the experiment (either true or false)  
puts(result.in_experiment ? 'included' : 'excluded')  

# The value of the assigned variation (either "red" or "green")  
puts(result.value)  

# The variation index (either 0 or 1)  
puts(result.variation_id)
There are lots of additional options when running inline experiments:
gb.run(Growthbook::InlineExperiment.new(  
  key: "my-experiment-key",  
  variations: ["red", "green"],  
  # Filter by context attributes  
  condition: {  
    country: {  
      "$in": ["US", "CA"]  
    }  
  },  
  # Adjust variation weights from the default 50/50 split  
  weights: [0.8, 0.2],  
  # Run for a subset of traffic (0 to 1, default = 1)  
  coverage: 0.5,  
  # Use a different context attribute for assigning a variation (default = "id")  
  hash_attribute: "device_id",  
  # Use a namespace to run mutually exclusive experiments  
  namespace: ["pricing-page", 0, 0.25]  
))

Working with Encrypted features

You can learn more about SDK Connection Endpoint Encryption. Create a GrowthBook::Context with an encrypted payload and a decryption key:
# TODO: Replace these values with your own:  
Growthbook::Context.new(  
  encrypted_features: 'm5ylFM6ndyOJA2OPadubkw==.Uu7ViqgKEt/dWvCyhI46q088PkAEJbnXKf3KPZjf9IEQQ+A8fojNoxw4wIbPX3aj',  
  decryption_key: 'Zvwv/+uhpFDznZ6SX28Yjg==',  
  attributes: {  
    id: '456',  
    country: 'CA'  
  }  
)
When fetching features from the GrowthBook SDK endpoint, the encrypted features are available on a property encryptedFeatures instead of plain text on the property features. Here’s an example with networking:
uri = URI('https://cdn.growthbook.io/api/features/MY_API_KEY')  
res = Net::HTTP.get_response(uri)  
encrypted_features = res.is_a?(Net::HTTPSuccess) ? JSON.parse(res.body)['encryptedFeatures'] : nil  

Growthbook::Context.new(  
  encrypted_features: encrypted_features,  
  decryption_key: '<key-for-decrypting>',  
  attributes: {  
    id: '456',  
    country: 'CA'  
  }  
)

Code Examples

Further Reading

Supported Features

FeaturesAll versions ExperimentationAll versions Prerequisites≥ v1.3.0 Sticky Bucketing≥ v1.3.0 SemVer Targeting≥ v1.2.2 Encrypted Features≥ v1.1.0 v2 Hashing≥ v1.0.0