omohokcoj/filterable

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"}}