Repository navigation
Using named bindings and has_named_binding?
How can I make it easier to keep track of my bindings when doing multiple joins in queries? I lose track of which bindings refer to which tables and having to bring the positional bindings along in every future
joinorwhereclause causes even more confusion.I'd also like a way to check if a query has already included a
joinor if I should be doing thejoinmyself in my function.
Named bindings in Ecto.Query are a great way to make managing your bindings more explicit and readable.
https://hexdocs.pm/ecto/Ecto.Query.html#join/5
Using the as: keyword option in a join you are able to set a name for the table in a join to something that you can later reference in other types of clauses.
Here is an example using your standard positional bindings
query =
User
|> join(:inner, [u], c in assoc(u, cats))
|> join(:inner, [u, c], b in assoc(c, beds))
|> where([u, c, b], b.texture == "Fleece")
Using positional binding as above you can sometimes run into an error if you forget a binding and Elixir gets confused as to which you're referencing. If you attempted to do a where clause leaving out one of the positional bindings it may complain that it doesn't know of an attribute you tried to use because you're referencing the wrong table
|> where([u, b], b.texture == "Sand")
May complain that column texture doesn't exist because it thinks you are talking about cats and not beds since you left out a binding for cats (bad example since cats do have texture in reality but let's say we aren't tracking that here).
Here is the example again using named bindings
query =
User
|> join(:inner, [u], c in assoc(u, cats), as: :cats)
|> join(:inner, [u, c], b in assoc(c, beds), as: :beds)
|> where([u, beds: b], b.texture == "Fleece")
See in this example we are setting the names :cats and :beds for the bindings using as:. We can then reference the :beds binding directly using beds: b in the where clause afterward.
Named bindings also give us the capability to check if a query already contains a join through the use of has_named_binding? before potentially trying to do that join again and causing problems or excessive queries.
The syntax for has_named_binding?(queryable, key) is pretty simple. You pass in the query as well as the key of the named binding you want to check and it will return true or false
https://hexdocs.pm/ecto/Ecto.Query.html#has_named_binding?/2
For our above query has_named_binding?(query, :cats) would return true while has_named_binding?(query, :dogs) would return false.
One application of this would be writing a function that you can pipe a query to that would conditionally attempt a join if it didn't already exist
def maybe_join_dogs(query) do
case Ecto.Query.has_named_binding?(query, :dogs) do
true -> query
false -> join(query, :inner, [u], d in assoc(u, :dogs), as :dogs)
end
end
This will join dogs to the user query we passed in if a named binding of :dogs hasn't already been attached and will just pass the query through untouched otherwise.
This does assume you are consistent in how you name your bindings throughout the application so try to have some kind of standard for that. Naming them the same thing as the table you're joining is probably a good place to start.
🏳️🌈🦄 Sharing is caring. 🦄🏳️🌈
TODO: can we recreate the tag system here roughly with sections about certain subjects?