Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add :or operator for compound field filter with = instead of like #518

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions lib/flop/adapter/ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ defmodule Flop.Adapter.Ecto do
:like_and,
:like_or,
:ilike_and,
:ilike_or
:ilike_or,
:or
]

@backend_options [
Expand Down Expand Up @@ -158,7 +159,8 @@ defmodule Flop.Adapter.Ecto do
:ilike_and,
:ilike_or,
:empty,
:not_empty
:not_empty,
:or
],
extra: %{fields: fields, type: :compound}
}}
Expand Down Expand Up @@ -538,6 +540,25 @@ defmodule Flop.Adapter.Ecto do
end
end

defp build_op(
schema_struct,
%FieldInfo{extra: %{type: :compound, fields: fields}},
%Filter{op: :or, value: value}
) do
fields = Enum.map(fields, &get_field_info(schema_struct, &1))

Enum.reduce(fields, false, fn field, inner_dynamic ->
dynamic_for_field =
build_op(schema_struct, field, %Filter{
field: field,
op: :==,
value: value
})

dynamic([r], ^inner_dynamic or ^dynamic_for_field)
end)
end

defp build_op(
schema_struct,
%FieldInfo{extra: %{type: :compound, fields: fields}},
Expand Down
11 changes: 11 additions & 0 deletions lib/flop/adapter/ecto/operators.ex
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,17 @@ defmodule Flop.Adapter.Ecto.Operators do
{fragment, prelude, combinator}
end

def op_config(:or) do
fragment =
quote do
field(r, ^var!(field)) == ^var!(value)
end

combinator = :or

{fragment, nil, combinator}
end

defp empty do
quote do
is_nil(field(r, ^var!(field))) == ^var!(value)
Expand Down
4 changes: 3 additions & 1 deletion lib/flop/filter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ defmodule Flop.Filter do
| :not_ilike
| :ilike_and
| :ilike_or
| :or

@operators [
:==,
Expand All @@ -118,7 +119,8 @@ defmodule Flop.Filter do
:ilike,
:not_ilike,
:ilike_and,
:ilike_or
:ilike_or,
:or
]

@primary_key false
Expand Down
15 changes: 8 additions & 7 deletions lib/flop/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ defprotocol Flop.Schema do

Setting the value to `nil` (default) allows all pagination types.

See also `t:Flop.option/0`.
See also `t:Flop.option/0`.

## Alias fields

Expand Down Expand Up @@ -199,18 +199,18 @@ defprotocol Flop.Schema do

### Filter operator rules

- `:=~` `:like` `:not_like` `:like_and` `:like_or` `:ilike` `:not_ilike` `:ilike_and` `:ilike_or`
- `:=~` `:like` `:not_like` `:like_and` `:like_or` `:ilike` `:not_ilike` `:ilike_and` `:ilike_or`
If a string value is passed it will be split at whitespace
characters and each segment will be checked separately. If a list of strings is
passed the individual strings are not split. The filter matches for a value
if it matches for any of the fields.
- `:empty`
- `:empty`
Matches if all fields of the compound field are `nil`.
- `:not_empty`
- `:not_empty`
Matches if any field of the compound field is not `nil`.
- `:==` `:!=` `:<=` `:<` `:>=` `:>` `:in` `:not_in` `:contains` `:not_contains`
- `:==` `:!=` `:<=` `:<` `:>=` `:>` `:in` `:not_in` `:contains` `:not_contains`
** These filter operators are ignored for compound fields at the moment.
This will be added in a future version.**
This will be added in a future version.**
The filter value is normalized by splitting the string at whitespaces and
joining it with a space. The values of all fields of the compound field are
split by whitespace character and joined with a space, and the resulting
Expand Down Expand Up @@ -655,7 +655,8 @@ defprotocol Flop.Schema do
:ilike_and,
:ilike_or,
:empty,
:not_empty
:not_empty,
:or
],
extra: %{type: :compound, fields: [:family_name, :given_name]}
}
Expand Down
17 changes: 17 additions & 0 deletions test/adapters/ecto/cases/flop_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,23 @@ defmodule Flop.Adapters.Ecto.FlopTest do
end
end

@tag :or
property "applies :or operator on compound field" do
check all pet_count <- integer(@pet_count_range),
pets = insert_list_and_sort(pet_count, :pet_with_owner),
field <- member_of([:pet_and_owner_name]),
pet <- member_of(pets),
value = Pet.get_field(pet, field) do
expected = filter_items(pets, field, :or, value)

assert query_pets_with_owners(%{
filters: [%{field: field, op: :or, value: value}]
}) == expected

checkin_checkout()
end
end

property "custom field filter" do
check all pet_count <- integer(@pet_count_range),
pets = insert_list_and_sort(pet_count, :pet_with_owner),
Expand Down
3 changes: 2 additions & 1 deletion test/base/flop/filter_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ defmodule Flop.FilterTest do
:ilike_and,
:ilike_or,
:empty,
:not_empty
:not_empty,
:or
]
end
end
Expand Down
3 changes: 3 additions & 0 deletions test/support/generators.ex
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ defmodule Flop.Generators do
def value_by_field(:owner_name),
do: string(:alphanumeric, min_length: 1)

def value_by_field(:pet_and_owner_name),
do: string(:alphanumeric, min_length: 1)

def compare_value_by_field(:age), do: integer(1..30)

def compare_value_by_field(:name),
Expand Down
17 changes: 17 additions & 0 deletions test/support/test_util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,21 @@ defmodule Flop.TestUtil do
end)
end

defp apply_filter_to_compound_fields(
pet,
fields,
:or,
value,
ecto_adapter
) do
filter_func = matches?(:or, value, ecto_adapter)

Enum.any?(fields, fn field ->
field_info = Flop.Schema.field_info(%Pet{}, field)
pet |> get_field(field_info) |> filter_func.()
end)
end

defp apply_filter_to_compound_fields(pet, fields, op, value, ecto_adapter) do
filter_func = matches?(op, value, ecto_adapter)

Expand Down Expand Up @@ -267,6 +282,8 @@ defmodule Flop.TestUtil do
&Enum.any?(values, fn v -> String.downcase(&1) =~ v end)
end

defp matches?(:or, v, _), do: &(&1 == v)

defp empty?(nil), do: true
defp empty?([]), do: true
defp empty?(map) when map == %{}, do: true
Expand Down