Packages

Create simple structs in a concise way. If they need to grow, expand them to regular structs.

Current section

Files

Jump to
simple_struct lib simple_struct.ex
Raw

lib/simple_struct.ex

defmodule SimpleStruct do
@moduledoc """
`SimpleStruct` is a library to create structs in a concise way.
This is _not_ meant as a replacement of Elixir's `defstruct`. In fact, it uses
it. It's meant to be used as a way to make it easier to define several structs
inside a module.
And if your struct needs to "graduate" at some point, you can easily extract
it and expand it.
## Example
Take an example where you want to define a bunch of events in your
application.
Ideally, you want each event to be a struct. That is our first-class data
structure in Elixir, and it allows things like safety when constructing it and
better pattern matching.
You have 2 options:
1. You have an explosion of files, each with a simple `defmodule` +
`defstruct` in it, OR
2. You have a really large module with tons of nested modules!
Both seem overkill for different reasons. So, maybe you take secret option (3)
and you don't make them into structs! :(
With `SimpleStruct`, it's simple.
Just `import SimpleStruct` and define all the structs you want in one place:
```elixir
defmodule MyApp.Events do
import SimpleStruct
defdata UserLoggedIn, [:user_id, action: "logged in"]
defdata OrderPlaced, [:user_id, :order_id, required: true]
defdata OrderCancelled, [:user_id, :order_id, required: [:order_id]]
end
```
"""
@doc """
Creates a module and defines a struct inside it with then given fields.
## Options
- `required`: defaults to empty. You can pass a list of keys that are required
or `true` to require all keys.
## Examples
```elixir
defmodule MyApp.Events do
import SimpleStruct
defdata UserLoggedIn, [:user_id, action: "logged in"]
defdata OrderPlaced, [:user_id, :order_id, required: true]
defdata OrderCancelled, [:user_id, :order_id, required: [:order_id]]
end
```
That will create three structs: `%Myapp.Events.UserLoggedIn{}`,
`%Myapp.Events.OrderPlaced{}`, and `%Myapp.Events.OrderCancelled{}`.
- `UserLoggedIn` will have a default `action` of `"logged in"`,
- `OrderPlaced` will enforce that both `:user_id` and `:order_id` keys are
provided when creating the struct, and
- `OrderCancelled` will only enforce `:order_id`
In addition, all modules will have a `new/1` helper function that can be used
to create the structs by passing keyword lists.
```elixir
UserLoggedIn.new(user_id: 1)
# => %Myapp.Events.UserLoggedIn{user_id: 1, action: "logged in"}
```
In other words, it's the equivalent of doing this:
```elixir
defmodule MyApp.Events do
defmodule UserLoggedIn do
defstruct [:user_id, action: "logged in"]
def new(attrs) do
struct!(__MODULE__, attrs)
end
end
defmodule OrderPlaced do
@enforce_keys [:user_id, :order_id]
defstruct [:user_id, :order_id]
def new(attrs) do
struct!(__MODULE__, attrs)
end
end
defmodule OrderCancelled do
@enforce_keys [:order_id]
defstruct [:user_id, :order_id]
def new(attrs) do
struct!(__MODULE__, attrs)
end
end
end
```
"""
defmacro defdata(name, fields, opts \\ []) do
required_keys =
case Keyword.get(opts, :required) do
true -> fields
keys when is_list(keys) -> keys
nil -> []
end
quote do
defmodule unquote(name) do
@enforce_keys unquote(required_keys)
defstruct unquote(fields)
def new(opts \\ []) when is_list(opts) do
struct!(unquote(name), opts)
end
end
end
end
end