# How to extend field from an Ecto schema with meta information?

I’d like to extend my Ecto schemas with meta information that I can use to compile to a JSON Schema. I’d like to do be able to do something like this:

schema "users" do
field :email, :string, constraints: [min: 1, max: 10]
end

And then to be able to reflect om that with something like User.__schema__(:options, :email).

Is it possible to achieve this without replacing all field-calls by something else? I was thinking something along the lines of:

defmodule Blueprint do
defmacro __using__(_) do
quote do
import Blueprint
use Ecto.Schema
Module.register_attribute(__MODULE__, :blueprint_fields, accumulate: true)
end
end

defmacro blueprint(source, [do: block]) do

# Do something to replace all calls in block to field by calls to blueprint_field

quote do
Ecto.Schema.schema(unquote(source), [do: unquote(block)])
def __blueprint__(), do: @blueprint_fields |> Enum.reverse
end
end

defmacro blueprint_field(name, type \\ :string, opts \\ []) do
quote do
Module.put_attribute(__MODULE__, :blueprint_fields, {unquote(name), unquote(type), unquote(opts)})
Ecto.Schema.__field__(__MODULE__, unquote(name), unquote(type), unquote(opts))
end
end
end

defmodule User do
use Blueprint

blueprint "users" do
field :email, :string, constraints: [min: 1, max: 10]
end
end

Is there a easy way to achieve the replacement of the field calls? (the part I left out at the start of the blueprint macro)
And, maybe even more important, should I want this? Or is this going against the spirit of the language? And, is there another way to achieve what I want?

I’m still interested whether or not this is the easiest wat to accomplish this, but I already found myself that I can implement this using Macro.prewalk/2

block = Macro.prewalk(block, fn
{:field, meta, args} -> {:blueprint_field, meta, args}
node -> node
end)
1 Like