# Using Bandera with Phoenix LiveView

This is a short, practical guide to gating UI and behavior in a Phoenix
LiveView app with Bandera. It assumes Bandera is already installed and
configured; see the [README](README.md) for that.

LiveViews are just processes, so calling Bandera from one is nothing special:
`Bandera.enabled?/2` works in `mount/3`, `handle_event/3`, `handle_info/2`, and
inside `~H` templates. What's worth spelling out is wiring the current user in as
the actor, and testing. Both are covered below.

## 1. Identify the current user

Most flags are evaluated "for" a user. Implement the `Bandera.Actor` protocol so
Bandera can derive a stable id for your user struct, and `Bandera.Group` if you
use group gates (e.g. roles):

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

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

The simplest way to have `current_user` available in every LiveView is the
`on_mount` hook generated by `mix phx.gen.auth`:

```elixir
# lib/my_app_web/router.ex
live_session :default, on_mount: [{MyAppWeb.UserAuth, :mount_current_user}] do
  live "/dashboard", DashboardLive
end
```

That puts `socket.assigns.current_user` in place before your `mount/3` runs.

## 2. Check a flag in a LiveView

Read the flag in `mount/3`, assign the result, and branch in the template with
`:if`:

```elixir
defmodule MyAppWeb.DashboardLive do
  use MyAppWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    user = socket.assigns.current_user

    {:ok, assign(socket, :new_dashboard?, Bandera.enabled?(:new_dashboard, for: user))}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <.new_dashboard :if={@new_dashboard?} />
    <.classic_dashboard :if={!@new_dashboard?} />
    """
  end
end
```

Assigning once in `mount/3` (rather than calling `Bandera.enabled?/2` directly in
the template) keeps the check out of the render path and makes the value easy to
override in tests.

A reusable helper keeps controllers and LiveViews consistent:

```elixir
# lib/my_app_web/feature_flags.ex
defmodule MyAppWeb.FeatureFlags do
  def assign_flag(socket_or_conn, key, flag, actor) do
    Phoenix.Component.assign(socket_or_conn, key, Bandera.enabled?(flag, for: actor))
  end
end
```

## 3. Gate events and actions

Don't rely on the UI alone. Re-check the flag where the action actually happens,
so a hidden control can't be triggered by a crafted event:

```elixir
@impl true
def handle_event("publish", _params, socket) do
  if Bandera.enabled?(:publishing, for: socket.assigns.current_user) do
    {:noreply, do_publish(socket)}
  else
    {:noreply, put_flash(socket, :error, "That feature isn't available yet.")}
  end
end
```

## 4. (Optional) React to flag changes live

`mount/3` reads the flag once. If you want a running LiveView to pick up a flag
change without a reload, re-read the flag on some trigger, for example a
periodic tick, or a `Phoenix.PubSub` broadcast you send when toggling flags:

```elixir
def mount(_params, _session, socket) do
  if connected?(socket), do: Phoenix.PubSub.subscribe(MyApp.PubSub, "feature_flags")
  {:ok, assign_new_dashboard(socket)}
end

def handle_info(:flags_changed, socket), do: {:noreply, assign_new_dashboard(socket)}

defp assign_new_dashboard(socket) do
  assign(socket, :new_dashboard?, Bandera.enabled?(:new_dashboard, for: socket.assigns.current_user))
end
```

For most apps the mount-time read is enough; reach for this only when live
toggling matters.

## 5. Outside LiveView

The same calls work everywhere: controllers, function components, and plugs:

```elixir
# controller
if Bandera.enabled?(:beta_export, for: conn.assigns.current_user) do
  # ...
end
```

```heex
<%!-- a function component receiving the flag as an assign --%>
<.banner :if={@promo_enabled?} />
```

## 6. Testing LiveViews

The test layer scopes overrides to the **test process and its descendants**. A
LiveView started by `Phoenix.LiveViewTest` is a descendant (Phoenix propagates
`$callers`), so flags you set in the test are visible inside the LiveView. Tests
stay `async: true`, never touch the database, and clean up automatically.

One-time setup (see the README for details):

```elixir
# config/test.exs
config :bandera, store: Bandera.Store.ProcessScoped

# test/test_helper.exs
Bandera.Test.start()
```

Then in a LiveView test, declare flags with the `@tag feature_flags:` tag or set
them imperatively:

```elixir
defmodule MyAppWeb.DashboardLiveTest do
  use MyAppWeb.ConnCase, async: true
  use Bandera.Test
  import Phoenix.LiveViewTest

  setup :register_and_log_in_user

  @tag feature_flags: [new_dashboard: true]
  test "renders the new dashboard when the flag is on", %{conn: conn} do
    {:ok, _live, html} = live(conn, ~p"/dashboard")
    assert html =~ "New dashboard"
  end

  test "falls back to the classic dashboard when off", %{conn: conn} do
    {:ok, _live, html} = live(conn, ~p"/dashboard")
    assert html =~ "Classic dashboard"
  end

  test "enables a flag for one actor only", %{conn: conn, user: user} do
    enable_flag(:new_dashboard, user)
    {:ok, _live, html} = live(conn, ~p"/dashboard")
    assert html =~ "New dashboard"
  end
end
```

Notes:

- `@tag feature_flags: [new_dashboard: true]` sets a global boolean override, so
  it's honored even when the LiveView checks `enabled?(:new_dashboard, for: user)`.
- Use `enable_flag(flag, actor)` / `disable_flag(flag, actor)` to override for a
  specific user, useful for testing per-actor or per-group gates.
- `register_and_log_in_user` is the `mix phx.gen.auth` helper; it puts the user
  in the session so `current_user` is assigned.
