View Source Flint.Extension behaviour (Flint v0.6.0)
Flint
extensions allow developers to easily hook into Flint
metaprogramming lifecycle to add extra data into the embedded
schema reflection functions.
Flint currently offers four ways to extend behavior:
- Schema-level attributes
- Field-level additional options
- Default
embedded_schema
definitions - Injected Code
Extension authors define what fields / options / attributes Flint
should look for in the module / schema definition and
strip out and store in a schema reflection function, but it is still the resposibility of either the extension author or
the end user to make use of the stored information.
Schema-Level Attributes
These are simply module attributes that are pre-registered with Flint
, and can be given a default value
as well as a validation function. When you use an extension that registers an attribute, then a new __schema__
reflection function is added for each attribute name, with the attribute name as the argument.
Note that the validation occurs at compile time
Example
Given the following extension:
defmodule Returnable do
use Flint.Extension
attribute :returns, validator: fn returns -> is_binary(returns) end
end
And the schema
defmodule Schema do
use Flint.Schema, extensions: [Returnable]
@returns "something"
embedded_schema do
...
end
end
Then you can reflect on this new attribute with:
Schema.__schema__(:returns)
Field-Level Options
You can also register additional field-level keyword options to be consumed in a downstream function.
These function similarly to the built-in extra options that Flint
provides, where the options are
stripped and stored in a module attribute (and subsequently in a __schema__
reflection function)
before passing the valid Ecto.Schema
options to Ecto
itself.
Note that the validation occurs at compile time
Example
Given the following extension that enables Go-like JSON marshalling options:
defmodule JSON do
use Flint.Extension
option :name, required: false, validator: &is_binary/1
option :omitempty, required: false, default: false, validator: &is_boolean/1
option :ignore, required: false, default: false, validator: &is_boolean/1
end
And the following schema:
defmodule Schema do
use Flint.Schema, extensions: [JSON]
embedded_schema do
field :myfield, :string, name: "my_field", omitempty: true
end
end
Then you can access these specific fields with:
Schema.__schema__(:extra_options)
[
myfield: [ignore: false, omitempty: true, name: "my_field"],
]
Default embedded_schema
definitions
Sometimes you might want to always have one of more fields defined for a given schema type without having
to write it each time. Much in the same way that, by default, the @prmimary_key
attribute will set an :id
field
for the schema, you might wish to template out some fields and values.
If you want to define fields that are defined by default in a schema that uses your extension, you can use the
embedded_schema
macro within an extension and all field
, embeds_one
and embeds_many
declarations will be
merged into the target schema that uses your extension.
These accept the same arguments as their counterparts in Flint.Schema
, and will be
added at the end of the embedded_schema
definition.
Duplicate Fields
Note that if you define a template for a field in your extension then anyone who uses your extension will be unable to override that field name with their own definition.
Example
Given the following extension:
defmodule Event do
use Flint.Extension
embedded_schema do
field! :timestamp, :utc_datetime_usec
field! :id, :binary_id
end
end
and this schema:
defmodule Webhook do
use Flint.Schema, extensions: [Event, Embedded]
embedded_schema do
field :route, :string
end
end
Then any schema that uses this extension will have these fields by default:
Webhook.__schema__(:fields)
# [:route, :timestamp, :id]
Injected Code
Lastly, extensions allow you to define custom __using__/1
macros which will be passed through
to the target schema module. This is one of the core functionalities of extensions, and works the
same as you would normally use
a module, and helps compartmentalize similar functionality.
Default Extensions
By default, Flint
will enable the following extensions:
-
Flint.Extensions.Block
-
Flint.Extensions.Typed
-
Flint.Extensions.PreTransforms
, -
Flint.Extensions.When
, -
Flint.Extensions.EctoValidations
, -
Flint.Extensions.PostTransforms
, -
Flint.Extensions.Accessible
, -
Flint.Extensions.Embedded
, -
Flint.Extensions.JSON
If you want to pass your own list of extensions for a module, you will need to explicitly pass the defaults
as well if you would like to keep them. You can use the convenience Flint.default_extensions/0
constant
if you want to include all of the defaults.
Options
:extensions
(list of module that adoptsSpark.Dsl.Extension
) - A list of DSL extensions to add to theSpark.Dsl
:otp_app
(atom/0
) - The otp_app to use for any application configurable options:fragments
(list ofmodule/0
) - Fragments to include in theSpark.Dsl
. See the fragments guide for more.
Summary
Callbacks
@callback changeset(Ecto.Changeset.t()) :: Ecto.Changeset.t()
@callback changeset( Ecto.Changeset.t(), keyword() ) :: Ecto.Changeset.t()