legendary-doc-site/apps/admin/kaffy/lib/kaffy/resource_schema.ex
2020-07-27 20:28:41 +00:00

315 lines
8 KiB
Elixir

defmodule Kaffy.ResourceSchema do
@moduledoc false
def primary_key(schema) do
schema.__schema__(:primary_key)
end
def excluded_fields(schema) do
{pk, _, _} = schema.__schema__(:autogenerate_id)
autogenerated = schema.__schema__(:autogenerate)
case length(autogenerated) do
1 ->
[{auto_fields, _}] = autogenerated
[pk] ++ auto_fields
_ ->
[pk]
end
end
def index_fields(schema) do
Keyword.drop(fields(schema), fields_to_be_removed(schema))
end
def form_fields(schema) do
to_be_removed = fields_to_be_removed(schema) ++ [:id, :inserted_at, :updated_at]
Keyword.drop(fields(schema), to_be_removed)
end
def cast_fields(schema) do
to_be_removed =
fields_to_be_removed(schema) ++
get_has_many_associations(schema) ++
get_has_one_assocations(schema) ++
get_many_to_many_associations(schema) ++ [:id, :inserted_at, :updated_at]
Keyword.drop(fields(schema), to_be_removed)
end
def fields(schema) do
schema
|> get_all_fields()
|> reorder_fields(schema)
end
defp get_all_fields(schema) do
schema.__changeset__()
|> Enum.map(fn {k, _} -> {k, default_field_options(schema, k)} end)
end
def default_field_options(schema, field) do
type = field_type(schema, field)
label = Kaffy.ResourceForm.form_label_string(field)
merge_field_options(%{label: label, type: type})
end
def merge_field_options(options) do
default = %{
create: :editable,
update: :editable,
label: nil,
type: nil,
choices: nil
}
Map.merge(default, options || %{})
end
defp fields_to_be_removed(schema) do
# if schema defines belongs_to assocations, remove assoc fields and keep their actual *_id fields.
schema.__changeset__()
|> Enum.reduce([], fn {field, type}, all ->
case type do
{:assoc, %Ecto.Association.BelongsTo{}} ->
[field | all]
{:assoc, %Ecto.Association.Has{cardinality: :many}} ->
[field | all]
{:assoc, %Ecto.Association.Has{cardinality: :one}} ->
[field | all]
_ ->
all
end
end)
end
defp reorder_fields(fields_list, schema) do
[_id, first_field | _fields] = schema.__schema__(:fields)
# this is a "nice" feature to re-order the default fields to put the specified fields at the top/bottom of the form
fields_list
|> reorder_field(first_field, :first)
|> reorder_field(:email, :first)
|> reorder_field(:name, :first)
|> reorder_field(:title, :first)
|> reorder_field(:id, :first)
|> reorder_field(:inserted_at, :last)
|> reorder_field(:updated_at, :last)
# |> reorder_field(Kaffy.ResourceSchema.embeds(schema), :last)
end
defp reorder_field(fields_list, [], _), do: fields_list
defp reorder_field(fields_list, [field | rest], position) do
fields_list = reorder_field(fields_list, field, position)
reorder_field(fields_list, rest, position)
end
defp reorder_field(fields_list, field_name, position) do
if field_name in Keyword.keys(fields_list) do
{field_options, fields_list} = Keyword.pop(fields_list, field_name)
case position do
:first -> [{field_name, field_options}] ++ fields_list
:last -> fields_list ++ [{field_name, field_options}]
end
else
fields_list
end
end
def has_field_filters?(resource) do
admin_fields = Kaffy.ResourceAdmin.index(resource)
fields_with_filters =
Enum.map(admin_fields, fn f -> kaffy_field_filters(resource[:schema], f) end)
Enum.any?(fields_with_filters, fn
{_, filters} -> filters
_ -> false
end)
end
def kaffy_field_filters(_schema, {field, options}) do
{field, Map.get(options || %{}, :filters, false)}
end
def kaffy_field_filters(_, _), do: false
def kaffy_field_name(schema, {field, options}) do
default_name = kaffy_field_name(schema, field)
name = Map.get(options || %{}, :name)
cond do
is_binary(name) -> name
is_function(name) -> name.(schema)
true -> default_name
end
end
def kaffy_field_name(_schema, field) when is_atom(field) do
Kaffy.ResourceAdmin.humanize_term(field)
end
def kaffy_field_value(conn, schema, {field, options}) do
default_value = kaffy_field_value(schema, field)
ft = Kaffy.ResourceSchema.field_type(schema.__struct__, field)
value = Map.get(options || %{}, :value)
cond do
is_function(value) ->
value.(schema)
is_map(value) && Map.has_key?(value, :__struct__) ->
if value.__struct__ in [NaiveDateTime, DateTime, Date, Time] do
value
else
Map.from_struct(value)
|> Map.drop([:__meta__])
|> Kaffy.Utils.json().encode!(escape: :html_safe, pretty: true)
end
Kaffy.Utils.is_module(ft) && Keyword.has_key?(ft.__info__(:functions), :render_index) ->
ft.render_index(conn, schema, field, options)
is_map(value) ->
Kaffy.Utils.json().encode!(value, escape: :html_safe, pretty: true)
is_binary(value) ->
value
true ->
default_value
end
end
def kaffy_field_value(schema, field) when is_atom(field) do
value = Map.get(schema, field, "")
cond do
is_map(value) && Map.has_key?(value, :__struct__) && value.__struct__ == Decimal ->
value
is_map(value) && Map.has_key?(value, :__struct__) ->
if value.__struct__ in [NaiveDateTime, DateTime, Date, Time] do
value
else
Map.from_struct(value)
|> Map.drop([:__meta__])
|> Kaffy.Utils.json().encode!(escape: :html_safe, pretty: true)
end
is_map(value) ->
Kaffy.Utils.json().encode!(value, escape: :html_safe, pretty: true)
is_binary(value) ->
String.slice(value, 0, 140)
true ->
value
end
end
def display_string_fields([], all), do: Enum.reverse(all) |> Enum.join(",")
def display_string_fields([{field, _} | rest], all) do
display_string_fields(rest, [field | all])
end
def display_string_fields([field | rest], all) do
display_string_fields(rest, [field | all])
end
def associations(schema) do
schema.__schema__(:associations)
end
def get_has_many_associations(schema) do
associations(schema)
|> Enum.filter(fn a ->
case association(schema, a) do
%Ecto.Association.Has{cardinality: :many} -> true
_ -> false
end
end)
end
def get_has_one_assocations(schema) do
associations(schema)
|> Enum.filter(fn a ->
case association(schema, a) do
%Ecto.Association.Has{cardinality: :one} -> true
_ -> false
end
end)
end
def get_many_to_many_associations(schema) do
associations(schema)
|> Enum.filter(fn a ->
case association(schema, a) do
%Ecto.Association.ManyToMany{cardinality: :many} -> true
_ -> false
end
end)
end
def association(schema, name) do
schema.__schema__(:association, name)
end
def association_schema(schema, assoc) do
association(schema, assoc).queryable
end
def embeds(schema) do
schema.__schema__(:embeds)
end
def embed(schema, name) do
schema.__schema__(:embed, name)
end
def embed_struct(schema, name) do
embed(schema, name).related
end
def search_fields(resource) do
schema = resource[:schema]
persisted_fields = schema.__schema__(:fields)
Enum.filter(fields(schema), fn f ->
field_name = elem(f, 0)
field_type(schema, f).type in [:string, :textarea, :richtext] &&
field_name in persisted_fields
end)
|> Enum.map(fn {f, _} -> f end)
end
def filter_fields(_), do: nil
def field_type(_schema, {_, type}), do: type
def field_type(schema, field), do: schema.__changeset__() |> Map.get(field, :string)
# def field_type(schema, field), do: schema.__schema__(:type, field)
def get_map_fields(schema) do
get_all_fields(schema)
|> Enum.filter(fn
{_f, options} ->
options.type == :map
f when is_atom(f) ->
f == :map
end)
end
def widgets(_resource) do
[]
end
end