Current section
Files
Jump to
Current section
Files
lib/CRUD.ex
defmodule CRUD do
@moduledoc """
A module for easy access to the database.
"""
@moduledoc since: "1.0.0"
use Ecto.Schema
defmacro __using__(opts) do
quote location: :keep do
import Ecto.Query, only: [from: 2, where: 2, where: 3, offset: 2]
@cont Keyword.get(unquote(opts), :context)
@behaviour unquote(__MODULE__)
def add(mod, opts), do: @cont.insert(set_field(mod, opts)) |> response(mod)
def get(mod, id) when is_integer(id) or is_binary(id),
do: @cont.get(mod, id) |> response(mod)
def get(mod, opts) when is_list(opts) or is_map(opts),
do: @cont.get_by(mod, opts_to_map(opts)) |> response(mod)
def get_all(mod),
do: {:ok, @cont.all(from(item in mod, select: item, order_by: item.id))}
def get_all(mod, opts) when is_list(opts) or is_map(opts),
do: {:ok, @cont.all(from(i in mod, select: i, order_by: i.id) |> filter(opts))}
def get_all(mod, limit) when is_integer(limit),
do: {:ok, @cont.all(from(i in mod, select: i, order_by: i.id, limit: ^limit))}
def get_all(mod, limit, offset) when is_integer(limit) and is_integer(offset) do
query = from(i in mod, select: i, order_by: i.id, limit: ^limit, offset: ^offset)
{:ok, @cont.all(query)}
end
def get_all(mod, limit, opts) when is_list(opts) or is_map(opts) do
query = from(i in mod, select: i, order_by: i.id, limit: ^limit)
{:ok, @cont.all(query |> filter(opts))}
end
def get_all(mod, limit, offset, opts) when is_list(opts) or is_map(opts) do
query = from(i in mod, select: i, limit: ^limit)
{:ok, @cont.all(query |> filter(opts) |> offset(^offset))}
end
def update(item, opts) when is_struct(item),
do: item.__struct__.changeset(item, opts_to_map(opts)) |> @cont.update()
def update(mod, id, opts) when is_integer(id) or is_binary(id),
do: get(mod, id) |> update_response(opts)
def update(mod, key, val, opts), do: get(mod, [{key, val}]) |> update_response(opts)
def delete(item) when is_struct(item) do
try do
@cont.delete(item)
rescue
_ -> {:error, module_title(item) <> " is not fount"}
end
end
def delete(mod, id), do: get(mod, id) |> delete_response()
def find(mod, opts),
do: from(item in mod, select: item) |> find(opts_to_map(opts), Enum.count(opts), 0)
defp set_field(mod, opts), do: mod.changeset(mod.__struct__, opts_to_map(opts))
defp opts_to_map(opts) when is_map(opts), do: opts
defp opts_to_map(opts) when is_list(opts),
do: Enum.reduce(opts, %{}, fn {key, value}, acc -> Map.put(acc, key, value) end)
defp find(query, opts, count, acc) do
{key, val} = Enum.at(opts, acc)
result = query |> where([i], ilike(field(i, ^key), ^"%#{val}%"))
if acc < count - 1,
do: find(result, opts, count, acc + 1),
else: {:ok, @cont.all(result)}
end
defp filter(query, opts), do: filter(query, opts, Enum.count(opts), 0)
defp filter(query, opts, count, acc) do
fields = Map.new([Enum.at(opts, acc)]) |> Map.to_list()
result = query |> where(^fields)
if acc < count - 1, do: filter(result, opts, count, acc + 1), else: result
end
defp module_title(mod) when is_struct(mod), do: module_title(mod.__struct__)
defp module_title(mod), do: Module.split(mod) |> Enum.at(Enum.count(Module.split(mod)) - 1)
defp error_handler(err) when is_struct(err),
do: Enum.map(err.errors, fn {key, {msg, _}} -> error_str(key, msg) end)
defp error_handler(err) when is_tuple(err),
do: Enum.map([err], fn {_, message} -> message end)
defp error_handler(error), do: error
defp delete_response({:error, reason}), do: {:error, error_handler(reason)}
defp delete_response({:ok, item}), do: delete(item)
defp update_response({:error, reason}, _opts), do: {:error, error_handler(reason)}
defp update_response({:ok, item}, opts), do: update(item, opts)
defp response(nil, mod), do: {:error, module_title(mod) <> " not found"}
defp response({:error, reason}, _module), do: {:error, error_handler(reason)}
defp response({:ok, item}, _module), do: {:ok, item}
defp response(item, _module), do: {:ok, item}
defp error_str(key, msg), do: "#{Atom.to_string(key) |> String.capitalize()}: #{msg}"
end
end
@doc """
##### Adds a new entity to the database #####
Takes in parameters:
- CRUD.add/2:
- `mod`: Module
- `opts`: Map or paramatras key: value separated by commas
Returns `{:ok, struct}` or `{:error, error as a string or list of errors}`
## Examples
iex> MyApp.CRUD.add(MyApp.MyModule, %{key1: value1, key2: value2})
or
iex> MyApp.CRUD.add(MyApp.MyModule, key1: value1, key2: value)
"""
@callback add(mod :: Module.t(), opts :: List.t() | Map.t()) ::
{:ok, Ecto.Schema.t()} | {:error, List.t()} | {:error, String.t()}
@doc """
##### Retrieves structure from DB #####
Takes in parameters:
- CRUD.get/2:
- `mod`: Module
- `id`: Structure identifier in the database
- CRUD.get/2:
- `mod`: Module
- `opts`: Map or paramatras key: value separated by commas
Returns `{:ok, struct}` or `{:error, error as a string}`
## Examples
iex> MyApp.CRUD.get(MyApp.MyModule, 1)
or
iex> MyApp.CRUD.add(MyApp.MyModule, "8892da9e-9cb5-49dd-922c-7371083cdd85")
or
iex> MyApp.CRUD.add(MyApp.MyModule, id: 1)
or
iex> MyApp.CRUD.add(MyApp.MyModule, %{id: 1})
"""
@callback get(mod :: Module.t(), id :: String.t() | Integer.t()) ::
{:ok, Ecto.Schema.t()} | {:error, List.t()} | {:error, String.t()}
@callback get(mod :: Module.t(), opts :: List.t() | Map.t()) ::
{:ok, Ecto.Schema.t()} | {:error, List.t()} | {:error, String.t()}
@doc """
##### Gets a list of structures from the database #####
Takes in parameters:
- CRUD.get_all/1:
- `mod`: Module
- CRUD.get_all/2:
- `mod`: Module
- `limit`: Number of items to display
- CRUD.get_all/2:
- `mod`: Module
- `opts`: Map or paramatras key: value separated by commas
- CRUD.get_all/3:
- `mod`: Module
- `limit`: Number of items to display
- `offset`: Number of elements at the beginning of the list to skip
- CRUD.get_all/3:
- `mod`: Module
- `limit`: Number of items to display
- `opts`: Map or paramatras key: value separated by commas
- CRUD.get_all/4:
- `mod`: Module
- `limit`: Number of items to display
- `offset`: Number of elements at the beginning of the list to skip
- `opts`: Map or paramatras key: value separated by commas
Returns `{:ok, list}`
## Examples
iex> MyApp.CRUD.get_all(MyApp.MyModule)
or
iex> MyApp.CRUD.get_all(MyApp.MyModule, 200)
or
iex> MyApp.CRUD.get_all(MyApp.MyModule, status: 1)
or
iex> MyApp.CRUD.get_all(MyApp.MyModule, %{status: 1})
or
iex> MyApp.CRUD.get_all(MyApp.MyModule, 200, 50)
or
iex> MyApp.CRUD.get_all(MyApp.MyModule, 200, status: 1)
or
iex> MyApp.CRUD.get_all(MyApp.MyModule, 200, %{status: 1})
or
iex> MyApp.CRUD.get_all(MyApp.MyModule, 200, 50, status: 1)
or
iex> MyApp.CRUD.get_all(MyApp.MyModule, 200, 50, %{status: 1})
"""
@callback get_all(mod :: Module.t()) :: {:ok, List.t()}
@callback get_all(mod :: Module.t(), opts :: List.t() | Map.t()) :: {:ok, List.t()}
@callback get_all(mod :: Module.t(), limit :: Integer.t()) :: {:ok, List.t()}
@callback get_all(mod :: Module.t(), limit :: Integer.t(), offset :: Integer.t()) ::
{:ok, List.t()}
@callback get_all(mod :: Module.t(), limit :: Integer.t(), opts :: List.t() | Map.t()) ::
{:ok, List.t()}
@callback get_all(
mod :: Module.t(),
limit :: Integer.t(),
offset :: Integer.t(),
opts :: List.t() | Map.t()
) ::
{:ok, List.t()}
@doc """
##### Changes the structure in the database #####
Takes in parameters:
- CRUD.update/2:
- `item`: struct
- `opts`: Map or paramatras key: value separated by commas
- CRUD.update/3:
- `mod`: Module
- `id`: Structure identifier in the database
- `opts`: Map or paramatras key: value separated by commas
- CRUD.update/4:
- `mod`: Module
- `key`: Name of one of the fields in the structure
- `val`: Field value
- `opts`: Map or paramatras key: value separated by commas
Returns `{:ok, struct}` or `{:error, error as a string or list of errors}`
## Examples
iex> MyApp.CRUD.update(struct, %{key1: value1, key2: value2})
or
iex> MyApp.CRUD.update(struct, key1: value1, key2: value)
or
iex> MyApp.CRUD.update(MyApp.MyModule, 5, %{key1: value1, key2: value})
or
iex> MyApp.CRUD.update(MyApp.MyModule, 5, key1: value1, key2: value)
or
iex> MyApp.CRUD.update(MyApp.MyModule, :id, 5, %{key1: value1, key2: value})
or
iex> MyApp.CRUD.update(MyApp.MyModule, :id, 5, key1: value1, key2: value)
"""
@callback update(item :: Ecto.Schema.t(), opts :: List.t() | Map.t()) ::
{:ok, Ecto.Schema.t()} | {:error, List.t()} | {:error, String.t()}
@callback update(mod :: Module.t(), id :: Integer.t() | String.t(), opts :: List.t() | Map.t()) ::
{:ok, Ecto.Schema.t()} | {:error, List.t()} | {:error, String.t()}
@callback update(
mod :: Module.t(),
key :: Atom.t(),
val :: Integer.t() | String.t(),
opts :: List.t() | Map.t()
) ::
{:ok, Ecto.Schema.t()} | {:error, List.t()} | {:error, String.t()}
@doc """
##### Removes a structure from the database #####
Takes in parameters:
- CRUD.delete/1:
- `item`: struct
- CRUD.delete/2:
- `mod`: Module
- `id`: Structure identifier in the database
Returns `{:ok, struct}` or `{:error, error as a string or list of errors}`
## Examples
iex> MyApp.CRUD.delete(struct)
or
iex> MyApp.CRUD.delete(MyApp.MyModule, 1)
"""
@callback delete(item :: Ecto.Schema.t()) ::
{:ok, Ecto.Schema.t()} | {:error, List.t()} | {:error, String.t()}
@callback delete(mod :: Module.t(), id :: Integer.t() | String.t()) ::
{:ok, Ecto.Schema.t()} | {:error, List.t()} | {:error, String.t()}
@doc """
##### Finds a list of elements from the database by matching part of a string to one of the item's fields in the pattern #####
Takes in parameters:
- CRUD.find/2:
- `mod`: Module
- `opts`: Map or paramatras key: value separated by commas
Returns `{:ok, list of structures}`
## Examples
iex> MyApp.CRUD.find(MyApp.MyModule, %{key: "sample"})
or
iex> MyApp.CRUD.find(MyApp.MyModule, key: "sample")
"""
@callback find(mod :: Module.t(), opts :: List.t() | Map.t()) :: {:ok, List.t()}
end