# Strukt
Strukt provides an extended `defstruct` macro which builds on top of `Ecto.Schema`
and `Ecto.Changeset` to remove the boilerplate of defining type specifications,
implementing validations, generating changesets from parameters, JSON serialization,
and support for autogenerated fields.
This builds on top of Ecto embedded schemas, so the same familiar syntax you use today
to define schema'd types in Ecto, can now be used to define structs for general purpose
usage.
The functionality provided by the `defstruct` macro in this module is strictly a superset
of the functionality provided both by `Kernel.defstruct/1`, as well as `Ecto.Schema`. If
you import it in a scope where you use `Kernel.defstruct/1` already, it will not interfere.
Likewise, the support for defining validation rules inline with usage of `field/3`, `embeds_one/3`,
etc., is strictly additive, and those additions are stripped from the AST before `field/3`
and friends ever see it.
## Installation
``` elixir
def deps do
[
{:strukt, "~> 0.1"}
]
end
```
## Example
The following is an example of using `defstruct/1` to define a struct with types, autogenerated
primary key, and validation rules.
``` elixir
defmodule Person do
use Strukt
@derives [Jason.Encoder]
@primary_key {:uuid, Ecto.UUID, autogenerate: true}
@timestamps_opts [autogenerate: {NaiveDateTime, :utc_now, []}]
defstruct do
field :name, :string, required: true
field :email, :string, format: ~r/^.+@.+$/
timestamps()
end
end
```
And an example of how you would create and use this struct:
``` elixir
# Creating from params, with autogeneration of fields
iex> {:ok, person} = Person.new(name: "Paul", email: "bitwalker@example.com")
...> person
%Person{
uuid: "d420aa8a-9294-4977-8b00-bacf3789c702",
name: "Paul",
email: "bitwalker@example.com",
inserted_at: ~N[2021-06-08 22:21:23.490554],
updated_at: ~N[2021-06-08 22:21:23.490554]
}
# Validation (Create)
iex> {:error, %Ecto.Changeset{valid?: false, errors: errors}} = Person.new(email: "bitwalker@example.com")
...> errors
[name: {"can't be blank", [validation: :required]}]
# Validation (Update)
iex> {:ok, person} = Person.new(name: "Paul", email: "bitwalker@example.com")
...> {:error, %Ecto.Changeset{valid?: false, errors: errors}} = Person.change(person, email: "foo")
...> errors
[email: {"has invalid format", [validation: :format]}]
# JSON Serialization/Deserialization
...> person == person |> Jason.encode!() |> Person.from_json()
true
```
## Custom Fields
use `:source` to fetch the field different than snake_case format.
``` elixir
defmodule Person do
use Strukt
@derives [Jason.Encoder]
@primary_key {:uuid, Ecto.UUID, autogenerate: true}
@timestamps_opts [autogenerate: {NaiveDateTime, :utc_now, []}]
defstruct do
field :name, :string, required: true, source: :NAME
field :email, :string, format: ~r/^.+@.+$/
timestamps()
end
end
# in iex
iex> {:ok, person} = Person.new(%{NAME: "Ivan", email: "ivan@example.com"})
...> person
%Person{
uuid: "f8736f15-bfdc-49bd-ac78-9da514208464",
name: "Ivan",
email: "ivan@example.com",
inserted_at: ~N[2021-06-08 22:21:23.490554],
updated_at: ~N[2021-06-08 22:21:23.490554]
}
```
For more, see the [usage docs](https://hexdocs.pm/strukt/usage.html)