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