View Source Schema configuration

Basics

Minimal configuration

defmodule Pet do
  use Ecto.Schema

  @derive {
    Flop.Schema,
    filterable: [:name, :species],
    sortable: [:name, :age]
  }

  schema "pets" do
    field :name, :string
    field :age, :integer
    field :species, :string
  end
end

Options

Limit

@derive {
  Flop.Schema,
  filterable: [:name, :species],
  sortable: [:name, :age],
  max_limit: 100,
  default_limit: 50
}

Order

@derive {
  Flop.Schema,
  filterable: [:name, :species],
  sortable: [:name, :age],
  default_order: %{
    order_by: [:name, :age],
    order_directions: [:asc, :desc]
  }
}

Pagination types

@derive {
  Flop.Schema,
  filterable: [:name, :species],
  sortable: [:name, :age],
  pagination_types: [:first, :last],
  default_pagination_type: :first
}

Alias fields

Schema

defmodule Owner do
  use Ecto.Schema

  @derive {
    Flop.Schema,
    filterable: [:name],
    sortable: [:name, :pet_count],
    adapter_opts: [
      alias_fields: [:pet_count]
    ]
  }

  schema "owners" do
    field :name, :string
    has_many :pets, Pet
  end
end

Query

params = %{order_by: [:pet_count]}

Owner
|> join(:left, [o], p in assoc(o, :pets), as: :pets)
|> group_by([o], o.id)
|> select(
  [o, pets: p],
  {o.id, p.id |> count() |> selected_as(:pet_count)}
)
|> Flop.validate_and_run(params, for: Owner)

Compound fields

Schema

defmodule User do
  use Ecto.Schema

  @derive {
    Flop.Schema,
    filterable: [:full_name],
    sortable: [:full_name],
    adapter_opts: [
      compound_fields: [
        full_name: [:family_name, :given_name]
      ]
    ]
  }

  schema "users" do
    field :family_name, :string
    field :given_name, :string
  end
end

Query

params = %{
  filters: [
    %{field: :full_name, op: :ilike_and, value: "pea"}
  ]
}

Flop.validate_and_run(User, params, for: Owner)

Join fields

Schema

Owner

defmodule Owner do
  use Ecto.Schema

  @derive {
    Flop.Schema,
    filterable: [:name, :pet_age],
    sortable: [:name],
    adapter_opts: [
      join_fields: [
        pet_age: [
          binding: :pets,
          field: :age,
          ecto_type: :integer
        ]
      ]
    ]
  }

  schema "owners" do
    field :name, :string
    has_many :pets, Pet
  end
end

Pet

defmodule Pet do
  use Ecto.Schema

  schema "pets" do
    field :age, :integer
  end
end

Query

Only filtering or sorting

params = %{
  filters: [
    %{field: :pet_age, op: :==, value: 8}
  ]
}

Owner
|> join([o], p in assoc(o, :pets), as: :pets)
|> Flop.validate_and_run(params, for: Pet)

With preload

Owner
|> join([o], p in assoc(o, :pets), as: :pets)
|> preload([pets: p], pets: p)
|> Flop.validate_and_run(params, for: Pet)

Join field for nested association

Schema

Owner

defmodule Owner do
  use Ecto.Schema

  @derive {
    Flop.Schema,
    filterable: [:name, :toy_description],
    sortable: [:name],
    adapter_opts: [
      join_fields: [
        pet_age: [
          binding: :toys,
          field: :description,
          ecto_type: :string,
          # only needed with cursor pagination when sorting
          # by the join field, so that Flop can find the
          # cursor value
          path: [:pets, :toys]
        ]
      ]
    ]
  }

  schema "owners" do
    field :name, :string
    has_many :pets, Pet
  end
end

Pet

defmodule Pet do
  use Ecto.Schema

  schema "pets" do
    field :age, :integer
    has_many :toys, Toy
  end
end

Toy

defmodule Toy do
  use Ecto.Schema

  schema "toys" do
    field :description, :string
  end
end

Query with preload

params = %{order_by: [:toy_description]}

Owner
|> join([o], p in assoc(o, :pets), as: :pets)
|> join([pets: p], t in assoc(p, :toys), as: :toys)
|> preload([pets: p, toys: t], pets: {p, toys: t})
|> Flop.validate_and_run(params, for: Owner)

Join field for subquery

Schema

Owner

defmodule Owner do
  use Ecto.Schema

  @derive {
    Flop.Schema,
    filterable: [:name],
    sortable: [:name, :pet_count],
    adapter_opts: [
      join_fields: [
        pet_count: [
          binding: :pet_count,
          field: :count
        ]
      ]
    ]
  }

  schema "owners" do
    field :name, :string
    has_many :pets, Pet
  end
end

Pet

defmodule Pet do
  use Ecto.Schema

  schema "pets" do
    field :age, :integer
  end
end

Query

params = %{filters: [%{field: :pet_count, op: :>, value: 2}]}

pet_count_query =
  Pet
  |> where([p], parent_as(:owner).id == p.owner_id)
  |> select([p], %{count: count(p)})

q =
  Owner
  |> from(as: :owner)
  |> join(:inner_lateral, [o], p in subquery(pet_count_query),
    as: :pet_count
  )
  |> Flop.validate_and_run(params, for: Owner)

Custom fields

Schema

defmodule Pet do
  use Ecto.Schema

  @derive {
    Flop.Schema,
    filterable: [:name, :human_age],
    sortable: [:name],
    adapter_opts: [
      custom_fields: [
        human_age: [
          filter: {CustomFilters, :human_age, []},
          ecto_type: :integer
        ]
      ]
    ]
  }

  schema "pets" do
    field :name, :string
    field :age, :integer
  end
end

Custom filter function

defmodule CustomFilters do
  import Ecto.Query

  def human_age(q, %Flop.Filter{value: value, op: op}, _) do
    case Ecto.Type.cast(:integer, value) do
      {:ok, human_years} ->
        value_in_dog_years = round(human_years / 7)

        case op do
          :== -> where(q, [p], p == ^value_in_dog_years)
          :!= -> where(q, [p], p != ^value_in_dog_years)
          :> -> where(q, [p], p > ^value_in_dog_years)
          :< -> where(q, [p], p < ^value_in_dog_years)
          :>= -> where(q, [p], p >= ^value_in_dog_years)
          :<= -> where(q, [p], p <= ^value_in_dog_years)
        end

      :error ->
        # cannot cast filter value, ignore
        q
    end
  end
end

Query

params = %{
  filters: [
    %{field: :human_age, op: :==, value: 30}
  ]
}

Flop.validate_and_run(Pet, params, for: Pet)