Skip to content
Composable query builder for Ecto
Elixir
Latest commit 0283c34 Jun 24, 2016 @bcardarella bcardarella committed on GitHub Merge pull request #11 from avitex/master
Upgrade Ecto to v2, add Elixir 1.3.0 to .travis.yml

README.md

Inquisitor

Easily build composable queries for Ecto.

Build Status

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
end

After 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
end

The 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)
end

That'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

@dockyard

Licensed under the MIT license

Something went wrong with that request. Please try again.