ExStructable

Customisable library that reduces struct method boilerplate duplication. module. Allows you validate your structs when they are created or updated.

The Problem

If you want to write some validation for your struct, you need to write the boilerplate new and put methods manually.

defmodule Point do
@enforce_keys [:x, :y]
defstruct [:x, :y, :z]
def new(args) do
args = Keyword.new(args)
__MODULE__
|> Kernel.struct!(args)
|> validate_struct()
end
def put(struct, args) do
args = Keyword.new(args)
struct
|> Kernel.struct!(args)
|> validate_struct()
end
def validate_struct(struct) do
if struct.x < 0 or struct.y < 0 or struct.z < 0 do
raise ArgumentError
end
struct
end
end

And if you don't want to bother with validation yet, you might want to still add new and put methods to be consistent (or to make it easier to add validation later).

defmodule PointNoValidation do
@enforce_keys [:x, :y]
defstruct [:x, :y, :z]
def new(args) do
args = Keyword.new(args)
__MODULE__
|> Kernel.struct!(args)
|> validate_struct()
end
def put(struct, args) do
args = Keyword.new(args)
struct
|> Kernel.struct!(args)
|> validate_struct()
end
def validate_struct(struct) do
struct
end
end

And you have to write this boilerplate for every module you have! That can be a lot of duplication!

The Solution

By the magic of Elixir macros, we can remove the duplication!

defmodule Point do
@enforce_keys [:x, :y]
defstruct [:x, :y, :z]
use ExStructable # Adds `new` and `put` dynamically
def validate_struct(struct) do
if struct.x < 0 or struct.y < 0 or struct.z < 0 do
raise ArgumentError
end
struct
end
end
Point.new(x: 1, y: 2)
# => %Point{x: 1, y: 2, z: nil} # Still works!
Point.new(x: -1, y: 2)
# Fails validation, as expected

And when we don't want validation...

defmodule PointNoValidation do
@enforce_keys [:x, :y]
defstruct [:x, :y, :z]
use ExStructable # Adds `new` and `put` dynamically
end
Point.new(x: 1, y: 2)
# => %Point{x: 1, y: 2, z: nil} # Still works!

Configuration

using arguments

The use has optional arguments. See the top of ExStructable.__using__/1 to see all their default values.

Customisable Hooks

See this file to see what looks you can implement..

ExConstructor Support

You can use appcues/ExConstructor at the same time using:

defmodule PointNoValidation do
@enforce_keys [:x, :y]
defstruct [:x, :y, :z]
use ExStructable, use_ex_constructor_library: true
end

(do not put use ExConstructor).

Or if you want to pass args to ExConstructor:

defmodule PointNoValidation do
@enforce_keys [:x, :y]
defstruct [:x, :y, :z]
use ExStructable, use_ex_constructor_library: [
# args for ExConstructor here
]
end

Installation

The package can be installed by adding ex_structable to your list of dependencies in mix.exs:

def deps do
[
{:ex_structable, "~> 0.1.0"},
]
end