woylie / flop Goto Github PK
View Code? Open in Web Editor NEWFiltering, ordering and pagination for Ecto
License: MIT License
Filtering, ordering and pagination for Ecto
License: MIT License
total_count
querytotal_count
query by default if using cursor-based paginationHey @woylie ๐
This is an amazing start for a library. I'm super impressed with the code and the general direction this library takes. I know it's still in early development, but I would bet it's here to stay. ๐
I'm currently evaluating using Flop in a new project instead of our old messy custom filtering code. It looks super promising so far, but one critical feature I'm missing is support for Ecto's :prefix
option to set the default query prefix. As far as I can tell, it shouldn't be too difficult to add support for it, and I hope you can see its usefulness. I already started a tiny patch, will submit the PR in a minute.
Thanks for the awesome work on this library!
Best,
malte
Is your feature request related to a problem? Please describe.
I have a datetime field (e.g. deactivated_at
) which can be NULL. I'd like to filter for all records which have a NULL value there. Or the opposite: have a value set there.
Describe the solution you'd like
Maybe just another operator which supports is_nil
.
Or is there another good way to do it?
validate configured fields etc.
There should be an option to silently ignore invalid parameters instead of returning changeset errors.
To support filters that work across multiple fields, Flop.Schema
should allow to define compound fields.
defmodule MyApp.User do
use Ecto.Schema
@derive {Flop.Schema, compound_fields: [name: [:family_name, :given_name]}
schema "users" do
field :family_name, :string
field :given_name, :string
field :email, :string
end
end
By passing a filter on the field :name
, Flop would add a WHERE
clause on both fields. When using simple filters, Flop would default to the :ilike_or
operator.
The default operator could be set with the :default_operators
option:
@derive {Flop.Schema,
compound_fields: [name: [:family_name, :given_name],
default_operators: [name: :ilike_and]}
The clauses on each field should be connected with an OR
, independent of the operator.
# search term: 'margo martindale'
# operator: :ilike_or
WHERE family_name ilike '%margo%'
OR given_name ilike '%margo%'
OR family_name ilike '%martindale%'
OR given_name ilike '%martindale%'
# search term: 'margo martindale'
# operator: :ilike_and
WHERE (family_name ilike '%margo%' OR given_name ilike '%martindale%')
AND (family_name ilike '%margo%' OR given_name ilike '%martindale%')
Support passing filters as a keyword list or map without the need to specify an operator.
params = %{filters: %{title: "Psycho"}}
Flop.validate_and_run(Movie, params, for: Movie)
The default operator would be :==
, but could be customized with the default_operators
option of Flop.Schema.
@derive {Flop.Schema,
filterable: [:title],
default_operators: [title: :ilike_and]}
:==
as defaultFlop.Schema
nest_filters/3
Hi there ๐
Describe the bug
I noticed that once I try to use the new compound fields in an Ecto.Schema, that imports Ecto.Changeset
I get the following error.
** (CompileError) .../my_schema.ex:22: imported Ecto.Changeset.get_field/2 conflicts with local function
(elixir 1.12.2) src/elixir_locals.erl:94: :elixir_locals."-ensure_no_import_conflict/3-lc$^0/1-0-"/2
(elixir 1.12.2) src/elixir_locals.erl:95: anonymous fn/3 in :elixir_locals.ensure_no_import_conflict/3
(flop 0.13.1) .../my_schema.ex:22: Flop.Schema.Any.__deriving__/3
To Reproduce
Essentially just import Ecto.Changeset
and use the @derive
directive with the new compound fields like so:
use Ecto.Schema
import Ecto.Changeset # <-- this one
@derive {Flop.Schema,
filterable: [:full_name],
sortable: [:full_name],
compound_fields: [full_name: [:first_name, :last_name]]}
Expected behavior
It does not clash with Ecto.
Versions:
Additional context
One can workaround this by importing / reimporting Ecto.Changeset
without get_field
(even works when a macro previously imported all of Ecto.Changeset
).
import Ecto.Changeset, except: [get_field: 2]
Thank you for creating & maintaining flop btw ๐ . It helps us a ton and we are soon taking it to production ๐ข
Cheers
Andi
Option to set default pagination type, so that default limit does not automatically translate to a limit
parameter.
Hi Woylie I'm working on writing a wrapper function for sortable columns as we had spoke about before but running into a snag (I'm positive it's me here) when trying to form a querystring that includes order_by details that are placed into an array.
Simply approaching this from determining what the querystring should look like I have the following:
http://localhost.local:4000/fruits?page=2&order_by=[name]
Although when it hits the server the params are converted to:
%{"order_by" => "[name]", "page" => "2"}
Where "[name]"
is not being accepted by Flops validation. I've tried different combinations of the order_by
value but nothing seems to work.
What do you think? How should we be passing these array based values through from outside?
Cheers and thanks!
PS Any additional thoughts or time coming up to put into our other wishlist items?
Hi ๐
Since #138 we see a dialyzer error originating in Flop.Schema.apply_order_by/3
. We have a compound field based on a join field.
The function call will not succeed.
Flop.Schema.apply_order_by(_ :: any(), _ :: any(), {_, _})
breaks the contract
(any(), Ecto.Query.t(), :elixir.keyword()) :: Ecto.Query.t()
Best,
malte
Is your feature request related to a problem? Please describe.
Now that we have compound and join fields it would be awesome to use them in conjunction, but I guess from what I can judge this is not possible yet.
@derive {Flop.Schema,
filterable: [:names],
join_fields: [pet_name: {:pets, :name}],
compound_fields: [:names [:name, :pet_name]]}
At the moment this leads (probably quite expectedly) to an error.
no function clause matching in Flop.Builder.get_field_type/2
Describe the solution you'd like
Having some way of specifying these would be great.
Describe alternatives you've considered
@derive {Flop.Schema,
filterable: [:names],
compound_fields: [:names [:name, {:pets, :name}]]}
Could maybe also work as a way of specifying these ๐คท , but it also obviously doesn't work yet. Maybe easier to implement though? Not sure :D.
Or did I overlook something?
Thanks in advance โ๏ธ
Cheers
Andi
Is your feature request related to a problem? Please describe.
When we have a number of complex queries that we use for pagination over a large data set through a pretty standard web ui (not LiveView in this case, but don't think there's a difference)
As the count query is just a wrapper around the given query (or in this case a replacement of the select columns and a removal of the order by) the count and planner will still likely consider any joins that are not important to the number of rows we're considering.
Describe the solution you'd like
Two things:
The ability to provide a query to use for counting which will override the automatic wrapping/substitution of the main pagination query. In this case I will provide a new query without all the additional joins that are not needed. Heck I might also be ok that the custom count query doesn't return the exact number of records that I'm expecting it to, as long as it's a decent approximation - it's all on me at that point.
In some cases as the dataset may grow even larger I would like to be able to disable the count query altogether. I'm already using first/previous/next/last links without all the in-between pages which would depend on knowing the amount of records overall. If the UX is done right, then typically a user is going to be provided a decent search box and only click through a couple pages of records if they can't find what they're looking for and then will go back and refine their search.
Describe alternatives you've considered
Lot's of playing around with indexes, some of what are useful, some not. It's really about being able to provide an alernate custom query for count to chew on.
Additional context
Have a look at Kaminari and will_paginate from the Ruby/Rails side as they offer these types of workarounds for larger and growing datasets.
Thanks Mattias!
Flop.Schema
requires default order parameters to be set as separate keys default_order_by
and default_order_directions
, but this is converted into a default_order: %{order_by: list, order_directions: list}
. This means you need to use the latter format to override the default in the opts (which is not documented). This should be unified, so that the same format is used everywhere.
lib/schema.ex:6:no_return
Function filterable/1 has no local return.
________________________________________________________________________________
lib/schema.ex:7:no_return
Function sortable/1 has no local return.
________________________________________________________________________________
Ecto.Repo callback offer the possibility to pass on opts
when executing queries. Query API
It would be nice to be able to pass those options through Flop when a project uses options apart from the basic ones which are already supported by Flop like :prefix
and such.
defmodule MyApp.Pets do
import Ecto.Query, warn: false
alias Ecto.Changeset
alias Flop
alias MyApp.{Pet, Repo}
@spec list_pets(Flop.t()) ::
{:ok, {[Pet.t()], Flop.Meta.t}} | {:error, Changeset.t()}
def list_pets(flop \\ %Flop{}) do
Flop.validate_and_run(Pet, flop, for: Pet, query_opts: [special_flag: true])
end
end
The underlying cause for the issue is the reliance on default query behaviour (for security reasons) that can be bypassed on a per-query basis using a special_flag
.
I considered using a flag which is set directly in the process a later evaluated by the optional prepare_query callback. But feels a bit too "fire-and-forget"-ish.
Another idea was to write a extra Repo module which delegates most calls to the underlying Ecto.Repo leaving out the security feature, but this solution seems to be a bit too error prone since it would rely on internal repo functions.
Is your feature request related to a problem? Please describe.
I have an HTML table that displays information from multiple db tables. When using the sort functionality from FlopPhoenix I'd like to specify a column to sort on that is not part of the main table.
Describe the solution you'd like
I believe there were plans for this in #48 and we've raised it in other tickets since then for the need to be able to have Flop be aware of other columns from passed in schemas/models.
Describe alternatives you've considered
Nothing.
Additional context
Let me know what you think @woylie. Looks like this has been on your radar for some time now. Wondering if you had any implementation ideas since you first created the original ticket here and the time you've spent with FlopPhoenix since then? Maybe there is some support already there that I can take advantage of?
Add function that returns the named bindings required for a Flop query.
...and related functions
:==
, :!=
, :<=
, :<
, :>=
, :>
, :in
At the moment, has_previous_page?
is only set with :before
, and has_next_page?
is only set with :first
. This is allowed according to the Relay specification, but it doesn't allow us to conditionally render a previous
link.
Flop.Meta
struct with total_count, total_pages, current_page, current_offset, has_previous_page?, has_next_page?, maybe page_sizeFlop.meta/3
, which returns the meta struct for the given queryable and flopFlop.all_with_meta/3
, which both the data and the meta data, and maybe validates the flopHelper functions for working with Phoenix and Phoenix templates. Should be part of a separate library.
It might be nice to have some wrapper functions to get the results and the meta data in one function call, to wrap a query into Flop validation/query.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.