Current section
Files
Jump to
Current section
Files
lib/data_morph/struct.ex
defmodule DataMorph.Struct do
@moduledoc ~S"""
Contains `from_rows/3` function that defines a struct and return structs
created from rows, and `defmodulestruct/2` macro to define a struct.
"""
@doc ~S"""
Defines a struct from given `kind` alias and list of `fields`.
When called a second time with additional new fields it redefines struct,
setting fields to be the union of the old and new fields.
## Examples
iex> DataMorph.Struct.defmodulestruct(Foo.Bar, [:baz, :boom])
{:module, Foo.Bar, _, %Foo.Bar{baz: nil, boom: nil}}
...> %Foo.Bar{baz: "zy", boom: "boom"}
%Foo.Bar{baz: "zy", boom: "boom"}
...> DataMorph.Struct.defmodulestruct(Foo.Bar, [:bish, :bash])
{:module, Foo.Bar, _, %Foo.Bar{bash: nil, baz: nil, bish: nil, boom: nil}}
...> %Foo.Bar{bish: "zy", bash: "boom"}
%Foo.Bar{bash: "boom", baz: nil, bish: "zy", boom: nil}
"""
defmacro defmodulestruct(kind, fields) do
quote do
module = unquote(kind)
fields = unquote(fields)
fields_changeset =
try do
existing_fields = (struct(module) |> Map.keys()) -- [:__struct__]
existing_fieldset = MapSet.new(existing_fields)
new_fieldset = MapSet.new(fields)
unless existing_fieldset === new_fieldset do
existing_fieldset
|> MapSet.union(new_fieldset)
|> MapSet.to_list()
end
rescue
UndefinedFunctionError -> fields
end
if fields_changeset do
defmodule(Module.concat([module]), do: defstruct(fields_changeset))
else
{:module, module, nil, struct(module)}
end
end
end
@doc ~S"""
Defines a struct and returns structs created from `rows` list or stream, and
a `namespace`, a `name`, and a list of `headers`.
Redefines struct when called again with same namespace and name but different
headers, sets struct fields to be the union of the old and new headers.
## Examples
Defines a struct and returns stream of structs created from stream of `rows`.
iex> headers = ["name","ISO code"]
...> [
...> ["New Zealand","nz"],
...> ["United Kingdom","gb"]
...> ] \
...> |> Stream.map(& &1) \
...> |> DataMorph.Struct.from_rows(OpenRegister, "country", headers) \
...> |> Enum.to_list
[%OpenRegister.Country{iso_code: "nz", name: "New Zealand"},
%OpenRegister.Country{iso_code: "gb", name: "United Kingdom"}]
Defines a struct and returns stream of structs created from list of `rows`.
iex> headers = ["name","ISO code"]
...> [
...> ["New Zealand","nz"],
...> ["United Kingdom","gb"]
...> ] \
...> |> DataMorph.Struct.from_rows("open-register", Country, headers) \
...> |> Enum.to_list
[%OpenRegister.Country{iso_code: "nz", name: "New Zealand"},
%OpenRegister.Country{iso_code: "gb", name: "United Kingdom"}]
"""
def from_rows(rows, namespace, name, headers) do
kind = DataMorph.Module.camelize_concat(namespace, name)
fields = headers |> Enum.map(&normalize/1)
defmodulestruct(kind, fields)
rows |> Stream.map(&convert_row(&1, kind, fields))
end
defp convert_row(row, kind, fields) do
tuples = fields |> Enum.zip(row)
struct(kind, tuples)
end
def normalize(string) do
string
|> String.downcase()
|> String.replace(~r"\W", " ")
|> String.replace(~r" +", " ")
|> String.trim()
|> String.replace(" ", "_")
|> String.to_atom()
end
end