GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - fetlife/rollout: Feature flippers.

BranchesTags
Open more actions menu

Latest commit

 

History

435 Commits
435 Commits

Folders and files

NameName
Last commit message
Last commit date
.github/workflows
.github/workflows
 
 
docs
docs
 
 
lib
lib
 
 
rollout-active_record-adapter
rollout-active_record-adapter
 
 
rollout-redis-adapter
rollout-redis-adapter
 
 
spec
spec
 
 
.gitignore
.gitignore
 
 
.rspec
.rspec
 
 
.rubocop.yml
.rubocop.yml
 
 
.ruby-version
.ruby-version
 
 
.travis.yml
.travis.yml
 
 
Gemfile
Gemfile
 
 
LICENSE
LICENSE
 
 
README.md
README.md
 
 
Rakefile
Rakefile
 
 
rollout.gemspec
rollout.gemspec
 
 

Repository files navigation

rollout

Fast feature flags.

Upgrading from Rollout 2? Follow the Rollout 3 upgrade guide before updating your dependencies.

Gem Version CI Code Climate Test Coverage

Install it

gem install rollout -v '~> 3.1'
gem install rollout-redis-adapter -v '~> 0.1'
gem "rollout", "~> 3.1"
gem "rollout-redis-adapter", "~> 0.1"

Active Record applications can use rollout-active_record-adapter instead of rollout-redis-adapter. See the Active Record adapter README. To copy current feature state from Redis, see Migrate from Redis.

How it works

Initialize a rollout object. I assign it to a global var.

require "redis"
require "rollout"
require "rollout-redis-adapter"

$redis = Redis.new
$rollout = Rollout.new(adapter: Rollout::Adapters::Redis.new($redis))

Update data specific to a feature:

$rollout.set_feature_data(:chat, description: 'foo', release_date: 'bar', whatever: 'baz')

Check whether a feature is active for a particular user:

$rollout.active?(:chat, User.first) # => true/false

Check whether a feature is active globally:

$rollout.active?(:chat)

You can activate features using a number of different mechanisms.

Groups

Rollout ships with one group by default: "all", which does exactly what it sounds like.

You can activate the all group for the chat feature like this:

$rollout.activate_group(:chat, :all)

You might also want to define your own groups. We have one for our caretakers:

$rollout.define_group(:caretakers) do |user|
  user.caretaker?
end

You can activate multiple groups per feature.

Deactivate groups like this:

$rollout.deactivate_group(:chat, :all)

Groups need to be defined every time your app starts. The logic is not persisted anywhere.

Specific Users

You might want to let a specific user into a beta test or something. If that user isn't part of an existing group, you can let them in specifically:

$rollout.activate_user(:chat, @user)

Deactivate them like this:

$rollout.deactivate_user(:chat, @user)

User Percentages

If you're rolling out a new feature, you might want to test the waters by slowly enabling it for a percentage of your users.

$rollout.activate_percentage(:chat, 20)

The algorithm for determining which users get let in is this:

Zlib.crc32(user.id.to_s) < (2**32 - 1) / 100.0 * percentage

The result is deterministic: the same user is always in or out at a given percentage, and users already included remain included as the percentage increases.

Deactivate all percentages like this:

$rollout.deactivate_percentage(:chat)

Note that activating a feature for 100% of users will also make it active "globally". That is when calling Rollout#active? without a user object.

In some cases you might want to have a feature activated for a random set of users. It can come specially handy when using Rollout for split tests.

$rollout = Rollout.new(
  adapter: Rollout::Adapters::Redis.new($redis),
  randomize_percentage: true,
)

When on randomize_percentage will make sure that 50% of users for feature A are selected independently from users for feature B.

Global actions

While groups can come in handy, the actual global setter for a feature does not require a group to be passed.

$rollout.activate(:chat)

In that case you can check the global availability of a feature using the following

$rollout.active?(:chat)

And if something is wrong you can set a feature off for everybody using

Deactivate everybody at once:

$rollout.deactivate(:chat)

For many of our features, we keep track of error rates using redis, and deactivate them automatically when a threshold is reached to prevent service failures from cascading. See https://github.com/jamesgolick/degrade for the failure detection code.

Check Rollout Feature

You can inspect the state of your feature using:

feature = $rollout.get(:chat)
feature.to_hash
# => { percentage: 5.0, groups: [:caretakers], users: ["1"], data: {} }

Defining features

Describe each feature in its own YAML file, named after the feature. Rollout looks in config/rollouts under the current directory, which is your app's root when you start it from there.

config/rollouts/
├── chat.yml
└── new_inbox.yml

Only .yml files directly in that folder are read. Subfolders, .yaml files and files starting with a dot are ignored. The file name without .yml is the feature name, the same one you pass to $rollout.active?.

# config/rollouts/chat.yml
description: Let members chat in groups.
# config/rollouts/new_inbox.yml
description: Show conversations as threads.
owner: messaging
release_date: "2026-11-15"
Rollout.definitions.keys                      # => [:chat, :new_inbox]
Rollout.definitions[:new_inbox].description   # => "Show conversations as threads."
Rollout.definitions[:new_inbox].path          # => "/app/config/rollouts/new_inbox.yml"
Rollout.definitions[:new_inbox].attributes
# => { "description" => "Show conversations as threads.", "owner" => "messaging", "release_date" => "2026-11-15" }

Definitions only describe features: they don't create or change stored features, and features without a file work as before. Rollout reads description. Every key, including your own like owner, is kept in attributes with string keys for your application.

Rollout.definitions reads the folder on every call. A missing folder means no definitions. description is optional.

Files are read with YAML.safe_load, so values can be strings, numbers, booleans, null, lists and mappings. Quote dates and times (release_date: "2026-10-28"). A file raises Rollout::InvalidDefinition if it isn't valid YAML, isn't a mapping, uses anything else (an unquoted date, an alias, a :symbol), or sets description to something other than a string.

Use another folder, or turn definitions off:

Rollout.definitions_path = Rails.root.join("config", "features")
Rollout.definitions_path = nil

Namespacing

Rollout separates its keys from other keys in the data store using the "feature" keyspace.

If you're using redis, you can namespace keys further to support multiple environments by using the redis-namespace gem.

gem "redis-namespace"
require "redis"
require "redis/namespace"
require "rollout"
require "rollout-redis-adapter"

$ns = Redis::Namespace.new(Rails.env, redis: $redis)
$rollout = Rollout.new(adapter: Rollout::Adapters::Redis.new($ns))
$rollout.activate_group(:chat, :all)

This example stores the chat feature at development:feature:chat when Rails.env is "development".

Frontend / UI

  • rollout-ui
  • Rollout-Dashboard

These integrations may not yet support Rollout 3. Use a version compatible with the Rollout release you install. If you depend on rollout-ui, wait for a Rollout 3-compatible UI release before upgrading production.

Implementations in other languages

  • Python: https://github.com/asenchi/proclaim
  • PHP: https://github.com/opensoft/rollout
  • Clojure: https://github.com/yeller/shoutout
  • Perl: https://metacpan.org/pod/Toggle
  • Golang: https://github.com/SalesLoft/gorollout

Contributors

  • James Golick - Creator - https://github.com/jamesgolick
  • Eric Rafaloff - Maintainer - https://github.com/EricR

Testing

Install dependencies first. Core and the adapter gems have separate Gemfiles:

bundle install
bundle install --gemfile=rollout-redis-adapter/Gemfile
bundle install --gemfile=rollout-active_record-adapter/Gemfile

Core tests do not need Redis:

bundle exec rake spec

Redis adapter tests flush database 7 by default. Use a disposable instance, not a shared or production Redis. Start Redis in a separate terminal:

docker run --rm -p 6379:6379 redis:7-alpine

Then run:

bundle exec rake spec:redis

Optional connection settings: REDIS_HOST, REDIS_PORT, REDIS_DB. REDIS_DB overrides the default database 7 that is flushed before each example.

Active Record adapter tests default to SQLite. PostgreSQL and MySQL are also supported:

bundle exec rake spec:active_record
ROLLOUT_AR_ADAPTER=postgresql bundle exec rake spec:active_record
ROLLOUT_AR_ADAPTER=mysql2 bundle exec rake spec:active_record

Releasing

Each gem has its own version and tag.

  • Configure a RubyGems trusted publisher for the gem you are releasing. Use repository owner fetlife, repository rollout, workflow filename release.yml, and no GitHub environment.
  • Update and commit the version in lib/rollout/version.rb, rollout-redis-adapter/rollout-redis-adapter.gemspec, or rollout-active_record-adapter/rollout-active_record-adapter.gemspec.
  • Tag the release commit with rollout/vX.Y.Z, rollout-redis-adapter/vX.Y.Z, or rollout-active_record-adapter/vX.Y.Z, matching the gem version.
  • Push the tag with git push origin <tag>. CI publishes the selected gem and creates its GitHub release.

Use package-prefixed tags, not vX.Y.Z. Publish core first when the adapter depends on a new core version.

Copyright

Copyright (c) 2010-InfinityAndBeyond BitLove, Inc. See LICENSE for details.

About

Feature flippers.

Topics

feature-flagspercentageredisrolloutruby

Resources

Readme
MIT license
Activity
Custom properties

Stars

2.9k stars

Watchers

59 watching

Forks

211 forks
Report repository

Releases

Packages

Used by

Contributors

Languages

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.