Inquisitor
Easily build composable queries for Ecto.
Usage
Adding Inquisitor to a project is simple:
defmodule MyApp.PostController do
use Inquisitor, with: MyApp.Post
def index(conn, params) do
posts =
build_post_query(params)
|> Repo.all()
json(conn, posts)
end
endAfter use Inquisitor, with: MyApp.Post a custom function is added to
the MyApp.PostController. In this case that function is
build_post_query. The name of the function is dynamically created
based upon the model name. So if the model was MyApp.FooBarBaz the
corresponding function would be build_foo_bar_baz_query.
This sets up a key/value queryable API for the Post model. Any
combination of fields on the model can be queried against. For example,
requesting [GET] /posts?foo=bar&baz=qux will create the query:
SELECT p0."foo", p0."baz" FROM posts as p0 WHERE (p0."foo" = $1) AND (p0."baz" = $1);$1 and $2 will get the values of "bar" and "qux",
Security
Inquisitor will allow any key/value pair to be queried against, you will
likely want to only allow access to a limited number of fields. To do
this you can whitelist any number of fields that can be allowed to
queried:
use Inquisitor, with: MyApp.Post, whitelist: ["title", "body"]Adding custom query handlers
Simple key/value matching is not always what you want. In that case you
can define custom handlers for certain keys. Let's say you want to query
based upon inserted_at values, querying for all values that came on
and after that date:
defmodule MyApp.PostsController do
use Inquisitor, with: MyApp.Post
def index(conn, params) do
posts =
build_post_query(params)
|> Repo.all()
json(conn, posts)
end
defp build_post_query(query, [{"inserted_at", date}|tail]) do
query
|> Ecto.Query.where([p], p.inserted_at >= ^date)
|> build_post_query(tail)
end
endThe query is built recursively by iterating over all the params. If there is a matching custom handler, it uses that otherwise defaults to the key/value handler.
Handing fields that don't exist on the model
The keys you query against don't need to exist on the model. Revisting the date example, let's say we want to find all posts inserted for a given month and year:
defp build_event_query(query, [{attr, value}|tail]) when attr == "month" or attr == "year" do
query
|> Ecto.Query.where([e], fragment("date_part(?, ?) = ?", ^attr, e.inserted_at, type(^value, :integer)))
|> build_event_query(tail)
endThat's it!
Built in handlers
There are a few built in handlers
Booleans
Booleans that come in as text will be typecast to an actual boolean type then passed on for handling. So even if the params come as:
%{ "foo" => "true" }You will want to pattern match on the actual boolean value:
defp build_event_query(query, [{"foo", true}|tail]) do
...Authors
We are very thankful for the many contributors
Versioning
This library follows Semantic Versioning
Want to help?
Please do! We are always looking to improve this library. Please see our Contribution Guidelines on how to properly submit issues and pull requests.
Legal
DockYard, Inc. © 2016