Build Status

Ruby graceful feature rollout and simple A/B testing

gem fluidfeatures-ruby is a pure Ruby client for the API of FluidFeatures.com, which provides an elegant way to wrap new code so you have real-time control over rolling out new features to your user-base.

Integration with Rails

If you are looking to use FluidFeatures with Rails then see https://github.com/FluidFeatures/fluidfeatures-rails

gem fluidfeatures-rails uses this gem fluidfeatures-ruby but integrates into Rails, which makes it quick and easy to use FluidFeatures with your Rails application.

Ruby specific usage (non-Rails usage)

This gem can be used in any Ruby application. This means a Sinatra application, offline processing or sending emails. Anything that touches your user-base can have a customized experience for each user and can be a vehicle for A/B testing. For instance, you can test different email formats and see which one results in more conversions on your website. You could even integrate into a Map-Reduce job.

Installation

gem install fluidfeatures-ruby

"Application"

You create a FluidFeatures::App (application) object with the credentials for accessing the FluidFeatures API. These credentials can be found of the application page of your FluidFeatures dashboard.

Call app on the FluidFeatures module to instantiate this object.

require 'fluidfeatures'

config = {
  "base_uri" => "https://www.fluidfeatures.com/service"
  "app_id" => "1vu33ki6emqe3"
  "secret" = "sssseeecrrreeetttt"
}

fluid_app = FluidFeatures.app(config)

It's also possible to pass a logger object, which will direct all logging to your logger.

fluid_app = FluidFeatures.app(config.update("logger" => logger))

User Transactions

Each interaction with a user is wrapped in a transaction. In fluidfeature-rails, a transaction wraps a single HTTP request.

Transactions guarantee that the feature set for a user will be consistent during the transaction. It is possible that you might reconfigure which users see which features during a transaction, so this ensures that each request is processed atomically regardless of any changes that are occurring outside of the transaction.

To create a new user transaction call user_transaction on the FluidFeatures::App object, which we assigned to fluid_app above.

fluid_user_transaction = fluid_app.user_transaction(user_id, url, display_name, is_anonymous, unique_attrs, cohort_attrs)

An transaction takes a few parameters.

user_id is the unique id that you generally refer to this user by. This can be a numeric or a string. If this is nil then the user is labeled anonymous and a unique id is generated to track this user. You can retrieve this unique id by using anonymous_user_id = fluid_user_transaction.user.unique_id. In HTTP context you should set a HTTP cookie with this anonymous_user_id, so that this user is consistently treated as the same user and experiences the same experience for each visit.

url is a reference to where the transaction is taking place. This has more meaning in HTTP context.

display_name is a human readable name of the user. eg. "Fred Flintstone". FluidFeatures will use this in the web dashboard for display purposes and also for searching for this user.

is_anonymous is a boolean that indicates whether this is an anonymous user or not. If this is true then user_id can be nil if you are not creating and tracking your own user ids for anonymous users.

unique_attrs is a Hash of other attributes that are unique to this user. For instance, their Twitter handle. Provide these are key-value pairs, which can be displayed and searched in the FluidFeatures dashboard. You can freely pass any key-value pairs you wish. For example, :nickname => "The Hungry Bear" or :twitter => "@philwhln".

cohort_attrs is similar to unique_attrs but are not unique to this user. This is a way that you can group users together when managing feature visibility. These can be anything. A common one is :admin => true or :admin => false. Other ones include :coffee_drinker => true, :age_group => "25-33" or :month_joined => "2012-july".

Within the FluidFeatures service, we plan to automatically add other cohorts, such as :is_early_adopter, :active_user, :stale_user to help you target your new features and engage the right users with the right features. This is outside the scope of this gem.

Is this feature enabled for this user?

This is at the core of graceful feature rollout. When the answer is always "no" for a feature, then you are able to push that feature into production without anyone knowing. After that you can start enabling it for specific users and groups of users.

To find out if the feature is enabled for the current user, call feature_enabled? on your FluidFeatures::UserTransaction object, which we assigned to fluid_transaction above.

if fluid_user_transaction.feature_enabled? feature_name
  # implement feature here
end

In the above example it will use "default" for the version of the feature. If you wish to define your own version, you can pass version_name as shown below.

if fluid_user_transaction.feature_enabled? feature_name, version_name
  # implement feature here
end

Real-world examples might be...

if fluid_user_transaction.feature_enabled? "theme", "old-one"
  # show the user the old theme
end
if fluid_user_transaction.feature_enabled? "theme", "new-one"
  # show the user the new theme
end
if fluid_user_transaction.feature_enabled? "email-sender", "postfix"
  # send email directly using postfix
end
if fluid_user_transaction.feature_enabled? "email-sender", "sendgrid-smtp"
  # send email using sendgrid's smtp interface
end
if fluid_user_transaction.feature_enabled? "email-sender", "sendgrid-api"
  # send email using sendgrid's http api
end

When FluidFeatures::UserTransaction gets a call to feature_enabled? it may not have seen this feature version before, so it will report this back to the FluidFeatures service as a new feature version. By default, new feature versions are disabled for all users until they are assigned to specific users, cohorts or percentage of users. This is not always ideal, since you may want to wrap an existing feature, such as the ["theme","old-one"] above, in order to phase it out or test it against a newer version. It that case you can tell FluidFeatures explicitly what the default enabled state of this feature version should be, by passing default_enabled boolean as true or false. true will result is all users initially seeing this feature version.

if fluid_user_transaction.feature_enabled? feature_name, version_name, default_enabled
  # implement feature here
end

It's a goal!

The main motivation behind FluidFeatures is to make your site better, increase your conversion rate and ensure that the features you are developing are something that your customers want.

Rolling out new features is fun and doing it gracefully is important, but rolling out the right features is even more important. With "goals" you can keep track of how well each feature is doing and even run short A/B or multi-variant tests to validate your hypothesis about which version of a feature you should rollout.

As you start to rollout a newer version of a feature, you can watch how well your goals are performing for that version and for other versions. If early indications show that your newer version sucks and conversions are lagging, then you can rollback or pause rollout until you can understand the issue better.

Common high-level goals are "signed-up-to-mailing-list", "clicked-buy-button" or "visited-at-least-5-pages-during-session". Lower-level ones might include, "page-loaded-in-under-one-second", "no-errors-written-to-log" or "cpu-stayed-below-80-percent". Facebook tracks spikes in user comments, which they have found usually results in a feature gone wrong and users complaining. Your goals do not have to positive.

Goals are boolean and when one fires for a user then any feature enabled for that user at that time is held accountant, whether the goal is positive or negative.

Flagging a goal is easy. You simply call goal_hit on the FluidFeatures::UserTransaction object, which in the example below is fluid_user_transaction.

fluid_user_transaction.goal_hit goal_name

Might look something like this...

def buy_button_clicked
  @fluid_user_transaction.goal_hit "clicked-buy-button"
end

This goal will automatically appear in your FluidFeatures dashboard after the first time goal_hit is called with this value.

More info

For more information visit http://www.fluidfeatures.com or email [email protected]