Flop - Filtering, sorting and pagination for Ecto

Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries.

  • offset-based pagination with offset/limit or page/page_size
  • cursor-based pagination
  • sorting
  • filtering
  • parameter validation
  • configurable filterable and sortable fields
  • join fields
  • compound fields
  • query and meta data helpers
  • Relay connection formatter (edges, nodes and page info)
  • UI helpers and URL builders through Flop Phoenix.
28 Likes

I found Flip and Flop_Phoenix a few days ago on GitHub and since then I’ve waited for this thread :wink:

Will definetly use it, very nice work :slight_smile:

Is there a small demo project available? As a beginner this is the best way for me to learn how to use a library.

2 Likes

There is no demo project at the moment, but there are step-by-step instructions in the readmes. If something is unclear, let me know, so that I can improve the documentation :slight_smile:

1 Like

Nice work! Can it join+preload associations and filter using fields from associations? This seems to be what’s missing from many of the similar libs.

It can not add join expressions or preload data for you. Flop does not try to be an alternate query DSL - it just adds WHERE, ORDER BY and LIMIT clauses derived from user-generated parameters to existing Ecto queries.

However, you can define join fields, which are basically a mapping from a field alias to a field name on a named binding (see join fields). You will have to ensure that the necessary named bindings are added to the Ecto query you pass to the Flop functions. This approach means that you can filter and sort by anything that Ecto allows to join on: not only associations, but also the results of subqueries. To avoid unnecessary joins, you can retrieve a list of necessary bindings for a given parameter map before building the query (see Flop.bindings/3).

Hi, just trying out your package, thanks for making it. :grinning:

Is it possible (for me as a package user) to add a way to find out if there is more records, in cursor-based pagination?

Ideally I would like actually to know if there are records before the first and after the last entities (“page”) currently fetched.

That means running min/max postgres functions on the query, without LIMIT and sorting on it.

Any ideas are welcome, and it does not have to be super-optimized.

Thanks!

If you run a cursor-paginated query, Flop will make the query with the given limit + 1 (first, last, or default_limit). The *run functions will remove the extra row from the result set. The returned Flop.Meta struct has has_next_page? or has_previous_page? fields, where the value of one of the fields depends on the existence of the extra row, and the other one depends on the presence of a after or before parameter.

2 Likes

Thanks. Is it possible to do a range/between filtering on the same field with this library? For example records between two dates.

Yes, filters are passed as a list, you can combine as many as you want, including ones on the same field. All filters are combined with a logical AND. One thing that isn’t possible at the moment is combining multiple filters with a logical OR. (If you want to filter a single string field, there are ilike_or and like_or operators`, though.)

1 Like

Have you given any thought to including this approach for performance in a future release?

Otherwise, looks great!

2 Likes

That is already implemented! You can choose between offset-based and cursor-based pagination. See also:

Flop.Phoenix has two different UI components for offset-based and cursor-based pagination.

4 Likes

My company has early-adopted Flop & Flop.Phoenix in May 2021 and we have been using it for admin dashboards in multiple apps since, in production environments. We especially love the sweetspot position Flop.Phoenix takes between “being a logic-less table component” and a full-blown UI generator, giving us enough out-of-the-box functionality while allowing for easy customization. It’s been working like a charm and @woylie has been very generously devoting time to the issues we’ve raised and ideas we had.

Sounds a bit like an advertisement ^^ :smile: What I’m trying to say is: This pair of libraries deserves a lot more attention.

Thank you @woylie for all the work :purple_heart:

7 Likes

Flop 0.19 comes with custom fields, a new helper for dynamically adding bindings, and a set of filter manipulation functions.

Added

  • Support for custom fields. These fields allow you to run custom filter
    functions for anything that cannot be expressed with Flop filters.
  • Added Flop.with_named_bindings/4 for dynamically adding bindings needed for
    a Flop query.
  • Added fetch, get, get_all, delete, delete_first, drop, new,
    take, pop, pop_first, put and put_new functions to Flop.Filter.
  • Added Flop.Meta.with_errors/3.
  • Added ecto_type option to join fields.
  • Added not_like and not_ilike filter operators.
  • Added a cheatsheet for schema configuration.

Changed

  • Renamed Flop.bindings/3 to Flop.named_bindings/3.
  • Flop.Filter.allowed_operators/2 now tries to determine the Ecto type by
    reading the Flop field type from the schema module. This function is used
    during parameter validation, which means the validation step will be a bit
    stricter now. For join and custom fields, the Ecto type is determined via the
    new ecto_type option. If the option is not set, the function returns all
    operators as before. For compound fields, only the supported operators are
    returned.
  • Added opts field to Flop.Meta struct.
7 Likes

Flop 0.20.0

Added

  • Added Flop.unnest_filters/3 as a reverse operation of Flop.nest_filters/3
    after retrieving data from the database.
  • Added Flop.Filter.fetch_value/2, Flop.Filter.get_value/2,
    Flop.Filter.put_value/4, Flop.Filter.put_new_value/4,
    Flop.Filter.pop_value/3 and Flop.Filter.pop_first_value/3.

Changed

  • Several of the functions for manipulating lists of filters in the
    Flop.Filter module now accept lists of maps with atom keys, lists of maps
    with string keys, and indexed maps as produced by Phoenix HTML forms as
    argument.
  • The empty and not_empty operators now treat empty maps as empty values on
    map fields and empty arrays as empty values on array fields.
  • % and _ characters in filter values for the like, ilike and =~
    operators are now escaped.

Fixed

  • Fixed an issue that caused filter conditions for like_and, like_or,
    ilike_and and ilike_or to be incorrectly combined when applied to compound
    fields.
4 Likes

Very cool library!

Wondering how to make the following possible - we want to be able to filter by any field as developers. But we want to also restrict what the user can filter on. What is the best way to implement it?

def list_workflows(%{} = params, opts \\ []) do
  opts = Keyword.put_new(:for, Workflow)

  with {:ok, %Flop{} = flop} <- Flop.validate(params, opts),
       {:ok, %Flop{} = flop} <- custom_validation(flop, params, opts) do
    Flop.run(Workflow, flop, for: Workflow)
  end
end

Would custom_validation() be a good place for it? Then I can pass the list of allowed fields in opts.

1 Like

Yes, that would be one way to do it, with the minor caveat that Flop would still validate all the filters in your example, even the ones that will be removed by the custom_validation function.

Since Flop 0.20.0, the functions in Flop.Filter are quite permissive regarding the input format, so you might be able to use Flop.Filter.drop/2 on the filter parameters before passing them to the validate function. The functions in the Filter module accept the list of filters as an argument, and not the whole parameter map, though, so depending on your application, you need to be careful when you get the filter list from the parameters, since the parameters can be a map with string keys or a map with atom keys.

Maybe it would be useful if we allow overriding the filterable fields in the options in a future version.

1 Like

Thanks. I also saw another one Flop.Filter.take/2 so I can just pass allowed fields to it.

1 Like
2 Likes

A post was split to a new topic: Flop ordering

[0.20.2] - 2023-06-09

Changed

  • Added nutrition facts about use Flop and @derive Flop.Schema.
  • The minimum Elixir version is now 1.11.

Fixed

  • Fixed a deprecation warning about Logger.warn/1.
  • Fixed a deprecation warning about passing an MFA to :with in
    cast_assoc/cast_embed introduced in Ecto 3.10.2.
4 Likes