Using Context

View Source

Sometimes you need to compute data once and share it across multiple tables. Blink provides the context feature for this purpose. Context data is not inserted into the database, but is available when building your table data.

What is context?

Context is arbitrary data stored in store.context that you can access from any table/2 or context/2 callback. It's useful for:

  • Sharing computed values across tables (e.g., timestamps, IDs)
  • Pre-generating data that multiple tables need
  • Storing lookup tables or reference data
  • Avoiding redundant computations

Basic example

Let's say we want to generate consistent timestamps and use them across multiple tables:

defmodule Blog.Seeders.BlogSeeder do
  use Blink

  def call do
    new()
    |> add_context("timestamps")  # Register context first
    |> add_table("users")
    |> add_table("posts")
    |> insert(Blog.Repo)
  end

  def context(_store, "timestamps") do
    # Generate 30 days of timestamps
    base = ~U[2024-01-01 00:00:00Z]
    for day <- 0..29, do: DateTime.add(base, day, :day)
  end

  def table(store, "users") do
    timestamps = store.context["timestamps"]

    for i <- 1..100 do
      %{
        id: i,
        name: "User #{i}",
        email: "user#{i}@example.com",
        inserted_at: Enum.random(timestamps),
        updated_at: Enum.random(timestamps)
      }
    end
  end

  def table(store, "posts") do
    users = store.tables["users"]
    timestamps = store.context["timestamps"]

    Enum.flat_map(users, fn user ->
      for i <- 1..5 do
        %{
          id: (user.id - 1) * 5 + i,
          title: "Post #{i}",
          body: "Content here",
          user_id: user.id,
          inserted_at: Enum.random(timestamps),
          updated_at: Enum.random(timestamps)
        }
      end
    end)
  end
end

In this example, we generate timestamps once and reuse them across both the users and posts tables.

Context with relationships

Context can help maintain referential integrity by providing consistent reference data:

def call do
  new()
  |> add_context("user_ids")
  |> add_table("users")
  |> add_table("posts")
  |> add_table("comments")
  |> insert(Blog.Repo)
end

def context(_store, "user_ids") do
  # Generate a pool of user IDs
  Enum.to_list(1..1000)
end

def table(store, "users") do
  user_ids = store.context["user_ids"]

  for id <- user_ids do
    %{
      id: id,
      name: "User #{id}",
      email: "user#{id}@example.com",
      inserted_at: ~U[2024-01-01 00:00:00Z],
      updated_at: ~U[2024-01-01 00:00:00Z]
    }
  end
end

def table(store, "posts") do
  user_ids = store.context["user_ids"]

  for i <- 1..5000 do
    %{
      id: i,
      title: "Post #{i}",
      body: "Content",
      user_id: Enum.random(user_ids),
      inserted_at: ~U[2024-01-01 00:00:00Z],
      updated_at: ~U[2024-01-01 00:00:00Z]
    }
  end
end

def table(store, "comments") do
  user_ids = store.context["user_ids"]
  posts = store.tables["posts"]

  Enum.flat_map(posts, fn post ->
    for i <- 1..3 do
      %{
        id: (post.id - 1) * 3 + i,
        body: "Comment #{i}",
        post_id: post.id,
        user_id: Enum.random(user_ids),
        inserted_at: ~U[2024-01-01 00:00:00Z],
        updated_at: ~U[2024-01-01 00:00:00Z]
      }
    end
  end)
end

Context for realistic data

Use context to generate realistic, consistent data:

def call do
  new()
  |> add_context("timestamps")
  |> add_table("users")
  |> add_table("posts")
  |> insert(Blog.Repo)
end

def context(_store, "timestamps") do
  base = ~U[2024-01-01 00:00:00Z]
  for day <- 0..29, do: DateTime.add(base, day, :day)
end

def table(store, "users") do
  timestamps = store.context["timestamps"]

  for i <- 1..100 do
    %{
      id: i,
      name: "User #{i}",
      email: "user#{i}@example.com",
      inserted_at: Enum.random(timestamps),
      updated_at: Enum.random(timestamps)
    }
  end
end

def table(store, "posts") do
  users = store.tables["users"]
  timestamps = store.context["timestamps"]

  Enum.flat_map(users, fn user ->
    # Only use timestamps after the user was created
    valid_timestamps =
      Enum.filter(timestamps, fn ts ->
        DateTime.compare(ts, user.inserted_at) == :gt
      end)

    for i <- 1..5 do
      %{
        id: (user.id - 1) * 5 + i,
        title: "Post #{i}",
        body: "Content here",
        user_id: user.id,
        inserted_at: Enum.random(valid_timestamps),
        updated_at: Enum.random(valid_timestamps)
      }
    end
  end)
end

In this example, we ensure posts are created after their associated user by filtering the available timestamps.

Summary

In this guide, we learned how to:

  • Add context data with add_context/2
  • Define context callbacks with context/2
  • Access context from table callbacks via store.context

For more information, see the Blink API documentation.