View Source Strukt behaviour (Strukt v0.3.2)
Link to this section Summary
Callbacks
See change/2
This callback can be overridden to provide custom change behavior.
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
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.
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.
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