Packages

Transforms string maps into nested structs: knits strings into something useful.

Current section

Files

Jump to
knit lib knit.ex
Raw

lib/knit.ex

defmodule Knit do
@moduledoc """
Handles populating models.
"""
def populate(module, params) do
if !function_exported?(module, :schema, 0) do
raise "#{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
if is_atom(type) && function_exported?(type, :schema, 0) do
# If the type is a model, populate the child struct.
populate(type, value)
else
convert_value(type, value)
end
end
defp convert_value(_, nil), do: nil
defp convert_value(:string, value), do: convert_string(value)
defp convert_value(:integer, value), do: convert_integer(value)
defp convert_value(:float, value), do: convert_float(value)
defp convert_value(:boolean, value), do: convert_boolean(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_string(value) when is_binary(value), do: value
defp convert_string(value), do: inspect(value)
defp convert_integer(value) when is_integer(value), do: value
defp convert_integer(value) when is_float(value), do: round(value)
defp convert_integer(value) when is_binary(value) do
case Integer.parse(value) do
{int, _} -> int
:error -> nil
end
end
defp convert_float(value) when is_float(value), do: value
defp convert_float(value) when is_integer(value), do: value / 1
defp convert_float(value) when is_binary(value) do
case Float.parse(value) do
{int, _} -> int
:error -> nil
end
end
defp convert_boolean(true), do: true
defp convert_boolean(false), do: false
defp convert_boolean(0), do: false
defp convert_boolean(0.0), do: false
defp convert_boolean(value) when is_binary(value) do
case String.downcase(value) do
"false" -> false
"" -> false
_ -> true
end
end
defp convert_boolean(_), do: true
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
end