View Source Type Only on Ecto Schema
Ecto
is a great library for working with both databases and data validation.
However, it has its own way of defining schemas and fields,
which results a struct but without type definitions.
This plugin automatically disables struct creation for Ecto
schemas.
Implementation
It uses the TypedStructor.Plugin.before_definition/2
callback to determine if the module is an Ecto
schema
by checking the @ecto_fields
module attribute. If it is, the :define_struct
option is
set to false
to prevent struct creation.
Here is the plugin(feel free to copy and paste):
defmodule Guides.Plugins.TypeOnlyOnEctoSchema do
use TypedStructor.Plugin
@impl TypedStructor.Plugin
defmacro before_definition(definition, _opts) do
quote do
if Module.has_attribute?(__MODULE__, :ecto_fields) do
Map.update!(unquote(definition), :options, fn opts ->
Keyword.put(opts, :define_struct, false)
end)
else
unquote(definition)
end
end
end
end
Usage
To use this plugin, you can add it to the typed_structor
block like this:
defmodule MyApp.User do
use TypedStructor
use Ecto.Schema
typed_structor do
plugin Guides.Plugins.TypeOnlyOnEctoSchema
field :id, integer(), enforce: true
field :name, String.t()
field :age, integer(), enforce: true # There is always a non-nil value
end
schema "source" do
field :name, :string
field :age, :integer, default: 20
end
end
Registering the plugin globally
config :typed_structor, plugins: [Guides.Plugins.TypeOnlyOnEctoSchema]
Note that the plugin is applied to all modules that use TypedStructor
,
you can opt-out by determining the module name or other conditions.
Let's change the plugin to only apply to modules from the MyApp
namespace(feel free to copy and paste):
defmodule Guides.Plugins.TypeOnlyOnEctoSchema do
use TypedStructor.Plugin
@impl TypedStructor.Plugin
defmacro before_definition(definition, _opts) do
quote do
# Check if the module is from the MyApp namespace
with "MyApp" <- __MODULE__ |> Module.split() |> hd(),
true <- Module.has_attribute?(__MODULE__, :ecto_fields) do
Map.update!(unquote(definition), :options, fn opts ->
Keyword.put(opts, :define_struct, false)
end)
else
_otherwise -> unquote(definition)
end
end
end
end
Now you can use typed_structor
without registering the plugin explicitly:
defmodule MyApp.User do
use TypedStructor
use Ecto.Schema
typed_structor do
- plugin Guides.Plugins.TypeOnlyOnEctoSchema
field :id, integer(), enforce: true
field :name, String.t()
field :age, integer(), enforce: true
end
schema "source" do
field :name, :string
field :age, :integer, default: 20
end
end