Packages

An Elixir Phoenix Auto Administration Package.

Retired package: Release invalid

Current section

Files

Jump to
ex_admin_runtime lib ex_admin csv.ex
Raw

lib/ex_admin/csv.ex

defmodule ExAdmin.CSV do
@moduledoc """
ExAdmin provides a CSV export link on the index page of each resource.
The CSV file format can be customized with the `csv` macro.
For example, give the following ecto model for Example.Contact:
defmodule Example.Contact do
use Ecto.Model
schema "contacts" do
field :first_name, :string, default: ""
field :last_name, :string, default: ""
field :email, :string, default: ""
belongs_to :category, Example.Category
has_many :contacts_phone_numbers, Example.ContactPhoneNumber
has_many :phone_numbers, through: [:contacts_phone_numbers, :phone_number]
has_many :contacts_groups, Example.ContactGroup
has_many :groups, through: [:contacts_groups, :group]
end
...
end
The following resource file will export the contact list as shown below:
defmodule Example.ExAdmin.Contact do
use ExAdmin.Register
alias Example.PhoneNumber
register_resource Example.Contact do
csv [
{"Surname", &(&1.last_name)},
{:category, &(&1.category.name)},
{"Groups", &(Enum.map(&1.groups, fn g -> g.name end) |> Enum.join("; "))},
] ++
(for label <- PhoneNumber.all_labels do
fun = fn c ->
c.phone_numbers
|> PhoneNumber.find_by_label(label)
|> Map.get(:number, "")
end
{label, fun}
end)
end
end
# output.csv
Surname,Given,Category,Groups,Home Phone,Business Phone,Mobile Phone
Pallen,Steve,R&D,Groop 1;Groop2,555-555-5555,555,555,1234
The macros available in the csv do block include
* `column` - Define a column in the exported CSV file
## Examples
# List format
csv [:name, :description]
# List format with functions
csv [:id, {:name, fn item -> "Mr. " <> item.name end}, :description]
# No header
csv header: false do
column :id
column :name
end
# Don't humanize the header name
csv [:name, :created_at], humanize: false
"""
require Logger
defmacro __using__(_opts \\ []) do
quote do
import unquote(__MODULE__), only: [csv: 1]
end
end
@doc """
Customize the exported CSV file.
"""
defmacro csv(opts \\ [], block \\ [])
defmacro csv(block_or_opts, block) do
{block, opts} = if block == [], do: {block_or_opts, block}, else: {block, block_or_opts}
quote location: :keep do
import ExAdmin.Register, except: [column: 1, column: 2]
import unquote(__MODULE__)
def build_csv(resources) do
var!(columns, ExAdmin.CSV) = []
unquote(block)
case var!(columns, ExAdmin.CSV) do
[] ->
unquote(block)
|> ExAdmin.CSV.build_csv(resources, unquote(opts))
schema ->
schema
|> Enum.reverse
|> ExAdmin.CSV.build_csv(resources, unquote(opts))
end
end
end
end
@doc """
Configure a column in the exported CSV file.
## Examples
csv do
column :id
column :name, fn user -> "#\{user.first_name} #\{user.last_name}" end
column :age
end
"""
defmacro column(field, fun \\ nil) do
quote do
entry = %{field: unquote(field), fun: unquote(fun)}
var!(columns, ExAdmin.CSV) = [entry | var!(columns, ExAdmin.CSV)]
end
end
@doc false
def default_schema([]), do: []
@doc false
def default_schema([resource | _]) do
resource.__struct__.__schema__(:fields)
|> Enum.map(&(build_default_column(&1)))
end
@doc false
def build_default_column(name) do
%{field: name, fun: nil}
end
@doc false
def build_csv(schema, resources, opts) do
schema = normalize_schema schema
Enum.reduce(resources, build_header_row(schema, opts), &(build_row(&2, &1, schema)))
|> Enum.reverse
|> CSVLixir.write
end
def build_csv(resources) do
default_schema(resources)
|> build_csv(resources, [])
end
defp normalize_schema(schema) do
Enum.map schema, fn
{name, fun} -> %{field: name, fun: fun}
name when is_atom(name) -> %{field: name, fun: nil}
map -> map
end
end
@doc false
def build_header_row(schema, opts) do
if Keyword.get(opts, :header, true) do
humanize? = Keyword.get(opts, :humanize, true)
[(for field <- schema, do: column_name(field[:field], humanize?))]
else
[]
end
end
defp column_name(field, true), do: ExAdmin.Utils.humanize(field)
defp column_name(field, _), do: Atom.to_string(field)
@doc false
def build_row(acc, resource, schema) do
row = Enum.reduce(schema, [], fn
%{field: name, fun: nil}, acc ->
[(Map.get(resource, name) |> ExAdmin.Render.to_string) | acc]
%{field: _name, fun: fun}, acc ->
[(fun.(resource) |> ExAdmin.Render.to_string) | acc]
end)
|> Enum.reverse
[row | acc]
end
@doc false
def write_csv(csv) do
csv
|> CSVLixir.write
end
end