Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into am/sqlite_tests
Browse files Browse the repository at this point in the history
  • Loading branch information
woylie committed Sep 10, 2024
2 parents 62b1b68 + c8015bb commit 994fc10
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 43 deletions.
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,46 @@

## Unreleased

## [0.26.1] - 2024-08-19

### Fixed

- Fixed allowed operators for `Ecto.Enum` fields in Ecto 3.12.
- Fixed type expansion when passing values with shortened syntax to
`Flop.Filter.expand_type/1`.
- Updated documentation example for setting `ecto_type` to parameterized types.

### Upgrade Guide

If you pass a parameterized type as `ecto_type` option, ensure that you use
`Ecto.ParameterizedType.init/2` instead of using the tuple representation as
suggested in the documentation before. The tuple representation is an internal
representation of Ecto and was changed in Ecto 3.12.

```diff
[
- ecto_type: {:parameterized, Ecto.Enum, Ecto.Enum.init(values: [:one, :two])}
+ ecto_type: Ecto.ParameterizedType.init(Ecto.Enum, values: [:one, :two])
]
```

For `Ecto.Enum` specifically, you can also use the short syntax
`{:ecto_enum, [:one, :two]}`.

## [0.26.0] - 2024-08-18

### Removed

- The previously deprecated tuple syntax for defining join fields has been
removed in favor of a keyword list.
- The previously deprecated function `Flop.Schema.field_type/2` was removed in
favor of `Flop.Schema.field_info/2`.

### Fixed

- Fixed a compatibility issue with Ecto 3.12 related to the initialization of
the `Ecto.Enum` type.

### Upgrade Guide

Replace the tuple syntax for join fields with a keyword list.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ file:
```elixir
def deps do
[
{:flop, "~> 0.25.0"}
{:flop, "~> 0.26.1"}
]
end
```
Expand Down Expand Up @@ -272,7 +272,7 @@ argument can be used to override certain options depending on the context in
which the function is called.

```elixir
def list_pets(%{} = args, opts \\ [], %User{} = current_user) do
def list_pets(%{} = params, opts \\ [], %User{} = current_user) do
flop_opts =
opts
|> Keyword.take([
Expand All @@ -285,7 +285,7 @@ def list_pets(%{} = args, opts \\ [], %User{} = current_user) do
Pet
|> scope(current_user)
|> apply_filters(opts)
|> Flop.validate_and_run(flop, flop_opts)
|> Flop.validate_and_run(params, flop_opts)
end

defp scope(q, %User{role: :admin}), do: q
Expand Down
4 changes: 2 additions & 2 deletions lib/flop.ex
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,8 @@ defmodule Flop do
@typedoc """
Represents the pagination type.
- `:offset` - pagination using the `offset` and `limit` parameters
- `:page` - pagination using the `page` and `page_size` parameters
- `:offset` - offset-based pagination using the `offset` and `limit` parameters
- `:page` - offset-based pagination using the `page` and `page_size` parameters
- `:first` - cursor-based pagination using the `first` and `after` parameters
- `:last` - cursor-based pagination using the `last` and `before` parameters
"""
Expand Down
2 changes: 1 addition & 1 deletion lib/flop/custom_types/any.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ defmodule Flop.CustomTypes.Any do
def type, do: :string
def load(_), do: :error
def dump(_), do: :error
# coveralls-ignore-end
# coveralls-ignore-stop
end
2 changes: 1 addition & 1 deletion lib/flop/custom_types/existing_atom.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ defmodule Flop.CustomTypes.ExistingAtom do
def type, do: :string
def load(_), do: :error
def dump(_), do: :error
# coveralls-ignore-end
# coveralls-ignore-stop
end
2 changes: 1 addition & 1 deletion lib/flop/custom_types/like.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ defmodule Flop.CustomTypes.Like do
def type, do: :string
def load(_), do: :error
def dump(_), do: :error
# coveralls-ignore-end
# coveralls-ignore-stop
end
61 changes: 41 additions & 20 deletions lib/flop/filter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ defmodule Flop.Filter do
end

defp expand_type({:ecto_enum, values}) do
{:parameterized, Ecto.Enum, Ecto.Enum.init(values: values)}
Ecto.ParameterizedType.init(Ecto.Enum, values: values)
end

defp expand_type(type), do: type
Expand Down Expand Up @@ -275,14 +275,19 @@ defmodule Flop.Filter do
end

def allowed_operators(%FieldInfo{ecto_type: ecto_type}) do
ecto_type |> expand_type() |> allowed_operators()
ecto_type |> expand_type() |> get_allowed_operators()
end

def allowed_operators(type) when type in [:decimal, :float, :id, :integer] do
def allowed_operators(type) do
type |> expand_type() |> get_allowed_operators()
end

defp get_allowed_operators(type)
when type in [:decimal, :float, :id, :integer] do
[:==, :!=, :empty, :not_empty, :<=, :<, :>=, :>, :in, :not_in]
end

def allowed_operators(type) when type in [:binary_id, :string] do
defp get_allowed_operators(type) when type in [:binary_id, :string] do
[
:==,
:!=,
Expand All @@ -306,11 +311,11 @@ defmodule Flop.Filter do
]
end

def allowed_operators(:boolean) do
defp get_allowed_operators(:boolean) do
[:==, :!=, :=~, :empty, :not_empty]
end

def allowed_operators({:array, _}) do
defp get_allowed_operators({:array, _}) do
[
:==,
:!=,
Expand All @@ -327,28 +332,44 @@ defmodule Flop.Filter do
]
end

def allowed_operators({:map, _}) do
defp get_allowed_operators({:map, _}) do
[:==, :!=, :empty, :not_empty, :in, :not_in]
end

def allowed_operators(:map) do
defp get_allowed_operators(:map) do
[:==, :!=, :empty, :not_empty, :in, :not_in]
end

def allowed_operators(type)
when type in [
:date,
:time,
:time_usec,
:naive_datetime,
:naive_datetime_usec,
:utc_datetime,
:utc_datetime_usec
] do
defp get_allowed_operators(type)
when type in [
:date,
:time,
:time_usec,
:naive_datetime,
:naive_datetime_usec,
:utc_datetime,
:utc_datetime_usec
] do
[:==, :!=, :empty, :not_empty, :<=, :<, :>=, :>, :in, :not_in]
end

def allowed_operators({:parameterized, Ecto.Enum, _}) do
defp get_allowed_operators({:parameterized, {Ecto.Enum, _}}) do
[
:==,
:!=,
:empty,
:not_empty,
:<=,
:<,
:>=,
:>,
:in,
:not_in
]
end

# for backward compatibility with Ecto < 3.12.0
defp get_allowed_operators({:parameterized, Ecto.Enum, _}) do
[
:==,
:!=,
Expand All @@ -363,7 +384,7 @@ defmodule Flop.Filter do
]
end

def allowed_operators(_) do
defp get_allowed_operators(_) do
[
:==,
:!=,
Expand Down
20 changes: 10 additions & 10 deletions lib/flop/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ defprotocol Flop.Schema do
## Restricting pagination types
By default, `page`/`page_size`, `offset`/`limit` and cursor-based pagination
(`first`/`after` and `last`/`before`) are enabled. If you wish to restrict the
pagination type for a schema, you can set the `pagination_types` option.
By default, all supported pagination types (`t:Flop.pagination_type/0`) are
enabled. If you wish to restrict the pagination type for a schema, you can
set the `:pagination_types` option.
@derive {
Flop.Schema,
Expand All @@ -106,8 +106,9 @@ defprotocol Flop.Schema do
pagination_types: [:first, :last]
}
See also `t:Flop.option/0` and `t:Flop.pagination_type/0`. Setting the value
to `nil` allows all pagination types.
Setting the value to `nil` (default) allows all pagination types.
See also `t:Flop.option/0`.
## Alias fields
Expand Down Expand Up @@ -488,7 +489,7 @@ defprotocol Flop.Schema do
For parameterized types, use the following syntax:
- `ecto_type: {:parameterized, Ecto.Enum, Ecto.Enum.init(values: [:one, :two])}`
- `ecto_type: Ecto.ParameterizedType.init(Ecto.Enum, values: [:one, :two])`
If you're working with `Ecto.Enum` types, you can use a more convenient
syntax:
Expand Down Expand Up @@ -612,16 +613,15 @@ defprotocol Flop.Schema do
- `:string`
- `:integer`
- `Ecto.UUID`
- `{:parameterized, Ecto.Enum, Ecto.Enum.init(values: [:one, :two])}`
- The result of `Ecto.ParameterizedType.init/2`.
Or reference a schema field:
`{:from_schema, MyApp.Pet, :mood}`
Or build an adhoc Ecto.Enum:
- `{:ecto_enum, [:one, :two]}` (This has the same effect as the `:parameterized`
example above.)
- `{:ecto_enum, [:one, :two]}`
- `{:ecto_enum, [one: 1, two: 2]}`
Note that if you make an `Ecto.Enum` type this way, the filter value will be
Expand Down Expand Up @@ -969,7 +969,7 @@ defimpl Flop.Schema, for: Any do
end

%{ecto_type: {:ecto_enum, values}} ->
type = {:parameterized, Ecto.Enum, Ecto.Enum.init(values: values)}
type = Ecto.ParameterizedType.init(Ecto.Enum, values: values)
field_info = %{field_info | ecto_type: type}

quote do
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Flop.MixProject do
use Mix.Project

@source_url "https://github.com/woylie/flop"
@version "0.25.0"
@version "0.26.1"
@adapters ~w(pg sqlite)

def project do
Expand Down
48 changes: 45 additions & 3 deletions test/flop/filter_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule Flop.FilterTest do
end

describe "allowed_operators/1" do
test "returns a list of operators for each native Ecto type" do
test "returns a list of operators for each Ecto type" do
types = [
:id,
:binary_id,
Expand All @@ -36,12 +36,54 @@ defmodule Flop.FilterTest do
:naive_datetime_usec,
:utc_datetime,
:utc_datetime_usec,
{:parameterized, Ecto.Enum, type: :string}
{:parameterized, {Ecto.Enum, %{type: :string}}},
{:ecto_enum, [:one, :two]},
{:from_schema, MyApp.Pet, :mood}
]

for type <- types do
assert [op | _] = Filter.allowed_operators(type)
assert [op | _] = ops_for_type = Filter.allowed_operators(type)
assert is_atom(op)

ops_for_field =
Filter.allowed_operators(%Flop.FieldInfo{ecto_type: type})

assert ops_for_type == ops_for_field
end
end

test "returns list of operators for enum" do
types = [
# by internal representation Ecto < 3.12.0
{:parameterized, Ecto.Enum, %{type: :string}},
# by internal representation Ecto >= 3.12.0
{:parameterized, {Ecto.Enum, %{type: :string}}},
# same with init function
Ecto.ParameterizedType.init(Ecto.Enum, values: [:one, :two]),
# by convenience format
{:ecto_enum, [:one, :two]},
# by reference
{:from_schema, MyApp.Pet, :mood}
]

expected_ops = [
:==,
:!=,
:empty,
:not_empty,
:<=,
:<,
:>=,
:>,
:in,
:not_in
]

for type <- types do
assert Filter.allowed_operators(type) == expected_ops

assert Filter.allowed_operators(%Flop.FieldInfo{ecto_type: type}) ==
expected_ops
end
end

Expand Down
18 changes: 18 additions & 0 deletions test/flop/validation_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,5 +1050,23 @@ defmodule Flop.ValidationTest do
assert {:error, changeset} = validate(params, for: Owner)
assert [%{value: ["is invalid"]}] = errors_on(changeset)[:filters]
end

test "casts filter values as ecto enums when using parameterized type" do
field = :pet_mood_as_parameterized_type

params = %{filters: [%{field: field, op: :==, value: "happy"}]}
assert %{filters: [%{value: :happy}]} = validate!(params, for: Owner)

params = %{filters: [%{field: field, op: :==, value: :happy}]}
assert %{filters: [%{value: :happy}]} = validate!(params, for: Owner)

params = %{filters: [%{field: field, op: :==, value: "joyful"}]}
assert {:error, changeset} = validate(params, for: Owner)
assert [%{value: ["is invalid"]}] = errors_on(changeset)[:filters]

params = %{filters: [%{field: field, op: :==, value: :joyful}]}
assert {:error, changeset} = validate(params, for: Owner)
assert [%{value: ["is invalid"]}] = errors_on(changeset)[:filters]
end
end
end
Loading

0 comments on commit 994fc10

Please sign in to comment.