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

Pau RiosaPau Riosa
2 min read

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

0
Subscribe to my newsletter

Read articles from Pau Riosa directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pau Riosa
Pau Riosa

๐ŸŽฅ Youtuber ๐Ÿ’ป. Software Developer ๐Ÿ“• Blogger I am software developer who has passion for career development and software engineering. I love building stuff that can help people all around the world. Let's connect and share that idea!