Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
Yesterday, while working on a RESTful API, I encountered a somehow tricky problem.
How do I filter an Ecto result based on the parameters passed in the URL?
In this mini-tutorial, we’ll build a reusable module that will handle the filtering.
Situation
We built an API endpoint for getting all the user’s todo items. The todos table has a state column and each todo item can have a state of done, doing or pending.
What we are trying to achieve
We want to filter the query results based on the request URL’s parameters. For example, we should only include todo items that have the doing or `done` state when the user calls this endpoint:
https://my-awesome-api.io/v1/todos?filter[state]=doing,done
Boilerplate
Usually, to get all the user’s todo items, we have this in our context file:
And we call this function in our controller like this:
Solution
Since the filter parameters will be passed in the URL, we need to pass the params variable to our context’s list_todos/2 function. We need to modify our controller as such:
By doing that, we also need to adjust the arity of our list_todos function in our context. We’re now also going to prepare our context in using the filter module that we will be building later.
In line 7, we just checked if the filter parameter exists or not. If it exists, we return the filter parameters in a list form and if it does not, we just return an empty list.
We will now build the FilterEx module which will handle the filtering.
Building the FilterEx Module
In building this module, we will make of use of Ecto.Query.dynamic/2. If you want to learn more about it, here’s the official documentation. Basically, Ecto.Query.dynamic/2 let’s us build query expressions bit by bit and interpolate it later in our main query.
“… interpolate it later in our main query”
You may be wondering why we did not just add the call to FilterEx in our context like so: where([t], t.user_id == ^user.id and ^FilterEx.filter(states, :state))
There’s a catch though.
dynamic can be interpolated at the root of a where, having or a join’s on.
That’s why we added our FilterEx call after our firstwhere clause and not just interpolate it. It must be interpolated at the root of the clause.
Let’s now define the module and import the necessary Ecto module.
defmodule FilterEx do import Ecto.Query
# ...end
FilterEx.filter/3 expects the first parameter to be the working query, second parameter to be the list of filters and the third parameter to be the column name.
defmodule FilterEx do import Ecto.Query
@spec filter(Ecto.Query.t, list, atom) :: Ecto.Query.t def filter(query, [head | tail], fieldname) do ... end
def filter(query, [], _), do: query
end
Inside the filter/3 function, we will be building our initial dynamic query which will be passed to the filter_field/3 function for further dynamic query building. Then we interpolate the dynamic_query to our main query and return it to the pipeline.
...
def filter(query, [head | tail], fieldname) do dynamic_query = dynamic([q], field(q, ^fieldname) == ^head) |> filter_field(tail, field_name)
query |> where(^dynamic_query) end
...
We’ll be using recursion method in our filter_field/3 function.
...
def filter_field(dynamic, [head | tail], fieldname) do dynamic([q], field(q, ^fieldname) == ^head or ^dynamic) |> filter_field(tail, fieldname) end
...
We have now finished building the FilterEx module.
Here’s the completed version:
Thank you for reading. If you have any questions, you can always talk to me on Twitter @VinceUrag
Learned something valuable? You can always buy me a coffee. ❤
Connect with me on Github:
Ecto: Multiple Filters via URL Parameters was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.