Skip to content

Commit 8337142

Browse files
committed
docs: add guide
1 parent becb870 commit 8337142

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2020 Zach Daniel
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
# Working With Existing Databases
8+
9+
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.
10+
11+
## The Problem
12+
13+
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:
14+
15+
- You don't want Ash generating migrations for a schema you don't control
16+
- The upstream schema may change, and you need to regenerate your resources to match
17+
- You still want to customize your resources with actions, calculations, validations, and other Ash features — without losing those customizations on regeneration
18+
19+
## The Workflow
20+
21+
### 1. Generate resources with `--fragments` and `--no-migrations`
22+
23+
```bash
24+
mix ash_postgres.gen.resources MyApp.ExternalDb \
25+
--tables users,orders,products \
26+
--no-migrations \
27+
--fragments
28+
```
29+
30+
This creates two files per table:
31+
32+
- **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.
33+
- **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.
34+
35+
The resource file will include `migrate? false` in its `postgres` block (from `--no-migrations`), telling Ash not to generate migrations for it:
36+
37+
```elixir
38+
defmodule MyApp.ExternalDb.User do
39+
use Ash.Resource,
40+
domain: MyApp.ExternalDb,
41+
data_layer: AshPostgres.DataLayer,
42+
fragments: [MyApp.ExternalDb.User.Model]
43+
44+
postgres do
45+
table "users"
46+
repo MyApp.Repo
47+
migrate? false
48+
end
49+
end
50+
```
51+
52+
The fragment file contains the schema details:
53+
54+
```elixir
55+
defmodule MyApp.ExternalDb.User.Model do
56+
use Spark.Dsl.Fragment,
57+
of: Ash.Resource
58+
59+
attributes do
60+
uuid_primary_key :id
61+
attribute :email, :string, public?: true
62+
attribute :name, :string, public?: true
63+
# ...
64+
end
65+
66+
relationships do
67+
has_many :orders, MyApp.ExternalDb.Order
68+
# ...
69+
end
70+
71+
identities do
72+
identity :unique_email, [:email]
73+
end
74+
end
75+
```
76+
77+
### 2. Customize your resources
78+
79+
Add actions, calculations, validations, changes, and anything else to the **resource file**. This is your space:
80+
81+
```elixir
82+
defmodule MyApp.ExternalDb.User do
83+
use Ash.Resource,
84+
domain: MyApp.ExternalDb,
85+
data_layer: AshPostgres.DataLayer,
86+
fragments: [MyApp.ExternalDb.User.Model]
87+
88+
actions do
89+
defaults [:read]
90+
91+
read :by_email do
92+
argument :email, :string, allow_nil?: false
93+
filter expr(email == ^arg(:email))
94+
end
95+
end
96+
97+
calculations do
98+
calculate :display_name, :string, expr(name || email)
99+
end
100+
101+
postgres do
102+
table "users"
103+
repo MyApp.Repo
104+
migrate? false
105+
end
106+
end
107+
```
108+
109+
### 3. Regenerate fragments when the schema changes
110+
111+
When the upstream database schema changes (new columns, new tables, changed relationships), re-run the same command:
112+
113+
```bash
114+
mix ash_postgres.gen.resources MyApp.ExternalDb \
115+
--tables users,orders,products \
116+
--no-migrations \
117+
--fragments
118+
```
119+
120+
Because the resource files already exist, **only the fragment files are regenerated**. Your customizations in the resource files are untouched.
121+
122+
### 4. Review the diff
123+
124+
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.
125+
126+
## Key Points
127+
128+
- **`--fragments`** splits generated schema details into a separate `Model` fragment module, keeping your resource file safe from regeneration
129+
- **`--no-migrations`** prevents migration generation and adds `migrate? false` to the `postgres` block
130+
- **Fragment files are disposable** — they are regenerated from the database each time. Don't put custom code in them.
131+
- **Resource files are yours** — once created on the first run, they won't be overwritten by subsequent runs
132+
- 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

mix.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ defmodule AshPostgres.MixProject do
109109
"documentation/topics/about-ash-postgres/what-is-ash-postgres.md",
110110
"documentation/topics/resources/references.md",
111111
"documentation/topics/resources/polymorphic-resources.md",
112+
"documentation/topics/resources/working-with-existing-databases.md",
112113
"documentation/topics/development/migrations-and-tasks.md",
113114
"documentation/topics/development/testing.md",
114115
"documentation/topics/development/upgrading-to-2.0.md",

0 commit comments

Comments
 (0)