View Source Flint.Extension (Flint v0.4.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 three ways to extend behavior:

  1. Schema-level attributes
  2. Field-level additional options
  3. 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"],
]

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:

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 adopts Spark.Dsl.Extension) - A list of DSL extensions to add to the Spark.Dsl

  • :otp_app (atom/0) - The otp_app to use for any application configurable options

  • :fragments (list of module/0) - Fragments to include in the Spark.Dsl. See the fragments guide for more.