Skip to content

Commit

Permalink
Merge 55f6124 into a57b718
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerWitt committed Apr 4, 2020
2 parents a57b718 + 55f6124 commit 29c4d48
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 233 deletions.
68 changes: 4 additions & 64 deletions lib/flippant.ex
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,6 @@ defmodule Flippant do
Flippant.load("flippant.dump")
"""

alias Flippant.Registry

# Adapter

@doc """
Expand Down Expand Up @@ -257,51 +255,25 @@ defmodule Flippant do
end

@doc """
Purge registered groups, features, or both.
Purge registered features.
This is particularly useful in testing when you want to reset to a clean
slate after a test.
## Examples
Clear everything:
Flippant.clear()
#=> :ok
Clear only features:
Flippant.clear(:features)
#=> :ok
Clear only groups:
Flippant.clear(:groups)
#=> :ok
"""
@spec clear(:all | :features | :groups) :: :ok
def clear(selection \\ :all)

def clear(:features) do
@spec clear() :: :ok
def clear do
GenServer.cast(adapter(), :clear)
end

def clear(:groups) do
Registry.clear()
end

def clear(:all) do
:ok = clear(:groups)
:ok = clear(:features)

:ok
end

@doc """
Disable a feature for a particular group.
The feature is kept in the registry, but any rules for that group are
removed.
The feature is kept, but any rules for that group are removed.
## Examples
Expand Down Expand Up @@ -519,36 +491,4 @@ defmodule Flippant do
|> String.downcase()
|> String.trim()
end

# Registry

@doc """
Register a new group name and function.
The function *must* have an arity of 2 or it won't be accepted. Registering
a group with the same name will overwrite the previous group.
## Examples
Flippant.register("evens", & rem(&1.id, 2) == 0)
#=> :ok
"""
@spec register(binary, (any, list -> boolean)) :: :ok
def register(group, fun) when is_binary(group) and is_function(fun, 2) do
Registry.register(group, fun)
end

@doc """
List all of the registered groups as a map where the keys are the names and
values are the functions.
## Examples
Flippant.registered()
#=> %{"staff" => #Function<20.50752066/0}
"""
@spec registered() :: map()
def registered do
Registry.registered()
end
end
1 change: 0 additions & 1 deletion lib/flippant/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ defmodule Flippant.Application do
@doc false
def start(_, _) do
children = [
worker(Flippant.Registry, []),
worker(adapter(), [flippant_opts()])
]

Expand Down
72 changes: 0 additions & 72 deletions lib/flippant/registry.ex

This file was deleted.

37 changes: 22 additions & 15 deletions lib/flippant/rules.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,34 @@ defmodule Flippant.Rules do
@type rules :: Enumerable.t()
@type actor :: term()

alias Flippant.Registry

@doc """
Check whether any rules are enabled for a particular actor. The function
accepts a list of names/value pairs and an actor.
Validate that an actor is both a member of `group` and in the `enabled_for` list. For example,
if we had a `staff` group, and a rule containing `%{"staff" => ids}`, we could have a function
like this:
# Example
```
def enabled?("staff", enabled_for, %{staff: true} = actor) do
Enum.any?(enabled_for, &actor.id == &1.id)
end
```
Flippant.Rules.enabled_for_actor?(rules, actor, groups)
This both validates the actor is staff, and that the actor is in the enabled list. Allowing
rules definition in this way makes the logic for rule checking very visible in application
code.
"""
@callback enabled?(group :: binary(), enabled_for :: list(), actor :: Rules.actor()) ::
boolean()

Without a third argument of the groups to be checked it falls back to
collecting the globally registered groups.
@doc """
Check whether any rules are enabled for a particular actor. The function
accepts a list of names/value pairs and an actor.
"""
@spec enabled_for_actor?(rules(), actor(), map() | nil) :: boolean()
def enabled_for_actor?(rules, actor, groups \\ nil) do
groups = groups || Registry.registered()
@spec enabled_for_actor?(rules(), actor()) :: boolean()
def enabled_for_actor?(rules, actor) do
ruleset = Application.fetch_env!(:flippant, :rules)

Enum.any?(rules, fn {name, values} ->
if fun = Map.get(groups, name) do
apply(fun, [actor, values])
end
Enum.any?(rules, fn {group, enabled_for} ->
ruleset.enabled?(group, enabled_for, actor)
end)
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ defmodule Flippant.Mixfile do
def application do
[
extra_applications: [:logger],
env: [adapter: Flippant.Adapter.Memory],
env: [adapter: Flippant.Adapter.Memory, rules: Flippant.Rules.Default],
mod: {Flippant.Application, []}
]
end
Expand Down
33 changes: 0 additions & 33 deletions test/flippant/registry_test.exs

This file was deleted.

57 changes: 50 additions & 7 deletions test/flippant/rules_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,58 @@ defmodule Flippant.RulesTest do
use ExUnit.Case, async: true

alias Flippant.Rules
alias Flippant.Rules.Default

describe "enabled_for_actor?/3" do
actor = %{id: 1, name: "Parker"}
group = %{"chosen" => fn %{id: id}, value -> id == value end}
describe "enabled_for_actor?/2 with default rules" do
test "nobody group is always disabled" do
actor = %{id: 1, name: "Parker"}
rule = %{"nobody" => []}

refute Rules.enabled_for_actor?([], actor)
refute Rules.enabled_for_actor?([], actor, %{})
refute Rules.enabled_for_actor?([{"chosen", 1}], actor, %{})
refute Rules.enabled_for_actor?(rule, actor)
end

assert Rules.enabled_for_actor?([{"chosen", 1}], actor, group)
test "everybody group is always enabled for a feature" do
actor = %{id: 1, name: "Parker"}
rule = %{"everybody" => []}

assert Rules.enabled_for_actor?(rule, actor)
end

test "unknown groups are always disabled for a feature" do
actor = %{id: 1, name: "Parker"}
rule = %{"users" => [1]}

refute Rules.enabled_for_actor?(rule, actor)
end
end

defmodule TestRules do
def enabled?("staff", _enabled_for, %{staff?: true}), do: true

def enabled?("users", enabled_for, actor) do
Enum.any?(enabled_for, &(actor.id == &1))
end

def enabled?(_group, _enabled_for, _actor), do: false
end

describe "enabled_for_actor?/2 with custom rules" do
setup do
Application.put_env(:flippant, :rules, TestRules)

on_exit(fn -> Application.put_env(:flippant, :rules, Default) end)
end

test "groups and membership are asserted through custom rules" do
staff_actor = %{id: 1, name: "Parker", staff?: true}
user_actor = %{id: 2, name: "Parker"}
non_membered_actor = %{id: 3, name: "Parker"}

rule = %{"staff" => [], "users" => [2]}

assert Rules.enabled_for_actor?(rule, staff_actor)
assert Rules.enabled_for_actor?(rule, user_actor)
refute Rules.enabled_for_actor?(rule, non_membered_actor)
end
end
end

0 comments on commit 29c4d48

Please sign in to comment.