Skip to content

Instantly share code, notes, and snippets.

@samjonester
Created March 8, 2019 02:56
Show Gist options
  • Save samjonester/83454f0e1a7b4c0dcdc6ed25a97ce9e0 to your computer and use it in GitHub Desktop.
Save samjonester/83454f0e1a7b4c0dcdc6ed25a97ce9e0 to your computer and use it in GitHub Desktop.
Making Schema
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
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
@samjonester
Copy link
Author

samjonester commented Mar 8, 2019

  • Macros should always return a quoted expression.
  • It's pretty cool that unquoting a quoted expression within a quote block will execute it in the appropriate context (Lines 20, 22, 23).
  • The need for a prelude and a postlude in ecto is because the struct definition has to happen after executing the "block" of code provided to Struct.schema/1.
  • Lines 20 & 21 seem to be included in Ecto.Schema.schema/4's prelude because they should happen before the postlude, though I pulled them out because that is really the interesting action that Ecto.Schema.schema/4 is performing.
  • Was unquote(value) like in Module.put_attribute(__MODULE__, :schema_fields, unquote(field_key)) the inspiration for the interpolation syntax in Ecto.Query?
  • accumulate: true seems kind of hacky and surprising when you don't have visibility into the registration of a module attribute.

@elbow-jason
Copy link

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