ash_sqlite/lib/repo.ex

156 lines
4.5 KiB
Elixir
Raw Normal View History

2023-09-23 14:52:22 +12:00
defmodule AshSqlite.Repo do
@moduledoc """
Resources that use `AshSqlite.DataLayer` use a `Repo` to access the database.
This repo is a thin wrapper around an `Ecto.Repo`.
You can use `Ecto.Repo`'s `init/2` to configure your repo like normal, but
instead of returning `{:ok, config}`, use `super(config)` to pass the
configuration to the `AshSqlite.Repo` implementation.
"""
@doc "Use this to inform the data layer about what extensions are installed"
@callback installed_extensions() :: [String.t()]
@doc """
Use this to inform the data layer about the oldest potential sqlite version it will be run on.
Must be an integer greater than or equal to 13.
"""
@callback min_pg_version() :: integer()
@doc "The path where your migrations are stored"
@callback migrations_path() :: String.t() | nil
@doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields"
@callback override_migration_type(atom) :: atom
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
otp_app = opts[:otp_app] || raise("Must configure OTP app")
use Ecto.Repo,
adapter: Ecto.Adapters.SQLite3,
otp_app: otp_app
@behaviour AshSqlite.Repo
defoverridable insert: 2, insert: 1, insert!: 2, insert!: 1
def installed_extensions, do: []
def migrations_path, do: nil
def override_migration_type(type), do: type
def min_pg_version, do: 10
def init(_, config) do
new_config =
config
|> Keyword.put(:installed_extensions, installed_extensions())
|> Keyword.put(:migrations_path, migrations_path())
2023-10-11 08:42:51 +13:00
|> Keyword.put(:case_sensitive_like, :on)
2023-09-23 14:52:22 +12:00
{:ok, new_config}
end
def insert(struct_or_changeset, opts \\ []) do
struct_or_changeset
|> to_ecto()
|> then(fn value ->
repo = get_dynamic_repo()
Ecto.Repo.Schema.insert(
__MODULE__,
repo,
value,
Ecto.Repo.Supervisor.tuplet(repo, prepare_opts(:insert, opts))
)
end)
|> from_ecto()
end
def insert!(struct_or_changeset, opts \\ []) do
struct_or_changeset
|> to_ecto()
|> then(fn value ->
repo = get_dynamic_repo()
Ecto.Repo.Schema.insert!(
__MODULE__,
repo,
value,
Ecto.Repo.Supervisor.tuplet(repo, prepare_opts(:insert, opts))
)
end)
|> from_ecto()
end
def from_ecto({:ok, result}), do: {:ok, from_ecto(result)}
def from_ecto({:error, _} = other), do: other
def from_ecto(nil), do: nil
def from_ecto(value) when is_list(value) do
Enum.map(value, &from_ecto/1)
end
def from_ecto(%resource{} = record) do
if Spark.Dsl.is?(resource, Ash.Resource) do
empty = struct(resource)
resource
|> Ash.Resource.Info.relationships()
|> Enum.reduce(record, fn relationship, record ->
case Map.get(record, relationship.name) do
%Ecto.Association.NotLoaded{} ->
Map.put(record, relationship.name, Map.get(empty, relationship.name))
value ->
Map.put(record, relationship.name, from_ecto(value))
end
end)
else
record
end
end
def from_ecto(other), do: other
def to_ecto(nil), do: nil
def to_ecto(value) when is_list(value) do
Enum.map(value, &to_ecto/1)
end
def to_ecto(%resource{} = record) do
if Spark.Dsl.is?(resource, Ash.Resource) do
resource
|> Ash.Resource.Info.relationships()
|> Enum.reduce(record, fn relationship, record ->
value =
case Map.get(record, relationship.name) do
%Ash.NotLoaded{} ->
%Ecto.Association.NotLoaded{
__field__: relationship.name,
__cardinality__: relationship.cardinality
}
value ->
to_ecto(value)
end
Map.put(record, relationship.name, value)
end)
else
record
end
end
def to_ecto(other), do: other
defoverridable init: 2,
installed_extensions: 0,
override_migration_type: 1,
min_pg_version: 0
end
end
end