View Source EctoModel.Queryable behaviour (EctoModel v0.0.1)

A behaviour for defining a query/2 callback that can be used as an easy-to-use and fluent DSL for building Ecto queries in a consistent manner across different schemas.

Usage

  1. Define a schema module and use the EctoModel.Queryable behaviour. By default, this is all you have to do unless you wish to customize the behaviour further.

    defmodule MyApp.User do
      use Ecto.Schema
      use EctoModel.Queryable
    
      schema "users" do
        field(:name, :string)
        field(:email, :string)
        field(:inserted_at, :utc_datetime)
        field(:updated_at, :utc_datetime)
      end
    end
    
    (iex)> MyApp.Repo.exists?(MyApp.User.query(name: "John", email: nil))
    true
    (iex)> MyApp.Repo.exists?(MyApp.User.query(email: nil, inserted_at: {:>=, ~U[2021-01-01 00:00:00Z]}))
    false
  2. Implement the base_query/0 optional callback if you want all queries, by default, to inherit a standard set of filters. This is useful for implementing soft deletes, for example.

    def base_query, do: from(x in __MODULE__, where: is_nil(x.deleted_at))
  3. Implement the query/2 callback to apply filters to the query. This is where you can extend the default supported filters or add custom filters that are specific to your schema.

    When you use the EctoModel.Queryable behaviour, you get a default implementation of the query/2 callback that looks like the following:

    def query(base_query \ base_query(), filters) do
      Enum.reduce(filters, base_query, &apply_filter(&2, &1))
    end

    If you want to add new logic while still inheriting the default behaviour, you can do so by ensuring a clause exists within your Enum.reduce/3 implementation that matches against any pattern and delegates to the default behaviour provided by EctoModel.Queryable.apply_filter/2.

Supported Filters

By default, all non-embedded fields should be supported by the default apply_filter/2 implementation for the following operators:

  • Equality (==) via field: value
  • Inclusion (in) via field: [value1, value2]
  • Exclusion (not in) via field: {:not, [value1, value2]}
  • Greater than (>) via field: {:gt, value} or field: {:>, value}
  • Greater than or equal to (>=) via field: {:gte, value} or field: {:>=, value}
  • Less than (<) via field: {:lt, value} or field: {:<, value}
  • Less than or equal to (<=) via field: {:lte, value} or field: {:<=, value}
  • Is null (nil) via field: nil
  • Is not null (not nil) via field: {:not, nil}
  • Like (like %value&) via field: ~r/value/
  • Case insensitive Like (ilike %value&) via field: ~r/value/i

Additionally, while not filters in the traditional sense, the following options are also supported:

  • Preloading (preload) via preload: :association or preload: [:association1, :association2]
  • Limiting (limit) via limit: 10
  • Offsetting (offset) via offset: 10
  • Ordering (order_by) via order_by: :field or order_by: {:desc, :field} or order_by: [:field1, :field2]

Summary

Functions

List of default filters that can be used in a schema's query/2 callback

Returns true a given module implements the Queryable behaviour

Callbacks

@callback base_query() :: Ecto.Queryable.t()
@callback query(Ecto.Queryable.t(), Keyword.t()) :: Ecto.Queryable.t()

Functions

Link to this function

apply_filter(query, arg2)

View Source
@spec apply_filter(
  Ecto.Queryable.t(),
  {field :: atom(), value :: term()}
) :: Ecto.Queryable.t()

List of default filters that can be used in a schema's query/2 callback

@spec implemented_by?(module()) :: boolean()

Returns true a given module implements the Queryable behaviour