Project

flippant

0.0
Repository is archived
No release in over 3 years
Low commit activity in last 3 years
Fast feature toggling for applications
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.0
>= 0
~> 4.0
~> 3.7
~> 0.52
 Project Readme

Flippant

Fast feature toggling for ruby applications, backed by Redis.

Installation

Add this line to your application's Gemfile:

gem 'flippant'

Usage

Flippant composes three constructs to determine whether a feature is enabled:

  • Actors - An actor can be any value, but typically it is a User or some other object representing a user.
  • Groups - Groups are used to identify and qualify actors. For example, "everybody", "nobody", "admins", "staff", "testers" could all be groups names.
  • Rules - Rules represent individual features which are evaluated against actors and groups. For example, "search", "analytics", "super-secret-feature" could all be rule names.

Group names may be either strings or symbols.

Let's walk through setting up a few example groups and rules. You'll want to establish groups at startup, as they aren't likely to change (and defining functions from a web interface isn't wise).

Groups

First, a group that nobody can belong to. This is useful for disabling a feature without deleting it:

Flippant.register("nobody", ->(actor, _) { false })

Now the opposite, a group that everybody can belong to:

Flippant.register("everybody", ->(actor, _) { true })

To be more exclusive and define staff-only features we need a "staff" group:

Flippant.register("staff", ->(actor, _) { actor.staff? })

Lastly, we'll roll out a feature out to a percentage of the actors:

Flippant.register("adopters", ->(actor, buckets) { buckets.include?(actor.id % 10) })

To tidy up a bit, we can define the registered group detection functions in a separate module.

module FeatureGroups
  def self.premium_subscriber?(actor, _)
    actor.premium_subscriber?
  end

  def self.allowed_user?(actor, allowed_ids)
    allowed_ids.include?(actor.id)
  end
end

Flippant.register("premium_subscriber", &FeatureGroups.method(:premium_subscriber?))
Flippant.register("allowed_user", &FeatureGroups.method(:allowed_user?))

With some core groups defined we now can set up some rules.

Rules

Rules are comprised of a name, a group, and an optional set of values. Starting with a simple example that builds on the groups we have already created, we'll enable the "search" feature:

# Any staff can use the "search" feature
Flippant.enable("search", "staff")

# 30% of "adopters" can use the "search" feature as well
Flippant.enable("search", "adopters", [0, 1, 2])

Because rules are only built of binaries and simple data they can be defined or refined at runtime. In fact, this is a crucial part of feature toggling. With a web interface rules can be added, removed, or modified.

# Turn search off for adopters
Flippant.disable("search", "adopters")

# On second thought, enable it again for 10%
Flippant.enable("search", "adopters", [3])

With a set of groups and rules defined we can check whether a feature is enabled for a particular actor:

class User
  attr_accessor :id, :is_staff

  def initialize(id, is_staff)
    @id = id
    @is_staff = is_staff
  end

  def staff?
    @is_staff
  end
end

staff_user = User.new(1, true)
early_user = User.new(2, false)
later_user = User.new(3, false)

Flippant.enabled?("search", staff_user) #=> true, staff
Flippant.enabled?("search", early_user) #=> false, not an adopter
Flippant.enabled?("search", later_user) #=> true, is an adopter

If an actor qualifies for multiple groups and any of the rules evaluate to true that feature will be enabled for them. Think of the "nobody" and "everybody" groups that were defined earlier:

Flippant.enable("search", "everybody")
Flippant.enable("search", "nobody")

Flippant.enabled?("search", User.new) #=> true

Breakdown

Evaluating rules requires a round trip to the database. Clearly, with a lot of rules it is inefficient to evaluate each one individually. There is a function to help with this exact scenario:

Flippant.enable("search", "staff")
Flippant.enable("delete", "everybody")
Flippant.enable("invite", "nobody")

user = User.new(1, true)
Flippant.breakdown(user) #=> {
  "search" => true,
  "delete" => true,
  "invite" => false
}

The breakdown is a simple hash of string keys to boolean values. This is extremely handy for single page applications where you can serialize the breakdown on boot or send it back from an endpoint as JSON.

Configuration

Both Redis and Memory adapters are available for Flippant's registry storage. Memory is the default.

The Memory adapter behaves identically to the Redis adapter, but will clear out its registry whenever the application is reloaded, so it may be especially useful in testing.

You may want to change this to Redis in production by overriding the adapter setting.

# In Rails, for instance, add this to `config/initializers/flippant.rb`:
Flippant.adapter = if Rails.env.test?
                     Flippant::Adapter::Memory.new
                   else
                     Flippant::Adapter::Redis.new
                   end

License

MIT License, see LICENSE.txt for details.