Providing Data Directly

View Source

When you already have your rows or context values in hand, Blink's put_context and put_table helpers let you add them to a seeder without defining table/2 or context/2 callbacks.

In this guide, we will:

  • Add context values with put_context
  • Add tables with put_table
  • Mix eager put_* helpers with callback-based with_*

When to use put* vs with*

Use with_table/2 and with_context/2 when data is computed inside callbacks — for example, when a table depends on rows declared earlier. Reach for put_table and put_context when the data is already available at the call site, with no callback needed. Both helpers are imported by use Blink, so you call them unqualified.

Adding context with put_context

put_context/3 stores a single value under a key. Context is auxiliary data: it is never inserted, but it stays available to later callbacks via seeder.context.

defmodule Blog.Seeder do
  use Blink

  def call(users) do
    new()
    |> put_context(:generated_at, DateTime.utc_now())
    |> put_table("users", users)
    |> run(Blog.Repo)
  end
end

To add several values at once, pass a keyword list to put_context/2. Pairs are applied in order:

new()
|> put_context(user_id: user_id, project_indices: project_indices)

Adding tables with put_table

put_table/3 adds rows under a table name. rows may be a list or a stream, and a stream is stored without being materialized:

new()
|> put_table("users", users)
|> run(Blog.Repo)

Add several tables at once with put_table/2. List order becomes insertion order, so declare parents before children:

new()
|> put_table(users: users, posts: posts, comments: comments)
|> run(Blog.Repo)

For per-table options — or a large table you want to stream — use put_table/4. It accepts the same per-table :batch_size and :max_concurrency options as with_table/4, and per-table values override the ones passed to run/3. The keyword form cannot carry options:

events = Stream.map(1..1_000_000, fn i -> %{id: i, name: "Event #{i}"} end)

new()
|> put_table("users", users)
|> put_table("events", events, batch_size: 1_000, max_concurrency: 2)
|> run(Blog.Repo)

Mixing put_* with callbacks

Eager and callback-based declarations interoperate. A later table/2 callback reads earlier rows and context off the seeder:

def call(users) do
  new()
  |> put_context(:tenant_id, 42)
  |> put_table("users", users)
  |> with_table("posts")
  |> run(Blog.Repo)
end

def table(seeder, "posts") do
  Enum.map(seeder.tables["users"], fn user ->
    %{title: "Welcome, #{user.name}", user_id: user.id, tenant_id: seeder.context[:tenant_id]}
  end)
end

Error handling

put_context and put_table raise ArgumentError when:

  • A key or table name already exists.
  • The same key appears twice within one keyword list, e.g. put_table(dup: [], dup: []).

Detection is string-normalized, so a string name and the atom keyword form count as the same table:

# ** (ArgumentError) key already exists in `:tables` of Seeder: users
new()
|> put_table("users", users)
|> put_table(users: more_users)

The keyword /2 forms are atom-keyed and carry no options. Use /3 or /4 for string table names, per-table options, or to add a single table — a bare list of rows is treated as pairs and will not work.

Summary

In this guide, we learned how to:

  • Add context values with put_context/2 and put_context/3
  • Add tables with put_table/2, put_table/3, and put_table/4
  • Combine eager put_* helpers with callback-based with_table/2 and with_context/2