Add to your mix.exs
file:
[
{:querie, "~> 1.0"}
]
It turns this:
http://localhost:4000/products?name__icontains=milk&price__ge=32&rating__sort=desc
Into this
from(p in Product, where: ilike(p.name, "%milk%") and p.price >= 32, order_by: [desc: :rating])
Cool, right?
- What is Querie?
- What Querie can do?
- How to use Querie?
- Build filter in your code
- Supported operators
Querie is a library that help you to build the query directly from the URL parameters without writing to much code. If you want to add more filter criteria? Don't worry, you only need to change the filter schema.
- Build Ecto Query dynamically
- Query reference tables
- Support common query operator:
>
>=
<
<=
=
not
like
ilike
between
is_nil
- Support sort query
Especially Querie does not use macro 😇
There are 3 steps to make it work 1. Define a filter schema
Schema is a map which define:* data type of field, so it can be parsed correctly
- which field can be filter, other extra fields are skip
- which tables are referenced and how to query referenced tables
For example you have a Post schema:
defmodule Example.Content.Post do
use Ecto.Schema
import Ecto.Changeset
def state_enum(), do: ~w(draft published archived trash)
schema "posts" do
field(:content, :string)
field(:state, :string, default: "draft")
field(:title, :string)
field(:view_count, :integer, default: 0)
belongs_to(:category, Example.PostMeta.Category)
belongs_to(:author, Example.Account.User)
end
end
And you want to filter the Post
by title
, state
, view_count
. This is the schema:
@schema %{
title: :string,
state: :string, # short form
view_count: [type: :integer] # long form
}
2. Parse request parameters and build the query
Use Querie.parse/2
to parse request parameters with your schema
alias Example.Content.Post
def index(conn, params) do
with {:ok, filter} <- Querie.parse(@schema, params) do
query = Querie.filter(Post, filter)
# Or you can pass a query like this
# query = from(p in Post, where: ....)
# query = Querie.filter(query, filter)
posts = Repo.all(query)
# do the rendering here
else
{:error, errors} ->
IO.puts(inspect(errors)
# or do anything with error
# error is a list of tuple {field, message}
end
end
3. Build the URL query
Parameter must follow this format: [field_name]__[operator]=[value]
. If no operator is specified, by defaut =
operator is used.
Supported operators are listed below.
For example you want to filter Post
which:
title
containselixir
state
ispublished
view_count
>= 100
URL query string would be: ?title__icontains=elixir&state=published&view_count__ge=100
Follow this format to sort by field: <field>__sort=<asc|desc>
For example you want to sort by title
ascending, add this to query: title__sort=asc
Simple, right?
You can set default sort order and sort priority for each field:
%{
view_count: [type: :integer, sort_default: :desc, sort_priority: 1]
title: [type: :string, sort_default: :asc, sort_priority: 2]
}
Field with smaller sort_priority
smaller is sorted first
Query
supports query between min
and max
value. It translates between
to > min and < max
. And inclusive version is ibetween
which translated to >= min and <= max
You don’t have to modify your schema to use between
.
From client you can send between value in 3 forms:
- value with separator:
view_count__between=20,60
- array of 2 value:
view_count__between[]=20&view_count__between[]=60
- map value with
min
andmax
:view_count__between[min]=20&view_count__between[max]=60
If min
or max
is omitted, it will use one compare operator.
For example, the Post
schema above references to 2 other schemas: User
and Category
you can filter with conditions on those 2 schema.
This is the schema for User
defmodule Example.Account.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field(:email, :string)
field(:first_name, :string)
field(:last_name, :string)
end
1. Update your schema
alias Example.Account.User
@schema %{
title: :string,
state: :string,
view_count: [type: :integer],
author: [
type: :ref, # this references to another schema
model: User, # which schema to query
schema: %{ # define filter schema for User
email: :string
}
]
}
2. Update your query
For example you want to query Post
by author whose email
contains sam
the query would be: ?author__ref[email__icontains]=sam
You can specify custom join field with 2 options:
foreign_key
default is[field]_id
. In the example above, it isauthor_id
references
is the key to join on the other table. Default isid
You can build filter for Querie
directly from your code
filters = %{
title: {:ilike, "elixir"},
tag: "elixir", # query with = operator,
category_id: [1, 2], # query with in operator,
view_count: {:between, [10, 50]}
}
Querie.filter(Post, filters}
This is list of supported operators with mapping key word.
operator | mapping keyword |
---|---|
= | is or omit |
!= | ne |
> | gt |
>= | ge |
< | lt |
<= | le |
like | contains or like |
ilike | icontains or ilike |
between | between |
inclusive between | ibetween |