Toggle

Toggle is a stupid-simple feature flag library for Elixir.

It provides a simple API for enabling and disabling feature flags in your application.

Installation

Add toggle to your list of dependencies in mix.exs:

def deps do
  [
    {:toggle, "~> 0.1.0"}
  ]
end

Usage

In order to use Toggle, you need to:

  1. Configure Toggle to use your application's Ecto Repo to store flags.
  2. Add an Ecto Migration to your application to set up the database table Toggle will use to store data.
  3. Set up EctoHooks in your application's Ecto Repo.

Repo Setup & Migrations

Currently, Toggle supports SQLite3 and Postgres as database backends.

You can set up Toggle to use your application's Ecto Repo by adding the following configuration to your config.exs:

config :toggle, repo: MyApp.Repo

Then, you can create migrations using the Toggle.Migrations.SQLite or Toggle.Migrations.Postgres helper modules:

defmodule MyApp.Repo.Migrations.CreateFlagsTables do
  use Ecto.Migration

  def up do
    # Replace `Toggle.Migrations.SQLite` with `Toggle.Migrations.Postgres` if you are using Postgres
    Toggle.Migrations.SQLite.up(table: "toggle_flags")
  end

  def down do
    # Replace `Toggle.Migrations.SQLite` with `Toggle.Migrations.Postgres` if you are using Postgres
    Toggle.Migrations.SQLite.down(table: "toggle_flags")
  end
end

EctoHooks Setup

Toggle uses EctoHooks to automatically update a cache whenever a flag is created, updated, or deleted.

Please add the following lines to your application's Repo module:

use EctoMiddleware

def middleware(_action, _resource) do
  [EctoHooks.Middleware.Before, EctoMiddleware.Super, EctoHooks.Middleware.After]
end

This will enable EctoHooks for your application's Repo module.

Please see the documentation for EctoHooks for further information, though this is not needed to use Toggle.

Using Toggle

After you have run your migrations and set up EctoHooks, you can start using Toggle in your application.

iex> Toggle.enabled?("my_feature")
false
iex> Toggle.enable!("my_feature")
:ok
iex> Toggle.enabled?("my_feature")
true

You can also enable flags specifically for some arbitrary resource:

iex> Toggle.disable!("my_feature")
:ok
iex> Toggle.enable!("my_feature", user_id: 123)
:ok
iex> Toggle.enabled?("my_feature")
false
iex> Toggle.enabled?("my_feature", user_id: 123)
true

Resources are key-value pairs that can be used to enable or disable a flag for a specific entity. The expected data type for both the key and value is an atom, string, or integer.

Today, toggle only supports boolean flags, but we plan to add support for more flag types in the future.

Whether or not a flag is enabled is determined by the following rules:

Info

One edge case is that Toggle.disabled?/1 and Toggle.disabled?/2 are implemented as inverses of Toggle.enabled?/1 and Toggle.enabled?/2 which means that if a flag does not exist, it is considered disabled.

Caching

Querying flags on every request can be expensive, so Toggle provides a caching layer to speed up flag lookups.

Whenever a flag is created, updated, or deleted, we update a cache with the latest flag data using Cachex under the :toggle_cache namespace.

Whenever the cache is mutated, Toggle.Cache will asynchronously publish the changes to all other connected nodes using Erlang's :erpc module. In the future, we plan to add support for other pubsub strategies.

Using Toggle.enabled?/1, Toggle.enabled?/2, Toggle.disabled?/1, or Toggle.disabled?/2 will automatically use the cache if it is available.

The cache has a default ttl of one minute.

Testing

If you're using and interacting Toggle in your tests, every time a flag value is created, updated, or deleted, Toggle will automatically cache the state of the flag.

This can lead to unexpected results in your tests, as unlike the datastore, the cache is not reset between tests by default.

You can reset the cache between tests by calling Toggle.Cache.reset/0 in your test setup or teardown.

defmodule MyApp.MyTest do
  use ExUnit.Case

  # Always reset cache at the beginning of each test.
  setup do
    Toggle.Cache.reset()
    :ok
  end

  # Or, reset the cache as tests teardown.
  setup do
    on_exit(fn -> Toggle.Cache.reset() end)
  end
end

Configuration

Toggle aims to be as simple as possible, and as such, it does not require any configuration.

The only possible configuration at the time of writing is:

See Toggle.Comptime for more information.

Further Reading

The core API for Toggle is provided by the Toggle module. You can use the Toggle module to enable and disable flags, and check if a flag is enabled or disabled.

For more complex querying, please see the Toggle.Flags module, the Toggle.Flags.Flag schema, and the libraries this application is built on: EctoModel and EctoHooks.

The Toggle.Flags.Flag schema implements the EctoModel.Queryable behaviour, which lends itself well to fluent-api style querying.

For example, you are able to query flags using the Toggle.Flags.Flag.query/2 function:

iex> MyApp.Repo.one(Toggle.Flags.Flag.query(name: "my_flag", disabled: true))
%Toggle.Flags.Flag{name: "my_flag", enabled: false}

Additionally, please feel free to query the Toggle.Flags.Flag schema directly.

Future Work