Current section
Files
Jump to
Current section
Files
lib/kiln/schema.ex
defmodule Kiln.Schema do
defmacro __using__(opts \\ []) do
quote do
use Ecto.Schema
import Ecto.Query
alias Ecto.{Changeset, Query, Schema}
alias Kiln.Builders.Schema, as: Builder
alias Kiln.Utils
@caller unquote(__CALLER__.module)
@name Utils.atom_name(@caller)
@repo Utils.option_or_mod_replace(@caller, unquote(opts), :repo)
@page_items Utils.option_or_config(@name, unquote(opts), :page_items, 20)
@preloads Keyword.get(unquote(opts), :preloads, [])
@required_fields Keyword.get(unquote(opts), :required_fields, [])
@writable_fields Keyword.get(unquote(opts), :writable_fields, [])
@private_fields Keyword.get(unquote(opts), :private_fields, [])
@reduceable_embeds Keyword.get(unquote(opts), :reduceable_embeds, %{})
@validations Keyword.get(unquote(opts), :validations, [])
@primary_key_type Utils.option_or_config(
@name,
unquote(opts),
:primary_key_type,
:binary_id
)
@foreign_key_type Utils.option_or_config(
@name,
unquote(opts),
:foreign_key_type,
:binary_id
)
@primary_key {:id, @primary_key_type, autogenerate: true}
def private_fields(), do: @private_fields
def to_map(struct) do
Builder.to_map(struct, @caller.private_fields())
end
def validate(changeset) do
changeset
|> Changeset.validate_required(@required_fields)
|> Builder.validate_id(@primary_key)
|> Builder.validate_foreign_keys(@caller)
|> Builder.apply_custom_validations(@validations)
end
def check_constraints(changeset), do: changeset
def changeset(struct, params) do
struct
|> Changeset.cast(params, @writable_fields)
|> @caller.validate()
|> @caller.check_constraints()
end
def new(params) do
__struct__()
|> @caller.changeset(params)
|> @repo.insert()
end
def embeds(params) do
Builder.embeds(params, @preloads, @reduceable_embeds)
end
def prepare_query(_params, _embeds), do: @caller
def filter_by_id(query, params) do
case params do
%{"id" => id} -> Query.where(query, [q], q.id == ^id)
_ -> query
end
end
def filter_by_params(query, params) do
@caller.filter_by_id(query, params)
end
def group_items(query, _params) do
Query.group_by(query, :id)
end
def order_items(query, _params) do
Query.order_by(query, desc: :id)
end
def limit_items(query, params) do
case params do
%{"items" => items} -> Query.limit(query, ^items)
_ -> Query.limit(query, @page_items)
end
end
def apply_offset(query, params) do
case params do
%{"page" => page, "items" => items} ->
Query.offset(query, ^Builder.offset_from_page(page, items))
%{"page" => page} ->
Query.offset(
query,
^Builder.offset_from_page(page, @page_items)
)
_ ->
query
end
end
def apply_preloads(query, _embeds) do
Query.preload(query, ^@preloads)
end
def fetch_items(query, params, embeds) do
query
|> @caller.group_items(params)
|> @caller.order_items(params)
|> @caller.limit_items(params)
|> @caller.apply_offset(params)
|> @caller.apply_preloads(embeds)
|> @repo.all()
end
def count_items(query, params) do
query
|> @caller.group_items(params)
|> Query.subquery()
|> Query.select([q], count(q.id, :distinct))
|> @repo.all()
|> List.first()
end
def get(params) do
embeds = @caller.embeds(params)
params
|> @caller.prepare_query(embeds)
|> @caller.filter_by_params(params)
|> @caller.fetch_items(params, embeds)
end
def get_count(params) do
embeds = @caller.embeds(params)
params
|> @caller.prepare_query(embeds)
|> @caller.filter_by_params(params)
|> @caller.count_items(params)
end
def show(id) do
@caller.get(%{"id" => id, "items" => 1})
end
def update(item, params) do
item
|> @caller.changeset(params)
|> @repo.update()
end
def edit(params, id) do
case @caller.show(id) do
[] -> {:error, :not_found}
[item] -> @caller.update(item, params)
end
end
def delete(id) do
@caller
|> Query.where(id: ^id)
|> @repo.delete_all()
|> Builder.handle_error()
end
defoverridable apply_offset: 2,
apply_preloads: 2,
check_constraints: 1,
count_items: 2,
embeds: 1,
filter_by_params: 2,
get: 1,
get_count: 1,
delete: 1,
edit: 2,
group_items: 2,
limit_items: 2,
order_items: 2,
prepare_query: 2,
update: 2
end
end
end