Current section
Files
Jump to
Current section
Files
lib/seedex.ex
defmodule Seedex do
@moduledoc """
Functions to populate database with seed data.
"""
require Logger
@doc """
`seed/3` inserts data in the given table
## Arguments
* `module` - The module containing the Ecto schema.
* `contraints` - The fields used to idenfity a record. The record will be updated
if a record with matching fields already exist.
* `data` - The data to insert. It can either be a list of maps, each containing a record
or a function which takes a `struct` as input and output.
## Examples
```elixir
seed MyApp.Point, [:x, :y], fn point ->
point
|> Map.put(:x, 4)
|> Map.put(:y, 7)
|> Map.put(:name, "Home")
end
seed MyApp.User, [
%{name: "Daniel", age: 26},
%{name: "Ai", age: 24}
]
```
"""
@spec seed(module :: atom, constraints :: [atom], data :: (() -> :ok) | [map]) :: :ok
def seed(module, constraints \\ [:id], data) do
do_seed(module, constraints, data, update: true)
end
@doc """
Same as `seed/3` but does not update the record if it already exists
"""
@spec seed_once(module :: atom, constraints :: [atom], data :: (() -> :ok) | [map]) :: :ok
def seed_once(module, constraints \\ [:id], data) do
do_seed(module, constraints, data, update: false)
end
defp do_seed(module, constraints, func, opts) when is_function(func, 1) do
record = func.(struct(module, %{}))
insert_seed(module, record, constraints, opts)
end
defp do_seed(module, constraints, data, opts) when is_list(data) do
Enum.each data, fn record ->
record = struct(module, record)
insert_seed(module, record, constraints, opts)
end
end
defp insert_seed(module, record, constraints, opts) do
existing = fetch_record(module, record, constraints)
cond do
existing && opts[:update] ->
update_record(record, existing)
!existing ->
Logger.debug("Inserting record #{inspect(record)}")
repo().insert(record)
true ->
:ok
end
end
defp fetch_record(module, record, constraints) do
case make_query(record, constraints) do
[] ->
nil
query ->
repo().get_by(module, query)
end
end
defp make_query(record, constraints) do
constraints
|> Enum.map(& {&1, Map.fetch!(record, &1)})
|> Enum.reject(fn {_k, v} -> is_nil(v) end)
end
defp update_record(record, existing) do
changeset = make_changeset(record, existing)
Logger.debug("Updating #{inspect(record)} with changes: #{inspect(changeset.changes)}")
repo().update!(changeset)
end
defp make_changeset(record, existing) do
{changeset, changes} = {Ecto.Changeset.change(existing), Map.from_struct(record)}
Enum.reduce changes, changeset, fn
{_key, %Ecto.Association.NotLoaded{}}, changeset ->
changeset
{_key, nil}, changeset ->
changeset
{key, _value}, changeset when key in ~w(__meta__) ->
changeset
{key, %Ecto.Association.BelongsTo{} = assoc}, changeset ->
Ecto.Changeset.put_assoc(changeset, key, assoc)
{key, value}, changeset ->
Ecto.Changeset.put_change(changeset, key, value)
end
end
defp repo do
Application.get_env(:seedex, :repo)
end
end