2019-12-16 13:20:44 +13:00
|
|
|
defmodule Ash.Test.Filter.FilterTest do
|
2020-06-02 17:47:25 +12:00
|
|
|
@moduledoc false
|
2019-12-16 13:20:44 +13:00
|
|
|
use ExUnit.Case, async: true
|
|
|
|
|
2022-06-22 13:00:47 +12:00
|
|
|
import Ash.Test
|
2021-04-19 11:41:49 +12:00
|
|
|
|
2020-06-19 14:59:30 +12:00
|
|
|
alias Ash.Filter
|
2024-03-28 09:06:40 +13:00
|
|
|
alias Ash.Test.Domain, as: Domain
|
2020-06-19 14:59:30 +12:00
|
|
|
|
2020-10-08 18:22:55 +13:00
|
|
|
require Ash.Query
|
|
|
|
|
2022-02-08 09:39:12 +13:00
|
|
|
defmodule EmbeddedBio do
|
|
|
|
@moduledoc false
|
|
|
|
use Ash.Resource, data_layer: :embedded
|
|
|
|
|
|
|
|
attributes do
|
|
|
|
uuid_primary_key :id
|
2024-03-28 09:06:40 +13:00
|
|
|
|
|
|
|
attribute :bio, :string do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
attribute :title, :string do
|
|
|
|
public?(true)
|
|
|
|
end
|
2022-02-08 09:39:12 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-12-20 18:30:27 +13:00
|
|
|
defmodule Profile do
|
2020-06-02 17:47:25 +12:00
|
|
|
@moduledoc false
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
2020-06-14 18:39:11 +12:00
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
2019-12-20 18:30:27 +13:00
|
|
|
|
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults [:read, :destroy, create: :*, update: :*]
|
2022-10-25 02:27:18 +13:00
|
|
|
|
|
|
|
read :get_path_search do
|
|
|
|
argument :input, :map
|
|
|
|
|
|
|
|
filter expr(get_path(embedded_bio, [:title]) == get_path(^arg(:input), :title))
|
2024-03-28 09:06:40 +13:00
|
|
|
filter expr(embedded_bio[:title] != "__fake_value_dude__")
|
2022-10-25 02:27:18 +13:00
|
|
|
end
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
2021-01-13 09:40:55 +13:00
|
|
|
uuid_primary_key :id
|
2024-03-28 09:06:40 +13:00
|
|
|
attribute :bio, :string, public?: true
|
|
|
|
attribute :embedded_bio, EmbeddedBio, public?: true
|
|
|
|
attribute :private, :string
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
2024-03-28 09:06:40 +13:00
|
|
|
belongs_to :user, Ash.Test.Filter.FilterTest.User do
|
|
|
|
public?(true)
|
|
|
|
end
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-12-16 13:20:44 +13:00
|
|
|
defmodule User do
|
2020-06-02 17:47:25 +12:00
|
|
|
@moduledoc false
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
2020-06-14 18:39:11 +12:00
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
2019-12-16 13:20:44 +13:00
|
|
|
|
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults [:read, :destroy, create: :*, update: :*]
|
2024-05-15 09:08:27 +12:00
|
|
|
|
|
|
|
read :with_invalid_value_behind_is_nil_check do
|
|
|
|
argument :ids, {:array, :uuid}
|
|
|
|
|
|
|
|
filter expr(
|
2024-05-15 09:58:13 +12:00
|
|
|
# credo:disable-for-next-line Credo.Check.Refactor.NegatedConditionsWithElse
|
2024-05-15 09:08:27 +12:00
|
|
|
if not is_nil(^arg(:ids)) do
|
|
|
|
id in ^arg(:ids)
|
|
|
|
else
|
|
|
|
true
|
|
|
|
end
|
|
|
|
)
|
|
|
|
end
|
2019-12-16 13:20:44 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
2021-01-13 09:40:55 +13:00
|
|
|
uuid_primary_key :id
|
2024-03-28 09:06:40 +13:00
|
|
|
attribute :name, :string, public?: true
|
|
|
|
attribute :allow_second_author, :boolean, public?: true
|
|
|
|
attribute :special, :boolean, public?: true
|
|
|
|
attribute :roles, {:array, :atom}, public?: true
|
2019-12-16 13:20:44 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
2024-03-28 09:06:40 +13:00
|
|
|
has_many :posts, Ash.Test.Filter.FilterTest.Post,
|
|
|
|
destination_attribute: :author1_id,
|
|
|
|
public?: true
|
2019-12-20 18:30:27 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
has_many :second_posts, Ash.Test.Filter.FilterTest.Post,
|
|
|
|
destination_attribute: :author1_id,
|
|
|
|
public?: true
|
2019-12-24 17:22:31 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
has_one :profile, Profile, destination_attribute: :user_id, public?: true
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
2024-07-11 04:14:27 +12:00
|
|
|
|
|
|
|
aggregates do
|
|
|
|
count :count_of_posts, :posts, public?: true
|
|
|
|
end
|
|
|
|
|
|
|
|
calculations do
|
|
|
|
calculate :has_friend, :boolean, expr(true) do
|
|
|
|
public? true
|
|
|
|
end
|
|
|
|
end
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
defmodule PostLink do
|
2020-06-02 17:47:25 +12:00
|
|
|
@moduledoc false
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
2020-06-14 18:39:11 +12:00
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
2019-12-20 18:30:27 +13:00
|
|
|
|
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults [:read, :destroy, create: :*, update: :*]
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
2021-01-13 09:40:55 +13:00
|
|
|
belongs_to :source_post, Ash.Test.Filter.FilterTest.Post,
|
|
|
|
primary_key?: true,
|
2024-03-28 09:06:40 +13:00
|
|
|
allow_nil?: false,
|
|
|
|
public?: true
|
2021-01-13 09:40:55 +13:00
|
|
|
|
|
|
|
belongs_to :destination_post, Ash.Test.Filter.FilterTest.Post,
|
|
|
|
primary_key?: true,
|
2024-03-28 09:06:40 +13:00
|
|
|
allow_nil?: false,
|
|
|
|
public?: true
|
2019-12-16 13:20:44 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defmodule Post do
|
2020-06-02 17:47:25 +12:00
|
|
|
@moduledoc false
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
2020-06-14 18:39:11 +12:00
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
2019-12-16 13:20:44 +13:00
|
|
|
|
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults [:read, :destroy, create: :*, update: :*]
|
2019-12-16 13:20:44 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
2021-01-13 09:40:55 +13:00
|
|
|
uuid_primary_key :id
|
2024-03-28 09:06:40 +13:00
|
|
|
|
|
|
|
attribute :title, :string do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
attribute :contents, :string do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
attribute :points, :integer do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
attribute :approved_at, :datetime do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
attribute :category, :ci_string do
|
|
|
|
public?(true)
|
|
|
|
end
|
2019-12-16 13:20:44 +13:00
|
|
|
end
|
|
|
|
|
2022-11-01 05:08:24 +13:00
|
|
|
calculations do
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :cool_titles, {:array, :string}, expr(["yo", "dawg"]) do
|
|
|
|
public?(true)
|
|
|
|
end
|
2022-11-01 05:08:24 +13:00
|
|
|
end
|
|
|
|
|
2019-12-16 13:20:44 +13:00
|
|
|
relationships do
|
|
|
|
belongs_to :author1, User,
|
2022-08-16 06:00:02 +12:00
|
|
|
destination_attribute: :id,
|
|
|
|
source_attribute: :author1_id,
|
2024-07-11 04:14:27 +12:00
|
|
|
attribute_public?: false,
|
2024-03-28 09:06:40 +13:00
|
|
|
public?: true
|
|
|
|
|
|
|
|
belongs_to :special_author1, User do
|
|
|
|
destination_attribute :id
|
|
|
|
source_attribute :author1_id
|
|
|
|
define_attribute? false
|
|
|
|
public? true
|
|
|
|
filter expr(special == true)
|
|
|
|
end
|
2021-04-29 09:39:14 +12:00
|
|
|
|
2019-12-16 13:20:44 +13:00
|
|
|
belongs_to :author2, User,
|
2022-08-16 06:00:02 +12:00
|
|
|
destination_attribute: :id,
|
2024-03-28 09:06:40 +13:00
|
|
|
source_attribute: :author2_id,
|
|
|
|
public?: true
|
2019-12-20 18:30:27 +13:00
|
|
|
|
|
|
|
many_to_many :related_posts, __MODULE__,
|
|
|
|
through: PostLink,
|
2022-08-16 06:00:02 +12:00
|
|
|
source_attribute_on_join_resource: :source_post_id,
|
2024-03-28 09:06:40 +13:00
|
|
|
destination_attribute_on_join_resource: :destination_post_id,
|
|
|
|
public?: true
|
2019-12-16 13:20:44 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-20 07:46:34 +12:00
|
|
|
defmodule SoftDeletePost do
|
|
|
|
@moduledoc false
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
2020-09-20 07:46:34 +12:00
|
|
|
|
|
|
|
ets do
|
|
|
|
private? true
|
|
|
|
end
|
|
|
|
|
|
|
|
resource do
|
|
|
|
base_filter is_nil: :deleted_at
|
|
|
|
end
|
|
|
|
|
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults [:read, create: :*, update: :*]
|
2020-09-20 07:46:34 +12:00
|
|
|
|
2021-03-08 18:59:32 +13:00
|
|
|
destroy :destroy do
|
2022-04-29 10:07:06 +12:00
|
|
|
primary? true
|
2020-09-20 07:46:34 +12:00
|
|
|
soft? true
|
|
|
|
|
|
|
|
change set_attribute(:deleted_at, &DateTime.utc_now/0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
2021-01-13 09:40:55 +13:00
|
|
|
uuid_primary_key :id
|
2021-10-07 19:41:02 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
attribute :deleted_at, :utc_datetime do
|
|
|
|
public?(true)
|
|
|
|
end
|
2020-06-14 18:39:11 +12:00
|
|
|
end
|
2019-12-16 13:20:44 +13:00
|
|
|
end
|
|
|
|
|
2022-11-01 05:08:24 +13:00
|
|
|
describe "in" do
|
|
|
|
test "in can be done with references on both sides" do
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "dawg"})
|
|
|
|
|> Ash.create!()
|
2022-11-01 05:08:24 +13:00
|
|
|
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "lame"})
|
|
|
|
|> Ash.create!()
|
2022-11-01 05:08:24 +13:00
|
|
|
|
|
|
|
assert [_] =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(title in cool_titles)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-11-01 05:08:24 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-02-25 05:12:34 +13:00
|
|
|
describe "predicate optimization" do
|
|
|
|
# Testing against the stringified query may be a bad idea, but its a quick win and we
|
|
|
|
# can switch to actually checking the structure if this bites us
|
|
|
|
test "equality simplifies to `in`" do
|
|
|
|
stringified_query =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(title == "foo" or title == "bar")
|
|
|
|
|> inspect()
|
|
|
|
|
|
|
|
assert stringified_query =~ ~S(title in ["bar", "foo"])
|
|
|
|
end
|
|
|
|
|
|
|
|
test "in with equality simplifies to `in`" do
|
|
|
|
stringified_query =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(title in ["foo", "bar", "baz"] or title == "bar")
|
|
|
|
|> inspect()
|
|
|
|
|
|
|
|
assert stringified_query =~ ~S(title in ["bar", "baz", "foo"])
|
|
|
|
end
|
|
|
|
|
2023-08-09 07:17:07 +12:00
|
|
|
test "in across ands in ors isn't optimized" do
|
|
|
|
stringified_query =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(
|
|
|
|
title == "foo" or
|
|
|
|
(title == "bar" and points == 5) or
|
|
|
|
(title == "baz" and points == 10)
|
|
|
|
)
|
|
|
|
|> inspect()
|
|
|
|
|
2023-08-10 05:43:55 +12:00
|
|
|
assert stringified_query =~ ~S(title == "foo")
|
|
|
|
assert stringified_query =~ ~S(title == "bar")
|
|
|
|
assert stringified_query =~ ~S(title == "baz")
|
2023-08-09 07:17:07 +12:00
|
|
|
end
|
|
|
|
|
2021-02-25 05:12:34 +13:00
|
|
|
test "in with non-equality simplifies to `in`" do
|
|
|
|
stringified_query =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(title in ["foo", "bar", "baz"] and title != "bar")
|
|
|
|
|> inspect()
|
|
|
|
|
|
|
|
assert stringified_query =~ ~S(title in ["baz", "foo"])
|
|
|
|
end
|
|
|
|
|
|
|
|
test "in with or-in simplifies to `in`" do
|
|
|
|
stringified_query =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(title in ["foo", "bar"] or title in ["bar", "baz"])
|
|
|
|
|> inspect()
|
|
|
|
|
|
|
|
assert stringified_query =~ ~S(title in ["bar", "baz", "foo"])
|
|
|
|
end
|
|
|
|
|
|
|
|
test "in with and-in simplifies to `in` when multiple values overlap" do
|
|
|
|
stringified_query =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(title in ["foo", "bar", "baz"] and title in ["bar", "baz", "bif"])
|
|
|
|
|> inspect()
|
|
|
|
|
|
|
|
assert stringified_query =~ ~S(title in ["bar", "baz"])
|
|
|
|
end
|
|
|
|
|
|
|
|
test "in with and-in simplifies to `eq` when one value overlaps" do
|
|
|
|
stringified_query =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(title in ["foo", "bar"] and title in ["bar", "baz", "bif"])
|
|
|
|
|> inspect()
|
|
|
|
|
|
|
|
assert stringified_query =~ ~S(title == "bar")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-12-20 18:30:27 +13:00
|
|
|
describe "simple attribute filters" do
|
|
|
|
setup do
|
2020-07-12 18:25:53 +12:00
|
|
|
post1 =
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "title1", contents: "contents1", points: 1})
|
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2020-07-12 18:25:53 +12:00
|
|
|
|
|
|
|
post2 =
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "title2", contents: "contents2", points: 2})
|
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2019-12-20 18:30:27 +13:00
|
|
|
|
|
|
|
%{post1: post1, post2: post2}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "single filter field", %{post1: post1} do
|
2020-05-10 14:23:23 +12:00
|
|
|
assert [^post1] =
|
2020-05-14 03:54:44 +12:00
|
|
|
Post
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(title == ^post1.title)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "multiple filter field matches", %{post1: post1} do
|
2020-05-10 14:23:23 +12:00
|
|
|
assert [^post1] =
|
2020-05-14 03:54:44 +12:00
|
|
|
Post
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(title == ^post1.title and contents == ^post1.contents)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "no field matches" do
|
2020-05-10 14:23:23 +12:00
|
|
|
assert [] =
|
2020-05-14 03:54:44 +12:00
|
|
|
Post
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(title == "no match")
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "no field matches single record, but each matches one record", %{
|
|
|
|
post1: post1,
|
|
|
|
post2: post2
|
|
|
|
} do
|
2020-05-10 14:23:23 +12:00
|
|
|
assert [] =
|
2020-05-14 03:54:44 +12:00
|
|
|
Post
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(title == ^post1.title and contents == ^post2.contents)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
2020-06-29 15:36:38 +12:00
|
|
|
|
|
|
|
test "less than works", %{
|
|
|
|
post1: post1,
|
|
|
|
post2: post2
|
|
|
|
} do
|
|
|
|
assert [^post1] =
|
|
|
|
Post
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(points < 2)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2020-06-29 15:36:38 +12:00
|
|
|
|
|
|
|
assert [^post1, ^post2] =
|
|
|
|
Post
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(points < 3)
|
2020-06-29 15:36:38 +12:00
|
|
|
|> Ash.Query.sort(points: :asc)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2020-06-29 15:36:38 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
test "greater than works", %{
|
|
|
|
post1: post1,
|
|
|
|
post2: post2
|
|
|
|
} do
|
|
|
|
assert [^post2] =
|
|
|
|
Post
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(points > 1)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2020-06-29 15:36:38 +12:00
|
|
|
|
|
|
|
assert [^post1, ^post2] =
|
|
|
|
Post
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(points > 0)
|
2020-06-29 15:36:38 +12:00
|
|
|
|> Ash.Query.sort(points: :asc)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2020-06-29 15:36:38 +12:00
|
|
|
end
|
2019-12-20 18:30:27 +13:00
|
|
|
end
|
|
|
|
|
2022-02-08 09:39:12 +13:00
|
|
|
describe "embedded filters" do
|
|
|
|
setup do
|
|
|
|
Profile
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{embedded_bio: %{title: "Dr.", bio: "foo"}})
|
|
|
|
|> Ash.create!()
|
2022-02-08 09:39:12 +13:00
|
|
|
|
|
|
|
Profile
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
|
|
embedded_bio: %{title: "Highlander", bio: "There can be only one"}
|
|
|
|
})
|
|
|
|
|> Ash.create!()
|
2022-02-08 09:39:12 +13:00
|
|
|
|
|
|
|
:ok
|
|
|
|
end
|
|
|
|
|
|
|
|
test "simple equality filters work" do
|
|
|
|
assert [%Profile{embedded_bio: %EmbeddedBio{title: "Dr."}}] =
|
|
|
|
Profile
|
|
|
|
|> Ash.Query.filter(embedded_bio[:title] == "Dr.")
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-02-08 09:39:12 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "expressions work on accessed values" do
|
|
|
|
assert [%Profile{embedded_bio: %EmbeddedBio{title: "Highlander"}}] =
|
|
|
|
Profile
|
|
|
|
|> Ash.Query.filter(contains(embedded_bio[:bio], "can be only one"))
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-02-08 09:39:12 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-12-20 18:30:27 +13:00
|
|
|
describe "relationship filters" do
|
|
|
|
setup do
|
2020-07-12 18:25:53 +12:00
|
|
|
post1 =
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "title1", contents: "contents1", points: 1})
|
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2020-07-12 18:25:53 +12:00
|
|
|
|
|
|
|
post2 =
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "title2", contents: "contents2", points: 2})
|
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2019-12-20 18:30:27 +13:00
|
|
|
|
2019-12-22 21:17:29 +13:00
|
|
|
post3 =
|
2020-07-12 18:25:53 +12:00
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "title3", contents: "contents3", points: 3})
|
2023-12-17 04:14:02 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:related_posts, [post1, post2],
|
|
|
|
type: :append_and_remove
|
|
|
|
)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2019-12-20 18:30:27 +13:00
|
|
|
|
2019-12-24 17:22:31 +13:00
|
|
|
post4 =
|
2020-07-12 18:25:53 +12:00
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "title4", contents: "contents4", points: 4})
|
2023-12-17 04:14:02 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:related_posts, [post3], type: :append_and_remove)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2019-12-24 17:22:31 +13:00
|
|
|
|
2020-07-12 18:25:53 +12:00
|
|
|
profile1 =
|
|
|
|
Profile
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{bio: "dope"})
|
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2019-12-20 18:30:27 +13:00
|
|
|
|
|
|
|
user1 =
|
2020-07-12 18:25:53 +12:00
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "broseph"})
|
2023-12-17 04:14:02 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:posts, [post1, post2], type: :append_and_remove)
|
|
|
|
|> Ash.Changeset.manage_relationship(:profile, profile1, type: :append_and_remove)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2020-07-12 18:25:53 +12:00
|
|
|
|
|
|
|
user2 =
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "broseph", special: false})
|
2023-12-17 04:14:02 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:posts, [post2], type: :append_and_remove)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2020-07-12 18:25:53 +12:00
|
|
|
|
|
|
|
profile2 =
|
|
|
|
Profile
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{bio: "dope2"})
|
2023-12-17 04:14:02 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:user, user2, type: :append_and_remove)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-06-22 13:00:47 +12:00
|
|
|
|> strip_metadata()
|
2019-12-20 18:30:27 +13:00
|
|
|
|
2019-12-22 22:06:33 +13:00
|
|
|
%{
|
2024-03-28 09:06:40 +13:00
|
|
|
post1: Ash.reload!(post1),
|
|
|
|
post2: Ash.reload!(post2),
|
|
|
|
post3: Ash.reload!(post3),
|
|
|
|
post4: Ash.reload!(post4),
|
|
|
|
profile1: Ash.reload!(profile1),
|
|
|
|
user1: Ash.reload!(user1),
|
|
|
|
user2: Ash.reload!(user2),
|
|
|
|
profile2: Ash.reload!(profile2)
|
2019-12-22 22:06:33 +13:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2021-01-23 10:41:32 +13:00
|
|
|
test "filtering on a has_one relationship", %{profile2: profile2, user2: %{id: user2_id}} do
|
|
|
|
assert [%{id: ^user2_id}] =
|
2020-05-14 03:54:44 +12:00
|
|
|
User
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(profile == ^profile2.id)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2019-12-24 17:22:31 +13:00
|
|
|
end
|
|
|
|
|
2021-01-23 10:41:32 +13:00
|
|
|
test "filtering on a belongs_to relationship", %{profile1: %{id: id}, user1: user1} do
|
|
|
|
assert [%{id: ^id}] =
|
2020-05-14 03:54:44 +12:00
|
|
|
Profile
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(user == ^user1.id)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2019-12-24 17:22:31 +13:00
|
|
|
end
|
|
|
|
|
2021-01-23 10:41:32 +13:00
|
|
|
test "filtering on a has_many relationship", %{user2: %{id: user2_id}, post2: post2} do
|
|
|
|
assert [%{id: ^user2_id}] =
|
2020-05-14 03:54:44 +12:00
|
|
|
User
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(posts == ^post2.id)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2019-12-24 17:22:31 +13:00
|
|
|
end
|
|
|
|
|
2021-01-23 10:41:32 +13:00
|
|
|
test "filtering on a many_to_many relationship", %{post4: %{id: post4_id}, post3: post3} do
|
|
|
|
assert [%{id: ^post4_id}] =
|
2020-05-14 03:54:44 +12:00
|
|
|
Post
|
2020-10-08 18:22:55 +13:00
|
|
|
|> Ash.Query.filter(related_posts == ^post3.id)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2019-12-24 17:22:31 +13:00
|
|
|
end
|
2021-04-29 09:39:14 +12:00
|
|
|
|
|
|
|
test "relationship filters are honored when filtering on relationships", %{post2: post} do
|
2024-03-28 09:06:40 +13:00
|
|
|
post = Ash.load!(post, [:special_author1, :author1])
|
2021-04-29 09:43:02 +12:00
|
|
|
|
|
|
|
assert post.author1
|
|
|
|
refute post.special_author1
|
2021-04-29 09:39:14 +12:00
|
|
|
end
|
2019-12-16 13:20:44 +13:00
|
|
|
end
|
2020-06-19 14:59:30 +12:00
|
|
|
|
|
|
|
describe "filter subset logic" do
|
|
|
|
test "can detect a filter is a subset of itself" do
|
2020-07-09 18:55:09 +12:00
|
|
|
filter = Filter.parse!(Post, %{points: 1})
|
2020-06-19 14:59:30 +12:00
|
|
|
|
|
|
|
assert Filter.strict_subset_of?(filter, filter)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "can detect a filter is a subset of itself *and* something else" do
|
2020-07-09 18:55:09 +12:00
|
|
|
filter = Filter.parse!(Post, points: 1)
|
2020-06-19 14:59:30 +12:00
|
|
|
|
|
|
|
candidate = Filter.add_to_filter!(filter, title: "Title")
|
|
|
|
|
|
|
|
assert Filter.strict_subset_of?(filter, candidate)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "can detect a filter is not a subset of itself *or* something else" do
|
2020-07-09 18:55:09 +12:00
|
|
|
filter = Filter.parse!(Post, points: 1)
|
2020-06-19 14:59:30 +12:00
|
|
|
|
2020-07-23 17:09:59 +12:00
|
|
|
candidate = Filter.add_to_filter!(filter, [title: "Title"], :or)
|
2020-06-19 14:59:30 +12:00
|
|
|
|
|
|
|
refute Filter.strict_subset_of?(filter, candidate)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "can detect a filter is a subset based on a simplification" do
|
2021-11-17 13:17:57 +13:00
|
|
|
query = Ash.Query.filter(Post, points in [1, 2])
|
2020-06-19 14:59:30 +12:00
|
|
|
|
2021-11-17 16:46:14 +13:00
|
|
|
assert Ash.Query.superset_of?(query, points == 1)
|
|
|
|
assert Ash.Query.subset_of?(query, points in [1, 2, 3])
|
|
|
|
assert Ash.Query.equivalent_to?(query, points == 1 or points == 2)
|
2020-06-19 14:59:30 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
test "can detect a filter is not a subset based on a simplification" do
|
2020-07-09 18:55:09 +12:00
|
|
|
filter = Filter.parse!(Post, points: [in: [1, 2]])
|
2020-06-19 14:59:30 +12:00
|
|
|
|
2020-07-09 18:55:09 +12:00
|
|
|
candidate = Filter.parse!(Post, points: 3)
|
2020-06-19 14:59:30 +12:00
|
|
|
|
|
|
|
refute Filter.strict_subset_of?(filter, candidate)
|
|
|
|
end
|
|
|
|
|
2021-12-07 10:50:33 +13:00
|
|
|
test "can detect that `not is_nil(field)` is the same as `field is_nil false`" do
|
|
|
|
filter = Filter.parse!(Post, not: [is_nil: :points])
|
|
|
|
candidate = Filter.parse!(Post, points: [is_nil: false])
|
|
|
|
|
|
|
|
assert Filter.strict_subset_of?(filter, candidate)
|
|
|
|
assert Filter.strict_subset_of?(candidate, filter)
|
|
|
|
end
|
|
|
|
|
2020-06-19 14:59:30 +12:00
|
|
|
test "can detect a more complicated scenario" do
|
2020-10-06 17:58:06 +13:00
|
|
|
filter = Filter.parse!(Post, or: [[points: [in: [1, 2, 3]]], [points: 4], [points: 5]])
|
2020-06-19 14:59:30 +12:00
|
|
|
|
2020-10-06 17:58:06 +13:00
|
|
|
candidate = Filter.parse!(Post, or: [[points: 1], [points: 3], [points: 5]])
|
2020-06-19 14:59:30 +12:00
|
|
|
|
|
|
|
assert Filter.strict_subset_of?(filter, candidate)
|
|
|
|
end
|
|
|
|
|
2020-10-06 17:58:06 +13:00
|
|
|
test "can detect less than and greater than closing in on a single value" do
|
|
|
|
filter = Filter.parse!(Post, points: [greater_than: 1, less_than: 3])
|
|
|
|
|
|
|
|
candidate = Filter.parse!(Post, points: 2)
|
|
|
|
|
|
|
|
assert Filter.strict_subset_of?(filter, candidate)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "doesnt have false positives on less than and greater than closing in on a single value" do
|
|
|
|
filter = Filter.parse!(Post, points: [greater_than: 1, less_than: 3])
|
|
|
|
|
|
|
|
candidate = Filter.parse!(Post, points: 4)
|
|
|
|
|
|
|
|
refute Filter.strict_subset_of?(filter, candidate)
|
|
|
|
end
|
|
|
|
|
2020-06-19 14:59:30 +12:00
|
|
|
test "understands unrelated negations" do
|
2020-10-06 17:58:06 +13:00
|
|
|
filter = Filter.parse!(Post, or: [[points: [in: [1, 2, 3]]], [points: 4], [points: 5]])
|
2020-06-19 14:59:30 +12:00
|
|
|
|
2020-10-06 17:58:06 +13:00
|
|
|
candidate =
|
|
|
|
Filter.parse!(Post, or: [[points: 1], [points: 3], [points: 5]], not: [points: 7])
|
2020-06-19 14:59:30 +12:00
|
|
|
|
|
|
|
assert Filter.strict_subset_of?(filter, candidate)
|
|
|
|
end
|
2020-06-22 15:26:47 +12:00
|
|
|
|
|
|
|
test "understands relationship filter subsets" do
|
2021-02-24 06:27:49 +13:00
|
|
|
id1 = Ash.UUID.generate()
|
|
|
|
id2 = Ash.UUID.generate()
|
2020-07-09 18:55:09 +12:00
|
|
|
filter = Filter.parse!(Post, author1: [id: [in: [id1, id2]]])
|
2020-06-22 15:26:47 +12:00
|
|
|
|
2020-07-09 18:55:09 +12:00
|
|
|
candidate = Filter.parse!(Post, author1: id1)
|
2020-06-22 15:26:47 +12:00
|
|
|
|
|
|
|
assert Filter.strict_subset_of?(filter, candidate)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "understands relationship filter subsets when a value coincides with the join field" do
|
2021-02-24 06:27:49 +13:00
|
|
|
id1 = Ash.UUID.generate()
|
|
|
|
id2 = Ash.UUID.generate()
|
2020-07-09 18:55:09 +12:00
|
|
|
filter = Filter.parse!(Post, author1: [id: [in: [id1, id2]]])
|
2020-06-22 15:26:47 +12:00
|
|
|
|
2020-07-09 18:55:09 +12:00
|
|
|
candidate = Filter.parse!(Post, author1_id: id1)
|
2020-06-22 15:26:47 +12:00
|
|
|
|
|
|
|
assert Filter.strict_subset_of?(filter, candidate)
|
|
|
|
end
|
2022-11-22 12:32:25 +13:00
|
|
|
|
2024-06-30 23:26:25 +12:00
|
|
|
test "allows to omit the join field for belongs_to relationships" do
|
|
|
|
id1 = Ash.UUID.generate()
|
|
|
|
id2 = Ash.UUID.generate()
|
|
|
|
filter = Filter.parse!(Post, author1: [in: [id1, id2]])
|
|
|
|
|
|
|
|
candidate = Filter.parse!(Post, author1: id1)
|
|
|
|
|
|
|
|
assert Filter.strict_subset_of?(filter, candidate)
|
2024-07-11 04:14:27 +12:00
|
|
|
|
|
|
|
# aggregate
|
|
|
|
assert Filter.parse!(Post, author1: [count_of_posts: 0])
|
|
|
|
|
|
|
|
# calculation
|
|
|
|
assert Filter.parse!(Post, author1: [has_friend: true])
|
|
|
|
|
|
|
|
# relationship
|
|
|
|
assert Filter.parse!(Post, author1: [profile: id1])
|
|
|
|
|
|
|
|
# combinations
|
|
|
|
assert Filter.parse!(Post, author1: [eq: 1, has_friend: true])
|
2024-06-30 23:26:25 +12:00
|
|
|
end
|
|
|
|
|
2022-11-22 12:32:25 +13:00
|
|
|
test "raises an error if the underlying parse returns an error" do
|
|
|
|
filter = Filter.parse!(Post, points: 1)
|
|
|
|
|
|
|
|
err =
|
2024-03-28 09:06:40 +13:00
|
|
|
assert_raise(Ash.Error.Query.NoSuchField, fn ->
|
2022-11-22 12:32:25 +13:00
|
|
|
Filter.add_to_filter!(filter, bad_field: "bad field")
|
|
|
|
end)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
[bread_crumbs] = err.bread_crumbs
|
|
|
|
assert bread_crumbs =~ "parsing addition of filter statement: [bad_field: \"bad field\"]"
|
|
|
|
assert bread_crumbs =~ ", to resource: " <> (Post |> Module.split() |> Enum.join("."))
|
2022-11-22 12:32:25 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "parse!" do
|
|
|
|
test "raises an error if the statement is invalid" do
|
|
|
|
err =
|
|
|
|
assert_raise(Ash.Error.Invalid, fn ->
|
2023-12-17 04:14:02 +13:00
|
|
|
Filter.parse!(Post, flarb: 1)
|
2022-11-22 12:32:25 +13:00
|
|
|
end)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
[bread_crumbs] = err.bread_crumbs
|
|
|
|
assert bread_crumbs =~ "parsing addition of filter statement: [flarb: 1]"
|
|
|
|
assert bread_crumbs =~ ", to resource: " <> (Post |> Module.split() |> Enum.join("."))
|
2022-11-22 12:32:25 +13:00
|
|
|
|
|
|
|
[inner_error] = err.errors
|
2024-03-28 09:06:40 +13:00
|
|
|
[inner_error_context] = inner_error.bread_crumbs
|
2023-12-17 04:14:02 +13:00
|
|
|
assert inner_error_context =~ "parsing addition of filter statement: [flarb: 1]"
|
2022-11-22 12:32:25 +13:00
|
|
|
|
|
|
|
assert inner_error_context =~
|
|
|
|
", to resource: " <> (Post |> Module.split() |> Enum.join("."))
|
|
|
|
end
|
2020-06-19 14:59:30 +12:00
|
|
|
end
|
2020-09-20 07:46:34 +12:00
|
|
|
|
2020-12-30 18:49:32 +13:00
|
|
|
describe "parse_input" do
|
|
|
|
test "parse_input works when no private attributes are used" do
|
|
|
|
Ash.Filter.parse_input!(Profile, bio: "foo")
|
|
|
|
end
|
|
|
|
|
|
|
|
test "parse_input fails when a private attribute is used" do
|
|
|
|
Ash.Filter.parse!(Profile, private: "private")
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert_raise(Ash.Error.Query.NoSuchField, fn ->
|
2020-12-30 18:49:32 +13:00
|
|
|
Ash.Filter.parse_input!(Profile, private: "private")
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-20 07:46:34 +12:00
|
|
|
describe "base_filter" do
|
|
|
|
test "resources that apply to the base filter are returned" do
|
|
|
|
%{id: id} =
|
|
|
|
SoftDeletePost
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{})
|
|
|
|
|> Ash.create!()
|
2020-09-20 07:46:34 +12:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert [%{id: ^id}] = Ash.read!(SoftDeletePost)
|
2020-09-20 07:46:34 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
test "resources that don't apply to the base filter are not returned" do
|
|
|
|
SoftDeletePost
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{})
|
|
|
|
|> Ash.create!()
|
|
|
|
|> Ash.destroy!()
|
2020-09-20 07:46:34 +12:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert [] = Ash.read!(SoftDeletePost)
|
2020-09-20 07:46:34 +12:00
|
|
|
end
|
|
|
|
end
|
2021-01-22 09:21:58 +13:00
|
|
|
|
2021-01-24 16:28:56 +13:00
|
|
|
describe "contains/2" do
|
|
|
|
test "works for simple strings" do
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "foobar"})
|
|
|
|
|> Ash.create!()
|
2021-01-24 16:28:56 +13:00
|
|
|
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "bazbuz"})
|
|
|
|
|> Ash.create!()
|
2021-01-24 16:28:56 +13:00
|
|
|
|
|
|
|
assert [%{title: "foobar"}] =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(contains(title, "oba"))
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-01-24 16:28:56 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "works for simple strings with a case insensitive search term" do
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "foobar"})
|
|
|
|
|> Ash.create!()
|
2021-01-24 16:28:56 +13:00
|
|
|
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "bazbuz"})
|
|
|
|
|> Ash.create!()
|
2021-01-24 16:28:56 +13:00
|
|
|
|
|
|
|
assert [%{title: "foobar"}] =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(contains(title, ^%Ash.CiString{string: "OBA"}))
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-01-24 16:28:56 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "works for case insensitive strings" do
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{category: "foobar"})
|
|
|
|
|> Ash.create!()
|
2021-01-24 16:28:56 +13:00
|
|
|
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{category: "bazbuz"})
|
|
|
|
|> Ash.create!()
|
2021-01-24 16:28:56 +13:00
|
|
|
|
|
|
|
assert [%{category: %Ash.CiString{string: "foobar"}}] =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(contains(category, "OBA"))
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-01-24 16:28:56 +13:00
|
|
|
end
|
2022-09-15 05:28:42 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
describe "length/1" do
|
2022-09-15 16:10:01 +12:00
|
|
|
test "with an attribute" do
|
2022-09-15 05:28:42 +12:00
|
|
|
user1 =
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{roles: [:user]})
|
|
|
|
|> Ash.create!()
|
2022-09-15 05:28:42 +12:00
|
|
|
|
|
|
|
_user2 =
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{roles: []})
|
|
|
|
|> Ash.create!()
|
2022-09-15 05:28:42 +12:00
|
|
|
|
|
|
|
user1_id = user1.id
|
|
|
|
|
|
|
|
assert [%User{id: ^user1_id}] =
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(length(roles) > 0)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-09-15 05:28:42 +12:00
|
|
|
end
|
|
|
|
|
2022-09-15 16:10:01 +12:00
|
|
|
test "with an explicit list" do
|
|
|
|
user1 =
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{roles: [:user]})
|
|
|
|
|> Ash.create!()
|
2022-09-15 16:10:01 +12:00
|
|
|
|
|
|
|
user1_id = user1.id
|
|
|
|
explicit_list = [:foo]
|
|
|
|
|
|
|
|
assert [%User{id: ^user1_id}] =
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(length(^explicit_list) > 0)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-09-15 16:10:01 +12:00
|
|
|
|
|
|
|
assert [] =
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(length(^explicit_list) > 1)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-09-15 16:10:01 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
test "when nil" do
|
2022-09-15 05:28:42 +12:00
|
|
|
user1 =
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{roles: [:user]})
|
|
|
|
|> Ash.create!()
|
2022-09-15 05:28:42 +12:00
|
|
|
|
|
|
|
_user2 =
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create)
|
|
|
|
|> Ash.create!()
|
2022-09-15 05:28:42 +12:00
|
|
|
|
|
|
|
user1_id = user1.id
|
|
|
|
|
|
|
|
assert [%User{id: ^user1_id}] =
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(length(roles || []) > 0)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-09-15 05:28:42 +12:00
|
|
|
end
|
2022-09-15 16:10:01 +12:00
|
|
|
|
|
|
|
test "with bad input" do
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "fred"})
|
|
|
|
|> Ash.create!()
|
2022-09-15 16:10:01 +12:00
|
|
|
|
|
|
|
assert_raise(Ash.Error.Unknown, fn ->
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(length(name) > 0)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-09-15 16:10:01 +12:00
|
|
|
end)
|
|
|
|
end
|
2021-01-24 16:28:56 +13:00
|
|
|
end
|
|
|
|
|
2022-10-25 02:27:18 +13:00
|
|
|
describe "get_path/2" do
|
|
|
|
test "it can be used by name" do
|
|
|
|
profile =
|
|
|
|
Profile
|
|
|
|
|> Ash.Changeset.for_create(:create, %{embedded_bio: %{title: "fred"}})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-10-25 02:27:18 +13:00
|
|
|
|
|
|
|
profile_id = profile.id
|
|
|
|
|
|
|
|
Profile
|
|
|
|
|> Ash.Changeset.for_create(:create, %{embedded_bio: %{title: "george"}})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-10-25 02:27:18 +13:00
|
|
|
|
|
|
|
assert [%{id: ^profile_id}] =
|
|
|
|
Profile
|
|
|
|
|> Ash.Query.filter(get_path(embedded_bio, :title) == "fred")
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-10-25 02:27:18 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "it can be used with arguments" do
|
|
|
|
profile =
|
|
|
|
Profile
|
|
|
|
|> Ash.Changeset.for_create(:create, %{embedded_bio: %{title: "fred"}})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-10-25 02:27:18 +13:00
|
|
|
|
|
|
|
profile_id = profile.id
|
|
|
|
|
|
|
|
Profile
|
|
|
|
|> Ash.Changeset.for_create(:create, %{embedded_bio: %{title: "george"}})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-10-25 02:27:18 +13:00
|
|
|
|
|
|
|
assert [%{id: ^profile_id}] =
|
|
|
|
Profile
|
|
|
|
|> Ash.Query.for_read(:get_path_search, %{input: %{title: "fred"}})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-10-25 02:27:18 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-15 09:08:27 +12:00
|
|
|
test "errors are not produced by eager evaluation" do
|
|
|
|
assert [] =
|
|
|
|
User
|
|
|
|
|> Ash.Query.for_read(:with_invalid_value_behind_is_nil_check, %{})
|
|
|
|
|> Ash.read!()
|
|
|
|
end
|
|
|
|
|
2021-01-22 09:21:58 +13:00
|
|
|
describe "calls in filters" do
|
|
|
|
test "calls are evaluated and can be used in predicates" do
|
|
|
|
post1 =
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{title: "title1", contents: "contents1", points: 2})
|
|
|
|
|> Ash.create!()
|
2021-01-22 09:21:58 +13:00
|
|
|
|
|
|
|
post_id = post1.id
|
|
|
|
|
|
|
|
assert [%Post{id: ^post_id}] =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(points + 1 == 3)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "function calls are evaluated properly" do
|
|
|
|
post1 =
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
2022-08-18 11:18:03 +12:00
|
|
|
title: "title1",
|
|
|
|
approved_at: DateTime.new!(Date.utc_today() |> Date.add(-7), Time.utc_now())
|
|
|
|
})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2021-01-22 09:21:58 +13:00
|
|
|
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
2022-08-18 11:18:03 +12:00
|
|
|
title: "title1",
|
|
|
|
approved_at: DateTime.new!(Date.utc_today() |> Date.add(-7 * 4), Time.utc_now())
|
|
|
|
})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2021-01-22 09:21:58 +13:00
|
|
|
|
|
|
|
post_id = post1.id
|
|
|
|
|
|
|
|
assert [%Post{id: ^post_id}] =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(approved_at > ago(2, :week))
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
2022-10-19 08:19:28 +13:00
|
|
|
|
|
|
|
test "now() evaluates to the current datetime" do
|
|
|
|
post1 =
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
2022-10-19 08:19:28 +13:00
|
|
|
title: "title1",
|
|
|
|
approved_at: DateTime.new!(Date.utc_today() |> Date.add(7), Time.utc_now())
|
|
|
|
})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-10-19 08:19:28 +13:00
|
|
|
|
|
|
|
Post
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
2022-10-19 08:19:28 +13:00
|
|
|
title: "title1",
|
|
|
|
approved_at: DateTime.new!(Date.utc_today() |> Date.add(-7), Time.utc_now())
|
|
|
|
})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2022-10-19 08:19:28 +13:00
|
|
|
|
|
|
|
post_id = post1.id
|
|
|
|
|
|
|
|
assert [%Post{id: ^post_id}] =
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(approved_at > now())
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-10-19 08:19:28 +13:00
|
|
|
end
|
2021-01-22 09:21:58 +13:00
|
|
|
end
|
2022-11-17 05:47:43 +13:00
|
|
|
|
2022-11-17 06:00:43 +13:00
|
|
|
test "using tuple instead of keyword list does not raise an error" do
|
|
|
|
Post
|
|
|
|
|> Ash.Query.filter(id: {:in, [Ash.UUID.generate()]})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-11-17 05:47:43 +13:00
|
|
|
end
|
2024-02-01 06:11:36 +13:00
|
|
|
|
|
|
|
test "parsing input with embedded references works" do
|
|
|
|
Profile
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
2024-02-01 06:11:36 +13:00
|
|
|
embedded_bio: %{title: "Mr."}
|
|
|
|
})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2024-02-01 06:11:36 +13:00
|
|
|
|
|
|
|
Profile
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
2024-02-01 06:11:36 +13:00
|
|
|
embedded_bio: %{title: "Dr."}
|
|
|
|
})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2024-02-01 06:11:36 +13:00
|
|
|
|
|
|
|
assert [%{embedded_bio: %{title: "Dr."}}] =
|
|
|
|
Profile
|
2024-02-01 09:36:36 +13:00
|
|
|
|> Ash.Query.filter_input(%{embedded_bio: %{at_path: [:title], eq: "Dr."}})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2024-02-01 06:11:36 +13:00
|
|
|
end
|
2019-12-16 13:20:44 +13:00
|
|
|
end
|