Flop (Flop v0.8.0) View Source

Flop is a helper library for filtering, ordering and pagination with Ecto.

Usage

Derive Flop.Schema in your Ecto schemas.

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
    field :social_security_number, :string
  end
end

Validate a parameter map to get a Flop.t/0 struct with Flop.validate/1. Add the Flop.t/0 to a Ecto.Queryable.t/0 with Flop.query/2.

iex> params = %{"order_by" => ["name", "age"], "limit" => 5}
iex> {:ok, flop} = Flop.validate(params, for: Flop.Pet)
{:ok,
 %Flop{
   filters: [],
   limit: 5,
   offset: 0,
   order_by: [:name, :age],
   order_directions: nil,
   page: nil,
   page_size: nil
 }}
iex> Flop.Pet |> Flop.query(flop)
#Ecto.Query<from p0 in Flop.Pet, order_by: [asc: p0.name, asc: p0.age], limit: ^5, offset: ^0>

Use Flop.validate_and_run/3, Flop.validate_and_run!/3, Flop.run/3, Flop.all/3 or Flop.meta/3 to query the database. Also consult the readme for more details.

Link to this section Summary

Types

Represents the supported order direction values.

t()

Represents the query parameters for filtering, ordering and pagination.

Functions

Applies the given Flop to the given queryable and returns all matchings entries.

Returns the total count of entries matching the filter conditions of the Flop.

Applies the filter parameter of a Flop.t/0 to an Ecto.Queryable.t/0.

Returns meta information for the given query and flop that can be used for building the pagination links.

Applies the order_by and order_directions parameters of a Flop.t/0 to an Ecto.Queryable.t/0.

Applies the pagination parameters of a Flop.t/0 to an Ecto.Queryable.t/0.

Adds clauses for filtering, ordering and pagination to a Ecto.Queryable.t/0.

Applies the given Flop to the given queryable, retrieves the data and the meta data.

Same as Flop.validate/2, but raises an Ecto.InvalidChangesetError if the parameters are invalid.

Validates the given flop parameters and retrieves the data and meta data on success.

Link to this section Types

Specs

order_direction() ::
  :asc
  | :asc_nulls_first
  | :asc_nulls_last
  | :desc
  | :desc_nulls_first
  | :desc_nulls_last

Represents the supported order direction values.

Specs

t() :: %Flop{
  after: String.t() | nil,
  before: String.t() | nil,
  filters: [Flop.Filter.t()] | nil,
  first: pos_integer() | nil,
  last: pos_integer() | nil,
  limit: pos_integer() | nil,
  offset: non_neg_integer() | nil,
  order_by: [atom() | String.t()] | nil,
  order_directions: [order_direction()] | nil,
  page: pos_integer() | nil,
  page_size: pos_integer() | nil
}

Represents the query parameters for filtering, ordering and pagination.

Fields

  • after: Used for cursor-based pagination. Must be used with first.
  • before: Used for cursor-based pagination. Must be used with last.
  • limit, offset: Used for pagination. May not be used together with page and page_size.
  • first Used for cursor-based pagination. Can be used alone to begin pagination or with after
  • last Used for cursor-based pagination. Must be used with before
  • page, page_size: Used for pagination. May not be used together with limit and offset.
  • order_by: List of fields to order by. Fields can be restricted by deriving Flop.Schema in your Ecto schema.
  • order_directions: List of order directions applied to the fields defined in order_by. If empty or the list is shorter than the order_by list, :asc will be used as a default for each missing order direction.
  • filters: List of filters, see Flop.Filter.t/0.

Link to this section Functions

Link to this function

all(q, flop, opts \\ [])

View Source (since 0.6.0)

Specs

all(Ecto.Queryable.t(), t(), keyword()) :: [any()]

Applies the given Flop to the given queryable and returns all matchings entries.

iex> Flop.all(Flop.Pet, %Flop{}, repo: Flop.Repo)
[]

You can also configure a default repo in your config files:

config :flop, repo: MyApp.Repo

This allows you to omit the third argument:

iex> Flop.all(Flop.Pet, %Flop{})
[]
Link to this function

count(q, flop, opts \\ [])

View Source (since 0.6.0)

Specs

Returns the total count of entries matching the filter conditions of the Flop.

The pagination and ordering option are disregarded.

iex> Flop.count(Flop.Pet, %Flop{}, repo: Flop.Repo)
0

You can also configure a default repo in your config files:

config :flop, repo: MyApp.Repo

This allows you to omit the third argument:

iex> Flop.count(Flop.Pet, %Flop{})
0

Specs

filter(Ecto.Queryable.t(), t()) :: Ecto.Queryable.t()

Applies the filter parameter of a Flop.t/0 to an Ecto.Queryable.t/0.

Used by Flop.query/2.

Link to this function

meta(query_or_results, flop, opts \\ [])

View Source (since 0.6.0)

Specs

meta(Ecto.Queryable.t() | [any()], t(), keyword()) :: Flop.Meta.t()

Returns meta information for the given query and flop that can be used for building the pagination links.

iex> Flop.meta(Flop.Pet, %Flop{limit: 10}, repo: Flop.Repo)
%Flop.Meta{
  current_offset: 0,
  current_page: 1,
  end_cursor: nil,
  flop: %Flop{limit: 10},
  has_next_page?: false,
  has_previous_page?: false,
  next_offset: nil,
  next_page: nil,
  page_size: 10,
  previous_offset: nil,
  previous_page: nil,
  start_cursor: nil,
  total_count: 0,
  total_pages: 0
}

The function returns both the current offset and the current page, regardless of the pagination type. If the offset lies in between pages, the current page number is rounded up. This means that it is possible that the values for current_page and next_page can be identical. This can only occur if you use offset/limit based pagination with arbitrary offsets, but in that case, you will use the previous_offset, current_offset and next_offset values to render the pagination links anyway, so this shouldn't be a problem.

Specs

order_by(Ecto.Queryable.t(), t()) :: Ecto.Queryable.t()

Applies the order_by and order_directions parameters of a Flop.t/0 to an Ecto.Queryable.t/0.

Used by Flop.query/2.

Specs

paginate(Ecto.Queryable.t(), t()) :: Ecto.Queryable.t()

Applies the pagination parameters of a Flop.t/0 to an Ecto.Queryable.t/0.

The function supports both offset/limit based pagination and page/page_size based pagination.

If you validated the Flop.t/0 with Flop.validate/1 before, you can be sure that the given Flop.t/0 only has pagination parameters set for one pagination method. If you pass an unvalidated Flop.t/0 that has pagination parameters set for multiple pagination methods, this function will arbitrarily only apply one of the pagination methods.

Used by Flop.query/2.

Specs

Adds clauses for filtering, ordering and pagination to a Ecto.Queryable.t/0.

The parameters are represented by the Flop.t/0 type. Any nil values will be ignored.

Examples

iex> flop = %Flop{limit: 10, offset: 19}
iex> Flop.query(Flop.Pet, flop)
#Ecto.Query<from p0 in Flop.Pet, limit: ^10, offset: ^19>

Or enhance an already defined query:

iex> require Ecto.Query
iex> flop = %Flop{limit: 10}
iex> Flop.Pet |> Ecto.Query.where(species: "dog") |> Flop.query(flop)
#Ecto.Query<from p0 in Flop.Pet, where: p0.species == "dog", limit: ^10>
Link to this function

run(q, flop, opts \\ [])

View Source (since 0.6.0)

Specs

run(Ecto.Queryable.t(), t(), keyword()) :: {[any()], Flop.Meta.t()}

Applies the given Flop to the given queryable, retrieves the data and the meta data.

This function does not validate the given flop parameters. You can validate the parameters with Flop.validate/2 or Flop.validate!/2, or you can use Flop.validate_and_run/3 or Flop.validate_and_run!/3 instead of this function.

iex> {data, meta} = Flop.run(Flop.Pet, %Flop{})
iex> data == []
true
iex> match?(%Flop.Meta{}, meta)
true
Link to this function

validate(flop, opts \\ [])

View Source

Specs

validate(t() | map(), keyword()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}

Validates a Flop.t/0.

Examples

iex> params = %{"limit" => 10, "offset" => 0, "texture" => "fluffy"}
iex> Flop.validate(params)
{:ok,
 %Flop{
   filters: [],
   limit: 10,
   offset: 0,
   order_by: nil,
   order_directions: nil,
   page: nil,
   page_size: nil
 }}

iex> flop = %Flop{offset: -1}
iex> {:error, changeset} = Flop.validate(flop)
iex> changeset.valid?
false
iex> changeset.errors
[
  offset: {"must be greater than or equal to %{number}",
   [validation: :number, kind: :greater_than_or_equal_to, number: 0]}
]

It also makes sure that only one pagination method is used.

iex> params = %{limit: 10, offset: 0, page: 5, page_size: 10}
iex> {:error, changeset} = Flop.validate(params)
iex> changeset.valid?
false
iex> changeset.errors
[limit: {"cannot combine multiple pagination types", []}]

If you derived Flop.Schema in your Ecto schema to define the filterable and sortable fields, you can pass the module name to the function to validate that only allowed fields are used. The function will also apply any default values set for the schema.

iex> params = %{"order_by" => ["species"]}
iex> {:error, changeset} = Flop.validate(params, for: Flop.Pet)
iex> changeset.valid?
false
iex> [order_by: {msg, [_, {_, enum}]}] = changeset.errors
iex> msg
"has an invalid entry"
iex> enum
[:name, :age]

Note that currently, trying to use an existing field that is not allowed as seen above will result in the error message has an invalid entry, while trying to use a field name that does not exist in the schema (or more precisely: a field name that doesn't exist as an atom) will result in the error message is invalid. This might change in the future.

Link to this function

validate!(flop, opts \\ [])

View Source (since 0.5.0)

Specs

validate!(t() | map(), keyword()) :: t()

Same as Flop.validate/2, but raises an Ecto.InvalidChangesetError if the parameters are invalid.

Link to this function

validate_and_run(q, flop, opts \\ [])

View Source (since 0.6.0)

Specs

validate_and_run(Ecto.Queryable.t(), map() | t(), keyword()) ::
  {:ok, {[any()], Flop.Meta.t()}} | {:error, Ecto.Changeset.t()}

Validates the given flop parameters and retrieves the data and meta data on success.

iex> {:ok, {[], %Flop.Meta{}}} =
...>   Flop.validate_and_run(Flop.Pet, %Flop{}, for: Flop.Pet)
iex> {:error, %Ecto.Changeset{} = changeset} =
...>   Flop.validate_and_run(Flop.Pet, %Flop{limit: -1})
iex> changeset.errors
[
  limit: {"must be greater than %{number}",
    [validation: :number, kind: :greater_than, number: 0]}
]

Options

  • for: Passed to Flop.validate/2.
  • repo: The Ecto.Repo module. Required if no default repo is configured.
  • get_cursor_value_func: An arity-2 function to be used to retrieve an unencoded cursor value from a query result item and the order_by fields. Defaults to Flop.get_cursor_value_from_map/2.
Link to this function

validate_and_run!(q, flop, opts \\ [])

View Source (since 0.6.0)

Specs

validate_and_run!(Ecto.Queryable.t(), map() | t(), keyword()) ::
  {[any()], Flop.Meta.t()}

Same as Flop.validate_and_run/3, but raises on error.