<!--
SPDX-FileCopyrightText: 2020 Zach Daniel
SPDX-License-Identifier: MIT
-->
# Working With Existing Databases
When you're building an Ash application against a database you don't own or control — such as a shared company database, a legacy system, or a third-party service's database — you need a workflow that lets you iterate on your Ash resources without generating migrations. The `--fragments` and `--no-migrations` options to `mix ash_postgres.gen.resources` are designed for exactly this.
## The Problem
Normally, Ash resources are the source of truth for your database schema, and migrations are generated from them. But when the database is managed externally:
- You don't want Ash generating migrations for a schema you don't control
- The upstream schema may change, and you need to regenerate your resources to match
- You still want to customize your resources with actions, calculations, validations, and other Ash features — without losing those customizations on regeneration
## The Workflow
### 1. Generate resources with `--fragments` and `--no-migrations`
```bash
mix ash_postgres.gen.resources MyApp.ExternalDb \
--tables users,orders,products \
--no-migrations \
--fragments
```
This creates two files per table:
- **The resource file** (e.g., `lib/my_app/external_db/user.ex`) — contains `use Ash.Resource`, the `postgres` block, and any actions. This is *your* file to customize.
- **The fragment file** (e.g., `lib/my_app/external_db/user/model.ex`) — contains the attributes, relationships, and identities introspected from the database. This file is regenerated by the tool.
The resource file will include `migrate? false` in its `postgres` block (from `--no-migrations`), telling Ash not to generate migrations for it:
```elixir
defmodule MyApp.ExternalDb.User do
use Ash.Resource,
domain: MyApp.ExternalDb,
data_layer: AshPostgres.DataLayer,
fragments: [MyApp.ExternalDb.User.Model]
postgres do
table "users"
repo MyApp.Repo
migrate? false
end
end
```
The fragment file contains the schema details:
```elixir
defmodule MyApp.ExternalDb.User.Model do
use Spark.Dsl.Fragment,
of: Ash.Resource
attributes do
uuid_primary_key :id
attribute :email, :string, public?: true
attribute :name, :string, public?: true
# ...
end
relationships do
has_many :orders, MyApp.ExternalDb.Order
# ...
end
identities do
identity :unique_email, [:email]
end
end
```
### 2. Customize your resources
Add actions, calculations, validations, changes, and anything else to the **resource file**. This is your space:
```elixir
defmodule MyApp.ExternalDb.User do
use Ash.Resource,
domain: MyApp.ExternalDb,
data_layer: AshPostgres.DataLayer,
fragments: [MyApp.ExternalDb.User.Model]
actions do
defaults [:read]
read :by_email do
argument :email, :string, allow_nil?: false
filter expr(email == ^arg(:email))
end
end
calculations do
calculate :display_name, :string, expr(name || email)
end
postgres do
table "users"
repo MyApp.Repo
migrate? false
end
end
```
### 3. Regenerate fragments when the schema changes
When the upstream database schema changes (new columns, new tables, changed relationships), re-run the same command:
```bash
mix ash_postgres.gen.resources MyApp.ExternalDb \
--tables users,orders,products \
--no-migrations \
--fragments
```
Because the resource files already exist, **only the fragment files are regenerated**. Your customizations in the resource files are untouched.
### 4. Review the diff
After regeneration, review the changes with `git diff` to see what changed in the schema. New columns will appear as new attributes, altered relationships will be updated, and so on.
## Key Points
- **`--fragments`** splits generated schema details into a separate `Model` fragment module, keeping your resource file safe from regeneration
- **`--no-migrations`** prevents migration generation and adds `migrate? false` to the `postgres` block
- **Fragment files are disposable** — they are regenerated from the database each time. Don't put custom code in them.
- **Resource files are yours** — once created on the first run, they won't be overwritten by subsequent runs
- You can also use `--skip-tables` to exclude tables, `--tables` to scope to specific schemas (e.g., `accounts.`), and `--extend` to apply extensions to generated resources