The problem
I have two schemas, coffees
and orders
wherein I want to get the recent completed order from each coffees
First solution
defmodule Schemas.Coffee do
# ... more code
has_many :completed_orders, Schemas.Order
@spec coffee_query(coffee_id :: Ecto.UUID.t()) :: Ecto.Query.t()
def coffee_query(coffee_id) do
from c in __MODULE__,
where: c.id == coffee_id,
preload: [completed_orders: Orders.completed_order],
limit: 1
end
end
defmodule Schemas.Order do
# ... more code
@completed_status :completed
@spec completed_order_query() :: Ecto.Query.t()
def completed_order_query() do
from o in __MODULE__,
where: o.status in ^@completed_status,
order_by: [desc: o.updated_at]
end
end
defmodule Contexts.Coffees do
# more code
@spec get_coffee_with_recent_completed_order(coffee_id :: Ecto.UUID.t())
:: Order.t()
def get_coffee_with_recent_completed_order(coffee_id) do
coffee = coffee_id
|> Schemas.Coffee.coffee_query()
|> Repo.one
List.first(coffee.completed_orders)
end
end
Based from the setup, I can actually get the
completed_orders
of coffees but the only downside for me is that the preload will return alist
Since it will return a
list
, that will result into another process for me just to get the recent completed order.
Then, has_one/3
came into my life
defmodule Schemas.Coffee do
# ... more code
has_one :completed_order, Schemas.Order,
where: [status: :completed],
preload_order: [desc: :updated_at]
@spec coffee_query(coffee_id :: Ecto.UUID.t()) :: Ecto.Query.t()
def coffee_query(coffee_id) do
from c in __MODULE__,
where: c.id == ^coffee_id
preload: [:completed_order],
limit: 1
end
end
defmodule Contexts.Coffees do
# more code
@spec get_coffee_with_recent_completed_order(coffee_id :: Ecto.UUID.t())
:: Order.t()
def get_coffee_with_recent_completed_order(coffee_id) do
coffee_id
|> Schemas.Coffee.coffee_query()
|> Repo.one
|> Map.get(:completed_order, nil)
end
end
What's the magic behind this scheme?
How does has_one/3
works?
Indicates a one-to-one association with another schema
From the current situation, coffee belongs_to order
. As a result, coffee has_many orders
, but I only need one
result to return coming from the orders schema to identify if the coffee order is completed already.
Using `has_one/3` I was able to achieve what I want.
has_one/3
- indicates a one-to-one association with another schema.where
- indicates aFiltering associations
to get my specific items.preload_order
- Sets the defaultorder_by
of the association, and since I am usinghas_one/3
Ecto set a queryLIMIT
set toone
.
[see has_one/3 on docs](https://hexdocs.pm/ecto/Ecto.Schema.html#has_one/3)
Happy Coding