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