View Source Flop (Flop v0.23.0)

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

Usage

The simplest way of using this library is just to use Flop.validate_and_run/3 and Flop.validate_and_run!/3. Both functions take a queryable and a parameter map, validate the parameters, run the query and return the query results and the meta information.

iex> Flop.Repo.insert_all(MyApp.Pet, [
...>   %{name: "Harry", age: 4, species: "C. lupus"},
...>   %{name: "Maggie", age: 1, species: "O. cuniculus"},
...>   %{name: "Patty", age: 2, species: "C. aegagrus"}
...> ])
iex> params = %{order_by: ["name", "age"], page: 1, page_size: 2}
iex> {:ok, {results, meta}} =
...>   Flop.validate_and_run(
...>     MyApp.Pet,
...>     params,
...>     repo: Flop.Repo
...>   )
iex> Enum.map(results, & &1.name)
["Harry", "Maggie"]
iex> meta.total_count
3
iex> meta.total_pages
2
iex> meta.has_next_page?
true

Under the hood, these functions just call Flop.validate/2 and Flop.run/3, which in turn calls Flop.all/3 and Flop.meta/3. If you need finer control about if and when to execute each step, you can call those functions directly.

See Flop.Meta for descriptions of the meta fields.

Global configuration

You can set some global options like the default Ecto repo via the application environment. All global options can be overridden by passing them directly to the functions or configuring the options for a schema module via Flop.Schema.

import Config

config :flop, repo: MyApp.Repo

See Flop.option/0 for a description of all available options.

Config modules

Instead of setting global options in the application environment, you can also create a Flop config module. This is especially useful in an umbrella application, or if you have multiple Repos.

defmodule MyApp.Flop do
  use Flop, repo: MyApp.Repo, default_limit: 25
end

use Flop

When you use Flop, the Flop module will define wrapper functions around all of the Flop functions that take a query, the Flop parameters, and options as arguments. The options passed to use Flop will be used as default options in all the wrapper functions, but you can still override them.

The wrapped functions are:

So instead of using Flop.validate_and_run/3, you would call MyApp.Flop.validate_and_run/3.

If you have both a config module and a global application config, Flop will fall back to the application config if an option is not set.

See Flop.option/0 for a description of all available options.

Schema options

You can set some options for a schema by deriving Flop.Schema. The options are evaluated at the validation step.

defmodule Pet do
  use Ecto.Schema

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

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

You need to pass the schema to Flop.validate/2 or any function that includes the validation step with the :for option.

iex> params = %{"order_by" => ["name", "age"], "limit" => 5}
iex> {:ok, flop} = Flop.validate(params, for: MyApp.Pet)
iex> flop.limit
5

iex> params = %{"order_by" => ["name", "age"], "limit" => 10_000}
iex> {:error, meta} = Flop.validate(params, for: MyApp.Pet)
iex> [limit: [{msg, _}]] = meta.errors
iex> msg
"must be less than or equal to %{number}"

iex> params = %{"order_by" => ["name", "age"], "limit" => 10_000}
iex> {:error, %Flop.Meta{} = meta} =
...>   Flop.validate_and_run(
...>     MyApp.Pet,
...>     params,
...>     for: MyApp.Pet
...>   )
iex> [limit: [{msg, _}]] = meta.errors
iex> msg
"must be less than or equal to %{number}"

Ordering

To add an ordering clause to a query, you need to set the :order_by and optionally the :order_directions parameter. :order_by should be the list of fields, while :order_directions is a list of Flop.order_direction/0. :order_by and :order_directions are zipped when generating the ORDER BY clause. If no order directions are given, :asc is used as default.

iex> params = %{
...>   "order_by" => ["name", "age"],
...>   "order_directions" => ["asc", "desc"]
...> }
iex> {:ok, flop} = Flop.validate(params)
iex> flop.order_by
[:name, :age]
iex> flop.order_directions
[:asc, :desc]

Flop uses these two fields instead of a keyword list, so that the order instructions can be easily passed in a query string.

Pagination

For queries using OFFSET and LIMIT, you have the choice between page-based pagination parameters:

%{page: 5, page_size: 20}

and offset-based pagination parameters:

%{offset: 100, limit: 20}

For cursor-based pagination, you can either use :first/:after or :last/:before. You also need to pass the :order_by parameter or set a default order for the schema via Flop.Schema.

iex> Flop.Repo.insert_all(MyApp.Pet, [
...>   %{name: "Harry", age: 4, species: "C. lupus"},
...>   %{name: "Maggie", age: 1, species: "O. cuniculus"},
...>   %{name: "Patty", age: 2, species: "C. aegagrus"}
...> ])
iex>
iex> # forward (first/after)
iex>
iex> params = %{first: 2, order_by: [:species, :name]}
iex> {:ok, {results, meta}} = Flop.validate_and_run(MyApp.Pet, params)
iex> Enum.map(results, & &1.name)
["Patty", "Harry"]
iex> meta.has_next_page?
true
iex> end_cursor = meta.end_cursor
"g3QAAAACdwRuYW1lbQAAAAVIYXJyeXcHc3BlY2llc20AAAAIQy4gbHVwdXM="
iex> params = %{first: 2, after: end_cursor, order_by: [:species, :name]}
iex> {:ok, {results, meta}} = Flop.validate_and_run(MyApp.Pet, params)
iex> Enum.map(results, & &1.name)
["Maggie"]
iex> meta.has_next_page?
false
iex>
iex> # backward (last/before)
iex>
iex> params = %{last: 2, order_by: [:species, :name]}
iex> {:ok, {results, meta}} = Flop.validate_and_run(MyApp.Pet, params)
iex> Enum.map(results, & &1.name)
["Harry", "Maggie"]
iex> meta.has_previous_page?
true
iex> start_cursor = meta.start_cursor
"g3QAAAACdwRuYW1lbQAAAAVIYXJyeXcHc3BlY2llc20AAAAIQy4gbHVwdXM="
iex> params = %{last: 2, before: start_cursor, order_by: [:species, :name]}
iex> {:ok, {results, meta}} = Flop.validate_and_run(MyApp.Pet, params)
iex> Enum.map(results, & &1.name)
["Patty"]
iex> meta.has_previous_page?
false

By default, it is assumed that the query result is a list of maps or structs. If your query returns a different data structure, you can pass the :cursor_value_func option to retrieve the cursor values. See Flop.option/0 and Flop.Cursor for more information.

You can restrict which pagination types are available. See Flop.option/0 for details.

Filters

Filters can be passed as a list of maps. It is recommended to define the filterable fields for a schema using Flop.Schema.

iex> Flop.Repo.insert_all(MyApp.Pet, [
...>   %{name: "Harry", age: 4, species: "C. lupus"},
...>   %{name: "Maggie", age: 1, species: "O. cuniculus"},
...>   %{name: "Patty", age: 2, species: "C. aegagrus"}
...> ])
iex>
iex> params = %{filters: [%{field: :name, op: :=~, value: "Mag"}]}
iex> {:ok, {results, meta}} = Flop.validate_and_run(MyApp.Pet, params)
iex> meta.total_count
1
iex> [pet] = results
iex> pet.name
"Maggie"

See Flop.Filter.op/0 for a list of all available filter operators.

GraphQL and Relay

The parameters used for cursor-based pagination follow the Relay specification, so you can just pass the arguments you get from the client on to Flop.

Flop.Relay can convert the query results returned by Flop.validate_and_run/3 into Edges and PageInfo formats required for Relay connections.

For example, if you have a context module like this:

defmodule MyApp.Flora
  import Ecto.query, warn: false

  alias MyApp.Flora.Plant

  def list_plants_by_continent(%Continent{} = continent, %{} = args) do
    Plant
    |> where(continent_id: ^continent.id)
    |> Flop.validate_and_run(args, for: Plant)
  end
end

Then your Absinthe resolver for the plants connection may look something like this:

def list_plants(args, %{source: %Continent{} = continent}) do
  with {:ok, result} <-
         Flora.list_plants_by_continent(continent, args) do
    {:ok, Flop.Relay.connection_from_result(result)}
  end
end

Summary

Types

A map with the order fields and the order directions.

These options can be passed to most functions or configured via the application environment.

Represents the supported order direction values.

Represents the pagination type.

t()

Represents the query parameters for filtering, ordering and pagination.

Query Functions

Returns the names of the alias fields that are required for the order clause of the given Flop.

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.

Returns the names of the bindings that are required for the filters and order clauses of the given Flop.

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.

Applies a callback function to a query for all named bindings that are necessary for the given Flop parameters.

Parameter Manipulation

Returns the current order direction for the given field.

Converts a map of filter conditions into a list of Flop filter params.

Converts key/value filter parameters at the root of a map, converts them into a list of filter parameter maps and nests them under the :filters key.

Updates the order_by and order_directions values of a Flop struct.

Removes the after and before cursors from a Flop struct.

Removes all filters from a Flop struct.

Removes the order parameters from a Flop struct.

Takes a Flop.Meta struct and returns a Flop struct with updated cursor pagination params for going to either the previous or the next page.

Sets the offset value of a Flop struct while also removing/converting pagination parameters for other pagination types.

Sets the page value of a Flop struct while also removing/converting pagination parameters for other pagination types.

Takes a Flop.Meta struct and returns a Flop struct with updated cursor pagination params for going to the next page.

Sets the offset of a Flop struct to the next page depending on the limit.

Sets the page of a Flop struct to the next page.

Takes a Flop.Meta struct and returns a Flop struct with updated cursor pagination params for going to the previous page.

Sets the offset of a Flop struct to the page depending on the limit.

Sets the page of a Flop struct to the previous page, but not less than 1.

Takes a Flop, converts it to a map and unnests the filters for the given fields.

Miscellaneous

Returns the option with the given key.

Functions

Options specific to the Ecto adapter.

Types

@type adapter_option() :: {:repo, module()} | {:query_opts, Keyword.t()}
@type default_order() :: %{
  :order_by => [atom()],
  optional(:order_directions) => [order_direction()]
}

A map with the order fields and the order directions.

Each atom in :order_by corresponds to a field by which the data is ordered. The list of order fields and order directions is zipped when the parameters are applied. If no order directions are given or the list of order directions is shorter than the list of order fields, :asc is used as a default order direction.

@type option() ::
  {:cursor_value_func, (any(), [atom()] -> map())}
  | {:default_limit, pos_integer() | false}
  | {:default_order, default_order()}
  | {:default_pagination_type, pagination_type() | false}
  | {:filtering, boolean()}
  | {:for, module()}
  | {:max_limit, pos_integer() | false}
  | {:count_query, Ecto.Queryable.t()}
  | {:count, integer()}
  | {:ordering, boolean()}
  | {:pagination, boolean()}
  | {:pagination_types, [pagination_type()]}
  | {:replace_invalid_params, boolean()}
  | {:extra_opts, Keyword.t()}
  | {:adapter_opts, adapter_option()}
  | adapter_option()
  | private_option()

These options can be passed to most functions or configured via the application environment.

Options

General

  • :for - The Ecto schema module for validation and query building. Flop.Schema must be derived for this module.
  • :cursor_value_func - A function used to extract the cursor value from a record. It takes the record and the list of fields used in the ORDER BY clause as arguments, and returns a map with the order fields as keys and the corresponding record values as values. Default is Flop.Cursor.get_cursor_from_node/2.
  • :replace_invalid_params - If set to true, invalid parameters are replaced with default values or removed instead of causing errors. Default is false.

Defaults

  • :default_limit - The default limit for queries. Used when no specific limit is set in the parameters or schema. Set to false to not set any default limit. Default is 50.
  • :default_order - The default ordering for a query when no order is specified in the parameters, or if ordering is disabled. Can be set in the schema or in the options passed to the query functions.
  • :default_pagination_type - The default pagination type when it cannot be inferred from the parameters.
  • :max_limit - The maximum limit for queries. Used when no maximum limit is set in the parameters or schema. Set to false to not set any maximum limit. Default is 1000.

Modifying counts

  • :count_query - A separate base query for counting. Can only be passed as an option to one of the query functions. See Flop.validate_and_run/3 and Flop.count/3.
  • :count - A precomputed count. Useful when the count is already known.

Disabling features

  • :filtering (boolean) - Enables or disables filtering. When set to false, filter parameters are ignored.
  • :ordering (boolean) - Enables or disables ordering. When set to false, order parameters are ignored, but the default order is still applied.
  • :pagination (boolean) - Enables or disables pagination. When set to false, pagination parameters are ignored.
  • :pagination_types - The allowed pagination types. Parameters for disallowed pagination types will not be cast. By default, all types are allowed. See also Flop.pagination_type/0.

Additional options

  • :extra_opts (keyword list) - Extra options for custom fields.
  • :adapter_opts - Adapter-specific options. For backward compatibility, options for the Ecto adapter can be set directly at the root level.

Look-up order

Options are looked up in the following order:

  1. Function arguments
  2. Schema-level options
  3. Module-level options in the config (backend) module
  4. Global options in the application environment
  5. Library defaults
@type order_direction() ::
  :asc
  | :asc_nulls_first
  | :asc_nulls_last
  | :desc
  | :desc_nulls_first
  | :desc_nulls_last

Represents the supported order direction values.

@type pagination_type() :: :offset | :page | :first | :last

Represents the pagination type.

  • :offset - pagination using the offset and limit parameters
  • :page - pagination using the page and page_size parameters
  • :first - cursor-based pagination using the first and after parameters
  • :last - cursor-based pagination using the last and before parameters
@type t() :: %Flop{
  after: String.t() | nil,
  before: String.t() | nil,
  decoded_cursor: map() | 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 or a default limit.
  • before: Used for cursor-based pagination. Must be used with last or a default limit.
  • decoded_cursor: Used internally to hold on to the decoded cursor between validation and query execution. Value is discarded when meta is built.
  • limit, offset: Used for offset-based pagination.
  • first: Used for cursor-based pagination. Can be used alone to begin pagination from the start or in conjunction with after.
  • last: Used for cursor-based pagination.
  • page, page_size: Used for offset-based pagination as an alternative to offset and limit.
  • 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. Each Filter.t() represents a filter operation on a specific field.

Note: Pagination fields are mutually exclusive.

Query Functions

Link to this function

aliases(flop, module)

View Source (since 0.18.0)
@spec aliases(t(), module()) :: [atom()]

Returns the names of the alias fields that are required for the order clause of the given Flop.

The second argument is the schema module that derives Flop.Schema.

For example, your schema module might define an alias field called :pet_count.

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

If you pass a Flop that orders by the :pet_count field, the returned list will include the :pet_count alias.

iex> aliases(%Flop{order_by: [:name, :pet_count]}, MyApp.Owner)
[:pet_count]

If on the other hand only normal fields are used in the order_by parameter, an empty list will be returned.

iex> aliases(%Flop{order_by: [:name]}, MyApp.Owner)
[]

You can use this to dynamically build the select clause needed for the query.

For more information about alias fields, refer to the module documentation of Flop.Schema.

Link to this function

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

View Source (since 0.6.0)
@spec all(Ecto.Queryable.t(), t(), [option()]) :: [any()]

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

iex> Flop.all(MyApp.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(MyApp.Pet, %Flop{}, for: MyApp.Pet)
[]

Note that when using cursor-based pagination, the applied limit will be first + 1 or last + 1. The extra record is removed by Flop.run/3, but not by this function.

Also note that you will need to pass the for option in order for Flop to be able to find your join, compound, alias and custom field configuration.

This function does not validate or apply default parameters to the given Flop struct. Be sure to validate any user-generated parameters with validate/2 or validate!/2 before passing them to this function.

Link to this function

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

View Source (since 0.6.0)
@spec count(Ecto.Queryable.t(), t(), [option()]) :: non_neg_integer()

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

The pagination and ordering option are disregarded.

iex> Flop.count(MyApp.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(MyApp.Pet, %Flop{})
0

You can override the default query by passing the :count_query option. This doesn't make a lot of sense when you use count/3 directly, but allows you to optimize the count query when you use one of the run/3, validate_and_run/3 and validate_and_run!/3 functions.

query = join(Pet, :left, [p], o in assoc(p, :owner))
count_query = Pet
count(query, %Flop{}, count_query: count_query, for: Pet)

The filter parameters of the given Flop are applied to the custom count query.

If for some reason you already have the count, you can pass it as the :count option.

count(query, %Flop{}, count: 42, for: Pet)

If you pass both the :count and the :count_query options, the :count option will take precedence.

This function does not validate or apply default parameters to the given Flop struct. Be sure to validate any user-generated parameters with validate/2 or validate!/2 before passing them to this function.

Note that you will need to pass the for option in order for Flop to be able to find your join, compound, alias and custom field configuration.

Link to this function

filter(q, flop, opt \\ [])

View Source
@spec filter(Ecto.Queryable.t(), t(), [option()]) :: Ecto.Queryable.t()

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

Used by Flop.query/2.

This function does not validate or apply default parameters to the given Flop struct. Be sure to validate any user-generated parameters with validate/2 or validate!/2 before passing them to this function.

Note that you will need to pass the for option in order for Flop to be able to find your join, compound, alias and custom field configuration.

Link to this function

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

View Source (since 0.6.0)
@spec meta(Ecto.Queryable.t() | [any()], t(), [option()]) :: Flop.Meta.t()

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

iex> Flop.meta(MyApp.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,
  opts: [repo: Flop.Repo],
  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.

Unless cursor-based pagination is used, this function will run a query to figure get the total count of matching records.

This function does not validate or apply default parameters to the given Flop struct. Be sure to validate any user-generated parameters with validate/2 or validate!/2 before passing them to this function.

Link to this function

named_bindings(flop, module, opts \\ [])

View Source (since 0.16.0)
@spec named_bindings(t(), module(), keyword()) :: [atom()]

Returns the names of the bindings that are required for the filters and order clauses of the given Flop.

The second argument is the schema module that derives Flop.Schema.

For example, your schema module might define a join field called :owner_age.

@derive {
  Flop.Schema,
  filterable: [:name, :owner_age],
  sortable: [:name, :owner_age],
  adapter_opts: [
    join_fields: [owner_age: {:owner, :age}]
  ]
}

If you pass a Flop with a filter on the :owner_age field, the returned list will include the :owner binding.

iex> named_bindings(
...>   %Flop{
...>     filters: [%Flop.Filter{field: :owner_age, op: :==, value: 5}]
...>   },
...>   MyApp.Pet
...> )
[:owner]

If on the other hand only normal fields or compound fields are used in the filter and order options, or if the filter values are nil, an empty list will be returned.

iex> named_bindings(
...>   %Flop{
...>     filters: [
...>       %Flop.Filter{field: :name, op: :==, value: "George"},
...>       %Flop.Filter{field: :owner_age, op: :==, value: nil}
...>     ]
...>   },
...>   MyApp.Pet
...> )
[]

If a join field is part of a compound field, it will be returned.

iex> named_bindings(
...>   %Flop{
...>     filters: [
...>       %Flop.Filter{field: :pet_and_owner_name, op: :==, value: "Mae"}
...>     ]
...>   },
...>   MyApp.Pet
...> )
[:owner]

For custom fields, you can set the :bindings option when you derive the Flop.Schema protocol.

You can use this to dynamically build the join clauses needed for the query. See also Flop.with_named_bindings/4.

For more information about join fields, refer to the module documentation of Flop.Schema.

Options

  • :order - If false, only bindings needed for filtering are included. Defaults to true.
Link to this function

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

View Source
@spec order_by(Ecto.Queryable.t(), t(), [option()]) :: 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.

This function does not validate or apply default parameters to the given Flop struct. Be sure to validate any user-generated parameters with validate/2 or validate!/2 before passing them to this function.

Note that you will need to pass the for option in order for Flop to be able to find your join, compound, alias and custom field configuration.

Link to this function

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

View Source
@spec paginate(Ecto.Queryable.t(), t(), [option()]) :: 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.

This function does not validate or apply default parameters to the given Flop struct. Be sure to validate any user-generated parameters with validate/2 or validate!/2 before passing them to this function.

Note that you will need to pass the for option in order for Flop to be able to find your join, compound, alias and custom field configuration.

Link to this function

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

View Source
@spec query(Ecto.Queryable.t(), t(), [option()]) :: Ecto.Queryable.t()

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.

This function does not validate or apply default parameters to the given Flop struct. Be sure to validate any user-generated parameters with validate/2 or validate!/2 before passing them to this function.

Examples

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

Or enhance an already defined query:

iex> require Ecto.Query
iex> flop = %Flop{limit: 10}
iex> q = Ecto.Query.where(MyApp.Pet, species: "dog")
iex> Flop.query(q, flop, for: MyApp.Pet)
#Ecto.Query<from p0 in MyApp.Pet, where: p0.species == "dog", limit: ^10>

Note that when using cursor-based pagination, the applied limit will be first + 1 or last + 1. The extra record is removed by Flop.run/3.

Also note that you will need to pass the for option in order for Flop to be able to find your join, compound, alias and custom field configuration.

Link to this function

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

View Source (since 0.6.0)
@spec run(Ecto.Queryable.t(), t(), [option()]) :: {[any()], Flop.Meta.t()}

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

This function does not validate or apply default parameters to 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.

Note that you will need to pass the for option to both to validate/2, validate!/2 and run/3. Otherwise, Flop would not know how to find your field configuration (join fields, alias fields, custom fields, compound fields).

iex> opts = [for: MyApp.Pet]
iex> flop = Flop.validate!(%{}, opts)
iex> {data, meta} = Flop.run(MyApp.Pet, flop, opts)
iex> data == []
true
iex> match?(%Flop.Meta{}, meta)
true

See the documentation for Flop.validate_and_run/3 for supported options.

Link to this function

validate(flop_or_map, opts \\ [])

View Source
@spec validate(t() | map(), [option()]) :: {:ok, t()} | {:error, Flop.Meta.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, %Flop.Meta{} = meta} = Flop.validate(flop)
iex> meta.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, %Flop.Meta{} = meta} = Flop.validate(params)
iex> meta.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, %Flop.Meta{} = meta} = Flop.validate(params, for: MyApp.Pet)
iex> [order_by: [{msg, [_, {_, enum}]}]] = meta.errors
iex> msg
"has an invalid entry"
iex> enum
[:name, :age, :owner_name, :owner_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_or_map, opts \\ [])

View Source (since 0.5.0)
@spec validate!(t() | map(), [option()]) :: t()

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

Link to this function

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

View Source (since 0.6.0)
@spec validate_and_run(Ecto.Queryable.t(), map() | t(), [option()]) ::
  {:ok, {[any()], Flop.Meta.t()}} | {:error, Flop.Meta.t()}

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

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

Options

  • for: Passed to Flop.validate/2. Also used to look up the join, alias, compound and custom field configuration.
  • repo: The Ecto.Repo module. Required if no default repo is configured.
  • 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.Cursor.get_cursor_from_node/2.
  • count_query: Lets you override the base query for counting, e.g. if you don't want to include unnecessary joins. The filter parameters are applied to the given query. See also Flop.count/3.
Link to this function

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

View Source (since 0.6.0)
@spec validate_and_run!(Ecto.Queryable.t(), map() | t(), [option()]) ::
  {[any()], Flop.Meta.t()}

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

Link to this function

with_named_bindings(query, flop, fun, opts)

View Source (since 0.19.0)
@spec with_named_bindings(
  Ecto.Queryable.t(),
  t(),
  (Ecto.Queryable.t(), atom() -> Ecto.Queryable.t()),
  keyword()
) :: Ecto.Queryable.t()

Applies a callback function to a query for all named bindings that are necessary for the given Flop parameters.

The callback function must accept a queryable and the name of the binding and return a query that includes the given binding. This is the same as Ecto.Query.with_named_binding/3, except that the callback function must take the second argument.

Options

  • :for (required) - The schema module that derives Flop.Schema.
  • :order - If false, only bindings needed for filtering are included. Defaults to true.

Example

def list_pets(params) do
  opts = [for: Pet]

  with {:ok, flop} <- Flop.validate(params, opts) do
    Pet
    |> Flop.with_named_bindings(flop, &join_pet_assocs/2, opts)
    |> Flop.run(flop, opts)
  end
end

defp join_pet_assocs(query, :owner) do
  join(query, [p], o in assoc(p, :owner), as: :owner)
end

defp join_pet_assocs(query, :toys) do
  join(query, [p], t in assoc(p, :toys), as: :toys)
end

Since the callback function has the same arguments and return value as the one passed to Ecto.Query.with_named_binding/3, you can reuse the function to add any other bindings you may need for the query besides the ones for Flop filters.

This also means you can use Ecto.Query.with_named_binding/3 to recursively add bindings in case you need intermediate joins to get to a nested association.

def list_owners(params) do
  opts = [for: Owner]

  with {:ok, flop} <- Flop.validate(params, opts) do
    Pet
    |> Flop.with_named_bindings(flop, &join_owner_assocs/2, opts)
    |> Flop.run(flop, opts)
  end
end

defp join_owner_assocs(query, :pets) do
  join(query, [o], p in assoc(o, :pets), as: :pets)
end

defp join_owner_assocs(query, :toys) do
  query
  |> Ecto.Query.with_named_binding(:pets, &join_owner_assocs/2)
  |> join(query, [pets: p], t in assoc(p, :toys), as: :toys)
end

Parameter Manipulation

Link to this function

current_order(flop, field)

View Source (since 0.15.0)
@spec current_order(t(), atom()) :: order_direction() | nil

Returns the current order direction for the given field.

Examples

iex> flop = %Flop{order_by: [:name, :age], order_directions: [:desc]}
iex> current_order(flop, :name)
:desc
iex> current_order(flop, :age)
:asc
iex> current_order(flop, :species)
nil
Link to this function

map_to_filter_params(map, opts \\ [])

View Source (since 0.14.0)
@spec map_to_filter_params(
  map(),
  keyword()
) :: [map()]

Converts a map of filter conditions into a list of Flop filter params.

The default operator is :==. nil values are excluded from the result.

iex> params = map_to_filter_params(
...>   %{name: "George", age: 8, species: nil}
...> )
iex> Enum.sort(params)
[
  %{field: :age, op: :==, value: 8},
  %{field: :name, op: :==, value: "George"}
]

iex> params = map_to_filter_params(
...>   %{"name" => "George", "age" => 8, "cat" => true}
...>  )
iex> Enum.sort(params)
[
  %{"field" => "age", "op" => :==, "value" => 8},
  %{"field" => "cat", "op" => :==, "value" => true},
  %{"field" => "name", "op" => :==, "value" => "George"}
]

You can optionally pass a mapping from field names to operators as a map with atom keys.

iex> params = map_to_filter_params(
...>   %{name: "George", age: 8, species: nil},
...>   operators: %{name: :ilike_and}
...> )
iex> Enum.sort(params)
[
  %{field: :age, op: :==, value: 8},
  %{field: :name, op: :ilike_and, value: "George"}
]

iex> map_to_filter_params(
...>   %{"name" => "George", "age" => 8, "cat" => true},
...>   operators: %{name: :ilike_and, age: :<=}
...> )
[
  %{"field" => "age", "op" => :<=, "value" => 8},
  %{"field" => "cat", "op" => :==, "value" => true},
  %{"field" => "name", "op" => :ilike_and, "value" => "George"}
]

You can also pass a map to rename fields.

iex> params = map_to_filter_params(
...>   %{s: "George", age: 8, species: nil},
...>   rename: %{s: :name}
...> )
iex> Enum.sort(params)
[
  %{field: :age, op: :==, value: 8},
  %{field: :name, op: :==, value: "George"}
]

iex> map_to_filter_params(
...>   %{"s" => "George", "cat" => true},
...>   rename: %{s: :name, cat: :dog}
...> )
[
  %{"field" => "dog", "op" => :==, "value" => true},
  %{"field" => "name", "op" => :==, "value" => "George"}
]

If both a rename option and an operator are set for a field, the operator option needs to use the new field name.

iex> map_to_filter_params(
...>   %{n: "George"},
...>   rename: %{n: :name},
...>   operators: %{name: :ilike_or}
...> )
[%{field: :name, op: :ilike_or, value: "George"}]

See also Flop.Filter.new/2.

Link to this function

nest_filters(args, fields, opts \\ [])

View Source (since 0.15.0)
@spec nest_filters(map(), [atom() | String.t()], keyword()) :: map()

Converts key/value filter parameters at the root of a map, converts them into a list of filter parameter maps and nests them under the :filters key.

This is useful in cases where you get some or all filter parameters as key/value pairs instead of a full map with operators, for example when you expose certain filters with fixed operators on an API, or if you want to reflect some or all filters in the URL as path parameters or simple query parameters (e.g. /posts/{tag} or /posts?s=searchterm).

The given map should have either string keys or atom keys. Passing a map with mixed keys will lead to unexpected results and will cause an Ecto error when the return value is passed to one of the validation functions.

The second argument is a list of fields as atoms.

The opts argument is passed to map_to_filter_params/2.

The function returns a map that eventually needs to be passed to one of the Flop validation functions (any Flop.validate* function) before it can be used to make a query.

Examples

Map with atom keys

iex> nest_filters(%{name: "Peter", page_size: 10}, [:name])
%{filters: [%{field: :name, op: :==, value: "Peter"}], page_size: 10}

Map with string keys

iex> nest_filters(%{"name" => "Peter"}, [:name])
%{"filters" => [%{"field" => "name", "op" => :==, "value" =>  "Peter"}]}

Specifying operators

iex> nest_filters(%{name: "Peter"}, [:name], operators: %{name: :!=})
%{filters: [%{field: :name, op: :!=, value: "Peter"}]}

Renaming fields

iex> nest_filters(%{nombre: "Peter", page_size: 10}, [:nombre],
...>   rename: %{nombre: :name}
...> )
%{filters: [%{field: :name, op: :==, value: "Peter"}], page_size: 10}

iex> nest_filters(%{"nombre" => "Peter"}, [:nombre],
...>   rename: %{nombre: :name}
...> )
%{"filters" => [%{"field" => "name", "op" => :==, "value" =>  "Peter"}]}

iex> nest_filters(%{"nombre" => "Peter"}, [:nombre],
...>   rename: %{nombre: :name},
...>   operators: %{name: :like}
...> )
%{"filters" => [%{"field" => "name", "op" => :like, "value" =>  "Peter"}]}

If the map already has a filters key, the extracted filters are added to the existing filters.

iex> nest_filters(%{name: "Peter", filters: [%{field: "age", op: ">", value: 25}]}, [:name])
%{filters: [%{field: "age", op: ">", value: 25}, %{field: :name, op: :==, value: "Peter"}]}

iex> nest_filters(%{"name" => "Peter", "filters" => [%{"field" => "age", "op" => ">", "value" => 25}]}, [:name])
%{"filters" => [%{"field" => "age", "op" => ">", "value" => 25}, %{"field" => "name", "op" => :==, "value" => "Peter"}]}

If the existing filters are formatted as a map with integer indexes as keys as produced by a form, the map is converted to a list first.

iex> nest_filters(%{name: "Peter", filters: %{"0" => %{field: "age", op: ">", value: 25}}}, [:name])
%{filters: [%{field: "age", op: ">", value: 25}, %{field: :name, op: :==, value: "Peter"}]}

iex> nest_filters(%{"name" => "Peter", "filters" => %{"0" => %{"field" => "age", "op" => ">", "value" => 25}}}, [:name])
%{"filters" => [%{"field" => "age", "op" => ">", "value" => 25}, %{"field" => "name", "op" => :==, "value" => "Peter"}]}
Link to this function

push_order(flop, field, opts \\ [])

View Source (since 0.10.0)
@spec push_order(t(), atom() | String.t(), keyword()) :: t()

Updates the order_by and order_directions values of a Flop struct.

  • If the field is not in the current order_by value, it will be prepended to the list. By default, the order direction for the field will be set to :asc.
  • If the field is already at the front of the order_by list, the order direction will be reversed.
  • If the field is already in the list, but not at the front, it will be moved to the front and the order direction will be set to :asc (or the custom asc direction supplied in the :directions option).
  • If the :directions option——a 2-element tuple——is passed, the first and second elements will be used as custom sort declarations for ascending and descending, respectively.

Examples

iex> flop = push_order(%Flop{}, :name)
iex> flop.order_by
[:name]
iex> flop.order_directions
[:asc]
iex> flop = push_order(flop, :age)
iex> flop.order_by
[:age, :name]
iex> flop.order_directions
[:asc, :asc]
iex> flop = push_order(flop, :age)
iex> flop.order_by
[:age, :name]
iex> flop.order_directions
[:desc, :asc]
iex> flop = push_order(flop, :species)
iex> flop.order_by
[:species, :age, :name]
iex> flop.order_directions
[:asc, :desc, :asc]
iex> flop = push_order(flop, :age)
iex> flop.order_by
[:age, :species, :name]
iex> flop.order_directions
[:asc, :asc, :asc]

By default, the function toggles between :asc and :desc. You can override this with the :directions option.

iex> directions = {:asc_nulls_last, :desc_nulls_last}
iex> flop = push_order(%Flop{}, :ttfb, directions: directions)
iex> flop.order_by
[:ttfb]
iex> flop.order_directions
[:asc_nulls_last]
iex> flop = push_order(flop, :ttfb, directions: directions)
iex> flop.order_by
[:ttfb]
iex> flop.order_directions
[:desc_nulls_last]

If a string is passed as the second argument, it will be converted to an atom using String.to_existing_atom/1. If the atom does not exist, the Flop struct will be returned unchanged.

iex> flop = push_order(%Flop{}, "name")
iex> flop.order_by
[:name]
iex> flop = push_order(%Flop{}, "this_atom_does_not_exist")
iex> flop.order_by
nil

Since the pagination cursor depends on the sort order, the :before and :after parameters are reset.

iex> push_order(%Flop{order_by: [:id], after: "ABC"}, :name)
%Flop{order_by: [:name, :id], order_directions: [:asc], after: nil}
iex> push_order(%Flop{order_by: [:id], before: "DEF"}, :name)
%Flop{order_by: [:name, :id], order_directions: [:asc], before: nil}
Link to this function

reset_cursors(flop)

View Source (since 0.15.0)
@spec reset_cursors(t()) :: t()

Removes the after and before cursors from a Flop struct.

Example

iex> reset_cursors(%Flop{after: "A"})
%Flop{}

iex> reset_cursors(%Flop{before: "A"})
%Flop{}
Link to this function

reset_filters(flop)

View Source (since 0.15.0)
@spec reset_filters(t()) :: t()

Removes all filters from a Flop struct.

Example

iex> reset_filters(%Flop{filters: [
...>   %Flop.Filter{field: :name, value: "Jim"}
...> ]})
%Flop{filters: []}
Link to this function

reset_order(flop)

View Source (since 0.15.0)
@spec reset_order(t()) :: t()

Removes the order parameters from a Flop struct.

Example

iex> reset_order(%Flop{order_by: [:name], order_directions: [:asc]})
%Flop{order_by: nil, order_directions: nil}
Link to this function

set_cursor(meta, atom)

View Source (since 0.15.0)
@spec set_cursor(Flop.Meta.t(), :previous | :next) :: t()

Takes a Flop.Meta struct and returns a Flop struct with updated cursor pagination params for going to either the previous or the next page.

See to_previous_cursor/1 and to_next_cursor/1 for details.

Examples

iex> set_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{first: 5, after: "a"},
...>     has_previous_page?: true, start_cursor: "b"
...>   },
...>   :previous
...> )
%Flop{last: 5, before: "b"}

iex> set_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{first: 5, after: "a"},
...>     has_next_page?: true, end_cursor: "b"
...>   },
...>   :next
...> )
%Flop{first: 5, after: "b"}
Link to this function

set_offset(flop, offset)

View Source (since 0.15.0)
@spec set_offset(t(), non_neg_integer() | binary()) :: t()

Sets the offset value of a Flop struct while also removing/converting pagination parameters for other pagination types.

iex> set_offset(%Flop{limit: 10, offset: 10}, 20)
%Flop{offset: 20, limit: 10}

iex> set_offset(%Flop{page: 5, page_size: 10}, 20)
%Flop{limit: 10, offset: 20, page: nil, page_size: nil}

iex> set_offset(%Flop{limit: 10, offset: 10}, "20")
%Flop{offset: 20, limit: 10}

The offset will not be allowed to go below 0.

iex> set_offset(%Flop{}, -5)
%Flop{offset: 0}
Link to this function

set_page(flop, page)

View Source (since 0.12.0)
@spec set_page(t(), pos_integer() | binary()) :: t()

Sets the page value of a Flop struct while also removing/converting pagination parameters for other pagination types.

iex> set_page(%Flop{page: 2, page_size: 10}, 6)
%Flop{page: 6, page_size: 10}

iex> set_page(%Flop{limit: 10, offset: 20}, 8)
%Flop{limit: nil, offset: nil, page: 8, page_size: 10}

iex> set_page(%Flop{page: 2, page_size: 10}, "6")
%Flop{page: 6, page_size: 10}

The page number will not be allowed to go below 1.

iex> set_page(%Flop{}, -5)
%Flop{page: 1}
Link to this function

to_next_cursor(meta)

View Source (since 0.15.0)
@spec to_next_cursor(Flop.Meta.t()) :: t()

Takes a Flop.Meta struct and returns a Flop struct with updated cursor pagination params for going to the next page.

Examples

iex> to_next_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{first: 5, after: "a"},
...>     has_next_page?: true, end_cursor: "b"
...>   }
...> )
%Flop{first: 5, after: "b"}

iex> to_next_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{last: 5, before: "b"},
...>     has_next_page?: true, end_cursor: "a"
...>   }
...> )
%Flop{first: 5, after: "a"}

If there is no next page, the Flop struct is returned unchanged.

iex> to_next_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{first: 5, after: "a"},
...>     has_next_page?: false, start_cursor: "b"
...>   }
...> )
%Flop{first: 5, after: "a"}

It can happen that has_next_page? is true, but no end cursor is set, for example if the user is on the second page and switches to the previous page right after the only item on the first page is deleted. Since the result set is empty and there is no end cursor in this case, this function sets the new after parameter to nil. By doing this, the user is sent to the first page without skipping any items.

iex> to_next_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{first: 5, after: "a"},
...>     has_next_page?: true, end_cursor: nil
...>   }
...> )
%Flop{first: 5, after: nil}

iex> to_next_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{last: 5, before: "b"},
...>     has_next_page?: true, end_cursor: nil
...>   }
...> )
%Flop{first: 5, after: nil}
Link to this function

to_next_offset(flop, total_count \\ nil)

View Source (since 0.15.0)
@spec to_next_offset(t(), non_neg_integer() | nil) :: t()

Sets the offset of a Flop struct to the next page depending on the limit.

If the total count is given as the second argument, the offset will not be increased if the last page has already been reached. You can get the total count from the Flop.Meta struct. If the Flop has an offset beyond the total count, the offset will be set to the last page.

Examples

iex> to_next_offset(%Flop{offset: 10, limit: 5})
%Flop{offset: 15, limit: 5}

iex> to_next_offset(%Flop{offset: 15, limit: 5}, 21)
%Flop{offset: 20, limit: 5}

iex> to_next_offset(%Flop{offset: 15, limit: 5}, 20)
%Flop{offset: 15, limit: 5}

iex> to_next_offset(%Flop{offset: 28, limit: 5}, 22)
%Flop{offset: 20, limit: 5}

iex> to_next_offset(%Flop{offset: -5, limit: 20})
%Flop{offset: 0, limit: 20}
Link to this function

to_next_page(flop, total_pages \\ nil)

View Source (since 0.15.0)
@spec to_next_page(t(), non_neg_integer() | nil) :: t()

Sets the page of a Flop struct to the next page.

If the total number of pages is given as the second argument, the page number will not be increased if the last page has already been reached. You can get the total number of pages from the Flop.Meta struct.

Examples

iex> to_next_page(%Flop{page: 5})
%Flop{page: 6}

iex> to_next_page(%Flop{page: 5}, 6)
%Flop{page: 6}

iex> to_next_page(%Flop{page: 6}, 6)
%Flop{page: 6}

iex> to_next_page(%Flop{page: 7}, 6)
%Flop{page: 6}

iex> to_next_page(%Flop{page: -5})
%Flop{page: 1}
Link to this function

to_previous_cursor(meta)

View Source (since 0.15.0)
@spec to_previous_cursor(Flop.Meta.t()) :: t()

Takes a Flop.Meta struct and returns a Flop struct with updated cursor pagination params for going to the previous page.

Examples

iex> to_previous_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{first: 5, after: "a"},
...>     has_previous_page?: true, start_cursor: "b"
...>   }
...> )
%Flop{last: 5, before: "b"}

iex> to_previous_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{last: 5, before: "b"},
...>     has_previous_page?: true, start_cursor: "a"
...>   }
...> )
%Flop{last: 5, before: "a"}

If there is no previous page, the Flop struct is returned unchanged.

iex> to_previous_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{first: 5, after: "b"},
...>     has_previous_page?: false, start_cursor: "a"
...>   }
...> )
%Flop{first: 5, after: "b"}

It can happen that has_previous_page? is true, but no start cursor is set, for example if the user is on the next-to-last page and switches to the next page right after the only item on the last page is deleted. Since the result set would be empty and there is no start cursor in this case, this function sets the new before parameter to nil. By doing this, the user is sent to the actual last page without skipping any items.

iex> to_previous_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{first: 5, after: "a"},
...>     has_previous_page?: true, start_cursor: nil
...>   }
...> )
%Flop{last: 5, before: nil}

iex> to_previous_cursor(
...>   %Flop.Meta{
...>     flop: %Flop{last: 5, before: "b"},
...>     has_previous_page?: true, start_cursor: nil
...>   }
...> )
%Flop{last: 5, before: nil}
Link to this function

to_previous_offset(flop)

View Source (since 0.15.0)
@spec to_previous_offset(t()) :: t()

Sets the offset of a Flop struct to the page depending on the limit.

Examples

iex> to_previous_offset(%Flop{offset: 20, limit: 10})
%Flop{offset: 10, limit: 10}

iex> to_previous_offset(%Flop{offset: 5, limit: 10})
%Flop{offset: 0, limit: 10}

iex> to_previous_offset(%Flop{offset: 0, limit: 10})
%Flop{offset: 0, limit: 10}

iex> to_previous_offset(%Flop{offset: -2, limit: 10})
%Flop{offset: 0, limit: 10}
Link to this function

to_previous_page(flop)

View Source (since 0.15.0)
@spec to_previous_page(t()) :: t()

Sets the page of a Flop struct to the previous page, but not less than 1.

Examples

iex> to_previous_page(%Flop{page: 5})
%Flop{page: 4}

iex> to_previous_page(%Flop{page: 1})
%Flop{page: 1}

iex> to_previous_page(%Flop{page: -2})
%Flop{page: 1}
Link to this function

unnest_filters(flop, fields, opts \\ [])

View Source (since 0.20.0)
@spec unnest_filters(t(), [atom()], keyword()) :: map()

Takes a Flop, converts it to a map and unnests the filters for the given fields.

This is the reverse operation of nest_filters/3, with the caveat that the result of nest_filters/3 needs to be validated to convert it to a Flop struct before it can be passed back to unnest_filters/3.

The function returns a map with nil values removed.

Examples

iex> unnest_filters(
...>   %Flop{
...>     filters: [%Flop.Filter{field: :name, op: :==, value: "Peter"}],
...>     page_size: 10
...>   },
...>   [:name]
...> )
%{name: "Peter", page_size: 10}

iex> unnest_filters(
...>   %Flop{
...>     filters: [%Flop.Filter{field: :name, op: :==, value: nil}],
...>     page_size: 10
...>   },
...>   [:name]
...> )
%{page_size: 10}

To rename fields, you can pass a map, where the keys are the field names in the unnested map and the values are the field names in the Flop.Filter struct. You can pass the exact same rename map to nest_filters/3 and unnest_filters/3 to revert the nesting/unnesting.

iex> unnest_filters(
...>   %Flop{
...>     filters: [%Flop.Filter{field: :name, op: :==, value: "Peter"}],
...>     page_size: 10
...>   },
...>   [:name],
...>   rename: %{nombre: :name}
...> )
%{nombre: "Peter", page_size: 10}

Miscellaneous

Link to this function

get_option(key, opts, default \\ nil)

View Source (since 0.11.0)
@spec get_option(atom(), [option()], any()) :: any()

Returns the option with the given key.

The look-up order is:

  1. the keyword list passed as the second argument
  2. the schema module that derives Flop.Schema, if the passed list includes the :for option
  3. the backend module with use Flop
  4. the application environment
  5. the default passed as the last argument

Functions

Options specific to the Ecto adapter.