Loupe

Coverage StatusElixir CI

Loupe is query language for Ecto schema inspection in a safe and configurable manner.

Installation

If available in Hex, the package can be installed by adding loupe to your list of dependencies in mix.exs:

def deps do
  [
    {:loupe, "~> 0.1.0"}
  ]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/loupe.

Syntax

The basic syntax has the following format

get [quantifier?] [schema] where [predicates]

Cool stuff

You can use k and m quantifiers for numbers. Writing get all User where money > 100k translates to get all User where money > 100000.

Operators

The are a couple of basic operators like <, >, <=, >=, =, !=.

But also some textual operators:

You can also use the keyword :empty as a null checker like age :empty.

Textual operators and :empty can be prefixed with not to negate the expression: not like, not in, age not :empty.

For boolean, the binding can be provided as is and prefixed by not for false. Example where active or where not enabled.

Boolean Operators

So far, the syntax supprts and and or and use parenthese to scope the expressions.

Ecto usage

Create a Definition module

The Definition module is necessary for Loupe to work with your Ecto schema. In this module you define the schemas that are allowed to be queried and the fields that are permitted for querying.

All callbacks accepts a last argument called "assigns". The assigns are provided to you when evaluating the query allowing you to alter the defition. You could, for instance, add a user's role to the assign and use that role to filter out the allowed schemas so that only admins can query Users.

defmodule MyApp.Loupe.Definition do
    @moduledoc """
    Example Ecto definition for the modules defined above.
    """
    @behaviour Loupe.Ecto.Definition

    @schemas %{
      "Post" => Post,
      "User" => User,
      "Role" => Role
    }

    @impl Loupe.Ecto.Definition
    def schemas(%{role: "admin"}), do: @schemas
    def schemas(_), do: Map.take(@schemas, ["Post", "User"])

    @impl Loupe.Ecto.Definition
    def schema_fields(_, %{role: "admin"}), do: :all
    def schema_fields(Post, _), do: {:only, [:title, :body]}
    def schema_fields(User, _), do: {:only, [:email, :posts]}
    def schema_fields(_, _), do: :all

    @impl Loupe.Ecto.Definition
    def scope_schema(schema, _), do: schema
end

Once you have this definition, you can try some queries

{:ok, ast} = Loupe.Language.compile(~s|get all User where age > 18|)
{:ok, ecto_query} = Loupe.Ecto.build_query(ast, MyApp.Loupe.Definition, %{role: "admin"})
Repo.all(ecto_query)

Todo

Here are some things that I would like Loupe to support:

Contributing

You can see the CONTRIBUTING.md file to know more about the contributing guidelines.

Pull requests are welcome!