top_param should return nested filter_values
Closed this issue · 4 comments
Hello,
When using top_param
I noticed that the filter_values does not retain the same level of nesting. Example
@options top_param: :q, cast: &String.downcase/1
filter name(list, name), do: ...
When running this filter I get back
{:ok, list, %{name: "search". ... }%}
This feels a bit inconsistent, I rather have it return back the original nesting of top_param
{:ok, list, %{q: %{name: "search"}, ...}%}
This way it's much easier writing an UI and when you want to indicate which filter fields are used (especially when using multiple search fields)
@JanStevens it seems like you want to separate "q"
value (actual filtering params) from pagination/ordering values?
i suggest you to split your filter into two/three separate filters:
defmodule ListFilter do
use Filterable.DSL
@options top_param: :q, cast: &String.downcase/1
filter name(list, value) do
IO.inspect "SEARCHING NAME"
list
end
@options top_param: :q, cast: &String.downcase/1
filter barcode(list, value) do
IO.inspect "SEARCHING barcode"
list
end
end
defmodule SortingFilter do
use Filterable.DSL
@options param: [:sort, :order], default: [order: :desc], cast: :atom, share: false
filter sort(list, %{sort: field, order: :asc}) do
list |> Enum.sort_by(&(&1[Atom.to_string(field)]), &<=/2)
end
filter sort(list, %{sort: field, order: :desc}) do
list |> Enum.sort_by(&(&1[Atom.to_string(field)]), &>=/2)
end
end
defmodule PaginationFilter do
use Filterable.DSL
@options param: [:page, :per_page], default: [page: 1, per_page: 10], cast: :integer, share: false
filter paginate(list, %{page: page, per_page: per_page}) do
paginated = list |> Enum.slice((page - 1) * per_page, per_page)
{paginated, Enum.count(list)}
end
end
list = [%{name: "Cat"}, %{name: "Dog"}]
params = %{q: %{name: "cat"}}
with {:ok, result, filter_values} <- ListFilter.apply_filters(list, params),
{:ok, result, sorting_values} <- SortingFilter.apply_filters(result, params),
{:ok, {result, total}, pagination_values} <- PaginationFilter.apply_filters(result, params),
metadata <- pagination_values |> Map.merge(sorting_values) |> Map.merge(%{total: total}),
do: {:ok, result, Map.merge(metadata, %{q: filter_values})}
it will return you the following results:
{:ok, [%{name: "Cat"}, %{name: "Dog"}],
%{paginate: %{page: 1, per_page: 10}, q: %{name: "cat"},
sort: %{order: :desc, sort: nil}, total: 2}}
so with separate pagination/sorting filters you can easily control filters behaviour and generate metadata in desired format.
please let me know if this could solve your problem.
Thanks that would indeed work but I find it a bit overkill to split everything up in different modules. This does increase the amount of lines needed to run the filters. When used for multiple resources the amount explodes :).
Thanks for the time and effort, I now just use an explicit list to nest the params. I do believe that retaining the nesting is correcter then flatting the result :)
filter_values
map has a bit different purpose.
it shows us which filters were applied and with which params.
for example we have filter name(list, value)
and we want filter collection on name "Bob".
then in any circumstancesfilter_values
should return simply - %{name: "Bob"}
so on top of this map we have just filter names.
that's where 'flatting' comes from and why it works that way.
@JanStevens to solve this problem we can add so-called scope
option to filter:
defmodule ListFilter do
use Filterable.DSL
@options top_param: :q, scope: :query, cast: &String.downcase/1
filter name(list, value) do
IO.inspect "SEARCHING NAME"
list
end
@options top_param: :q, scope: :query, cast: &String.downcase/1
filter barcode(list, value) do
IO.inspect "SEARCHING barcode"
list
end
end
so it will return something like:
%{query: %{name: "test", barcode: "test"}}