# Migrating from fun_with_flags to Bandera

Bandera keeps the same public API and gate model you already use, so migration
is mostly a find-replace plus a config move. The one structural difference is
that **all configuration is read at runtime**. There is no `compile_env`, so
config can live in `config/runtime.exs` and change without a recompile.

This guide walks through the change end to end. Most apps only need steps 1-3.

## At a glance

| fun_with_flags | Bandera |
| --- | --- |
| `{:fun_with_flags, "~> x.y"}` | `{:bandera, "~> 0.1.0"}` |
| `FunWithFlags.enabled?/2`, `enable/2`, `disable/2`, `clear/2` | identical on `Bandera` |
| `FunWithFlags.Actor` / `FunWithFlags.Group` | `Bandera.Actor` / `Bandera.Group` |
| `config :fun_with_flags, :cache, ...` | `config :bandera, cache: [...]` |
| `FunWithFlags.Store.Persistent.Ecto` | `Bandera.Store.Persistent.Ecto` |
| `FunWithFlags.Store.Persistent.Redis` | `Bandera.Store.Persistent.Redis` |
| `FunWithFlags.Notifications.Redis` | `Bandera.Notifications.Redis` |
| `FunWithFlags.Notifications.PhoenixPubSub` | `Bandera.Notifications.PhoenixPubSub` |

## Step 1: Swap the dependency

In `mix.exs`, replace the dependency:

```elixir
# before
{:fun_with_flags, "~> 1.0"},

# after
{:bandera, "~> 0.1.0"},
```

Keep whichever backend deps you already had (`ecto_sql`, `redix`,
`phoenix_pubsub`). They are optional in Bandera too; add only what you use.
If you adopt the test layer (step 7), also add:

```elixir
{:nimble_ownership, "~> 1.0", only: :test}
```

Then run `mix deps.get`.

## Step 2: Rename the API calls

The function names and signatures are unchanged; only the module changes.
Find-replace `FunWithFlags` → `Bandera` across your code:

```elixir
# before
FunWithFlags.enable(:checkout)
FunWithFlags.enabled?(:beta, for: current_user)

# after
Bandera.enable(:checkout)
Bandera.enabled?(:beta, for: current_user)
```

All of these carry over with identical behavior:
`enabled?/2`, `enable/2`, `disable/2`, `clear/2`, `get_flag/1`, `all_flags/0`,
`all_flag_names/0`. The gate options (`for_actor:`, `for_group:`,
`for_percentage_of: {:time | :actors, ratio}`) are the same.

## Step 3: Move the configuration

Change the config key from `:fun_with_flags` to `:bandera`. Bandera reads each
setting as a keyword under the app, and reads it at runtime, so you can keep it
in `config/config.exs` or move it to `config/runtime.exs`.

```elixir
# before (fun_with_flags)
config :fun_with_flags, :cache,
  enabled: true,
  ttl: 900

config :fun_with_flags, :persistence,
  adapter: FunWithFlags.Store.Persistent.Ecto,
  repo: MyApp.Repo

config :fun_with_flags, :cache_bust_notifications,
  enabled: true,
  adapter: FunWithFlags.Notifications.PhoenixPubSub,
  client: MyApp.PubSub
```

```elixir
# after (Bandera)
config :bandera,
  cache: [enabled: true, ttl: 900],
  persistence: [
    adapter: Bandera.Store.Persistent.Ecto,
    repo: MyApp.Repo
  ],
  cache_bust_notifications: [
    enabled: true,
    adapter: Bandera.Notifications.PhoenixPubSub,
    client: MyApp.PubSub
  ]
```

Defaults if you omit a section: in-memory store, cache on (900s TTL),
notifications off. Call `Bandera.reload_config/0` to re-read config at runtime.

> **Note on Redis connection options.** fun_with_flags uses one shared
> `config :fun_with_flags, :redis, [...]`. Bandera nests the Redix options under
> whichever component uses them:
>
> ```elixir
> config :bandera,
>   persistence: [adapter: Bandera.Store.Persistent.Redis, redis: [host: "localhost", port: 6379]],
>   cache_bust_notifications: [enabled: true, adapter: Bandera.Notifications.Redis, redis: [host: "localhost", port: 6379]]
> ```

## Step 4: Re-implement the protocols

If you defined `FunWithFlags.Actor` / `FunWithFlags.Group` for your structs,
re-implement them under the `Bandera` namespace. The callbacks are the same:
`id/1` for actors, `in?/2` for groups:

```elixir
# before
defimpl FunWithFlags.Actor, for: MyApp.User do
  def id(user), do: "user:#{user.id}"
end

defimpl FunWithFlags.Group, for: MyApp.User do
  def in?(user, group_name), do: group_name in user.roles
end

# after
defimpl Bandera.Actor, for: MyApp.User do
  def id(user), do: "user:#{user.id}"
end

defimpl Bandera.Group, for: MyApp.User do
  def in?(user, group_name), do: group_name in user.roles
end
```

Bandera ships built-in implementations for binaries, integers, and maps with an
`:id` / `:groups` key, just like fun_with_flags.

## Step 5: Persistence data

The Ecto table schema is the same shape as fun_with_flags (`flag_name`,
`gate_type`, `target`, `enabled`), so you have two options:

**Option A: keep your existing table.** Point Bandera at it by name and skip
any data migration:

```elixir
config :bandera,
  persistence: [
    adapter: Bandera.Store.Persistent.Ecto,
    repo: MyApp.Repo,
    ecto_table_name: "fun_with_flags_toggles"
  ]
```

**Option B: create a fresh Bandera table** and copy your rows over. Generate
the table from a migration:

```elixir
defmodule MyApp.Repo.Migrations.CreateBanderaFlags do
  use Ecto.Migration

  def up, do: Bandera.Ecto.Migrations.up()
  def down, do: Bandera.Ecto.Migrations.down()
end
```

The table name defaults to `"bandera_flags"` and is read from runtime config.

For **Redis** persistence, Bandera uses its own key prefix
(`bandera:flag:<name>` hashes plus a `bandera:flag_names` set). Either re-create
flags via the API after cutover, or migrate keys to the new prefix.

## Step 6: Notifications

Swap the notification adapter module names; the behavior (cross-node
cache-busting) is unchanged:

- `FunWithFlags.Notifications.Redis` → `Bandera.Notifications.Redis`
- `FunWithFlags.Notifications.PhoenixPubSub` → `Bandera.Notifications.PhoenixPubSub`

The Phoenix.PubSub adapter still takes your PubSub server via `client:`.

## Step 7: Tests (the setup is different)

**This is the one area where the migration is more than a rename.** With
fun_with_flags, flag state is global (shared ETS/DB), so toggling a flag in one
test is visible to every other test. The usual workarounds are running flag
tests with `async: false` and clearing flags manually (e.g. `FunWithFlags.clear/1`
in `setup`/`on_exit`). Tests that write flags can also deadlock under the Ecto
SQL sandbox.

Bandera replaces that model entirely with an **async-safe, process-scoped test
layer**. Overrides are scoped to the test process (and its spawned tasks), so
`async: true` tests don't bleed into each other, toggling a flag never touches
the database, and cleanup is automatic when the test process exits, with no
`setup` or `on_exit` plumbing.

Because the model is different, you need to opt into it explicitly (this has no
fun_with_flags equivalent):

```elixir
# config/test.exs: select the process-scoped store
config :bandera, store: Bandera.Store.ProcessScoped

# test/test_helper.exs: start the override server once
Bandera.Test.start()
```

Add the test-layer dependency to `mix.exs` (see step 1):

```elixir
{:nimble_ownership, "~> 1.0", only: :test}
```

Then drop the `async: false` you needed for flag tests and `use Bandera.Test`:

```elixir
defmodule MyApp.CheckoutTest do
  use ExUnit.Case, async: true
  use Bandera.Test

  @tag feature_flags: [checkout: true]
  test "feature on via tag" do
    assert Bandera.enabled?(:checkout)
  end

  test "toggle in the body" do
    enable_flag(:beta)
    assert Bandera.enabled?(:beta)
  end
end
```

Existing tests that call `Bandera.enable/2`, `Bandera.disable/2`, etc. keep
working unchanged: when `Bandera.Store.ProcessScoped` is configured, those calls
are transparently redirected to the process-scoped overrides instead of the
shared store.

## Done

Steps 1-4 cover most apps; add 5-7 as needed. Same behavior as before, with
config now resolved at runtime.
