ash/test/filter/filter_interaction_test.exs

312 lines
6.4 KiB
Elixir
Raw Normal View History

2020-06-30 09:20:29 +12:00
defmodule Ash.Test.Filter.FilterInteractionTest do
use ExUnit.Case, async: false
import Ash.Changeset
import ExUnit.CaptureLog
2022-06-22 13:00:47 +12:00
import Ash.Test
2020-06-30 09:42:01 +12:00
alias Ash.DataLayer.Mnesia
2020-10-08 18:22:55 +13:00
require Ash.Query
2020-06-30 09:20:29 +12:00
defmodule Profile do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Ets
actions do
defaults [:create, :read, :update, :destroy]
2020-06-30 09:20:29 +12:00
end
attributes do
uuid_primary_key :id
attribute(:bio, :string)
2020-06-30 09:20:29 +12:00
end
relationships do
belongs_to(:user, Ash.Test.Filter.FilterInteractionTest.User)
2020-06-30 09:20:29 +12:00
end
end
defmodule User do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Ets
actions do
defaults [:create, :read, :update, :destroy]
2020-06-30 09:20:29 +12:00
end
attributes do
uuid_primary_key :id
attribute(:name, :string)
attribute(:allow_second_author, :boolean)
2020-06-30 09:20:29 +12:00
end
relationships do
has_many(:posts, Ash.Test.Filter.FilterInteractionTest.Post,
destination_attribute: :author_id
)
2020-06-30 09:20:29 +12:00
has_many(:second_posts, Ash.Test.Filter.FilterInteractionTest.Post,
destination_attribute: :author_id
)
2020-06-30 09:20:29 +12:00
has_one(:profile, Profile, destination_attribute: :user_id)
2020-06-30 09:20:29 +12:00
end
end
defmodule PostLink do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Mnesia
actions do
defaults [:create, :read, :update, :destroy]
2020-06-30 09:20:29 +12:00
end
relationships do
belongs_to(:source_post, Ash.Test.Filter.FilterInteractionTest.Post,
primary_key?: true,
required?: true
)
belongs_to(:destination_post, Ash.Test.Filter.FilterInteractionTest.Post,
primary_key?: true,
required?: true
)
2020-06-30 09:20:29 +12:00
end
end
defmodule Post do
@moduledoc false
use Ash.Resource, data_layer: Ash.DataLayer.Mnesia
actions do
defaults [:create, :read, :update, :destroy]
2020-06-30 09:20:29 +12:00
end
attributes do
uuid_primary_key :id
attribute(:title, :string)
attribute(:contents, :string)
attribute(:points, :integer)
2020-06-30 09:20:29 +12:00
end
relationships do
belongs_to(:author, User,
destination_attribute: :id,
source_attribute: :author_id
)
2020-06-30 09:20:29 +12:00
many_to_many(:related_posts, __MODULE__,
2020-06-30 09:20:29 +12:00
through: PostLink,
source_attribute_on_join_resource: :source_post_id,
destination_attribute_on_join_resource: :destination_post_id
)
2020-06-30 09:20:29 +12:00
end
end
defmodule Registry do
@moduledoc false
use Ash.Registry
entries do
entry(Post)
entry(User)
entry(Profile)
entry(PostLink)
end
end
2020-06-30 09:20:29 +12:00
defmodule Api do
@moduledoc false
use Ash.Api
resources do
registry Registry
2020-06-30 09:20:29 +12:00
end
end
setup do
capture_log(fn ->
Mnesia.start(Api)
end)
2020-06-30 09:20:29 +12:00
on_exit(fn ->
capture_log(fn ->
:mnesia.stop()
:mnesia.delete_schema([node()])
end)
2020-06-30 09:20:29 +12:00
end)
end
test "mnesia data layer sanity test" do
2020-07-12 18:25:53 +12:00
post =
Post
|> new(%{title: "best"})
2020-07-12 18:25:53 +12:00
|> Api.create!()
2022-06-22 13:00:47 +12:00
|> strip_metadata()
2020-06-30 09:20:29 +12:00
2022-06-22 13:00:47 +12:00
assert [^post] = strip_metadata(Api.read!(Post))
2020-06-30 09:20:29 +12:00
post |> new(%{title: "worst"}) |> Api.update!()
2020-06-30 09:20:29 +12:00
new_post = %{post | title: "worst"}
2022-06-22 13:00:47 +12:00
assert [^new_post] = strip_metadata(Api.read!(Post))
2020-06-30 09:20:29 +12:00
Api.destroy!(post)
assert [] = Api.read!(Post)
end
describe "cross data layer filtering" do
test "it properly filters with a simple filter" do
2022-06-22 13:00:47 +12:00
author =
User
|> new(%{name: "best author"})
|> Api.create!()
2020-06-30 09:20:29 +12:00
2022-06-22 13:00:47 +12:00
post1 =
Post
|> new(%{title: "best"})
|> replace_relationship(:author, author)
|> Api.create!()
feat: freeform expressions feat: validatiosn in actions feat: query arguments feat: add `Ash.Query.for_read/3` feat: return changeset with API errors feat: add case insensitive string `CiString`/`:ci_string` feat: support `context/1` and `arg/1` in filter templates feat: support targeting notifications with the `for` option feat: add `ago/2` query function feat: add basic arithmetic operators (+, *, -, /) feat: `sensitive?` option for attributes feat: `sensitive?` option for arguments feat: `private` arguments, which can’t be set using `for_<action>` feat: add `prevent_change` which will erase changes just before the changeset is committed feat: add `match?` validation that supports a custom error message feat: add `interval` type to support `ago/2` function feat: add `url_encoded_binary` type feat: add `function` type improvement: `changing?` is now a validation improvement: add `Transformer.get_persisted/3` improvement: add `api` field to `Notification` improvement: standardize errors, add `to_error_class` improvement: use `Comp` everywhere Improvement: use action on changeset if set by `for_<action_type>` improvement: `action_failed?` field on change sets improvement: remove ability for data layers to add operators (for now at least) Improvement: Changeset.apply_attributes/2 now returns an error tuple Improvement: add a bunch of new/informative errors improvement: runtime filter now uses left join logic (a naive implementation of it) improvement: support more filter templates in resources Improvement: basic/naive type system for operators/functions Fix: properly expand module aliases for options w/o compile time dependency chore(engine): track changeset changes for the request with `manage_changeset?: true`
2021-01-22 09:21:58 +13:00
2022-06-22 13:00:47 +12:00
post1_id = post1.id
2022-06-22 13:00:47 +12:00
Post
|> new(%{title: "worst"})
|> Api.create!()
2020-06-30 09:20:29 +12:00
query =
Post
2020-10-08 18:22:55 +13:00
|> Ash.Query.filter(author.name == "best author")
2020-06-30 09:20:29 +12:00
2022-06-22 13:00:47 +12:00
assert [%{id: ^post1_id}] = Api.read!(query)
2020-06-30 09:20:29 +12:00
end
test "parallelizable filtering of related resources with a data layer that cannot join" do
2020-07-12 18:25:53 +12:00
post2 =
Post
|> new(%{title: "two"})
2020-07-12 18:25:53 +12:00
|> Api.create!()
Post
|> new(%{title: "three"})
2020-07-12 18:25:53 +12:00
|> Api.create!()
2020-06-30 09:20:29 +12:00
post1 =
2020-07-12 18:25:53 +12:00
Post
|> new(%{title: "one"})
2020-07-12 18:25:53 +12:00
|> replace_relationship(:related_posts, [post2])
|> Api.create!()
2020-06-30 09:20:29 +12:00
query =
Post
2020-10-08 18:22:55 +13:00
|> Ash.Query.filter(related_posts.title == "two")
2020-06-30 09:20:29 +12:00
post1_id = post1.id
feat: freeform expressions feat: validatiosn in actions feat: query arguments feat: add `Ash.Query.for_read/3` feat: return changeset with API errors feat: add case insensitive string `CiString`/`:ci_string` feat: support `context/1` and `arg/1` in filter templates feat: support targeting notifications with the `for` option feat: add `ago/2` query function feat: add basic arithmetic operators (+, *, -, /) feat: `sensitive?` option for attributes feat: `sensitive?` option for arguments feat: `private` arguments, which can’t be set using `for_<action>` feat: add `prevent_change` which will erase changes just before the changeset is committed feat: add `match?` validation that supports a custom error message feat: add `interval` type to support `ago/2` function feat: add `url_encoded_binary` type feat: add `function` type improvement: `changing?` is now a validation improvement: add `Transformer.get_persisted/3` improvement: add `api` field to `Notification` improvement: standardize errors, add `to_error_class` improvement: use `Comp` everywhere Improvement: use action on changeset if set by `for_<action_type>` improvement: `action_failed?` field on change sets improvement: remove ability for data layers to add operators (for now at least) Improvement: Changeset.apply_attributes/2 now returns an error tuple Improvement: add a bunch of new/informative errors improvement: runtime filter now uses left join logic (a naive implementation of it) improvement: support more filter templates in resources Improvement: basic/naive type system for operators/functions Fix: properly expand module aliases for options w/o compile time dependency chore(engine): track changeset changes for the request with `manage_changeset?: true`
2021-01-22 09:21:58 +13:00
assert [%{id: ^post1_id}] = Api.read!(query)
feat: freeform expressions feat: validatiosn in actions feat: query arguments feat: add `Ash.Query.for_read/3` feat: return changeset with API errors feat: add case insensitive string `CiString`/`:ci_string` feat: support `context/1` and `arg/1` in filter templates feat: support targeting notifications with the `for` option feat: add `ago/2` query function feat: add basic arithmetic operators (+, *, -, /) feat: `sensitive?` option for attributes feat: `sensitive?` option for arguments feat: `private` arguments, which can’t be set using `for_<action>` feat: add `prevent_change` which will erase changes just before the changeset is committed feat: add `match?` validation that supports a custom error message feat: add `interval` type to support `ago/2` function feat: add `url_encoded_binary` type feat: add `function` type improvement: `changing?` is now a validation improvement: add `Transformer.get_persisted/3` improvement: add `api` field to `Notification` improvement: standardize errors, add `to_error_class` improvement: use `Comp` everywhere Improvement: use action on changeset if set by `for_<action_type>` improvement: `action_failed?` field on change sets improvement: remove ability for data layers to add operators (for now at least) Improvement: Changeset.apply_attributes/2 now returns an error tuple Improvement: add a bunch of new/informative errors improvement: runtime filter now uses left join logic (a naive implementation of it) improvement: support more filter templates in resources Improvement: basic/naive type system for operators/functions Fix: properly expand module aliases for options w/o compile time dependency chore(engine): track changeset changes for the request with `manage_changeset?: true`
2021-01-22 09:21:58 +13:00
end
test "parallelizable filter with filtered loads" do
2020-07-12 18:25:53 +12:00
post2 =
Post
|> new(%{title: "two"})
2020-07-12 18:25:53 +12:00
|> Api.create!()
post3 =
Post
|> new(%{title: "three"})
2020-07-12 18:25:53 +12:00
|> Api.create!()
post1 =
2020-07-12 18:25:53 +12:00
Post
|> new(%{title: "one"})
2020-07-12 18:25:53 +12:00
|> replace_relationship(:related_posts, [post2, post3])
|> Api.create!()
post2
|> Api.load!(:related_posts)
posts_query =
Post
2020-10-08 18:22:55 +13:00
|> Ash.Query.filter(title == "three")
query =
Post
2020-10-08 18:22:55 +13:00
|> Ash.Query.filter(related_posts.title == "two")
|> Ash.Query.load(related_posts: posts_query)
post1_id = post1.id
post3_id = post3.id
assert [%{id: ^post1_id, related_posts: [%{id: ^post3_id}]}] = Api.read!(query)
end
2022-09-07 10:02:01 +12:00
test "exists/2 in the same data layer" do
post2 =
Post
|> new(%{title: "two"})
|> Api.create!()
post3 =
Post
|> new(%{title: "three"})
|> Api.create!()
post1 =
Post
|> new(%{title: "one"})
|> replace_relationship(:related_posts, [post2, post3])
|> Api.create!()
Post
|> new(%{title: "four"})
|> replace_relationship(:related_posts, [post3])
|> Api.create!()
post2
|> Api.load!(:related_posts)
query =
Post
|> Ash.Query.filter(exists(related_posts, title == "two"))
post1_id = post1.id
assert [%{id: ^post1_id}] = Api.read!(query)
end
test "exists/2 across data layers" do
author =
User
|> new(%{name: "best author"})
|> Api.create!()
author2 =
User
|> new(%{name: "worst author"})
|> Api.create!()
post1 =
Post
|> new(%{title: "best"})
|> replace_relationship(:author, author)
|> Api.create!()
post1_id = post1.id
Post
|> new(%{title: "worst"})
|> replace_relationship(:author, author2)
|> Api.create!()
query = Ash.Query.filter(Post, exists(author, contains(name, "best")))
assert [%{id: ^post1_id}] = Api.read!(query)
end
2020-06-30 09:20:29 +12:00
end
end