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
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).
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.
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.)
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 ^^ What I’m trying to say is: This pair of libraries deserves a lot more attention.
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 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.
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.
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.