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!