Current section
Files
Jump to
Current section
Files
lib/molasses.ex
defmodule Molasses do
alias Molasses.StorageAdapter.Redis
alias Molasses.StorageAdapter.Postgres
@moduledoc ~S"""
A feature toggle library using redis or SQL (using Ecto) as a backing service. It allows you to roll out to users based on a percentage. Alternatively, you can use Molasses to deploy to a group of users or user ids.
## Installation
1. Add `molasses` to your list of dependencies in `mix.exs` and run `mix deps.get`:
```elixir
def deps do
[{:molasses, "~> 0.2.0"}]
end
```
2. Ensure `molasses` is started before your application:
```elixir
def application do
[applications: [:molasses]]
end
```
3. Install related dependencies by including `ExRedis` or `Ecto` and one of its adapter libraries for Postgres or Mysql.
1. Redis
For Redis, you will just need to include exredis:
```elixir
def deps do
[
{:molasses, "~> 0.2.0"},
{:exredis, ">= 0.2.4"}
]
end
```
2. SQL using Ecto
For Ecto with Postgres, install `ecto` and `postgrex`. You will also need to start ecto and postgrex as applications :
```elixir
def deps do
[
{:molasses, "~> 0.2.0"},
{:ecto, "~> 2.1.1"},
{:postgrex, ">= 0.0.0"}
]
end
def application do
[applications: [:ecto, :postgrex]]
end
```
Your config will also need to change. You will need to set up an Ecto Repo like you would [here](https://hexdocs.pm/ecto/Ecto.html#module-repositories). As well as set the Molasses adapter to postgres.
```elixir
# molasses adapter setting
config :molasses, adapter: "postgres"
```
## Usage
Molasses uses the same interface whether you are using Redis or SQL. Each function takes an `Ecto.Repo` or the `ExRedis` client as the first argument.
"""
@doc """
Check to see if a feature is active for all users.
"""
def is_active(client, key) do
case get_feature(client, key) do
{:error, _} -> false
%{active: false} -> false
%{active: true,percentage: 100, users: []} -> true
%{active: true,percentage: 100} -> false
%{active: true,percentage: _} -> false
end
end
@doc """
Check to see if a feature is active for a specific user.
"""
def is_active(client, key, id) do
case get_feature(client, key) do
{:error, _} -> false
%{active: false} -> false
%{active: true,percentage: 100, users: []} -> true
%{active: true,percentage: 100, users: users} -> Enum.member?(users, id)
%{active: true,percentage: percentage} when is_integer(id) ->
value = Integer.to_string(id) |> :erlang.crc32 |> rem(100) |> abs
value <= percentage
%{active: true,percentage: percentage} when is_bitstring(id) ->
value = id |> :erlang.crc32 |> rem(100) |> abs
value <= percentage
end
end
@doc """
Returns a struct of the feature in question.
"""
def get_feature(client, key) do
case Application.get_env(:molasses, :adapter) do
"ecto" -> Postgres.get_feature(client,key)
_ -> Redis.get_feature(client,key)
end
end
@doc """
Activates a feature for all users.
"""
def activate(client, key) do
case Application.get_env(:molasses, :adapter) do
"ecto" -> Postgres.activate(client,key)
_ -> Redis.activate(client,key)
end
end
@doc """
Activates a feature for some users.
When the group argument is an integer then it sets the feature active for a percentage of users.
When the group argument is a string then it sets a feature active for that specific user or user group.
When the group argument is a list then it sets a feature active for that specific list of users or user groups
## Examples
# activate a feature for a percentage of users
Molasses.activate(client, "my_feature", 75)
# activate a feature for a subset of integer based userIds
Molasses.activate(client, "my_feature", [2, 4, 5])
# activate a feature for a subset of string based userIds (think a mongoId) or a list of groups
Molasses.activate(client, "my_feature", ["admins", "super admins"])
# activate a feature for only one group of users
Molasses.activate(client, "my_feature", "powerusers")
"""
def activate(client, key, group) do
case Application.get_env(:molasses, :adapter) do
"ecto" -> Postgres.activate(client,key, group)
_ -> Redis.activate(client,key, group)
end
end
@doc """
Dectivates a feature for all users.
"""
def deactivate(client, key) do
case Application.get_env(:molasses, :adapter) do
"ecto" -> Postgres.deactivate(client,key)
_ -> Redis.deactivate(client,key)
end
end
end