An Interest In:
Web News this Week
- March 16, 2024
- March 15, 2024
- March 14, 2024
- March 13, 2024
- March 12, 2024
- March 11, 2024
- March 10, 2024
Phoenix without Contexts
When you learn about the Phoenix framework you will come across contexts. While contexts are great, they are not always needed. Your app might only have a couple of schemas or, like in our case, it's just easier and faster (to develop the app) to use something else instead of Phoenix contexts. The main issue we at Novistore had was, that it took too long to figure out how to structure our contexts.
In the below code I'm going to show a way to dynamically build MyApp.<Schema>.find/1
, MyApp.<Schema>.create/1
and MyApp.<Schema>.update/2
for all your schemas.
Schema
We use a separate Schema module to define some helper functions on our schemas so we can use it in a more simpler way.
First we create a straightforward schema that we're going to use in our schemas:
defmodule MyApp.Schema do @moduledoc """ Define commonly used imports for all `Ecto.Schema`s here. """ defmacro __using__(_) do quote do use Ecto.Schema import Ecto.Changeset end endend
User schema
Next up, a simple user schema:
defmodule MyApp.User do use MyApp.Schema schema "users" do field :name, :string timestamps() endend
Once our schema is done we would like to have functions like MyApp.User.find/1
Example
iex> MyApp.User.find(1)%User{id: 1, name: "Jane"}
Let's add .find/1
to our schema:
defmodule MyApp.Schema do @moduledoc """ Define commonly used imports for all `Ecto.Schema`s here. """ defmacro __using__(_) do quote do use Ecto.Schema import Ecto.Changeset alias MyApp.Repo @type id :: non_neg_integer @spec find(id) :: {:ok, __MODULE__.t()} | {:error, {__MODULE__, :not_found}} def find(id) do case Repo.get(__MODULE__, id) do nil -> {:error, {__MODULE__, :not_found}} struct -> {:ok, struct} end end end endend
The next function is create/1
.
Example
iex> User.insert(%{name: "Jane"}){:ok, %User{id: 1, name: "Jane"}}
Let's add .create/1
to our schema:
defmodule MyApp.Schema do @moduledoc """ Define commonly used imports for all `Ecto.Schema`s here. """ defmacro __using__(_) do quote do use Ecto.Schema import Ecto.Changeset alias MyApp.Repo @type id :: non_neg_integer @spec find(id) :: {:ok, __MODULE__.t()} | {:error, {__MODULE__, :not_found}} def find(id) do case Repo.get(__MODULE__, id) do nil -> {:error, {__MODULE__, :not_found}} struct -> {:ok, struct} end end @spec insert(map, keyword) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} def insert(params, opts \\ []) when is_map(params) do changeset = changeset(params) Repo.insert(changeset, opts) end end endend
Our User
schema needs a .changeset/1
so let's add one:
defmodule MyApp.User do use MyApp.Schema schema "users" do field :name, :string timestamps() end @required [:name] optional = [] @fields optional ++ @required @spec changeset(t, map) :: Ecto.Changeset.t() def changeset(struct \\ %__MODULE__{}, params) when is_map(params) do struct |> cast(params, @fields) |> validate_required(@required) endend
The last one that we need is .update/2
defmodule MyApp.Schema do @moduledoc """ Define commonly used imports for all `Ecto.Schema`s here. """ defmacro __using__(_) do quote do use Ecto.Schema import Ecto.Changeset alias MyApp.Repo @type id :: non_neg_integer @spec find(id) :: {:ok, __MODULE__.t()} | {:error, {__MODULE__, :not_found}} def find(id) do case Repo.get(__MODULE__, id) do nil -> {:error, {__MODULE__, :not_found}} struct -> {:ok, struct} end end @spec insert(map, keyword) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} def insert(params, opts \\ []) when is_map(params) do changeset = changeset(params) Repo.insert(changeset, opts) end @spec update(struct, map, keyword) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} def update(%{__struct__: __MODULE__} = struct, params, opts \\ []) when is_map(params) do changeset = changeset(struct, params) Repo.update(changeset, opts) end end endend
This way, every schema that uses use MyApp.Schema
, has all the functions defined that we need to retrieve, create and update our schemas.
Bonus
You can also expand your schema module to import commonly used changeset functions like .validate_email/3
or add queries helpers like .filter_by/2
:
defmodule MyApp.Schema do @moduledoc """ Define commonly used imports for all `Ecto.Schema`s here. """ defmacro __using__(_) do quote do use Ecto.Schema import Ecto.Changeset import MyApp.Schema alias MyApp.Repo @type id :: non_neg_integer @spec find(id) :: {:ok, __MODULE__.t()} | {:error, {__MODULE__, :not_found}} def find(id) do case Repo.get(__MODULE__, id) do nil -> {:error, {__MODULE__, :not_found}} struct -> {:ok, struct} end end @spec insert(map, keyword) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} def insert(params, opts \\ []) when is_map(params) do changeset = changeset(params) Repo.insert(changeset, opts) end @spec update(struct, map, keyword) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} def update(%{__struct__: __MODULE__} = struct, params, opts \\ []) when is_map(params) do changeset = changeset(struct, params) Repo.update(changeset, opts) end @spec filter_by(Ecto.Queryable.t(), keyword) :: Ecto.Queryable.t() def filter_by(queryable \\ __MODULE__, clauses) do Enum.reduce(clauses, queryable, fn {k, v}, query -> from q in query, where: field(q, ^k) == ^v end) end end end import Ecto.Changeset @spec validate_email(Ecto.Changeset.t(), atom) :: Ecto.Changeset.t() def validate_email(%{valid?: true} = changeset, key) do case fetch_change(changeset, key) do {:ok, _email} -> validate_format(changeset, key, ~r@) :error -> changeset end endend
Original Link: https://dev.to/novistore/phoenix-without-contexts-4d33
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To