Things I Learned using Ecto.Schema in 2024 (Part 1)

Photo by C D-X on Unsplash

Things I Learned using Ecto.Schema in 2024 (Part 1)

and how did I leverage the power of using `has_one/3`

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 a list

  • 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 a Filtering associations to get my specific items.

  • preload_order - Sets the default order_by of the association, and since I am using has_one/3 Ecto set a query LIMIT set to one .

[see has_one/3 on docs](https://hexdocs.pm/ecto/Ecto.Schema.html#has_one/3)

Happy Coding

ย