Current section
Files
Jump to
Current section
Files
lib/pointable.ex
defmodule Pointers.Pointable do
@moduledoc """
Sets up an Ecto Schema for a Pointable table.
## Sample Usage
```
use Pointers.Pointable,
otp_app: :my_app, # your OTP application's name
source: "my_table", # default name of table in database
table_id: "01EBTVSZJ6X02J01R1XWWPWGZW" # valid ULID to identify table
```
"""
alias Pointers.Util
defmacro __using__(options), do: using(__CALLER__.module, options)
@must_be_in_module "Pointers.Pointable may only be used inside a defmodule!"
defp using(nil, _options), do: raise CompileError, description: @must_be_in_module
defp using(module, options) do
# raise early if not present
Util.get_source(options)
Util.get_otp_app(options)
get_table_id(options)
app = Keyword.fetch!(options, :otp_app)
Module.put_attribute(module, __MODULE__, options)
config = Application.get_env(app, module, [])
pointable = emit_pointable(config ++ options)
quote do
use Ecto.Schema
require Flexto
import Pointers.Pointable
unquote_splicing(pointable)
end
end
@bad_table_id "You must provide a ULID-formatted binary :table_id option."
@must_use "You must use Pointers.Pointable before calling pointable_schema/1."
defp get_table_id(opts), do: check_table_id(Keyword.get(opts, :table_id))
defp check_table_id(x) when is_binary(x), do: check_table_id_valid(x, Pointers.ULID.cast(x))
defp check_table_id(_), do: raise ArgumentError, message: @bad_table_id
defp check_table_id_valid(x, {:ok, x}), do: x
defp check_table_id_valid(_, _), do: raise ArgumentError, message: @bad_table_id
defmacro pointable_schema(body)
defmacro pointable_schema([do: body]) do
module = __CALLER__.module
schema_check_attr(Module.get_attribute(module, __MODULE__), module, body)
end
@default_timestamps_opts [type: :utc_datetime_usec]
# verifies that the module was `use`d and generates a new schema
defp schema_check_attr(options, module, body) when is_list(options) do
otp_app = Keyword.fetch!(options, :otp_app)
config = Application.get_env(otp_app, module, [])
source = Util.get_source(config ++ options)
quote do
unquote(schema_primary_key(module, options))
unquote(Util.schema_foreign_key_type(module))
unquote(Util.put_new_attribute(module, :timestamps_opts, @default_timestamps_opts))
schema unquote(source) do
unquote(body)
Flexto.flex_schema(unquote(otp_app))
end
end
end
defp schema_check_attr(_, _, _), do: raise CompileError, message: @must_use
# defaults @primary_key
defp schema_primary_key(module, opts) do
autogen = Keyword.get(opts, :autogenerate, true)
schema_pk(Module.get_attribute(module, :primary_key), module, autogen)
end
defp schema_pk(nil, module, autogenerate) do
data = Macro.escape({:id, Pointers.ULID, autogenerate: autogenerate})
quote do
@primary_key unquote(data)
end
end
defp schema_pk(_, _, _), do: :ok
# defines __pointable__
defp emit_pointable(config) do
table_id = Pointers.ULID.cast!(Keyword.fetch!(config, :table_id))
otp_app = Keyword.fetch!(config, :otp_app)
[ pointable_clause(:table_id, table_id),
pointable_clause(:otp_app, otp_app) ]
end
defp pointable_clause(arg, value) do
quote do
def __pointable__(unquote(arg)), do: unquote(value)
end
end
end