seva

seva

I’m new to Ecto and Elixir, so sorry if this is something obvious.

I’m trying to filter on the join, e.g. get all authors with available books:

SELECT *
FROM "authors"
    INNER JOIN "books"
        ON "books"."author_id" = "authors"."id"
WHERE "books"."available" = TRUE;

Coming from Rails, this is something that’s fairy easy to achieve by merging a named scope into the join:

class Book < ApplicationRecord
  belongs_to :author
  scope :available, ->{ where(available: true) }
end

class Author < ApplicationRecord
  has_many :books
  scope :with_available_books, ->{ joins(:books).merge(Book.available) }      
end

Calling Author.with_available_books, produces the required query, and I can reuse Book#available for Book queries as well.

I was able to construct initial sql with Ecto successfully:

from a in Author,
  join: b in assoc(a, :books),
  where: b.availabile == true)

However, I now need to refactor the b.availabile == true into Book module, so I can reuse it both for Author joins and queries on the Books.

Following the Dynamic query docs, I was able to partly solve this:

defmodule Hello.Book do
  def available, do: dynamic([books: b], b.available == true)
end

# Works successfully:
Author
|> join(:inner, [a], assoc(a, :books), as: :books)
|> where(^Book.available)

However, getting available books (e.g. where(Book, ^Book.available)) throws:

** (Ecto.QueryError) unknown bind name `:books` in query:

from b0 in Hello.Book

…unless I remove the named binding from the composable function (in which case I can no longer use it on the join):

def available, do: dynamic([b], b.available == true)

Any pointers on how to get the composable query to play nice with both use cases would be appreciated!

Showing Posts 1 to 2

MrDoops

MrDoops

TLDR; look into Named Bindings.

You can conditionally check and join in your required joins within a function using has_named_binding/2

e.g.

def base_query(), do: from(a in Author, as: :author)

def with_books(query \\ base_query()) do
  join(query, :inner, [author: a], assoc(a, :books), as: :books)
end

def where_books_available(query) do
  if has_named_binding?(query, :books) do
    where(query, [books: b], b.available == true)
  else
    query
    |> with_books()
    |> where([books: b], b.available == true)
  end
end

I usually try to avoid using Dynamic until the problem gets complex (dynamic field selection + joins + complex subquerying, etc) and prefer composing with pipe-able Ecto query functions when re-usability is desired.

seva

seva OP

Thanks for the reply and for sharing the example @MrDoops.

Unfortunately, the example keeps book’s query logic (i.e. b.available == true) within Authors#where_books_available, so I’m unable to reuse it to query Books.

The example also repeats the query bit b.available == true twice. In my real case I have many decorating functions similar to available/0 in Books that I need to chain and reuse together for either querying Books directly or as part of a join in Authors and other related tables. So the solution needs to be DRYer.

I now managed to use has_named_binding? to workaround my initial issue:

defmodule Hello.Book do
  def available(query) do:
    new_query = if has_named_binding?(query, :books) do
      dynamic([books: b], b.available == true)
    else
      dynamic([b], b.available == true)
    end

    where(query, ^new_query)
  end
end

This allows me to successfully use it for querying books:

Book |> Book.available |> last |> Repo.one

as well as within joins:

Author
|> join(:inner, [a], assoc(a, :books), as: :books)
|> Book.available

However, my implementation of available/1 seems like an overkill, as there’s again repetition of the query logic (b.available == true). Plus there are many other similar composable functions, so suddenly a 1-line function becomes 7.

So wondering if there might be a neater solution?

— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews