View Source Strukt behaviour (Strukt v0.3.2)

Link to this section Summary

Callbacks

This callback can be overridden to provide custom change behavior.

See new/1

This callback can be overridden to provide custom initialization behavior.

This callback can be overridden to manually implement your own validation logic.

Functions

This variant of defstruct can accept a list of fields, just like Kernel.defstruct/1, in which case it simply defers to Kernel.defstruct/1 and does nothing; or it can be passed a block containing an Ecto.Schema definition. The resulting struct/schema is defined in the current module scope, and will inherit attributes like @derive, @primary_key, etc., which are already defined in the current scope.

This variant of defstruct takes a module name and block containing a struct schema and any other module contents desired, and defines a new module with that name, generating a struct just like Strukt.defstruct/1.

Defines a validation rule for the current struct validation pipeline.

Link to this section Callbacks

@callback change(Ecto.Changeset.t() | term()) :: Ecto.Changeset.t()

See change/2

@callback change(Ecto.Changeset.t() | term(), Keyword.t() | map()) :: Ecto.Changeset.t()

This callback can be overridden to provide custom change behavior.

The default implementation provided for you creates a changeset and applies all of the inline validations defined on the schema.

NOTE: It is recommended that if you need to perform custom validations, that you use the validation/1 and validation/2 facility for performing custom validations in a module or function, and if necessary, override validate/1 instead of performing validations in this callback. If you need to override this callback specifically for some reason, make sure you call super/2 at some point during your implementation to ensure that validations are run.

@callback new() :: {:ok, struct()} | {:error, Ecto.Changeset.t()}

See new/1

@callback new(Keyword.t() | map()) :: {:ok, struct()} | {:error, Ecto.Changeset.t()}

This callback can be overridden to provide custom initialization behavior.

The default implementation provided for you performs all of the necessary validation and autogeneration of fields with those options set.

NOTE: It is critical that if you do override this callback, that you call super/1 to run the default implementation at some point in your implementation.

@callback validate(Ecto.Changeset.t()) :: Ecto.Changeset.t()

This callback can be overridden to manually implement your own validation logic.

The default implementation handles invoking the validation rules expressed inline or via the validation/1 and validation/2 macros. You may still invoke the default validations from your own implementation using super/1.

This function can be called directly on a changeset, and is automatically invoked by the default new/1 and change/2 implementations.

Link to this section Functions

Link to this macro

defstruct(arg)

View Source (macro)

This variant of defstruct can accept a list of fields, just like Kernel.defstruct/1, in which case it simply defers to Kernel.defstruct/1 and does nothing; or it can be passed a block containing an Ecto.Schema definition. The resulting struct/schema is defined in the current module scope, and will inherit attributes like @derive, @primary_key, etc., which are already defined in the current scope.

example

Example

defmodule Passthrough do
  use Strukt

  defstruct [:name]
end

defmodule Person do
  use Strukt

  @derive [Jason.Encoder]
  defstruct do
    field :name, :string
  end

  def say_hello(%__MODULE__{name: name}), do: "Hello #{name}!"
end

Above, even though Strukt.defstruct/1 is in scope, it simply passes through the list of fields to Kernel.defstruct/1, as without a proper schema, there isn't much useful we can do. This allows intermixing uses of defstruct/1 in the same scope without conflict.

Link to this macro

defstruct(name, list)

View Source (macro)

This variant of defstruct takes a module name and block containing a struct schema and any other module contents desired, and defines a new module with that name, generating a struct just like Strukt.defstruct/1.

example

Example

use Strukt

defstruct Person do
  @derive [Jason.Encoder]

  field :name, :string

  def say_hello(%__MODULE__{name: name}), do: "Hello #{name}!"
end

NOTE: Unlike Strukt.defstruct/1, which inherits attributes like @derive or @primary_key from the surrounding scope; this macro requires them to be defined in the body, as shown above.

Link to this macro

validation(validator, opts \\ [])

View Source (macro)

Defines a validation rule for the current struct validation pipeline.

A validation pipeline is constructed by expressing the rules in the order in which you want them applied, from top down. The validations may be defined anywhere in the module, but the order of application is always top down.

You may define either module validators or function validators, much like Plug.Builder. For module validators, the module is expected to implement the Strukt.Validator behavior, consisting of the init/1 and validate/2 callbacks. For function validators, they are expected to be of arity 2. Both the validate/2 callback and function validators receive the changeset to validate/manipulate as their first argument, and options passed to the validation/2 macro, if provided.

guards

Guards

Validation rules that should be applied conditionally can either handle the conditional logic in their implementation, or if simple, can use guards to express this instead, which can be more efficient.

Guards may use the changeset being validated in their conditions by referring to changeset. See the example below to see how these can be expressed.

example

Example

defmodule Upload do
  use Strukt

  @allowed_content_types ["application/json", "application/pdf", "text/csv"]

  defstruct do
    field :filename, :string
    field :content, :binary, default: <<>>
    field :content_type, :string, required: true
  end

  # A simple function validator, expects a function in the same module
  validation :validate_filename

  # A function validator with a guard clause, only applied when the guard is successful
  validation :validate_content_type when is_map_key(changeset.changes, :content_type)

  # A module validator with options
  validation MyValidations.EnsureContentMatchesType, @allowed_content_types

  # A validator with options and a guard clause
  validation :example, [foo: :bar] when changeset.action == :update

  defp validate_filename(changeset, _opts), do: changeset
end