Created
March 8, 2019 02:56
-
-
Save samjonester/83454f0e1a7b4c0dcdc6ed25a97ce9e0 to your computer and use it in GitHub Desktop.
Making Schema
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Schema do | |
defmacro field(field_key) do | |
quote do | |
Module.put_attribute(__MODULE__, :schema_fields, unquote(field_key)) | |
end | |
end | |
defmacro schema(do: block) do | |
prelude = | |
quote do | |
Module.register_attribute(__MODULE__, :schema_fields, accumulate: true) | |
end | |
postlude = | |
quote do | |
defstruct Module.get_attribute(__MODULE__, :schema_fields) | |
end | |
quote do | |
unquote(prelude) | |
import Schema, only: [field: 1] | |
unquote(block) | |
unquote(postlude) | |
end | |
end | |
defmacro __using__(_opts) do | |
quote do | |
import Schema, only: [schema: 1] | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule QuoteTest do | |
use ExUnit.Case | |
defmodule Stub do | |
use Schema | |
schema do | |
field(:id) | |
end | |
end | |
test "try the thing" do | |
subject = %Stub{id: :foo} | |
assert subject.id == :foo | |
end | |
end |
Macros should always return valid AST, usually a quoted expression.
defmacro double(item1, item2) do
{:{}, [], [item1, item2]}
end
iex(1)> double(:ok, :value)
{:ok, :value}
I really like your prelude and postlude usage.
I think the decision to use ^
in Ecto was because the ^
already existed in Elixir making it valid looking syntax. This question might be better answerable by ericmj.
I agree that accumulate: true
is hacky. It's feels bad to me because it's seems to create a mutable variable. There might be a way to introspect what module attributes are in scope, but it would only work at compile-time I bet. Most Module
functions require the module to be open/not-yet-compiled.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
prelude
and apostlude
in ecto is because the struct definition has to happen after executing the "block" of code provided toStruct.schema/1
.Ecto.Schema.schema/4
'sprelude
because they should happen before thepostlude
, though I pulled them out because that is really the interesting action thatEcto.Schema.schema/4
is performing.unquote(value)
like inModule.put_attribute(__MODULE__, :schema_fields, unquote(field_key))
the inspiration for the interpolation syntax inEcto.Query
?accumulate: true
seems kind of hacky and surprising when you don't have visibility into the registration of a module attribute.