Provide a way to export/present Flop and Flop.Meta data
hgg opened this issue ยท 7 comments
Is your feature request related to a problem? Please describe.
I'm using Flop in a small project where the intended output is JSON, to be consumed by a FE app. When introducing pagination, filtering and ordering to the API, I have to introduce code on my end to deal with parsing the Flop.Meta
structure properly so that it can be returned to the FE.
Describe the solution you'd like
I think ideally the lib would provide clear ways to present information about what kind of filters, orderings and pagination are active. I believe this can easily be achieved with functions on the Meta module.
I don't know if there are any standards for something like this.
My suggestion is a function Meta.output_config(%Meta{}) :: %{filtering: map() | nil, ordering: map() | nil, pagination: map() | nil}
. With something like this, people could then take the information and adapt it to whatever their API response structure is.
I'm not sure if you would rather place this on the flop_phoenix
library.
Describe alternatives you've considered
We could also go for a more phoenix approach and create a JSON view for the Meta object. This would probably make more sense in the flop_phoenix lib. The downside I see is I think the base lib should be capable of "exporting" this information.
Another, simpler, approach could be to just add a JSON encoder to the Meta struct. If it's coupled to a specific JSON lib, that could definitely be a downside. If it's not, I don't see much the difference from the initial suggestion.
Additional context
I'm happy to work on this if you'll have my help. Been looking to doing more open-source contributions and this seems like an approachable one, if it does go forward.
I don't quite understand what you have in mind. Can you give me a concrete example for what format you want to return to the front end?
Something like
%{
filter: [%{value: "john ", op: :ilike, field: :name}],
limit: 10,
order: %{order_by: [:name], order_directions: [:asc]},
pagination: %{
current_page: 2,
has_next_page?: false,
has_previous_page?: true,
next_page: nil,
page_size: 2,
previous_page: 1,
total_pages: 2
}
}
Basically something that tells the FE exactly what happened with the query on the backend in terms of filtering, sorting and pagination so that the FE can react to it. I think the best example is pagination: if I don't tell the FE something, how does it know it can ask for more pages?
EDIT: I generated this map from information inside the %Flop.Meta{}
I get from running the query.
Well, the Meta struct has all that information, and the cast and validated Flop struct is under meta.flop
. You just want it in some other arbitrary format.
If you just wanted convert the Meta struct into JSON as it is, in the easiest case, you could convert the Meta struct, the nested Flop struct and the nested Filter structs into maps with Map.from_struct/1
and pass the result to Jason.encode/1
. I don't want Flop to depend on a JSON library, but you can also implement the Jason.Encode
protocol for these structs using defimpl
instead of using @derive
(see https://hexdocs.pm/jason/Jason.Encoder.html#module-example).
I see your point ๐๐ผ
I think what made me think this would be interesting was the fact that we have ways to use Flop out-of-the-box on HTML views, so I thought JSON views could have the same kind of support. Otherwise people using this with JSON need to figure out each and every field that are relevant for their use-case.
For instance in my case, I ended up creating a module to convert the structure. But I'm not really sure I even got all the relevant fields, and it feels weird to go get data from inside the structure like this. Almost like I'm breaking the boundary of the lib. I'll paste it here just to give you context.
defmodule MyApp.MetaJSON do
def data(%Flop.Meta{} = meta) do
%{
order: order_data(meta),
filter: filter_data(meta),
pagination: pagination_data(meta),
limit: limit_data(meta)
}
end
defp order_data(%Flop.Meta{} = meta) do
%{order_by: meta.flop.order_by, order_directions: meta.flop.order_directions}
end
defp filter_data(%Flop.Meta{} = meta) do
Enum.map(meta.flop.filters, &Map.from_struct/1)
end
defp pagination_data(%Flop.Meta{start_cursor: start_cursor} = meta)
when is_binary(start_cursor) do
Map.take(meta, [:start_cursor, :end_cursor])
end
defp pagination_data(%Flop.Meta{flop: %Flop{page: current_page}} = meta)
when is_integer(current_page) do
Map.take(meta, [
:current_page,
:has_next_page?,
:has_previous_page?,
:next_page,
:page_size,
:previous_page,
:total_pages
])
end
defp pagination_data(%Flop.Meta{flop: %Flop{offset: current_offset}} = meta)
when is_integer(current_offset) do
Map.take(meta, [
:current_offset,
:next_offset,
:previous_offset,
:total_count
])
end
defp pagination_data(_meta), do: %{}
defp limit_data(%Flop.Meta{} = meta) do
meta.flop.limit ||
meta.opts[:for]
|> struct()
|> Flop.Schema.default_limit()
end
end
And this is being called from other JSON view files.
Anyways, thanks for the quick responses. Feel free to close this ๐
Maybe we can add a to_map
function to the Meta
module. But only as a convenience to get the meta and the nested structs as maps, without any transformations (maybe nil
values can be removed).