Current section
Files
Jump to
Current section
Files
lib/ecto_resource.ex
defmodule EctoResource do
@moduledoc """
This module represents the generic CRUD functionality that is boilerplate within
Phoenix context files. It provides a DSL to easily generate the basic functions
for a schema. This allows the context to focus on interesting, atypical implementations
rather than the redundent, drifting crud functions.
defmodule MyContext do
use EctoResource
using_repo(Repo) do
resource(MySchema)
end
end
This will generate the functions:
`MyContext.change_my_schema(changable)`
- changable object to generate changeset for
`MyContext.create_my_schema(attributes)`
- attributes
`MyContext.delete_my_schema(deletable)`
- deletable: Schema object to delete
`MyContext.get_my_schema(id, options)`
- id: id field for the schema value to be queried for
- options:
- preloads
`MyContext.all_my_schema(options)`
- options:
- preloads
- order_by
`MyContext.paginate_my_schema(pagination, options)`
- pagination
- options:
- preloads
`MyContext.update_my_schema(updatable, attributes)`
- updatable: Schema object to update
- attributes
There are also introspection functions to understand what is generated by the macro
`MyContext.__resource__(:resources)` - A listing of the defined resources and their generated functions
"""
import Ecto.Query
alias __MODULE__
@spec change(module, Ecto.Schema.t()) :: Ecto.Changeset.t()
def change(schema, changable) do
changable
|> schema.changeset(%{})
end
@spec create(Ecto.Repo.t(), module, map()) ::
{:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
def create(repo, schema, attributes) do
schema
|> struct()
|> schema.changeset(attributes)
|> repo.insert([])
end
@spec delete(Ecto.Repo.t(), Ecto.Schema.t()) ::
{:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
def delete(repo, deletable) do
deletable
|> repo.delete([])
end
@spec get(Ecto.Repo.t(), module, term(), term()) :: Ecto.Schema.t() | nil
def get(repo, schema, id, options \\ []) do
preloads = Keyword.get(options, :preloads, [])
schema
|> preload(^preloads)
|> repo.get(id, [])
end
@spec all(Ecto.Repo.t(), module, term()) :: list(Ecto.Schema.t())
def all(repo, schema, options \\ []) do
preloads = Keyword.get(options, :preloads, [])
order_by = Keyword.get(options, :order_by, [])
schema
|> preload(^preloads)
|> order_by(^order_by)
|> repo.all([])
end
@spec paginate(Ecto.Repo.t(), module, Engine.Pagination.t(), term()) :: Scrivener.Page.t()
def paginate(repo, schema, pagination, options \\ []) do
preloads = Keyword.get(options, :preloads, [])
schema
|> preload(^preloads)
|> repo.paginate(pagination)
end
@spec update(Ecto.Repo.t(), module, Ecto.Schema.t(), map()) ::
{:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
def update(repo, schema, updateable, attributes) do
updateable
|> schema.changeset(attributes)
|> repo.update([])
end
def underscore_module_name(module) do
module
|> Macro.underscore()
|> String.split("/")
|> List.last()
end
defmacro __using__(_) do
quote do
import EctoResource, only: [using_repo: 2]
end
end
defmacro using_repo(repo, do: block) do
quote do
Module.register_attribute(__MODULE__, :repo, [])
Module.put_attribute(__MODULE__, :repo, unquote(repo))
Module.register_attribute(__MODULE__, :resources, accumulate: true)
import EctoResource, only: [resource: 1]
unquote(block)
def __resource__(:resources), do: @resources
Module.delete_attribute(__MODULE__, :resources)
Module.delete_attribute(__MODULE__, :repo)
end
end
defmacro resource(schema) do
quote bind_quoted: [schema: schema] do
function_suffix = EctoResource.underscore_module_name(schema)
change_function = :"change_#{function_suffix}"
create_function = :"create_#{function_suffix}"
delete_function = :"delete_#{function_suffix}"
get_function = :"get_#{function_suffix}"
all_function = :"all_#{function_suffix}"
paginate_function = :"paginate_#{function_suffix}"
update_function = :"update_#{function_suffix}"
Module.put_attribute(
__MODULE__,
:resources,
{@repo, schema,
[
"#{change_function}/1",
"#{create_function}/1",
"#{delete_function}/1",
"#{get_function}/2",
"#{all_function}/1",
"#{paginate_function}/2",
"#{update_function}/2"
]}
)
def unquote(change_function)(changable),
do: EctoResource.change(unquote(schema), changable)
def unquote(create_function)(attributes),
do: EctoResource.create(@repo, unquote(schema), attributes)
def unquote(delete_function)(deletable),
do: EctoResource.delete(@repo, deletable)
def unquote(get_function)(id, options \\ []),
do: EctoResource.get(@repo, unquote(schema), id, options)
def unquote(all_function)(options \\ []),
do: EctoResource.all(@repo, unquote(schema), options)
def unquote(paginate_function)(pagination, options \\ []),
do: EctoResource.paginate(@repo, unquote(schema), pagination, options)
def unquote(update_function)(updatable, attributes),
do: EctoResource.update(@repo, unquote(schema), updatable, attributes)
end
end
end