View Source Schemas

Strukt builds on Ecto.Schema, so much of what you need to know can be found in the Ecto docs. However, there are a few quality-of-life improvements implemented in Strukt that extend the schema syntax to allow for some additional functionality.

schema-extensions

Schema Extensions

The following is list of the changes we've made to the syntax provided by Ecto.Schema:

  • Support for defining validations inline with the field definition, i.e. alongside standard options provided to field/embeds_one/embeds_many.
  • Support for autogenerated fields with embedded_schema. Typically autogenerated fields in Ecto are intended to be provided by the database, but without a database in the loop, nothing happens. Since we're defining new/1 for the structs we define, it gives us an opportunity to ensure that the autogenerated fields get values immediately on struct creation.
  • Support for defstruct's extended functionality within embeds_one/embeds_many inline definitions, to any arbitrary nesting depth.

One of the biggest quality-of-life improvements provided here is the ability to express validations inline with the field definition, as well as the automatic generation of validation code which is automatically applied when you call change/2 or changeset/2. These build upon and operate on Ecto.Changeset, which is a powerful way of expressing constraints, and they compose well, especially when you have embeds involved.

expressing-validations

Expressing Validations

Let's take a look at the use of inline validation rules, which you pass along with the standard options to macros like field/3. The following validation options are provided:

  • required: boolean | Keyword.t, options same as validate_required/3

  • length: Keyword.t, options same as validate_length/3
  • format: Keyword.t, options same as validate_format/4
  • one_of: list(term) | [values: list(term), message: String.t], i.e. validate_inclusion/4

  • none_of: list(term) | [values: list(term), message: String.t], i.e. validate_exclusion/4

  • subset_of: list(term) | [values: list(term), message: String.t], i.e. validate_subset/4

  • range: Range.t | [value: Range.t, message: String.t], i.e. validate_inclusion/4

  • number: Keyword.t, i.e. validate_number/4

In all of the above, you can provide a custom validation message using the keyword list variant, and setting :message to the desired format string.

custom-validations

Custom Validations

If you need to perform custom validation, or express conditional constraints, then you can override the definition of validate/1, which takes an Ecto.Changeset on which you can apply arbitrary validation logic. This callback is also invoked any time you call new/1, so be aware that if you need to conditionally apply validation rules, you will need to be able to determine the condition from the data the changeset is based on, see the Ecto.Changeset API if you aren't sure how to do that.