You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A :truncate option (default: false) on Blink.Seeder.run/3 and Blink.copy_to_table/4, making seeds re-runnable: the seed replaces its tables' contents instead of adding to them, so running it N times ends like running it once. run/3 truncates every declared table in one TRUNCATE ... RESTART IDENTITY statement before the first copy — foreign keys between declared tables need no ordering, while a foreign key from an undeclared table fails the truncate rather than silently cascading. In an atomic seed the truncate joins the transaction, so a failed re-seed rolls back to the previous data; with atomic: false the truncate commits before the first batch, and a failure leaves partial state that the next run's truncate cleans up — fix-and-re-run converges in both modes. RESTART IDENTITY makes database-assigned ids deterministic across runs, and pairing with reset_sequences: true does the same for explicit-id seeds. Run-level only — per-table truncate: raises ArgumentError, like :atomic and :timeout. On a direct copy_to_table/4 call it truncates the one copied table (a delete-and-reload), even when the input is empty. Adapter support comes from a new optional truncate/3 callback on Blink.Adapter, implemented by Blink.Adapter.Postgres; truncate: true with an adapter lacking it raises ArgumentError. The copy telemetry events report :truncate in their metadata alongside the other copy options. Destructive by design: meant for databases the seed owns, never live tables.
Blink.to_row/2 and Blink.to_rows/2 (imported by use Blink), converting Ecto schema structs into row maps at the seeder boundary — for factories shared with the test suite, which should keep returning structs. The row keeps only the schema's persisted fields (__meta__, associations, and virtual fields would otherwise become COPY columns, since the column list is read from the map keys). The :id option picks the primary-key policy: :database (default) drops the primary key so the database assigns it, :keep trusts the struct's own ids, and a literal value on to_row/2 sets an explicit, referenceable id. to_rows/2 adds drop_nil_columns: true, dropping every column that is nil in all rows so database defaults apply, as Repo.insert/2 would — decided per table, never per row, because rows must share their keys. This promotes the ExMachina guide's hand-rolled to_row/2 + drop_all_nil_columns/1 recipe into the API; the guide now uses the built-ins.
[:blink, :copy, :exception], emitted when a table's copy — or its sequence reset, with reset_sequences: true — fails, completing the per-table copy events. A failed copy previously emitted only :start: inside Blink.Seeder.run/3 the failure surfaced through the run span, but a direct copy_to_table/4 call (which emits no run events) failed with no event at all, leaving the raised exception as the only signal. Measurements: :duration; metadata: the :start metadata plus :kind, :reason, and :stacktrace. Blink.Telemetry.attach_default_logger/1 attaches it and logs copy failures at :error, naming the failed table — inside a seeder run alongside the run span's error line, which names the seed.
Changed
Breaking:use Blink imports the new to_row/1,2 and to_rows/1,2, so a seeder module that defines its own to_row — as the ExMachina guide instructed before this release — no longer compiles: imported Blink.to_row/2 conflicts with local function. Delete the local definition and use the import, moving the id to a keyword option (to_row(struct, id: id) instead of to_row(struct, id)); or rename the local function to keep it.
Documentation
Every complete seeder module in the README, the guides, and the Blink moduledoc now carries @impl true on its first table/2 clause; only the Building Rows guide did before. The examples implemented a behaviour callback without annotating it, so a reader copying one into a project starts from code Elixir will not check against Blink: misspell or mis-arity a table/2 clause later and it silently becomes an ordinary private-looking function instead of a compile-time warning. Partial snippets that show a clause without its surrounding defmodule are left alone, since @impl reads as noise outside a module.
The Bulk Imports guide now shows the seeder pipeline as an import entry point alongside the direct copy_to_table/4 call: a use Blink import module mixing a hand-assigned import_batches row (put_table/3) with CSV-derived readings (with_table/2 + from_csv/2), which makes the guide's own delete-by-batch-id advice concrete. The guide previously told every reader to skip the seeder machinery — right for a single table, but it left multi-table imports, where declaration order, cross-table references, and the single-transaction run apply just as well, reimplementing run/3 by hand.
Two honesty fixes in the Building Rows guide's canonical conventions: the string-table-keys bullet now acknowledges that atoms work identically (the choice is a preference, and the API reference always said both work), and the one-timestamp bullet warns to use distinct stamps when the application orders or paginates by inserted_at without a tiebreaker — rows tied on a single timestamp paginate nondeterministically.
Refined the Building Rows guide's data-placement convention: row data lives in the table/2 clause that uses it, and moves to a module attribute only when it is shared across clauses, derived at compile time, or large enough to bury the clause's logic. The guide previously prescribed module attributes unconditionally, which hoisted single-use lists away from their point of use; the canonical example now keeps its data in the clauses.