# `DoubleDown.Repo.InMemory`
[🔗](https://github.com/mccraigmccraig/double_down/blob/main/lib/double_down/repo/in_memory.ex#L2)

Stateful in-memory Repo fake (closed-world). **Recommended default.**

The state is the complete truth — if a record isn't in the store,
it doesn't exist. This makes the adapter authoritative for all
bare schema operations without needing a fallback function.

Implements `DoubleDown.Contract.Dispatch.FakeHandler`, so it can
be used by module name with `Double.fake`:

## Usage with Double.fake

    # Basic — all bare-schema reads work without fallback:
    DoubleDown.Double.fake(DoubleDown.Repo, DoubleDown.Repo.InMemory)

    # With seed data:
    DoubleDown.Double.fake(DoubleDown.Repo, DoubleDown.Repo.InMemory,
      [%User{id: 1, name: "Alice"}, %Post{id: 1, title: "Hello"}])

    # Layer expects for failure simulation:
    DoubleDown.Repo
    |> DoubleDown.Double.fake(DoubleDown.Repo.InMemory)
    |> DoubleDown.Double.expect(:insert, fn [changeset] ->
      {:error, Ecto.Changeset.add_error(changeset, :email, "taken")}
    end)

## ExMachina integration

    setup do
      DoubleDown.Double.fake(DoubleDown.Repo, DoubleDown.Repo.InMemory)
      insert(:user, name: "Alice", email: "alice@example.com")
      insert(:user, name: "Bob", email: "bob@example.com")
      :ok
    end

    test "lists all users" do
      assert [_, _] = MyApp.Repo.all(User)
    end

    test "finds user by email" do
      assert %User{name: "Alice"} =
        MyApp.Repo.get_by(User, email: "alice@example.com")
    end

    test "count users" do
      assert 2 = MyApp.Repo.aggregate(User, :count, :id)
    end

## Authoritative operations (bare schema queryables)

| Category | Operations | Behaviour |
|----------|-----------|-----------|
| **Writes** | `insert`, `update`, `delete` | Store in state |
| **PK reads** | `get`, `get!` | `nil`/raise on miss (no fallback) |
| **Clause reads** | `get_by`, `get_by!` | Scan and filter |
| **Collection** | `all`, `one`/`one!`, `exists?` | Scan state |
| **Aggregates** | `aggregate` | Compute from state |
| **Bulk writes** | `insert_all`, `delete_all`, `update_all` (`set:`) | Modify state |
| **Transactions** | `transact`, `rollback` | Delegate to sub-operations |

## Ecto.Query fallback

Operations with `Ecto.Query` queryables (containing `where`,
`join`, `select` etc.) cannot be evaluated in-memory. These fall
through to the fallback function, or raise with a clear error:

    DoubleDown.Double.fake(DoubleDown.Repo, DoubleDown.Repo.InMemory, [],
      fallback_fn: fn
        _contract, :all, [%Ecto.Query{}], _state -> []
      end
    )

## When to use which Repo fake

| Fake | State | Best for |
|------|-------|----------|
| `Repo.Stub` | None | Fire-and-forget writes, canned reads |
| **`Repo.InMemory`** | **Complete store** | **All bare-schema reads; ExMachina factories** |
| `Repo.OpenInMemory` | Partial store | PK reads in state, fallback for rest |

## Limitations

The following `insert_all` options are silently ignored because they
depend on database constraints that don't exist in memory:

- `on_conflict` — upsert behaviour (`:nothing`, `{:replace, fields}`, etc.)
- `conflict_target` — which fields/index define the conflict

This is intentional: test code should match production code without
modification. Testing constraint behaviour requires a real database
via `DataCase`.

When `returning:` is a list of fields, `insert_all` returns maps
containing only those fields (matching Ecto adapter behaviour).
`returning: true` returns full structs.

Binary table name sources (e.g. `"users"`) are not supported by
`insert_all` — use a fallback function or `Double.expect` for these.

## See also

- `DoubleDown.Repo.OpenInMemory` — open-world variant where absence
  is inconclusive, requiring a fallback for most reads.
- `DoubleDown.Repo.Stub` — stateless stub for fire-and-forget writes.

# `store`

```elixir
@type store() :: DoubleDown.Repo.Impl.InMemoryShared.store()
```

# `dispatch`

```elixir
@spec dispatch(module(), atom(), list(), store()) :: {term(), store()}
```

Stateful handler function with closed-world semantics.

Bare schema reads are authoritative — the state is the full truth.
`Ecto.Query` reads fall through to the fallback function.

# `new`

```elixir
@spec new(
  term(),
  keyword()
) :: store()
```

Create a new InMemory state map. Same API as `Repo.OpenInMemory.new/2`.

# `seed`

```elixir
@spec seed([struct()]) :: store()
```

Convert a list of structs into the nested state map for seeding.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
