Phoenix/Elixir: Chain Composable Queries with Ecto Named Bindings.
Make Easily Composable and Reusable Queries.
In my earlier article, Ecto with Phoenix in 4 Minutes, I summarized how Ecto handles data persistence and validation in your phoenix app. If you would like a refresher on Ecto queries, I recommend you start there.
What are composable queries?
Composable queries are a way of building queries to your database such that you can combine them.
For example, let’s pretend you are building a blog app. You may wish to query posts for a variety of reasons. For example, you might want to find all posts for a particular author, written in a certain year, with a certain tag, etc.
Normally with Ecto, that code looks something like this:
Writing reusable chainable query methods saves you from rewriting similar code over and over. So you can refactor the above into:
Query methods like
where take in a query and return another query.
Thus you can compose them together, typically using Pipe-based syntax.
Pipe-based syntax in Ecto composes queries together, passing the query through a series of query methods. Here’s an example using multiple
join calls to add the author, comments, and tags for a list of posts:
To make it more clear that the query is being passed through to each query method, here’s the same code without using the pipe operator:
Notice that when you call the
join method and attach another schema such as Author or Comment onto the post, the binding for the post’s comment and author are based on the position in the list.
Here’s the same code, but with the post, author, comment, and tag bindings written more verbosely to clarify their position.
Positional bindings are a problem when trying to chain queries together Because you run into positional conflicts if the order changes. Thus you couple your code to the order that the queries are called in.
For example, you cannot retrieve the comments before the tags. You would need to change the position of the comments and tags in the list of bound values.
Chain Composable Queries.
The goal is to create reusable and chainable methods by extracting common queries you make into their own method. When I say chain, I mean that you can call the extracted query methods in a pipe chain like so:
However, because of positional bindings, these methods are coupled to each other and their order.
You can solve the problem of positional bindings by using named bindings. Where positional bindings are bound to the next position in a list, named bindings allow you to set their name. Like so:
Then you can use the named binding instead of a position if needed for a later query. Here’s an example method that filters posts for a specific author.
This prevents position conflicts across joins so that you can chain your methods in any order.
So long as you do not reuse the same name, you can compose the methods in any order.
However, if a method requires a named binding, it will still have to follow the function that creates it. For example, a
for_author method would have to be called after the
with_author method because it relies on the
author: named binding.
You learned about composable Ecto queries using the Pipe-based syntax. You learned how to extract those composable Ecto queries into separate methods, and you learned how to use named bindings to deal with the position conflicts caused by positional bindings.
Have a comment or question? I’d be happy to help. You can reach out on Twitter https://twitter.com/BrooklinJMyers or here on Medium!