Features
Features are the heart of Flipper. They allow you to control code execution based on a few different types of enablement.
Boolean
All on or all off. Think top level things like :stats, :search, :logging, etc. Also, an easy way to release a new feature, as once a feature is boolean enabled it is on for every situation.
Boolean Example
user = User.find(...)
Flipper.enabled?(:search) # false
Flipper.enabled?(:search, user) # false
Flipper.enable(:search)
Flipper.enabled?(:search) # true
Flipper.enabled?(:search, user) # true
Group
Turn on feature based on the return value of block. Super flexible way to turn on a feature for multiple things (users, people, accounts, etc.) as long as the thing returns true when passed to the block.
Group Example
user = User.find(...) # user who returns false for admin? method
admin = User.find(...) # user who returns true for admin? method
# Define the block that will return true or false based on actor. Registers a
# group named admins which saves the block to be called later.
Flipper.register(:admins) do |actor, context|
actor.respond_to?(:admin?) && actor.admin?
end
Flipper.enabled?(:documentation, user) # false
Flipper.enabled?(:documentation, admin) # false
# Enable the block named admins which means future enabled? checks will see if
# the passing the actor to the block returns true.
Flipper.enable_group(:documentation, :admins)
# When user is passed to enabled?, Flipper knows that the admins group is
# enabled. It executes the registered block above, yielding user. user responds
# to admin?, but admin? returns false, which means the feature will not be
# enabled for the user (true && false == false).
Flipper.enabled?(:documentation, user) # false
# Same as the previous check, but this time admin? returns true for the user
# which means the feature will be enabled (true && true == true).
Flipper.enabled?(:documentation, admin) # true
There is no requirement that the thing yielded to the block be a user model or whatever. It can be anything you want, therefore it is a good idea to check that the thing passed into the group block actually responds to what you are trying to do in the register
proc. This is why the :admin
group above uses respond_to?
.
Actor
Turn feature on for individual thing. Think enable feature for someone to test or for a buddy.
Actor Example
user = User.find(...)
other_user = User.find(...)
Flipper.enabled?(:verbose_logging) # false
Flipper.enabled?(:verbose_logging, user) # false
Flipper.enable_actor(:verbose_logging, user)
Flipper.enabled?(:verbose_logging) # false
Flipper.enabled?(:verbose_logging, user) # true
Flipper.enabled?(:verbose_logging, other_user) # false
The only requirement for an individual actor is that it must have a unique flipper_id
. Include the Flipper::Identifier
module for a default implementation which combines the class name and id
(e.g. User;6
).
class User < Struct.new(:id)
include Flipper::Identifier
end
User.new(5).flipper_id # => "User;5"
You can also define your own implementation:
class Organization < Struct.new(:uuid)
def flipper_id
uuid
end
end
Organization.new("DEB3D850-39FB-444B-A1E9-404A990FDBE0").flipper_id
# => "DEB3D850-39FB-444B-A1E9-404A990FDBE0"
Just make sure each type of object has a unique flipper_id
.
Note: enable_actor
is not designed for hundreds or thousands of actors to be enabled. This is an explicit choice to make it easier to batch load data from the adapters instead of performing individual checks for actors over and over. If you need to enable something for more than 100 individual actors, use a group or % of actors.
Percentage of Actors
Turn this on for a percentage of actors (think user, member, account, group, whatever). Consistently on or off for this user as long as percentage increases. Think slow rollout of a new feature to a percentage of things.
Percentage of Actors Example
user = User.find(...) # responds to flipper_id method call
Flipper.enable_percentage_of_actors(:new_design, 25)
# returns true or false consistently for the same enabled percentage and actor
Flipper.enabled?(:new_design, user)
Percentage of actors also takes into account the feature name to ensure that the same actors are not granted access first to every feature.
Note: 100% of actors will not return true if no actor is provided.
user = User.find(...) # responds to flipper_id method call
# turn on for all actors
Flipper.enable_percentage_of_actors(:new_design, 100)
# true because 100% and actor is passed
Flipper.enabled?(:new_design, user) # true
# false because no actor provided
Flipper.enabled?(:new_design) # => false
Flipper.enabled?(:new_design, nil) # => false
Percentage of Time
Turn this on for a percentage of time. Think load testing new features behind the scenes and such.
Percentage of Time Example
user = User.find(...)
Flipper.enable_percentage_of_time(:dark_ship_new_feature, 25)
# returns true for ~25% of the enabled? calls irregardless of the actor provided
Flipper.enabled?(:dark_ship_new_feature)
Flipper.enabled?(:dark_ship_new_feature, user)
Percentage of time is not a good idea for enabling new features in a UI. Most often you'll use percentage of actors, but there are definitely times when I have found percentage of time to be very useful.
Caveats
- The
disable
method exists only to clear something that is enabled. If the thing you are disabling is not enabled, the disable is pointless. This means that if you enable one group an actor is in and disable another group, the feature will be enabled for the actor. (related issue)