Current section
Files
Jump to
Current section
Files
lib/knit.ex
defmodule Knit do
@moduledoc """
Handles populating models.
"""
@spec populate(map, module) :: struct
def populate(params, module) do
if !is_atom(module) || !function_exported?(module, :schema, 0) do
raise "#{inspect(module)} does not implement Knit.Model!"
end
schema = module.schema
populated_map =
module
|> struct
|> ExConstructor.populate_struct(params)
# Can't enumerate otherwise
|> Map.from_struct
|> Enum.map(&(convert(schema, &1)))
|> Enum.into(%{})
# Re-convert to struct
struct(module, populated_map)
end
defp convert(schema, {field, value}) do
# Get the data about the field from the schema.
type = schema[field]
new_value =
case type do
[type] ->
convert_list_collection(type, value)
{type} ->
convert_list_collection(type, value)
|> Enum.into({})
%{map: type} ->
convert_map_collection(type, value)
type ->
convert_type(type, value)
end
{field, new_value}
end
# Convert a list-type collection (list or tuple)
defp convert_list_collection(_, nil), do: []
defp convert_list_collection(type, values) do
# Handle the collection being in a map.
# TODO: Make sure this is ordered by key.
values_list = if is_map(values), do: Map.values(values), else: values
Enum.map(values_list, &(convert_type(type, &1)))
end
# If the collection is a map with string keys and model values.
defp convert_map_collection(_, nil), do: %{}
defp convert_map_collection(type, values) do
Enum.map(values, fn {key, value} ->
{key, convert_type(type, value)}
end)
|> Enum.into(%{})
end
defp convert_type(_, nil), do: nil
defp convert_type(type, value) do
cond do
is_atom(type) && function_exported?(type, :schema, 0) ->
# If the type is a model, populate the child struct.
populate(value, type)
is_atom(type) && function_exported?(type, :convert, 1) ->
# If the type is a custom type, use that to convert
convert_custom_type(type, value)
true ->
convert_value(type, value)
end
end
defp convert_value(_, nil), do: nil
defp convert_value(:string, value), do: Knit.StringType.convert(value)
defp convert_value(:integer, value), do: Knit.IntegerType.convert(value)
defp convert_value(:float, value), do: Knit.FloatType.convert(value)
defp convert_value(:boolean, value), do: Knit.BooleanType.convert(value)
defp convert_value({:enum, opts}, value) when is_list(opts), do: convert_enum(value, opts)
defp convert_value(:any, value), do: value
defp convert_enum(value, opts) do
keyword =
opts
|> Enum.find(fn {_, input_value} ->
input_value == value
end)
case keyword do
{atom, _} -> atom
nil -> nil
end
end
defp convert_custom_type(type, value) do
type.convert(value)
end
end