Ruby
View the full source code at https://github.com/growthbook/growthbook-ruby.
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 requestinit_feature_flags
: a method intended to be used as abefore_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:
- With each request, the
init_feature_flags
method is called. This creates a new instance ofGrowthbook::Context
- 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.
- 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'
}
)