We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Stop rewriting Ecto queries from scratch - use `Ecto.Query.exclude/2` instead
almirsarajcic
Building complex queries often means starting with a base query, then customizing it for different use cases. Most developers duplicate the entire query logic instead of using Ecto’s built-in composition tools.
# ❌ DUPLICATED LOGIC - Rewriting the entire query
def recent_posts_by_category(category) do
from(p in Post,
join: u in User, on: p.user_id == u.id, # Duplicated join
where: p.published == true, # Duplicated condition
where: p.inserted_at > ago(30, "day"), # Duplicated condition
where: p.category == ^category, # New filter
order_by: [desc: p.inserted_at], # Duplicated ordering
select: {p.title, u.name} # Duplicated select
)
end
# ✅ COMPOSABLE - Build on existing queries with exclude/2
def recent_posts_by_category(category) do
base_recent_posts()
|> exclude(:where) # Remove existing where clauses
|> where([p], p.published == true)
|> where([p], p.inserted_at > ago(30, "day"))
|> where([p], p.category == ^category) # Add new filter
end
# ✅ COMPOSABLE - Build on existing queries with exclude/2
def base_recent_posts do
from(p in Post,
join: u in User, on: p.user_id == u.id,
where: p.published == true,
where: p.inserted_at > ago(30, "day"),
order_by: [desc: p.inserted_at],
select: {p.title, u.name}
)
end
def recent_posts_by_category(category) do
base_recent_posts()
|> exclude(:where) # Remove existing where clauses
|> where([p], p.published == true) # Re-add base conditions
|> where([p], p.inserted_at > ago(30, "day"))
|> where([p], p.category == ^category) # Add new filter
end
def recent_posts_different_order do
base_recent_posts()
|> exclude(:order_by) # Remove existing ordering
|> order_by([p], [asc: p.title]) # Apply different ordering
end
Why exclude/2
works
Ecto queries are composable data structures. exclude/2
removes specific query parts (:where
, :order_by
, :select
, :join
, etc.) so you can replace them without starting from scratch.
# You can exclude multiple parts at once
base_query
|> exclude([:where, :order_by])
|> where([p], p.status == :active)
|> order_by([p], [desc: p.priority])
# Common excludable parts:
# :distinct, :where, :order_by, :group_by, :having
# :limit, :offset, :select, :join
This prevents query logic duplication and makes your context functions more maintainable. Instead of copy-pasting complex joins and conditions, you compose from proven base queries.
Pro tip: Use exclude/2
with dynamic filters to build search interfaces that modify base queries instead of constructing them conditionally.
copied to clipboard