Feature flags for Rails
Ruby JavaScript CSS
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
app
config
lib
script
spec
.gitignore
.travis.yml
Gemfile Create and destroy opt outs Dec 30, 2013
LICENSE.txt
README.md
Rake.md Move Rake task outline to Rake.md Jan 3, 2014
Rakefile
detour.gemspec test functionality for preserving options between failing flag saves Jan 30, 2014

README.md

Detour

Rollouts for ActiveRecord models. It is a spiritual fork of ArRollout.

development status master status Code Climate
development status master status Code Climate

Contents


About

Detour helps you manage feature flags in your Rails app, allowing you to test out new features via incremental rollouts to subsets of records, rather than exposing your entire userbase to beta features.

Requirements

Detour currently requires:

  • Ruby >= 1.9.3
  • Rails 3.2 (support for Rails 4 coming)

Installation

Add it to your Gemfile:

gem 'detour'

Bundle:

$ bundle install

Run the initializer:

$ rails generate detour

Run the Detour migrations:

$ rake db:migrate

Configuration

The Detour initializer creates an initializer at config/initializers/detour.rb.

Mount Detour in your app

Detour is a Rails::Engine, and needs to be mounted at a point of your choosing in your config/routes.rb:

Rails.application.routes.draw do
  mount Detour::Engine => "/admin/detour"
end

Make your models flaggable

Each model that you'd like to be able to be flagged into and out of features needs to call acts_as_flaggable:

class User < ActiveRecord::Base
  acts_as_flaggable
end

class Widget < ActiveRecord::Base
  acts_as_flaggable
end

When flagging in users, for example, in the Detour admin interface, you probably won't know their ID values. You can tell Detour that you'd like to find users by email, instead (or any other attribute):

class User < ActiveRecord::Base
  acts_as_flaggable find_by: :email
end

class User < ActiveRecord::Base
  acts_as_flaggable find_by: :name
end

Define flaggable types

Detour needs a list of all classes that will potentially be flagged into features. This list can be set with the flaggable_types setting, and needs to match the list of classes that call acts_as_flaggable.

Detour.configure do |config|
  # The `User` and `Widget` models will be able to be flagged into features.
  config.flaggable_types = %w[User Widget]
end

Define search directories

Detour's admin interface can tell you exactly where you're checking for each feature in your code, but it needs to be told what directories to search through:

Detour.configure do |config|
  # Detour will search for `.rb` and `.erb` files anywhere in your `app`
  # directory for feature checks.
  config.feature_search_dirs = %w[app/**/*.{rb,erb}]
end

Define a feature check regex

In order for the Detour admin interface to tell you where you're checking for specific features, Detour by default looks for things that look like .has_feature?(:feature_name) and .has_feature?("feature_name"). However, you may have aliased this method, or wrapped it in something else. You can change Detour's search regular expression:

Detour.configure do |config|
  # Detour will look for feature checks that look more like `rollout?(:feature)`
  config.feature_search_regex = /rollout\?\s*\(?[:"]([\w-]+)/
end

Define groups

Detour can roll out features to records in groups. These groups can either be defined in the database, which you manage in the Detour admin interface, or they can be defined in code, in Detour's initializer. The blocks that you use to define your groups will get a record passed in to them, that you can check for specific traits. If the block returns a truthy value, the record will be considered part of the group.

Detour.configure do |config|
  # Define a group for your admins
  config.define_users_group :admins do |user|
    user.admin?
  end

  # Define a group for your super users
  config.define_users_group :super_users do |user|
    user.super_user?
  end

  # Define a group for your premium widgets
  config.define_widgets_group :premiums do |widget|
    widget.is_premium?
  end
end

Restrict the admin interface

You probably don't want any user to have access to your Detour admin interface, which is open to all by defaut. The easier way to restrict access is to class_eval Detour's main controller in the Detour initializer. Detour::ApplicationController probably won't have methods like current_user available to it, so it's easiest to include them via a module, as below:

Detour::ApplicationController.class_eval do
  include CurrentUser

  before_filter :admin_required!

  private

  def admin_required!
    if current_user && current_user.admin?
      true
    else
      # Redirect a non-admin user somewhere else
    end
  end
end

Usage

The usage examples all assume that Detour is mounted on the /detour path, and they assume a User class that more or less looks like the following:

class User < ActiveRecord::Base
  acts_as_flaggable find_by: :email
end

In these examples, we'll be logged in as the user with email "user@example.com", and we'll be viewing the following page:

<% if current_user.has_feature?(:foo_feature) %>
 <h1>User has "foo_feature"!</h1>
<% end %>

Flagging in a single user

Flagging in a defined group

Flagging in a managed group

Flagging in a percentage of users

Opting out a user

Contributing

  1. Fork it ( http://github.com//detour/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Thanks, Heroku

While I created and maintain this project, it was done while I was an employee of Heroku on the Human Interfaces Team, and they were kind enough to allow me to open source the work. Heroku is awesome.