improvement!: 3.0 (#227)

* WIP

* chore: fix mix.lock merge issues

* improvement: upgrade to 3.0

* chore: remove `repo.to_tenant`

* chore: continue removal of unnecessary helper

* chore: use `Ash.ToTenant`
This commit is contained in:
Zach Daniel 2024-03-27 16:52:28 -04:00 committed by GitHub
parent ec75b41dbe
commit 37cc01957d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
112 changed files with 3581 additions and 3975 deletions

View file

@ -1,10 +1,9 @@
--- ---
name: Proposal name: Proposal
about: Suggest an idea for this project about: Suggest an idea for this project
title: '' title: ""
labels: enhancement, needs review labels: enhancement, needs review
assignees: '' assignees: ""
--- ---
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
@ -29,7 +28,7 @@ For example
Or Or
```elixir ```elixir
Api.read(:resource, bar: 10) # <- Adding `bar` here would cause <x> Ash.read(Resource, bar: 10) # <- Adding `bar` here would cause <x>
``` ```
**Additional context** **Additional context**

View file

@ -11,7 +11,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
postgres-version: ["10", "12", "14", "16"] postgres-version: ["14", "15", "16"]
uses: ash-project/ash/.github/workflows/ash-ci.yml@main uses: ash-project/ash/.github/workflows/ash-ci.yml@main
with: with:
postgres: true postgres: true

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ Then, configure each of your `Ash.Resource` resources by adding `use Ash.Resourc
```elixir ```elixir
defmodule MyApp.SomeResource do defmodule MyApp.SomeResource do
use Ash.Resource, data_layer: AshPostgres.DataLayer use Ash.Resource, domain: MyDomain, data_layer: AshPostgres.DataLayer
postgres do postgres do
repo MyApp.Repo repo MyApp.Repo

View file

@ -1,4 +1,4 @@
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.{Domain, Post}
ten_rows = ten_rows =
1..10 1..10
@ -25,7 +25,7 @@ hundred_thousand_rows =
end) end)
# do them both once to warm things up # do them both once to warm things up
Api.bulk_create(ten_rows, Post, :create, Ash.bulk_create(ten_rows, Post, :create,
batch_size: 10, batch_size: 10,
max_concurrency: 2 max_concurrency: 2
) )
@ -39,13 +39,13 @@ batch_size = 200
Benchee.run( Benchee.run(
%{ %{
"ash sync": fn input -> "ash sync": fn input ->
%{error_count: 0} = Api.bulk_create(input, Post, :create, %{error_count: 0} = Ash.bulk_create(input, Post, :create,
batch_size: batch_size, batch_size: batch_size,
transaction: false transaction: false
) )
end, end,
"ash sync assuming casted": fn input -> "ash sync assuming casted": fn input ->
%{error_count: 0} = Api.bulk_create(input, Post, :create, %{error_count: 0} = Ash.bulk_create(input, Post, :create,
batch_size: batch_size, batch_size: batch_size,
transaction: false, transaction: false,
assume_casted?: true assume_casted?: true
@ -62,7 +62,7 @@ Benchee.run(
input input
|> Stream.chunk_every(batch_size) |> Stream.chunk_every(batch_size)
|> Task.async_stream(fn batch -> |> Task.async_stream(fn batch ->
%{error_count: 0} = Api.bulk_create(batch, Post, :create, %{error_count: 0} = Ash.bulk_create(batch, Post, :create,
transaction: false transaction: false
) )
end, max_concurrency: max_concurrency, timeout: :infinity) end, max_concurrency: max_concurrency, timeout: :infinity)
@ -72,7 +72,7 @@ Benchee.run(
input input
|> Stream.chunk_every(batch_size) |> Stream.chunk_every(batch_size)
|> Task.async_stream(fn batch -> |> Task.async_stream(fn batch ->
%{error_count: 0} = Api.bulk_create(batch, Post, :create, %{error_count: 0} = Ash.bulk_create(batch, Post, :create,
transaction: false, transaction: false,
assume_casted?: true assume_casted?: true
) )
@ -80,14 +80,14 @@ Benchee.run(
|> Stream.run() |> Stream.run()
end, end,
"ash using own async option": fn input -> "ash using own async option": fn input ->
%{error_count: 0} = Api.bulk_create(input, Post, :create, %{error_count: 0} = Ash.bulk_create(input, Post, :create,
transaction: false, transaction: false,
max_concurrency: max_concurrency, max_concurrency: max_concurrency,
batch_size: batch_size batch_size: batch_size
) )
end, end,
"ash using own async option assuming casted": fn input -> "ash using own async option assuming casted": fn input ->
%{error_count: 0} = Api.bulk_create(input, Post, :create, %{error_count: 0} = Ash.bulk_create(input, Post, :create,
transaction: false, transaction: false,
assume_casted?: true, assume_casted?: true,
max_concurrency: max_concurrency, max_concurrency: max_concurrency,

View file

@ -15,8 +15,8 @@ if Mix.env() == :dev do
end end
if Mix.env() == :test do if Mix.env() == :test do
config :ash, :validate_api_resource_inclusion?, false config :ash, :validate_domain_resource_inclusion?, false
config :ash, :validate_api_config_inclusion?, false config :ash, :validate_domain_config_inclusion?, false
config :ash_postgres, AshPostgres.TestRepo, config :ash_postgres, AshPostgres.TestRepo,
username: "postgres", username: "postgres",
@ -42,10 +42,10 @@ if Mix.env() == :test do
config :ash_postgres, config :ash_postgres,
ecto_repos: [AshPostgres.TestRepo, AshPostgres.TestNoSandboxRepo], ecto_repos: [AshPostgres.TestRepo, AshPostgres.TestNoSandboxRepo],
ash_apis: [ ash_domains: [
AshPostgres.Test.Api, AshPostgres.Test.Domain,
AshPostgres.MultitenancyTest.Api, AshPostgres.MultitenancyTest.Domain,
AshPostgres.Test.ComplexCalculations.Api AshPostgres.Test.ComplexCalculations.Domain
] ]
config :logger, level: :warning config :logger, level: :warning

View file

@ -38,7 +38,7 @@ defmodule Helpdesk.Support.Ticket.Relationships.TicketsAboveThreshold do
|> Enum.group_by(& &1.representative_id)} |> Enum.group_by(& &1.representative_id)}
end end
# query is the "source" query that is being built. # query is the "source" query that is being built.
# _opts are options provided to the manual relationship, i.e `{Manual, opt: :val}` # _opts are options provided to the manual relationship, i.e `{Manual, opt: :val}`

View file

@ -25,7 +25,7 @@ AshPostgres is built on top of ecto, so much of its behavior is pass-through/orc
For more information on generating migrations, see the module documentation here: For more information on generating migrations, see the module documentation here:
`Mix.Tasks.AshPostgres.GenerateMigrations`, or run `mix help ash_postgres.generate_migrations` `Mix.Tasks.AshPostgres.GenerateMigrations`, or run `mix help ash_postgres.generate_migrations`
For running your migrations, there is a mix task that will find all of the repos configured in your apis and run their For running your migrations, there is a mix task that will find all of the repos configured in your domains and run their
migrations. It is a thin wrapper around `mix ecto.migrate`. Ours is called `mix ash_postgres.migrate` migrations. It is a thin wrapper around `mix ecto.migrate`. Ours is called `mix ash_postgres.migrate`
If you want to run or rollback individual migrations, use the corresponding If you want to run or rollback individual migrations, use the corresponding
@ -146,17 +146,17 @@ Tasks that need to be executed in the released application (because mix is not p
end end
defp repos do defp repos do
apis() domains()
|> Enum.flat_map(fn api -> |> Enum.flat_map(fn domain ->
api domain
|> Ash.Api.Info.resources() |> Ash.Domain.Info.resources()
|> Enum.map(&AshPostgres.DataLayer.Info.repo/1) |> Enum.map(&AshPostgres.DataLayer.Info.repo/1)
end) end)
|> Enum.uniq() |> Enum.uniq()
end end
defp apis do defp domains do
Application.fetch_env!(@app, :ash_apis) Application.fetch_env!(@app, :ash_domains)
end end
defp load_app do defp load_app do

View file

@ -5,6 +5,7 @@ To support leveraging the same resource backed by multiple tables (useful for th
```elixir ```elixir
defmodule MyApp.Reaction do defmodule MyApp.Reaction do
use Ash.Resource, use Ash.Resource,
domain: MyDomain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
postgres do postgres do
@ -12,9 +13,9 @@ defmodule MyApp.Reaction do
end end
attributes do attributes do
attribute(:resource_id, :uuid) attribute :resource_id, :uuid, public?: true
end end
... ...
end end
``` ```
@ -24,6 +25,7 @@ Then, in your related resources, you set the table context like so:
```elixir ```elixir
defmodule MyApp.Post do defmodule MyApp.Post do
use Ash.Resource, use Ash.Resource,
domain: MyDomain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
... ...
@ -37,6 +39,7 @@ end
defmodule MyApp.Comment do defmodule MyApp.Comment do
use Ash.Resource, use Ash.Resource,
domain: MyDomain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
... ...
@ -61,6 +64,7 @@ For example:
```elixir ```elixir
defmodule MyApp.Reaction do defmodule MyApp.Reaction do
# ...
actions do actions do
read :for_comments do read :for_comments do
prepare set_context(%{data_layer: %{table: "comment_reactions"}}) prepare set_context(%{data_layer: %{table: "comment_reactions"}})
@ -78,5 +82,5 @@ end
When a migration is marked as `polymorphic? true`, the migration generator will look at When a migration is marked as `polymorphic? true`, the migration generator will look at
all resources that are related to it, that set the `%{data_layer: %{table: "table"}}` context. all resources that are related to it, that set the `%{data_layer: %{table: "table"}}` context.
For each of those, a migration is generated/managed automatically. This means that adding reactions For each of those, a migration is generated/managed automatically. This means that adding reactions
to a new resource is as easy as adding the relationship and table context, and then running to a new resource is as easy as adding the relationship and table context, and then running
`mix ash_postgres.generate_migrations`. `mix ash_postgres.generate_migrations`.

View file

@ -68,7 +68,7 @@ import Config
# This should already have been added in the first # This should already have been added in the first
# getting started guide # getting started guide
config :helpdesk, config :helpdesk,
ash_apis: [Helpdesk.Support] ash_domains: [Helpdesk.Support]
config :helpdesk, config :helpdesk,
ecto_repos: [Helpdesk.Repo] ecto_repos: [Helpdesk.Repo]
@ -155,6 +155,7 @@ Now we can add the data layer to our resources. The basic configuration for a re
# in lib/helpdesk/support/resources/ticket.ex # in lib/helpdesk/support/resources/ticket.ex
use Ash.Resource, use Ash.Resource,
domain: Helpdesk.Support,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
postgres do postgres do
@ -167,6 +168,7 @@ Now we can add the data layer to our resources. The basic configuration for a re
# in lib/helpdesk/support/resources/representative.ex # in lib/helpdesk/support/resources/representative.ex
use Ash.Resource, use Ash.Resource,
domain: Helpdesk.Support,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
postgres do postgres do

View file

@ -499,8 +499,7 @@ defmodule AshPostgres.Aggregate do
end end
defp resource_aggregates_to_aggregates(resource, aggregates) do defp resource_aggregates_to_aggregates(resource, aggregates) do
aggregates Enum.reduce_while(aggregates, {:ok, []}, fn
|> Enum.reduce_while({:ok, []}, fn
%Ash.Query.Aggregate{} = aggregate, {:ok, aggregates} -> %Ash.Query.Aggregate{} = aggregate, {:ok, aggregates} ->
{:cont, {:ok, [aggregate | aggregates]}} {:cont, {:ok, [aggregate | aggregates]}}
@ -523,6 +522,8 @@ defmodule AshPostgres.Aggregate do
default: aggregate.default, default: aggregate.default,
filterable?: aggregate.filterable?, filterable?: aggregate.filterable?,
type: aggregate.type, type: aggregate.type,
sortable?: aggregate.filterable?,
include_nil?: aggregate.include_nil?,
constraints: aggregate.constraints, constraints: aggregate.constraints,
implementation: aggregate.implementation, implementation: aggregate.implementation,
uniq?: aggregate.uniq?, uniq?: aggregate.uniq?,
@ -685,11 +686,11 @@ defmodule AshPostgres.Aggregate do
type: type, type: type,
constraints: constraints constraints: constraints
} -> } ->
{:ok, new_calc} = Ash.Query.Calculation.new(name, module, opts, {type, constraints}) {:ok, new_calc} = Ash.Query.Calculation.new(name, module, opts, type, constraints)
expression = module.expression(opts, aggregate.context) expression = module.expression(opts, aggregate.context)
expression = expression =
Ash.Filter.build_filter_from_template( Ash.Expr.fill_template(
expression, expression,
aggregate.context[:actor], aggregate.context[:actor],
aggregate.context, aggregate.context,
@ -719,11 +720,11 @@ defmodule AshPostgres.Aggregate do
expression = module.expression(opts, aggregate.context) expression = module.expression(opts, aggregate.context)
expression = expression =
Ash.Filter.build_filter_from_template( Ash.Expr.fill_template(
expression, expression,
aggregate.context[:actor], aggregate.context.actor,
aggregate.context, aggregate.context.arguments,
aggregate.context aggregate.context.source_context
) )
{:ok, expression} = {:ok, expression} =
@ -1189,6 +1190,13 @@ defmodule AshPostgres.Aggregate do
has_sort? = has_sort?(aggregate.query) has_sort? = has_sort?(aggregate.query)
array_agg =
if AshPostgres.DataLayer.Info.pg_version_matches?(aggregate.resource, ">= 16.0.0") do
"any_value"
else
"array_agg"
end
{sorted, query} = {sorted, query} =
if has_sort? || first_relationship.sort not in [nil, []] do if has_sort? || first_relationship.sort not in [nil, []] do
{sort, binding} = {sort, binding} =
@ -1211,31 +1219,63 @@ defmodule AshPostgres.Aggregate do
:return :return
) )
question_marks = Enum.map(sort_expr, fn _ -> " ? " end) if aggregate.include_nil? do
question_marks = Enum.map(sort_expr, fn _ -> " ? " end)
{:ok, expr} = {:ok, expr} =
AshPostgres.Functions.Fragment.casted_new( Ash.Query.Function.Fragment.casted_new(
["array_agg(? ORDER BY #{question_marks})", field] ++ sort_expr ["#{array_agg}(? ORDER BY #{question_marks} FILTER (WHERE ? IS NOT NULL))", field] ++
) sort_expr ++ [field]
)
{sort_expr, acc} = {sort_expr, acc} =
AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false)
query = query =
AshPostgres.DataLayer.merge_expr_accumulator(query, acc) AshPostgres.DataLayer.merge_expr_accumulator(query, acc)
{sort_expr, query} {sort_expr, query}
else
question_marks = Enum.map(sort_expr, fn _ -> " ? " end)
{:ok, expr} =
Ash.Query.Function.Fragment.casted_new(
["#{array_agg}(? ORDER BY #{question_marks})", field] ++ sort_expr
)
{sort_expr, acc} =
AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false)
query =
AshPostgres.DataLayer.merge_expr_accumulator(query, acc)
{sort_expr, query}
end
else else
{Ecto.Query.dynamic( case array_agg do
[row], "array_agg" ->
fragment("array_agg(?)", ^field) {Ecto.Query.dynamic(
), query} [row],
fragment("array_agg(?)", ^field)
), query}
"any_value" ->
{Ecto.Query.dynamic(
[row],
fragment("any_value(?)", ^field)
), query}
end
end end
{query, filtered} = {query, filtered} =
filter_field(sorted, query, aggregate, relationship_path, is_single?) filter_field(sorted, query, aggregate, relationship_path, is_single?)
value = Ecto.Query.dynamic(fragment("(?)[1]", ^filtered)) value =
if array_agg == "array_agg" do
Ecto.Query.dynamic(fragment("(?)[1]", ^filtered))
else
filtered
end
with_default = with_default =
if aggregate.default_value do if aggregate.default_value do
@ -1327,10 +1367,26 @@ defmodule AshPostgres.Aggregate do
"" ""
end end
{:ok, expr} = expr =
AshPostgres.Functions.Fragment.casted_new( if aggregate.include_nil? do
["array_agg(#{distinct}? ORDER BY #{question_marks})", field] ++ sort_expr {:ok, expr} =
) Ash.Query.Function.Fragment.casted_new(
[
"array_agg(#{distinct}? ORDER BY #{question_marks} FILTER (WHERE ? IS NOT NULL))",
field
] ++
sort_expr ++ [field]
)
expr
else
{:ok, expr} =
Ash.Query.Function.Fragment.casted_new(
["array_agg(#{distinct}? ORDER BY #{question_marks})", field] ++ sort_expr
)
expr
end
{expr, acc} = {expr, acc} =
AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false) AshPostgres.Expr.dynamic_expr(query, expr, query.__ash_bindings__, false)

View file

@ -73,10 +73,11 @@ defmodule AshPostgres.Calculation do
expression = expression =
Ash.Actions.Read.add_calc_context_to_filter( Ash.Actions.Read.add_calc_context_to_filter(
expression, expression,
calculation.context[:actor], calculation.context.actor,
calculation.context[:authorize?], calculation.context.authorize?,
calculation.context[:tenant], calculation.context.tenant,
calculation.context[:tracer] calculation.context.tracer,
nil
) )
{expr, acc} = {expr, acc} =

View file

@ -384,11 +384,12 @@ defmodule AshPostgres.DataLayer do
use Spark.Dsl.Extension, use Spark.Dsl.Extension,
sections: @sections, sections: @sections,
transformers: [ verifiers: [
AshPostgres.Transformers.ValidateReferences, AshPostgres.Verifiers.VerifyPostgresVersion,
AshPostgres.Transformers.EnsureTableOrPolymorphic, AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates,
AshPostgres.Transformers.PreventMultidimensionalArrayAggregates, AshPostgres.Verifiers.ValidateReferences,
AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchType AshPostgres.Verifiers.PreventAttributeMultitenancyAndNonFullMatchType,
AshPostgres.Verifiers.EnsureTableOrPolymorphic
] ]
def migrate(args) do def migrate(args) do
@ -704,7 +705,7 @@ defmodule AshPostgres.DataLayer do
repo = dynamic_repo(resource, query) repo = dynamic_repo(resource, query)
with_savepoint(repo, query, fn -> with_savepoint(repo, query, fn ->
{:ok, repo.all(query, repo_opts(nil, nil, resource)) |> remap_mapped_fields(query)} {:ok, repo.all(query, repo_opts(repo, nil, nil, resource)) |> remap_mapped_fields(query)}
end) end)
end end
rescue rescue
@ -715,7 +716,7 @@ defmodule AshPostgres.DataLayer do
defp no_table?(%{from: %{source: {"", _}}}), do: true defp no_table?(%{from: %{source: {"", _}}}), do: true
defp no_table?(_), do: false defp no_table?(_), do: false
defp repo_opts(timeout, nil, resource) do defp repo_opts(_repo, timeout, nil, resource) do
if schema = AshPostgres.DataLayer.Info.schema(resource) do if schema = AshPostgres.DataLayer.Info.schema(resource) do
[prefix: schema] [prefix: schema]
else else
@ -724,9 +725,9 @@ defmodule AshPostgres.DataLayer do
|> add_timeout(timeout) |> add_timeout(timeout)
end end
defp repo_opts(timeout, tenant, resource) do defp repo_opts(_repo, timeout, tenant, resource) do
if Ash.Resource.Info.multitenancy_strategy(resource) == :context do if Ash.Resource.Info.multitenancy_strategy(resource) == :context do
[prefix: tenant] [prefix: Ash.ToTenant.to_tenant(resource, tenant)]
else else
if schema = AshPostgres.DataLayer.Info.schema(resource) do if schema = AshPostgres.DataLayer.Info.schema(resource) do
[prefix: schema] [prefix: schema]
@ -748,7 +749,6 @@ defmodule AshPostgres.DataLayer do
config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config()
functions = [ functions = [
AshPostgres.Functions.Fragment,
AshPostgres.Functions.Like, AshPostgres.Functions.Like,
AshPostgres.Functions.ILike AshPostgres.Functions.ILike
] ]
@ -844,7 +844,8 @@ defmodule AshPostgres.DataLayer do
%{} %{}
_ -> _ ->
dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) repo = dynamic_repo(resource, query)
repo.one(query, repo_opts(repo, nil, nil, resource))
end end
{:ok, add_single_aggs(result, resource, original_query, cant_group)} {:ok, add_single_aggs(result, resource, original_query, cant_group)}
@ -880,10 +881,12 @@ defmodule AshPostgres.DataLayer do
|> Ecto.Query.select(%{}) |> Ecto.Query.select(%{})
end end
repo = dynamic_repo(resource, filtered)
Map.put( Map.put(
result || %{}, result || %{},
agg.name, agg.name,
dynamic_repo(resource, filtered).exists?(filtered, repo_opts(nil, nil, resource)) repo.exists?(filtered, repo_opts(repo, nil, nil, resource))
) )
agg, result -> agg, result ->
@ -953,9 +956,11 @@ defmodule AshPostgres.DataLayer do
first_relationship first_relationship
) )
repo = dynamic_repo(resource, query)
Map.merge( Map.merge(
result || %{}, result || %{},
dynamic_repo(resource, query).one(query, repo_opts(nil, nil, resource)) repo.one(query, repo_opts(repo, nil, nil, resource))
) )
end) end)
end end
@ -1049,9 +1054,11 @@ defmodule AshPostgres.DataLayer do
%{} %{}
_ -> _ ->
dynamic_repo(source_resource, query).one( repo = dynamic_repo(source_resource, query)
repo.one(
query, query,
repo_opts(nil, nil, source_resource) repo_opts(repo, nil, nil, source_resource)
) )
end end
@ -1082,10 +1089,12 @@ defmodule AshPostgres.DataLayer do
|> elem(0) |> elem(0)
|> Map.get(:resource) |> Map.get(:resource)
repo = dynamic_repo(source_resource, lateral_join_query)
results = results =
dynamic_repo(source_resource, lateral_join_query).all( repo.all(
lateral_join_query, lateral_join_query,
repo_opts(nil, nil, source_resource) repo_opts(repo, nil, nil, source_resource)
) )
|> remap_mapped_fields(query) |> remap_mapped_fields(query)
@ -1357,7 +1366,8 @@ defmodule AshPostgres.DataLayer do
@doc false @doc false
def set_subquery_prefix(data_layer_query, source_query, resource) do def set_subquery_prefix(data_layer_query, source_query, resource) do
config = AshPostgres.DataLayer.Info.repo(resource, :mutate).config() repo = AshPostgres.DataLayer.Info.repo(resource, :mutate)
config = repo.config()
case data_layer_query do case data_layer_query do
%{__ash_bindings__: %{context: %{data_layer: %{schema: schema}}}} when not is_nil(schema) -> %{__ash_bindings__: %{context: %{data_layer: %{schema: schema}}}} when not is_nil(schema) ->
@ -1375,20 +1385,16 @@ defmodule AshPostgres.DataLayer do
%{ %{
data_layer_query data_layer_query
| prefix: | prefix:
to_string( query_tenant || AshPostgres.DataLayer.Info.schema(resource) ||
query_tenant || AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] ||
config[:default_prefix] || "public"
"public"
)
} }
else else
%{ %{
data_layer_query data_layer_query
| prefix: | prefix:
to_string( AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] ||
AshPostgres.DataLayer.Info.schema(resource) || config[:default_prefix] || "public"
"public"
)
} }
end end
end end
@ -1418,7 +1424,7 @@ defmodule AshPostgres.DataLayer do
data data
end end
|> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset)))
|> ecto_changeset(changeset, :update, true, true) |> ecto_changeset(changeset, :update, true)
case bulk_updatable_query(query, resource, changeset.atomics, changeset.context) do case bulk_updatable_query(query, resource, changeset.atomics, changeset.context) do
{:error, error} -> {:error, error} ->
@ -1427,12 +1433,12 @@ defmodule AshPostgres.DataLayer do
{:ok, query} -> {:ok, query} ->
try do try do
repo = dynamic_repo(resource, changeset) repo = dynamic_repo(resource, changeset)
repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) repo_opts = repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource)
case query_with_atomics( case query_with_atomics(
resource, resource,
query, query,
ecto_changeset.filters, changeset.filter,
changeset.atomics, changeset.atomics,
ecto_changeset.changes, ecto_changeset.changes,
[] []
@ -1582,7 +1588,7 @@ defmodule AshPostgres.DataLayer do
data data
end end
|> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset))) |> Map.update!(:__meta__, &Map.put(&1, :source, table(resource, changeset)))
|> ecto_changeset(changeset, :update, true, true) |> ecto_changeset(changeset, :update, true)
try do try do
query = query =
@ -1599,10 +1605,10 @@ defmodule AshPostgres.DataLayer do
end end
|> Ecto.Query.exclude(:order_by) |> Ecto.Query.exclude(:order_by)
repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource)
repo = dynamic_repo(resource, changeset) repo = dynamic_repo(resource, changeset)
repo_opts = repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource)
query = query =
if Enum.any?(query.joins, &(&1.qual != :inner)) do if Enum.any?(query.joins, &(&1.qual != :inner)) do
root_query = root_query =
@ -1663,7 +1669,11 @@ defmodule AshPostgres.DataLayer do
@impl true @impl true
def bulk_create(resource, stream, options) do def bulk_create(resource, stream, options) do
opts = repo_opts(nil, options[:tenant], resource) changesets = Enum.to_list(stream)
repo = dynamic_repo(resource, Enum.at(changesets, 0))
opts = repo_opts(repo, nil, options[:tenant], resource)
opts = opts =
if options.return_records? do if options.return_records? do
@ -1672,8 +1682,6 @@ defmodule AshPostgres.DataLayer do
opts opts
end end
changesets = Enum.to_list(stream)
repo = dynamic_repo(resource, Enum.at(changesets, 0))
source = resolve_source(resource, Enum.at(changesets, 0)) source = resolve_source(resource, Enum.at(changesets, 0))
try do try do
@ -1681,7 +1689,7 @@ defmodule AshPostgres.DataLayer do
if options[:upsert?] do if options[:upsert?] do
# Ash groups changesets by atomics before dispatching them to the data layer # Ash groups changesets by atomics before dispatching them to the data layer
# this means that all changesets have the same atomics # this means that all changesets have the same atomics
%{atomics: atomics, filters: filters} = Enum.at(changesets, 0) %{atomics: atomics, filter: filter} = Enum.at(changesets, 0)
query = from(row in source, as: ^0) query = from(row in source, as: ^0)
@ -1696,7 +1704,7 @@ defmodule AshPostgres.DataLayer do
case query_with_atomics( case query_with_atomics(
resource, resource,
query, query,
filters, filter,
atomics, atomics,
%{}, %{},
upsert_set upsert_set
@ -1967,10 +1975,6 @@ defmodule AshPostgres.DataLayer do
end) end)
end end
defp handle_errors({:error, %Ecto.Changeset{errors: errors}}) do
{:error, Enum.map(errors, &to_ash_error/1)}
end
defp to_ash_error({field, {message, vars}}) do defp to_ash_error({field, {message, vars}}) do
Ash.Error.Changes.InvalidAttribute.exception( Ash.Error.Changes.InvalidAttribute.exception(
field: field, field: field,
@ -1979,25 +1983,7 @@ defmodule AshPostgres.DataLayer do
) )
end end
defp ecto_changeset(record, changeset, type, table_error? \\ true, bulk_update? \\ false) do defp ecto_changeset(record, changeset, type, table_error? \\ true) do
filters =
if changeset.action_type == :create do
%{}
else
Map.get(changeset, :filters, %{})
end
filters =
if changeset.action_type == :create || bulk_update? do
filters
else
changeset.resource
|> Ash.Resource.Info.primary_key()
|> Enum.reduce(filters, fn key, filters ->
Map.put(filters, key, Map.get(record, key))
end)
end
attributes = attributes =
changeset.resource changeset.resource
|> Ash.Resource.Info.attributes() |> Ash.Resource.Info.attributes()
@ -2013,7 +1999,6 @@ defmodule AshPostgres.DataLayer do
|> to_ecto() |> to_ecto()
|> set_table(changeset, type, table_error?) |> set_table(changeset, type, table_error?)
|> Ecto.Changeset.change(Map.take(changeset.attributes, attributes_to_change)) |> Ecto.Changeset.change(Map.take(changeset.attributes, attributes_to_change))
|> Map.update!(:filters, &Map.merge(&1, filters))
|> add_configured_foreign_key_constraints(record.__struct__) |> add_configured_foreign_key_constraints(record.__struct__)
|> add_unique_indexes(record.__struct__, changeset) |> add_unique_indexes(record.__struct__, changeset)
|> add_check_constraints(record.__struct__) |> add_check_constraints(record.__struct__)
@ -2615,7 +2600,11 @@ defmodule AshPostgres.DataLayer do
|> ecto_changeset(changeset, :update) |> ecto_changeset(changeset, :update)
source = resolve_source(resource, changeset) source = resolve_source(resource, changeset)
query = from(row in source, as: ^0) |> default_bindings(resource, changeset.context)
query =
from(row in source, as: ^0)
|> default_bindings(resource, changeset.context)
|> pkey_filter(changeset.data)
select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource) select = Keyword.keys(changeset.atomics) ++ Ash.Resource.Info.primary_key(resource)
@ -2630,7 +2619,7 @@ defmodule AshPostgres.DataLayer do
case query_with_atomics( case query_with_atomics(
resource, resource,
query, query,
ecto_changeset.filters, changeset.filter,
changeset.atomics, changeset.atomics,
ecto_changeset.changes, ecto_changeset.changes,
[] []
@ -2639,13 +2628,12 @@ defmodule AshPostgres.DataLayer do
{:ok, changeset.data} {:ok, changeset.data}
{:ok, query} -> {:ok, query} ->
repo_opts = repo_opts(changeset.timeout, changeset.tenant, changeset.resource) repo = dynamic_repo(resource, changeset)
repo_opts = repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource)
repo_opts = repo_opts =
Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics)) Keyword.put(repo_opts, :returning, Keyword.keys(changeset.atomics))
repo = dynamic_repo(resource, changeset)
result = result =
with_savepoint(repo, query, fn -> with_savepoint(repo, query, fn ->
repo.update_all( repo.update_all(
@ -2660,7 +2648,7 @@ defmodule AshPostgres.DataLayer do
{:error, {:error,
Ash.Error.Changes.StaleRecord.exception( Ash.Error.Changes.StaleRecord.exception(
resource: resource, resource: resource,
filters: ecto_changeset.filters filters: changeset.filter
)} )}
{1, [result]} -> {1, [result]} ->
@ -2684,20 +2672,29 @@ defmodule AshPostgres.DataLayer do
end end
end end
defp pkey_filter(query, %resource{} = record) do
pkey =
record
|> Map.take(Ash.Resource.Info.primary_key(resource))
|> Map.to_list()
Ecto.Query.where(query, ^pkey)
end
defp query_with_atomics( defp query_with_atomics(
resource, resource,
query, query,
filters, filter,
atomics, atomics,
updating_one_changes, updating_one_changes,
existing_set existing_set
) do ) do
query = query =
Enum.reduce(filters, query, fn {key, value}, query -> if is_nil(filter) do
from(row in query, query
where: field(row, ^key) == ^value else
) filter(query, filter, resource)
end) end
atomics_result = atomics_result =
Enum.reduce_while(atomics, {:ok, query, []}, fn {field, expr}, {:ok, query, set} -> Enum.reduce_while(atomics, {:ok, query, []}, fn {field, expr}, {:ok, query, set} ->
@ -2792,20 +2789,23 @@ defmodule AshPostgres.DataLayer do
try do try do
repo = dynamic_repo(resource, changeset) repo = dynamic_repo(resource, changeset)
result = source = resolve_source(resource, changeset)
repo.delete(
ecto_changeset,
repo_opts(changeset.timeout, changeset.tenant, changeset.resource)
)
result from(row in source, as: ^0)
|> from_ecto() |> default_bindings(resource, changeset.context)
|> filter(changeset.filter, resource)
|> case do |> case do
{:ok, _record} -> {:ok, query} ->
query
|> pkey_filter(record)
|> repo.delete_all(
repo_opts(repo, changeset.timeout, changeset.tenant, changeset.resource)
)
:ok :ok
{:error, error} -> {:error, error} ->
handle_errors({:error, error}) {:error, error}
end end
rescue rescue
e -> e ->
@ -3201,9 +3201,13 @@ defmodule AshPostgres.DataLayer do
|> Enum.reduce(query, fn filter, query -> |> Enum.reduce(query, fn filter, query ->
{dynamic, acc} = AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__) {dynamic, acc} = AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__)
query if is_nil(dynamic) do
|> Ecto.Query.where(^dynamic) query
|> merge_expr_accumulator(acc) else
query
|> Ecto.Query.where([], ^dynamic)
|> merge_expr_accumulator(acc)
end
end) end)
end end

View file

@ -14,6 +14,21 @@ defmodule AshPostgres.DataLayer.Info do
end end
end end
@doc "Checks a version requirement against the resource's repo's postgres version"
def pg_version_matches?(resource, requirement) do
resource
|> pg_version()
|> Version.match?(requirement)
end
@doc "Gets the resource's repo's postgres version"
def pg_version(resource) do
case repo(resource, :read).pg_version() do
%Version{} = version -> version
string when is_binary(string) -> Version.parse!(string)
end
end
@doc "The configured table for a resource" @doc "The configured table for a resource"
def table(resource) do def table(resource) do
Extension.get_opt(resource, [:postgres], :table, nil, true) Extension.get_opt(resource, [:postgres], :table, nil, true)

View file

@ -14,6 +14,7 @@ defmodule AshPostgres.Expr do
DateAdd, DateAdd,
DateTimeAdd, DateTimeAdd,
Error, Error,
Fragment,
FromNow, FromNow,
GetPath, GetPath,
If, If,
@ -21,6 +22,7 @@ defmodule AshPostgres.Expr do
Length, Length,
Now, Now,
Round, Round,
StringDowncase,
StringJoin, StringJoin,
StringLength, StringLength,
StringSplit, StringSplit,
@ -29,7 +31,7 @@ defmodule AshPostgres.Expr do
Type Type
} }
alias AshPostgres.Functions.{Fragment, ILike, Like, TrigramSimilarity, VectorCosineDistance} alias AshPostgres.Functions.{ILike, Like, TrigramSimilarity, VectorCosineDistance}
require Ecto.Query require Ecto.Query
@ -725,6 +727,27 @@ defmodule AshPostgres.Expr do
) )
end end
defp do_dynamic_expr(
query,
%StringDowncase{arguments: [value], embedded?: pred_embedded?},
bindings,
embedded?,
acc,
type
) do
do_dynamic_expr(
query,
%Fragment{
embedded?: pred_embedded?,
arguments: [raw: "lower(", expr: value, raw: ")"]
},
bindings,
embedded?,
acc,
type
)
end
defp do_dynamic_expr( defp do_dynamic_expr(
query, query,
%StringTrim{arguments: [value], embedded?: pred_embedded?}, %StringTrim{arguments: [value], embedded?: pred_embedded?},
@ -1074,10 +1097,11 @@ defmodule AshPostgres.Expr do
expression = expression =
Ash.Actions.Read.add_calc_context_to_filter( Ash.Actions.Read.add_calc_context_to_filter(
expression, expression,
calculation.context[:actor], calculation.context.actor,
calculation.context[:authorize?], calculation.context.authorize?,
calculation.context[:tenant], calculation.context.tenant,
calculation.context[:tracer] calculation.context.tracer,
nil
) )
do_dynamic_expr( do_dynamic_expr(
@ -1190,7 +1214,8 @@ defmodule AshPostgres.Expr do
aggregate.context[:actor], aggregate.context[:actor],
aggregate.context[:authorize?], aggregate.context[:authorize?],
aggregate.context[:tenant], aggregate.context[:tenant],
aggregate.context[:tracer] aggregate.context[:tracer],
nil
) )
{value, acc} = do_dynamic_expr(query, ref, query.__ash_bindings__, false, acc) {value, acc} = do_dynamic_expr(query, ref, query.__ash_bindings__, false, acc)
@ -1452,10 +1477,10 @@ defmodule AshPostgres.Expr do
end end
{encoded, acc} = {encoded, acc} =
if Ash.Filter.TemplateHelpers.expr?(input) do if Ash.Expr.expr?(input) do
frag_parts = frag_parts =
Enum.flat_map(input, fn {key, value} -> Enum.flat_map(input, fn {key, value} ->
if Ash.Filter.TemplateHelpers.expr?(value) do if Ash.Expr.expr?(value) do
[ [
expr: to_string(key), expr: to_string(key),
raw: "::text, ", raw: "::text, ",
@ -1720,7 +1745,7 @@ defmodule AshPostgres.Expr do
when is_map(value) and not is_struct(value) do when is_map(value) and not is_struct(value) do
if bindings[:location] == :update && if bindings[:location] == :update &&
Enum.any?(value, fn {key, value} -> Enum.any?(value, fn {key, value} ->
Ash.Filter.TemplateHelpers.expr?(key) || Ash.Filter.TemplateHelpers.expr?(value) Ash.Expr.expr?(key) || Ash.Expr.expr?(value)
end) do end) do
elements = elements =
value value
@ -1770,7 +1795,7 @@ defmodule AshPostgres.Expr do
if other && is_atom(other) && !is_boolean(other) do if other && is_atom(other) && !is_boolean(other) do
{to_string(other), acc} {to_string(other), acc}
else else
if Ash.Filter.TemplateHelpers.expr?(other) do if Ash.Expr.expr?(other) do
if is_list(other) do if is_list(other) do
list_expr(query, other, bindings, true, acc, type) list_expr(query, other, bindings, true, acc, type)
else else
@ -1801,7 +1826,7 @@ defmodule AshPostgres.Expr do
end end
defp do_dynamic_expr(query, value, bindings, false, acc, type) do defp do_dynamic_expr(query, value, bindings, false, acc, type) do
if Ash.Filter.TemplateHelpers.expr?(value) do if Ash.Expr.expr?(value) do
if is_list(value) do if is_list(value) do
list_expr(query, value, bindings, false, acc, type) list_expr(query, value, bindings, false, acc, type)
else else
@ -2033,7 +2058,7 @@ defmodule AshPostgres.Expr do
defp list_expr(query, value, bindings, embedded?, acc, type) do defp list_expr(query, value, bindings, embedded?, acc, type) do
if !Enum.empty?(value) && if !Enum.empty?(value) &&
Enum.any?(value, fn value -> Enum.any?(value, fn value ->
Ash.Filter.TemplateHelpers.expr?(value) || is_map(value) || is_list(value) Ash.Expr.expr?(value) || is_map(value) || is_list(value)
end) do end) do
type = type =
case type do case type do

View file

@ -1,72 +0,0 @@
defmodule AshPostgres.Functions.Fragment do
@moduledoc """
A function that maps to ecto's `fragment` function
https://hexdocs.pm/ecto/Ecto.Query.API.html#fragment/1
"""
use Ash.Query.Function, name: :fragment
def private?, do: true
# Varargs is special, and should only be used in rare circumstances (like this one)
# no type casting or help can be provided for these functions.
def args, do: :var_args
def new([fragment | _]) when not is_binary(fragment) do
{:error, "First argument to `fragment` must be a string."}
end
def new([fragment | rest]) do
split = split_fragment(fragment)
if Enum.count(split, &(&1 == :slot)) != length(rest) do
{:error,
"fragment(...) expects extra arguments in the same amount of question marks in string. " <>
"It received #{Enum.count(split, &(&1 == :slot))} extra argument(s) but expected #{length(rest)}"}
else
{:ok, %__MODULE__{arguments: merge_fragment(split, rest)}}
end
end
def casted_new([fragment | _]) when not is_binary(fragment) do
{:error, "First argument to `fragment` must be a string."}
end
def casted_new([fragment | rest]) do
split = split_fragment(fragment)
if Enum.count(split, &(&1 == :slot)) != length(rest) do
{:error,
"fragment(...) expects extra arguments in the same amount of question marks in string. " <>
"It received #{Enum.count(split, &(&1 == :slot))} extra argument(s) but expected #{length(rest)}"}
else
{:ok, %__MODULE__{arguments: merge_fragment(split, rest, :casted_expr)}}
end
end
defp merge_fragment(expr, args, tag \\ :expr)
defp merge_fragment([], [], _tag), do: []
defp merge_fragment([:slot | rest], [arg | rest_args], tag) do
[{tag, arg} | merge_fragment(rest, rest_args, tag)]
end
defp merge_fragment([val | rest], rest_args, tag) do
[{:raw, val} | merge_fragment(rest, rest_args, tag)]
end
defp split_fragment(frag, consumed \\ "")
defp split_fragment(<<>>, consumed),
do: [consumed]
defp split_fragment(<<??, rest::binary>>, consumed),
do: [consumed, :slot | split_fragment(rest, "")]
defp split_fragment(<<?\\, ??, rest::binary>>, consumed),
do: split_fragment(rest, consumed <> <<??>>)
defp split_fragment(<<first::utf8, rest::binary>>, consumed),
do: split_fragment(rest, consumed <> <<first::utf8>>)
end

View file

@ -20,11 +20,11 @@ defmodule AshPostgres.MigrationGenerator do
check: false, check: false,
drop_columns: false drop_columns: false
def generate(apis, opts \\ []) do def generate(domains, opts \\ []) do
apis = List.wrap(apis) domains = List.wrap(domains)
opts = opts(opts) opts = opts(opts)
all_resources = Enum.uniq(Enum.flat_map(apis, &Ash.Api.Info.resources/1)) all_resources = Enum.uniq(Enum.flat_map(domains, &Ash.Domain.Info.resources/1))
{tenant_snapshots, snapshots} = {tenant_snapshots, snapshots} =
all_resources all_resources
@ -60,8 +60,8 @@ defmodule AshPostgres.MigrationGenerator do
Does not support everything supported by the migration generator. Does not support everything supported by the migration generator.
""" """
def take_snapshots(api, repo, only_resources \\ nil) do def take_snapshots(domain, repo, only_resources \\ nil) do
all_resources = api |> Ash.Api.Info.resources() |> Enum.uniq() all_resources = domain |> Ash.Domain.Info.resources() |> Enum.uniq()
all_resources all_resources
|> Enum.filter(fn resource -> |> Enum.filter(fn resource ->

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.MixHelpers do defmodule AshPostgres.MixHelpers do
@moduledoc false @moduledoc false
def apis!(opts, args) do def domains!(opts, args) do
apps = apps =
if apps_paths = Mix.Project.apps_paths() do if apps_paths = Mix.Project.apps_paths() do
apps_paths |> Map.keys() |> Enum.sort() apps_paths |> Map.keys() |> Enum.sort()
@ -8,46 +8,46 @@ defmodule AshPostgres.MixHelpers do
[Mix.Project.config()[:app]] [Mix.Project.config()[:app]]
end end
configured_apis = Enum.flat_map(apps, &Application.get_env(&1, :ash_apis, [])) configure_domains = Enum.flat_map(apps, &Application.get_env(&1, :ash_domains, []))
apis = domains =
if opts[:apis] && opts[:apis] != "" do if opts[:domains] && opts[:domains] != "" do
opts[:apis] opts[:domains]
|> Kernel.||("") |> Kernel.||("")
|> String.split(",") |> String.split(",")
|> Enum.flat_map(fn |> Enum.flat_map(fn
"" -> "" ->
[] []
api -> domain ->
[Module.concat([api])] [Module.concat([domain])]
end) end)
else else
configured_apis configure_domains
end end
apis domains
|> Enum.map(&ensure_compiled(&1, args)) |> Enum.map(&ensure_compiled(&1, args))
|> case do |> case do
[] -> [] ->
raise "must supply the --apis argument, or set `config :my_app, ash_apis: [...]` in config" raise "must supply the --domains argument, or set `config :my_app, ash_domains: [...]` in config"
apis -> domains ->
apis domains
end end
end end
def repos!(opts, args) do def repos!(opts, args) do
apis = apis!(opts, args) domains = domains!(opts, args)
resources = resources =
apis domains
|> Enum.flat_map(&Ash.Api.Info.resources/1) |> Enum.flat_map(&Ash.Domain.Info.resources/1)
|> Enum.filter(&(Ash.DataLayer.data_layer(&1) == AshPostgres.DataLayer)) |> Enum.filter(&(Ash.DataLayer.data_layer(&1) == AshPostgres.DataLayer))
|> case do |> case do
[] -> [] ->
raise """ raise """
No resources with `data_layer: AshPostgres.DataLayer` found in the apis #{Enum.map_join(apis, ",", &inspect/1)}. No resources with `data_layer: AshPostgres.DataLayer` found in the domains #{Enum.map_join(domains, ",", &inspect/1)}.
Must be able to find at least one resource with `data_layer: AshPostgres.DataLayer`. Must be able to find at least one resource with `data_layer: AshPostgres.DataLayer`.
""" """
@ -64,7 +64,7 @@ defmodule AshPostgres.MixHelpers do
|> case do |> case do
[] -> [] ->
raise """ raise """
No repos could be found configured on the resources in the apis: #{Enum.map_join(apis, ",", &inspect/1)} No repos could be found configured on the resources in the domains: #{Enum.map_join(domains, ",", &inspect/1)}
At least one resource must have a repo configured. At least one resource must have a repo configured.
@ -98,7 +98,7 @@ defmodule AshPostgres.MixHelpers do
end end
end end
defp ensure_compiled(api, args) do defp ensure_compiled(domain, args) do
if Code.ensure_loaded?(Mix.Tasks.App.Config) do if Code.ensure_loaded?(Mix.Tasks.App.Config) do
Mix.Task.run("app.config", args) Mix.Task.run("app.config", args)
else else
@ -106,18 +106,18 @@ defmodule AshPostgres.MixHelpers do
"--no-compile" not in args && Mix.Task.run("compile", args) "--no-compile" not in args && Mix.Task.run("compile", args)
end end
case Code.ensure_compiled(api) do case Code.ensure_compiled(domain) do
{:module, _} -> {:module, _} ->
api domain
|> Ash.Api.Info.resources() |> Ash.Domain.Info.resources()
|> Enum.each(&Code.ensure_compiled/1) |> Enum.each(&Code.ensure_compiled/1)
# TODO: We shouldn't need to make sure that the resources are compiled # TODO: We shouldn't need to make sure that the resources are compiled
api domain
{:error, error} -> {:error, error} ->
Mix.raise("Could not load #{inspect(api)}, error: #{inspect(error)}. ") Mix.raise("Could not load #{inspect(domain)}, error: #{inspect(error)}. ")
end end
end end

View file

@ -5,7 +5,7 @@ defmodule Mix.Tasks.AshPostgres.Create do
@switches [ @switches [
quiet: :boolean, quiet: :boolean,
apis: :string, domains: :string,
no_compile: :boolean, no_compile: :boolean,
no_deps_check: :boolean no_deps_check: :boolean
] ]
@ -15,16 +15,16 @@ defmodule Mix.Tasks.AshPostgres.Create do
] ]
@moduledoc """ @moduledoc """
Create the storage for repos in all resources for the given (or configured) apis. Create the storage for repos in all resources for the given (or configured) domains.
## Examples ## Examples
mix ash_postgres.create mix ash_postgres.create
mix ash_postgres.create --apis MyApp.Api1,MyApp.Api2 mix ash_postgres.create --domains MyApp.Domain1,MyApp.Domain2
## Command line options ## Command line options
* `--apis` - the apis who's repos you want to migrate. * `--domains` - the domains who's repos you want to migrate.
* `--quiet` - do not log output * `--quiet` - do not log output
* `--no-compile` - do not compile before creating * `--no-compile` - do not compile before creating
* `--no-deps-check` - do not compile before creating * `--no-deps-check` - do not compile before creating
@ -41,7 +41,7 @@ defmodule Mix.Tasks.AshPostgres.Create do
["-r", to_string(repo)] ["-r", to_string(repo)]
end) end)
rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--apis") rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--domains")
Mix.Task.reenable("ecto.create") Mix.Task.reenable("ecto.create")
Mix.Task.run("ecto.create", repo_args ++ rest_opts) Mix.Task.run("ecto.create", repo_args ++ rest_opts)

View file

@ -1,7 +1,7 @@
defmodule Mix.Tasks.AshPostgres.Drop do defmodule Mix.Tasks.AshPostgres.Drop do
use Mix.Task use Mix.Task
@shortdoc "Drops the repository storage for the repos in the specified (or configured) apis" @shortdoc "Drops the repository storage for the repos in the specified (or configured) domains"
@default_opts [force: false, force_drop: false] @default_opts [force: false, force_drop: false]
@aliases [ @aliases [
@ -13,7 +13,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do
force: :boolean, force: :boolean,
force_drop: :boolean, force_drop: :boolean,
quiet: :boolean, quiet: :boolean,
apis: :string, domains: :string,
no_compile: :boolean, no_compile: :boolean,
no_deps_check: :boolean no_deps_check: :boolean
] ]
@ -24,11 +24,11 @@ defmodule Mix.Tasks.AshPostgres.Drop do
## Examples ## Examples
mix ash_postgres.drop mix ash_postgres.drop
mix ash_postgres.drop -r MyApp.Api1,MyApp.Api2 mix ash_postgres.drop -r MyApp.Repo1,MyApp.Repo2
## Command line options ## Command line options
* `--apis` - the apis who's repos should be dropped * `--domains` - the domains who's repos should be dropped
* `-q`, `--quiet` - run the command quietly * `-q`, `--quiet` - run the command quietly
* `-f`, `--force` - do not ask for confirmation when dropping the database. * `-f`, `--force` - do not ask for confirmation when dropping the database.
Configuration is asked only when `:start_permanent` is set to true Configuration is asked only when `:start_permanent` is set to true
@ -51,7 +51,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do
["-r", to_string(repo)] ["-r", to_string(repo)]
end) end)
rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--apis") rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--domains")
Mix.Task.reenable("ecto.drop") Mix.Task.reenable("ecto.drop")
Mix.Task.run("ecto.drop", repo_args ++ rest_opts) Mix.Task.run("ecto.drop", repo_args ++ rest_opts)

View file

@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do
Options: Options:
* `apis` - a comma separated list of API modules, for which migrations will be generated * `domains` - a comma separated list of Domain modules, for which migrations will be generated
* `snapshot-path` - a custom path to store the snapshots, defaults to "priv/resource_snapshots" * `snapshot-path` - a custom path to store the snapshots, defaults to "priv/resource_snapshots"
* `migration-path` - a custom path to store the migrations, defaults to "priv". * `migration-path` - a custom path to store the migrations, defaults to "priv".
Migrations are stored in a folder for each repo, so `priv/repo_name/migrations` Migrations are stored in a folder for each repo, so `priv/repo_name/migrations`
@ -87,7 +87,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do
{opts, _} = {opts, _} =
OptionParser.parse!(args, OptionParser.parse!(args,
strict: [ strict: [
apis: :string, domains: :string,
snapshot_path: :string, snapshot_path: :string,
migration_path: :string, migration_path: :string,
tenant_migration_path: :string, tenant_migration_path: :string,
@ -100,7 +100,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do
] ]
) )
apis = AshPostgres.MixHelpers.apis!(opts, args) domains = AshPostgres.MixHelpers.domains!(opts, args)
opts = opts =
opts opts
@ -119,6 +119,6 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do
""") """)
end end
AshPostgres.MigrationGenerator.generate(apis, opts) AshPostgres.MigrationGenerator.generate(domains, opts)
end end
end end

View file

@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
import AshPostgres.MixHelpers, import AshPostgres.MixHelpers,
only: [migrations_path: 2, tenant_migrations_path: 2, tenants: 2] only: [migrations_path: 2, tenant_migrations_path: 2, tenants: 2]
@shortdoc "Runs the repository migrations for all repositories in the provided (or congigured) apis" @shortdoc "Runs the repository migrations for all repositories in the provided (or congigured) domains"
@aliases [ @aliases [
n: :step n: :step
@ -20,7 +20,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
pool_size: :integer, pool_size: :integer,
log_sql: :boolean, log_sql: :boolean,
strict_version_order: :boolean, strict_version_order: :boolean,
apis: :string, domains: :string,
no_compile: :boolean, no_compile: :boolean,
no_deps_check: :boolean, no_deps_check: :boolean,
migrations_path: :keep, migrations_path: :keep,
@ -42,7 +42,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
specific version number, supply `--to version_number`. To migrate a specific version number, supply `--to version_number`. To migrate a
specific number of times, use `--step n`. specific number of times, use `--step n`.
This is only really useful if your api or apis only use a single repo. This is only really useful if your domains only use a single repo.
If you have multiple repos and you want to run a single migration and/or If you have multiple repos and you want to run a single migration and/or
migrate/roll them back to different points, you will need to use the migrate/roll them back to different points, you will need to use the
ecto specific task, `mix ecto.migrate` and provide your repo name. ecto specific task, `mix ecto.migrate` and provide your repo name.
@ -53,7 +53,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
## Examples ## Examples
mix ash_postgres.migrate mix ash_postgres.migrate
mix ash_postgres.migrate --apis MyApp.Api1,MyApp.Api2 mix ash_postgres.migrate --domains MyApp.Domain1,MyApp.Domain2
mix ash_postgres.migrate -n 3 mix ash_postgres.migrate -n 3
mix ash_postgres.migrate --step 3 mix ash_postgres.migrate --step 3
@ -62,7 +62,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
## Command line options ## Command line options
* `--apis` - the apis who's repos should be migrated * `--domains` - the domains who's repos should be migrated
* `--tenants` - Run the tenant migrations * `--tenants` - Run the tenant migrations
@ -107,7 +107,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
rest_opts = rest_opts =
args args
|> AshPostgres.MixHelpers.delete_arg("--apis") |> AshPostgres.MixHelpers.delete_arg("--domains")
|> AshPostgres.MixHelpers.delete_arg("--migrations-path") |> AshPostgres.MixHelpers.delete_arg("--migrations-path")
|> AshPostgres.MixHelpers.delete_flag("--tenants") |> AshPostgres.MixHelpers.delete_flag("--tenants")
|> AshPostgres.MixHelpers.delete_flag("--only-tenants") |> AshPostgres.MixHelpers.delete_flag("--only-tenants")

View file

@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do
import AshPostgres.MixHelpers, import AshPostgres.MixHelpers,
only: [migrations_path: 2, tenant_migrations_path: 2, tenants: 2] only: [migrations_path: 2, tenant_migrations_path: 2, tenants: 2]
@shortdoc "Rolls back the repository migrations for all repositories in the provided (or configured) apis" @shortdoc "Rolls back the repository migrations for all repositories in the provided (or configured) domains"
@moduledoc """ @moduledoc """
Reverts applied migrations in the given repository. Reverts applied migrations in the given repository.
@ -16,7 +16,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do
specific number of times, use `--step n`. To undo all applied specific number of times, use `--step n`. To undo all applied
migrations, provide `--all`. migrations, provide `--all`.
This is only really useful if your api or apis only use a single repo. This is only really useful if your domains only use a single repo.
If you have multiple repos and you want to run a single migration and/or If you have multiple repos and you want to run a single migration and/or
migrate/roll them back to different points, you will need to use the migrate/roll them back to different points, you will need to use the
ecto specific task, `mix ecto.migrate` and provide your repo name. ecto specific task, `mix ecto.migrate` and provide your repo name.
@ -30,7 +30,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do
mix ash_postgres.rollback --to 20080906120000 mix ash_postgres.rollback --to 20080906120000
## Command line options ## Command line options
* `--apis` - the apis who's repos should be rolledback * `--domains` - the domains who's repos should be rolledback
* `--all` - revert all applied migrations * `--all` - revert all applied migrations
* `--step` / `-n` - revert n number of applied migrations * `--step` / `-n` - revert n number of applied migrations
* `--to` / `-v` - revert all migrations down to and including version * `--to` / `-v` - revert all migrations down to and including version
@ -66,7 +66,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do
rest_opts = rest_opts =
args args
|> AshPostgres.MixHelpers.delete_arg("--apis") |> AshPostgres.MixHelpers.delete_arg("--domains")
|> AshPostgres.MixHelpers.delete_arg("--migrations-path") |> AshPostgres.MixHelpers.delete_arg("--migrations-path")
|> AshPostgres.MixHelpers.delete_flag("--tenants") |> AshPostgres.MixHelpers.delete_flag("--tenants")
|> AshPostgres.MixHelpers.delete_flag("--only-tenants") |> AshPostgres.MixHelpers.delete_flag("--only-tenants")

View file

@ -44,6 +44,9 @@ defmodule AshPostgres.Repo do
@doc "Use this to inform the data layer about what extensions are installed" @doc "Use this to inform the data layer about what extensions are installed"
@callback installed_extensions() :: [String.t() | module()] @callback installed_extensions() :: [String.t() | module()]
@doc "Configure the version of postgres that is being used."
@callback pg_version() :: Version.t()
@doc """ @doc """
Use this to inform the data layer about the oldest potential postgres version it will be run on. Use this to inform the data layer about the oldest potential postgres version it will be run on.
@ -54,7 +57,6 @@ defmodule AshPostgres.Repo do
For things like `Fly.Repo`, where you might need to have more fine grained control over the repo module, For things like `Fly.Repo`, where you might need to have more fine grained control over the repo module,
you can use the `define_ecto_repo?: false` option to `use AshPostgres.Repo`. you can use the `define_ecto_repo?: false` option to `use AshPostgres.Repo`.
""" """
@callback min_pg_version() :: integer()
@callback on_transaction_begin(reason :: Ash.DataLayer.transaction_reason()) :: term @callback on_transaction_begin(reason :: Ash.DataLayer.transaction_reason()) :: term
@ -66,6 +68,7 @@ defmodule AshPostgres.Repo do
@callback migrations_path() :: String.t() | nil @callback migrations_path() :: String.t() | nil
@doc "The default prefix(postgres schema) to use when building queries" @doc "The default prefix(postgres schema) to use when building queries"
@callback default_prefix() :: String.t() @callback default_prefix() :: String.t()
@doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields" @doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields"
@callback override_migration_type(atom) :: atom @callback override_migration_type(atom) :: atom
@ -88,7 +91,6 @@ defmodule AshPostgres.Repo do
def migrations_path, do: nil def migrations_path, do: nil
def default_prefix, do: "public" def default_prefix, do: "public"
def override_migration_type(type), do: type def override_migration_type(type), do: type
def min_pg_version, do: 10
def transaction!(fun) do def transaction!(fun) do
case fun.() do case fun.() do
@ -224,8 +226,7 @@ defmodule AshPostgres.Repo do
all_tenants: 0, all_tenants: 0,
tenant_migrations_path: 0, tenant_migrations_path: 0,
default_prefix: 0, default_prefix: 0,
override_migration_type: 1, override_migration_type: 1
min_pg_version: 0
end end
end end
end end

View file

@ -1,14 +1,14 @@
defmodule AshPostgres.Transformers.EnsureTableOrPolymorphic do defmodule AshPostgres.Verifiers.EnsureTableOrPolymorphic do
@moduledoc false @moduledoc false
use Spark.Dsl.Transformer use Spark.Dsl.Verifier
alias Spark.Dsl.Transformer alias Spark.Dsl.Verifier
def transform(dsl) do def verify(dsl) do
if Transformer.get_option(dsl, [:postgres], :polymorphic?) || if Verifier.get_option(dsl, [:postgres], :polymorphic?) ||
Transformer.get_option(dsl, [:postgres], :table) do Verifier.get_option(dsl, [:postgres], :table) do
{:ok, dsl} :ok
else else
resource = Transformer.get_persisted(dsl, :module) resource = Verifier.get_persisted(dsl, :module)
raise Spark.Error.DslError, raise Spark.Error.DslError,
module: resource, module: resource,

View file

@ -1,10 +1,10 @@
defmodule AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchType do defmodule AshPostgres.Verifiers.PreventAttributeMultitenancyAndNonFullMatchType do
@moduledoc false @moduledoc false
use Spark.Dsl.Transformer use Spark.Dsl.Verifier
alias Spark.Dsl.Transformer alias Spark.Dsl.Verifier
def transform(dsl) do def verify(dsl) do
if Transformer.get_option(dsl, [:multitenancy], :strategy) == :attribute do if Verifier.get_option(dsl, [:multitenancy], :strategy) == :attribute do
dsl dsl
|> AshPostgres.DataLayer.Info.references() |> AshPostgres.DataLayer.Info.references()
|> Enum.filter(&(&1.match_type && &1.match_type != :full)) |> Enum.filter(&(&1.match_type && &1.match_type != :full))
@ -14,7 +14,7 @@ defmodule AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchTy
if uses_attribute_strategy?(relationship) and if uses_attribute_strategy?(relationship) and
not targets_primary_key?(relationship) and not targets_primary_key?(relationship) and
not targets_multitenancy_attribute?(relationship) do not targets_multitenancy_attribute?(relationship) do
resource = Transformer.get_persisted(dsl, :module) resource = Verifier.get_persisted(dsl, :module)
raise Spark.Error.DslError, raise Spark.Error.DslError,
module: resource, module: resource,
@ -28,9 +28,9 @@ defmodule AshPostgres.Transformers.PreventAttributeMultitenancyAndNonFullMatchTy
:ok :ok
end end
end) end)
else
{:ok, dsl}
end end
:ok
end end
defp uses_attribute_strategy?(relationship) do defp uses_attribute_strategy?(relationship) do

View file

@ -1,12 +1,10 @@
defmodule AshPostgres.Transformers.PreventMultidimensionalArrayAggregates do defmodule AshPostgres.Verifiers.PreventMultidimensionalArrayAggregates do
@moduledoc false @moduledoc false
use Spark.Dsl.Transformer use Spark.Dsl.Verifier
alias Spark.Dsl.Transformer alias Spark.Dsl.Verifier
def after_compile?, do: true def verify(dsl) do
resource = Verifier.get_persisted(dsl, :module)
def transform(dsl) do
resource = Transformer.get_persisted(dsl, :module)
dsl dsl
|> Ash.Resource.Info.aggregates() |> Ash.Resource.Info.aggregates()
@ -35,6 +33,6 @@ defmodule AshPostgres.Transformers.PreventMultidimensionalArrayAggregates do
end end
end) end)
{:ok, dsl} :ok
end end
end end

View file

@ -1,23 +1,21 @@
defmodule AshPostgres.Transformers.ValidateReferences do defmodule AshPostgres.Verifiers.ValidateReferences do
@moduledoc false @moduledoc false
use Spark.Dsl.Transformer use Spark.Dsl.Verifier
alias Spark.Dsl.Transformer alias Spark.Dsl.Verifier
def after_compile?, do: true def verify(dsl) do
def transform(dsl) do
dsl dsl
|> AshPostgres.DataLayer.Info.references() |> AshPostgres.DataLayer.Info.references()
|> Enum.each(fn reference -> |> Enum.each(fn reference ->
unless Ash.Resource.Info.relationship(dsl, reference.relationship) do unless Ash.Resource.Info.relationship(dsl, reference.relationship) do
raise Spark.Error.DslError, raise Spark.Error.DslError,
path: [:postgres, :references, reference.relationship], path: [:postgres, :references, reference.relationship],
module: Transformer.get_persisted(dsl, :module), module: Verifier.get_persisted(dsl, :module),
message: message:
"Found reference configuration for relationship `#{reference.relationship}`, but no such relationship exists" "Found reference configuration for relationship `#{reference.relationship}`, but no such relationship exists"
end end
end) end)
{:ok, dsl} :ok
end end
end end

View file

@ -0,0 +1,37 @@
defmodule AshPostgres.Verifiers.VerifyPostgresVersion do
@moduledoc false
use Spark.Dsl.Verifier
def verify(dsl) do
read_repo = AshPostgres.DataLayer.Info.repo(dsl, :read)
mutate_repo = AshPostgres.DataLayer.Info.repo(dsl, :mutate)
read_version =
read_repo.pg_version() |> parse!(read_repo)
mutation_version = mutate_repo.pg_version() |> parse!(mutate_repo)
if Version.match?(read_version, ">= 14.0.0") && Version.match?(mutation_version, ">= 14.0.0") do
:ok
else
{:error, "AshPostgres now only supports versions >= 14.0."}
end
end
defp parse!(%Version{} = version, _repo) do
version
end
defp parse!(version, repo) do
Version.parse!(version)
rescue
e ->
reraise ArgumentError,
"""
Failed to parse version in `#{inspect(repo)}.pg_version()`: #{inspect(version)}
Error: #{Exception.message(e)}
""",
__STACKTRACE__
end
end

View file

@ -45,7 +45,7 @@ defmodule AshPostgres.MixProject do
if Mix.env() == :test do if Mix.env() == :test do
def application() do def application() do
[ [
applications: [:ecto, :ecto_sql, :jason, :ash, :postgrex, :tools, :benchee], applications: [:ecto, :ecto_sql, :jason, :ash, :postgrex, :tools, :benchee, :xmerl],
mod: {AshPostgres.TestApp, []} mod: {AshPostgres.TestApp, []}
] ]
end end
@ -140,7 +140,6 @@ defmodule AshPostgres.MixProject do
EctoMigrationDefault EctoMigrationDefault
], ],
Expressions: [ Expressions: [
AshPostgres.Functions.Fragment,
AshPostgres.Functions.TrigramSimilarity, AshPostgres.Functions.TrigramSimilarity,
AshPostgres.Functions.ILike, AshPostgres.Functions.ILike,
AshPostgres.Functions.Like, AshPostgres.Functions.Like,
@ -157,7 +156,10 @@ defmodule AshPostgres.MixProject do
{:ecto, "~> 3.9"}, {:ecto, "~> 3.9"},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:postgrex, ">= 0.0.0"}, {:postgrex, ">= 0.0.0"},
{:ash, ash_version("~> 2.19 and >= 2.20.3")}, {:spark, path: "../spark", override: true},
# dev/test dependencies
{:simple_sat, "~> 0.1"},
{:ash, ash_version(github: "ash-project/ash", branch: "3.0")},
{:benchee, "~> 1.1", only: [:dev, :test]}, {:benchee, "~> 1.1", only: [:dev, :test]},
{:git_ops, "~> 2.5", only: [:dev, :test]}, {:git_ops, "~> 2.5", only: [:dev, :test]},
{:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false},

View file

@ -1,5 +1,5 @@
%{ %{
"ash": {:hex, :ash, "2.20.3", "2ded1295fd20e2a45b01c678fe93c51397384ec5e5e4babc80f1ae9ce896ca82", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.6", [hex: :reactor, repo: "hexpm", optional: false]}, {:spark, ">= 1.1.55 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b53374c6c70da21bb8d53fefb88e1b0dc7a6fd8cf48ecaff4d6e57d2e69afbca"}, "ash": {:git, "https://github.com/ash-project/ash.git", "37587cbc580c30248044eea01d461f578df6cbc4", [branch: "3.0"]},
"benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
@ -8,11 +8,9 @@
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
"ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
"ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
"elixir_make": {:hex, :elixir_make, "0.8.2", "cd4a5a75891362e9207adaac7e66223fd256ec2518ae013af7f10c9c85b50b5c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "9d9607d640c372a7291e5a56ce655aa2351897929be20bd211648fdb79e725dc"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
"ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, "ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"},
@ -26,14 +24,14 @@
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"},
"postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"},
"reactor": {:hex, :reactor, "0.7.0", "fb76d23d95829b28ac9b9d654620c43c890c6a32ea26ac13086c48540b34e8c5", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4310da820d753aafd7dc4ee8cc687b84565dd6d9536e38806ee211da792178fd"}, "reactor": {:hex, :reactor, "0.8.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"},
"simple_sat": {:hex, :simple_sat, "0.1.1", "68a5ebe6f6d5956bd806e4881c495692c14580a2f1a4420488985abd0fba2119", [:mix], [], "hexpm", "63571218f92ff029838df7645eb8f0c38df8ed60d2d14578412a8d142a94471e"},
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"},
"spark": {:hex, :spark, "1.1.55", "d20c3f899b23d841add29edc912ffab4463d3bb801bc73448738631389291d2e", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "bbc15a4223d8e610c81ceca825d5d0bae3738d1c4ac4dbb1061749966776c3f1"}, "spark": {:hex, :spark, "2.1.6", "15c6725836607322e867b40fb6bdeb13f4606d48a001d2a74518559678e26895", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "6838d226e83acedb3a6169169bfc2d7afde19ef6a37fb14f0572d39db9d56c7b"},
"splode": {:hex, :splode, "0.2.0", "a1f3b5a8e7c957be495bf0f22dd9e0567a87ec63559963a0ce0c3f0e8dfacedc", [:mix], [], "hexpm", "7cfecc5913ff7feeb04f143e2494cfa7bc6d5bb5bec70f7ffac94c18ea97f303"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@ defmodule AshPostgresTest do
test "transaction metadata is given to on_transaction_begin" do test "transaction metadata is given to on_transaction_begin" do
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
assert_receive %{ assert_receive %{
type: :create, type: :create,
@ -15,8 +15,8 @@ defmodule AshPostgresTest do
test "filter policies are applied" do test "filter policies are applied" do
post = post =
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Changeset.new(%{title: "good"}) |> Ash.Changeset.for_create(:create, %{title: "good"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
assert_raise Ash.Error.Forbidden, fn -> assert_raise Ash.Error.Forbidden, fn ->
post post
@ -24,13 +24,13 @@ defmodule AshPostgresTest do
authorize?: true, authorize?: true,
actor: %{id: Ash.UUID.generate()} actor: %{id: Ash.UUID.generate()}
) )
|> AshPostgres.Test.Api.update!() |> Ash.update!()
|> Map.get(:title) |> Map.get(:title)
end end
post post
|> Ash.Changeset.for_update(:update, %{title: "okay"}, authorize?: true) |> Ash.Changeset.for_update(:update, %{title: "okay"}, authorize?: true)
|> AshPostgres.Test.Api.update!() |> Ash.update!()
|> Map.get(:title) |> Map.get(:title)
end end
end end

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.AtomicsTest do defmodule AshPostgres.AtomicsTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
import Ash.Expr import Ash.Expr
@ -10,55 +10,57 @@ defmodule AshPostgres.AtomicsTest do
Post Post
|> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true) |> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true)
|> Ash.Changeset.atomic_update(:price, expr(price + 1)) |> Ash.Changeset.atomic_update(:price, expr(price + 1))
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true) |> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true)
|> Ash.Changeset.atomic_update(:price, expr(price + 1)) |> Ash.Changeset.atomic_update(:price, expr(price + 1))
|> Api.create!() |> Ash.create!()
assert [%{price: 2}] = Post |> Api.read!() assert [%{price: 2}] = Post |> Ash.read!()
end end
test "a basic atomic works" do test "a basic atomic works" do
post = post =
Post Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Api.create!() |> Ash.create!()
assert %{price: 2} = assert %{price: 2} =
post post
|> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.atomic_update(:price, expr(price + 1)) |> Ash.Changeset.atomic_update(:price, expr(price + 1))
|> Api.update!() |> Ash.update!()
end end
test "an atomic works with a datetime" do test "an atomic works with a datetime" do
post = post =
Post Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Api.create!() |> Ash.create!()
now = DateTime.utc_now() now = DateTime.utc_now()
assert %{created_at: ^now} = assert %{created_at: ^now} =
post post
|> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.new()
|> Ash.Changeset.atomic_update(:created_at, expr(^now)) |> Ash.Changeset.atomic_update(:created_at, expr(^now))
|> Api.update!() |> Ash.Changeset.for_update(:update, %{})
|> Ash.update!()
end end
test "an atomic that violates a constraint will return the proper error" do test "an atomic that violates a constraint will return the proper error" do
post = post =
Post Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Api.create!() |> Ash.create!()
assert_raise Ash.Error.Invalid, ~r/does not exist/, fn -> assert_raise Ash.Error.Invalid, ~r/does not exist/, fn ->
post post
|> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.new()
|> Ash.Changeset.atomic_update(:organization_id, Ash.UUID.generate()) |> Ash.Changeset.atomic_update(:organization_id, Ash.UUID.generate())
|> Api.update!() |> Ash.Changeset.for_update(:update, %{})
|> Ash.update!()
end end
end end
@ -66,13 +68,13 @@ defmodule AshPostgres.AtomicsTest do
post = post =
Post Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Api.create!() |> Ash.create!()
post = post =
post post
|> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.atomic_update(:score, expr(score_after_winning)) |> Ash.Changeset.atomic_update(:score, expr(score_after_winning))
|> Api.update!() |> Ash.update!()
assert post.score == 1 assert post.score == 1
end end
@ -81,7 +83,7 @@ defmodule AshPostgres.AtomicsTest do
post = post =
Post Post
|> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1})
|> Api.create!() |> Ash.create!()
assert Post.increment_score!(post, 2).score == 2 assert Post.increment_score!(post, 2).score == 2

View file

@ -1,20 +1,20 @@
defmodule AshPostgres.BulkCreateTest do defmodule AshPostgres.BulkCreateTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
describe "bulk creates" do describe "bulk creates" do
test "bulk creates insert each input" do test "bulk creates insert each input" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
assert [%{title: "fred"}, %{title: "george"}] = assert [%{title: "fred"}, %{title: "george"}] =
Post Post
|> Ash.Query.sort(:title) |> Ash.Query.sort(:title)
|> Api.read!() |> Ash.read!()
end end
test "bulk creates can be streamed" do test "bulk creates can be streamed" do
assert [{:ok, %{title: "fred"}}, {:ok, %{title: "george"}}] = assert [{:ok, %{title: "fred"}}, {:ok, %{title: "george"}}] =
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create, Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create,
return_stream?: true, return_stream?: true,
return_records?: true return_records?: true
) )
@ -26,7 +26,7 @@ defmodule AshPostgres.BulkCreateTest do
{:ok, %{title: "fred", uniq_one: "one", uniq_two: "two", price: 10}}, {:ok, %{title: "fred", uniq_one: "one", uniq_two: "two", price: 10}},
{:ok, %{title: "george", uniq_one: "three", uniq_two: "four", price: 20}} {:ok, %{title: "george", uniq_one: "three", uniq_two: "four", price: 20}}
] = ] =
Api.bulk_create!( Ash.bulk_create!(
[ [
%{title: "fred", uniq_one: "one", uniq_two: "two", price: 10}, %{title: "fred", uniq_one: "one", uniq_two: "two", price: 10},
%{title: "george", uniq_one: "three", uniq_two: "four", price: 20} %{title: "george", uniq_one: "three", uniq_two: "four", price: 20}
@ -42,7 +42,7 @@ defmodule AshPostgres.BulkCreateTest do
{:ok, %{title: "fred", uniq_one: "one", uniq_two: "two", price: 1000}}, {:ok, %{title: "fred", uniq_one: "one", uniq_two: "two", price: 1000}},
{:ok, %{title: "george", uniq_one: "three", uniq_two: "four", price: 20_000}} {:ok, %{title: "george", uniq_one: "three", uniq_two: "four", price: 20_000}}
] = ] =
Api.bulk_create!( Ash.bulk_create!(
[ [
%{title: "something", uniq_one: "one", uniq_two: "two", price: 1000}, %{title: "something", uniq_one: "one", uniq_two: "two", price: 1000},
%{title: "else", uniq_one: "three", uniq_two: "four", price: 20_000} %{title: "else", uniq_one: "three", uniq_two: "four", price: 20_000}
@ -76,7 +76,7 @@ defmodule AshPostgres.BulkCreateTest do
# id: org_id, # id: org_id,
# title: "Avengers" # title: "Avengers"
# }) # })
# |> Api.create!() # |> Ash.create!()
# assert [ # assert [
# {:ok, # {:ok,
@ -94,7 +94,7 @@ defmodule AshPostgres.BulkCreateTest do
# organization_id: org_id # organization_id: org_id
# }} # }}
# ] = # ] =
# Api.bulk_create!( # Ash.bulk_create!(
# [ # [
# %{ # %{
# name: "Tony Stark", # name: "Tony Stark",
@ -135,7 +135,7 @@ defmodule AshPostgres.BulkCreateTest do
# role: "master in chief" # role: "master in chief"
# }} # }}
# ] = # ] =
# Api.bulk_create!( # Ash.bulk_create!(
# [ # [
# %{ # %{
# name: "Tony Stark", # name: "Tony Stark",
@ -169,7 +169,7 @@ defmodule AshPostgres.BulkCreateTest do
# end # end
test "bulk creates can create relationships" do test "bulk creates can create relationships" do
Api.bulk_create!( Ash.bulk_create!(
[%{title: "fred", rating: %{score: 5}}, %{title: "george", rating: %{score: 0}}], [%{title: "fred", rating: %{score: 5}}, %{title: "george", rating: %{score: 0}}],
Post, Post,
:create :create
@ -182,14 +182,14 @@ defmodule AshPostgres.BulkCreateTest do
Post Post
|> Ash.Query.sort(:title) |> Ash.Query.sort(:title)
|> Ash.Query.load(:ratings) |> Ash.Query.load(:ratings)
|> Api.read!() |> Ash.read!()
end end
end end
describe "validation errors" do describe "validation errors" do
test "skips invalid by default" do test "skips invalid by default" do
assert %{records: [_], errors: [_]} = assert %{records: [_], errors: [_]} =
Api.bulk_create!([%{title: "fred"}, %{title: "not allowed"}], Post, :create, Ash.bulk_create!([%{title: "fred"}, %{title: "not allowed"}], Post, :create,
return_records?: true, return_records?: true,
return_errors?: true return_errors?: true
) )
@ -197,7 +197,7 @@ defmodule AshPostgres.BulkCreateTest do
test "returns errors in the stream" do test "returns errors in the stream" do
assert [{:ok, _}, {:error, _}] = assert [{:ok, _}, {:error, _}] =
Api.bulk_create!([%{title: "fred"}, %{title: "not allowed"}], Post, :create, Ash.bulk_create!([%{title: "fred"}, %{title: "not allowed"}], Post, :create,
return_records?: true, return_records?: true,
return_stream?: true, return_stream?: true,
return_errors?: true return_errors?: true
@ -209,7 +209,7 @@ defmodule AshPostgres.BulkCreateTest do
describe "database errors" do describe "database errors" do
test "database errors affect the entire batch" do test "database errors affect the entire batch" do
# assert %{records: [_], errors: [_]} = # assert %{records: [_], errors: [_]} =
Api.bulk_create( Ash.bulk_create(
[%{title: "fred"}, %{title: "george", organization_id: Ash.UUID.generate()}], [%{title: "fred"}, %{title: "george", organization_id: Ash.UUID.generate()}],
Post, Post,
:create, :create,
@ -219,11 +219,11 @@ defmodule AshPostgres.BulkCreateTest do
assert [] = assert [] =
Post Post
|> Ash.Query.sort(:title) |> Ash.Query.sort(:title)
|> Api.read!() |> Ash.read!()
end end
test "database errors don't affect other batches" do test "database errors don't affect other batches" do
Api.bulk_create( Ash.bulk_create(
[%{title: "george", organization_id: Ash.UUID.generate()}, %{title: "fred"}], [%{title: "george", organization_id: Ash.UUID.generate()}, %{title: "fred"}],
Post, Post,
:create, :create,
@ -234,7 +234,7 @@ defmodule AshPostgres.BulkCreateTest do
assert [%{title: "fred"}] = assert [%{title: "fred"}] =
Post Post
|> Ash.Query.sort(:title) |> Ash.Query.sort(:title)
|> Api.read!() |> Ash.read!()
end end
end end
end end

View file

@ -1,50 +1,50 @@
defmodule AshPostgres.BulkDestroyTest do defmodule AshPostgres.BulkDestroyTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Expr require Ash.Expr
require Ash.Query require Ash.Query
test "bulk destroys can run with nothing in the table" do test "bulk destroys can run with nothing in the table" do
Api.bulk_destroy!(Post, :update, %{title: "new_title"}) Ash.bulk_destroy!(Post, :update, %{title: "new_title"})
end end
test "bulk destroys destroy everything pertaining to the query" do test "bulk destroys destroy everything pertaining to the query" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Api.bulk_destroy!(Post, :update, %{}) Ash.bulk_destroy!(Post, :update, %{})
assert Api.read!(Post) == [] assert Ash.read!(Post) == []
end end
test "bulk destroys only apply to things that the query produces" do test "bulk destroys only apply to things that the query produces" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Post Post
|> Ash.Query.filter(title == "fred") |> Ash.Query.filter(title == "fred")
|> Api.bulk_destroy!(:update, %{}) |> Ash.bulk_destroy!(:update, %{})
# 😢 sad # 😢 sad
assert [%{title: "george"}] = Api.read!(Post) assert [%{title: "george"}] = Ash.read!(Post)
end end
test "the query can join to related tables when necessary" do test "the query can join to related tables when necessary" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Post Post
|> Ash.Query.filter(author.first_name == "fred" or title == "fred") |> Ash.Query.filter(author.first_name == "fred" or title == "fred")
|> Api.bulk_destroy!(:update, %{}, return_records?: true) |> Ash.bulk_destroy!(:update, %{}, return_records?: true)
assert [%{title: "george"}] = Api.read!(Post) assert [%{title: "george"}] = Ash.read!(Post)
end end
test "bulk destroys can be done even on stream inputs" do test "bulk destroys can be done even on stream inputs" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Post Post
|> Api.read!() |> Ash.read!()
|> Api.bulk_destroy!(:destroy, %{}) |> Ash.bulk_destroy!(:destroy, %{}, strategy: :stream, return_errors?: true)
assert [] = Api.read!(Post) assert [] = Ash.read!(Post)
end end
end end

View file

@ -1,30 +1,30 @@
defmodule AshPostgres.BulkUpdateTest do defmodule AshPostgres.BulkUpdateTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Expr require Ash.Expr
require Ash.Query require Ash.Query
test "bulk updates can run with nothing in the table" do test "bulk updates can run with nothing in the table" do
Api.bulk_update!(Post, :update, %{title: "new_title"}) Ash.bulk_update!(Post, :update, %{title: "new_title"})
end end
test "bulk updates update everything pertaining to the query" do test "bulk updates update everything pertaining to the query" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Api.bulk_update!(Post, :update, %{}, Ash.bulk_update!(Post, :update, %{},
atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")} atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}
) )
posts = Api.read!(Post) posts = Ash.read!(Post)
assert Enum.all?(posts, &String.ends_with?(&1.title, "_stuff")) assert Enum.all?(posts, &String.ends_with?(&1.title, "_stuff"))
end end
test "a map can be given as input" do test "a map can be given as input" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Post Post
|> Api.bulk_update!( |> Ash.bulk_update!(
:update, :update,
%{list_of_stuff: [%{a: 1}]}, %{list_of_stuff: [%{a: 1}]},
return_records?: true, return_records?: true,
@ -36,25 +36,25 @@ defmodule AshPostgres.BulkUpdateTest do
test "a map can be given as input on a regular update" do test "a map can be given as input on a regular update" do
%{records: [post | _]} = %{records: [post | _]} =
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create, Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create,
return_records?: true return_records?: true
) )
post post
|> Ash.Changeset.for_update(:update, %{list_of_stuff: [%{a: [:a, :b]}, %{a: [:c, :d]}]}) |> Ash.Changeset.for_update(:update, %{list_of_stuff: [%{a: [:a, :b]}, %{a: [:c, :d]}]})
|> Api.update!() |> Ash.update!()
end end
test "bulk updates only apply to things that the query produces" do test "bulk updates only apply to things that the query produces" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Post Post
|> Ash.Query.filter(title == "fred") |> Ash.Query.filter(title == "fred")
|> Api.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) |> Ash.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")})
titles = titles =
Post Post
|> Api.read!() |> Ash.read!()
|> Enum.map(& &1.title) |> Enum.map(& &1.title)
|> Enum.sort() |> Enum.sort()
@ -62,15 +62,15 @@ defmodule AshPostgres.BulkUpdateTest do
end end
test "the query can join to related tables when necessary" do test "the query can join to related tables when necessary" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Post Post
|> Ash.Query.filter(author.first_name == "fred" or title == "fred") |> Ash.Query.filter(author.first_name == "fred" or title == "fred")
|> Api.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}) |> Ash.bulk_update!(:update, %{}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")})
titles = titles =
Post Post
|> Api.read!() |> Ash.read!()
|> Enum.map(& &1.title) |> Enum.map(& &1.title)
|> Enum.sort() |> Enum.sort()
@ -78,18 +78,19 @@ defmodule AshPostgres.BulkUpdateTest do
end end
test "bulk updates can be done even on stream inputs" do test "bulk updates can be done even on stream inputs" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Post Post
|> Api.read!() |> Ash.read!()
|> Api.bulk_update!(:update, %{}, |> Ash.bulk_update!(:update, %{},
atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")}, atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")},
return_records?: true return_records?: true,
strategy: [:stream]
) )
titles = titles =
Post Post
|> Api.read!() |> Ash.read!()
|> Enum.map(& &1.title) |> Enum.map(& &1.title)
|> Enum.sort() |> Enum.sort()
@ -97,14 +98,17 @@ defmodule AshPostgres.BulkUpdateTest do
end end
test "bulk updates that require initial data must use streaming" do test "bulk updates that require initial data must use streaming" do
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create) Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
Post assert_raise Ash.Error.Invalid, ~r/had no matching bulk strategy that could be used/, fn ->
|> Ash.Query.for_read(:paginated, authorize?: true) Post
|> Api.bulk_update!(:requires_initial_data, %{}, |> Ash.Query.for_read(:paginated, authorize?: true)
authorize?: true, |> Ash.bulk_update!(:requires_initial_data, %{},
allow_stream_with: :full_read, authorize?: true,
authorize_query?: false allow_stream_with: :full_read,
) authorize_query?: false,
return_errors?: true
)
end
end end
end end

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.CalculationTest do defmodule AshPostgres.CalculationTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Account, Api, Author, Comment, Post, User} alias AshPostgres.Test.{Account, Author, Comment, Post, User}
require Ash.Query require Ash.Query
import Ash.Expr import Ash.Expr
@ -8,48 +8,48 @@ defmodule AshPostgres.CalculationTest do
test "an expression calculation can be filtered on" do test "an expression calculation can be filtered on" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Api.create!() |> Ash.create!()
post2 = post2 =
Post Post
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
post3 = post3 =
Post Post
|> Ash.Changeset.new(%{title: "title3"}) |> Ash.Changeset.for_create(:create, %{title: "title3"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "_"}) |> Ash.Changeset.for_create(:create, %{title: "_"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "_"}) |> Ash.Changeset.for_create(:create, %{title: "_"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "_"}) |> Ash.Changeset.for_create(:create, %{title: "_"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
post post
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [post2, post3], type: :append_and_remove) |> Ash.Changeset.manage_relationship(:linked_posts, [post2, post3], type: :append_and_remove)
|> Api.update!() |> Ash.update!()
post2 post2
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [post3], type: :append_and_remove) |> Ash.Changeset.manage_relationship(:linked_posts, [post3], type: :append_and_remove)
|> Api.update!() |> Ash.update!()
assert [%{c_times_p: 6, title: "match"}] = assert [%{c_times_p: 6, title: "match"}] =
Post Post
|> Ash.Query.load(:c_times_p) |> Ash.Query.load(:c_times_p)
|> Api.read!() |> Ash.read!()
|> Enum.filter(&(&1.c_times_p == 6)) |> Enum.filter(&(&1.c_times_p == 6))
assert [ assert [
@ -57,12 +57,12 @@ defmodule AshPostgres.CalculationTest do
] = ] =
Post Post
|> Ash.Query.filter(c_times_p == 6) |> Ash.Query.filter(c_times_p == 6)
|> Api.read!() |> Ash.read!()
assert [] = assert [] =
Post Post
|> Ash.Query.filter(author: [has_posts: true]) |> Ash.Query.filter(author: [has_posts: true])
|> Api.read!() |> Ash.read!()
end end
test "calculations can refer to to_one path attributes in filters" do test "calculations can refer to to_one path attributes in filters" do
@ -72,18 +72,18 @@ defmodule AshPostgres.CalculationTest do
first_name: "Foo", first_name: "Foo",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [%{author_first_name_calc: "Foo"}] = assert [%{author_first_name_calc: "Foo"}] =
Post Post
|> Ash.Query.filter(author_first_name_calc == "Foo") |> Ash.Query.filter(author_first_name_calc == "Foo")
|> Ash.Query.load(:author_first_name_calc) |> Ash.Query.load(:author_first_name_calc)
|> Api.read!() |> Ash.read!()
end end
test "calculations can refer to to_one path attributes in sorts" do test "calculations can refer to to_one path attributes in sorts" do
@ -93,44 +93,44 @@ defmodule AshPostgres.CalculationTest do
first_name: "Foo", first_name: "Foo",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [%{author_first_name_calc: "Foo"}] = assert [%{author_first_name_calc: "Foo"}] =
Post Post
|> Ash.Query.sort(:author_first_name_calc) |> Ash.Query.sort(:author_first_name_calc)
|> Ash.Query.load(:author_first_name_calc) |> Ash.Query.load(:author_first_name_calc)
|> Api.read!() |> Ash.read!()
end end
test "calculations can refer to embedded attributes" do test "calculations can refer to embedded attributes" do
author = author =
Author Author
|> Ash.Changeset.for_create(:create, %{bio: %{title: "Mr.", bio: "Bones"}}) |> Ash.Changeset.for_create(:create, %{bio: %{title: "Mr.", bio: "Bones"}})
|> Api.create!() |> Ash.create!()
assert %{title: "Mr."} = assert %{title: "Mr."} =
Author Author
|> Ash.Query.filter(id == ^author.id) |> Ash.Query.filter(id == ^author.id)
|> Ash.Query.load(:title) |> Ash.Query.load(:title)
|> Api.read_one!() |> Ash.read_one!()
end end
test "calculations can use the || operator" do test "calculations can use the || operator" do
author = author =
Author Author
|> Ash.Changeset.for_create(:create, %{bio: %{title: "Mr.", bio: "Bones"}}) |> Ash.Changeset.for_create(:create, %{bio: %{title: "Mr.", bio: "Bones"}})
|> Api.create!() |> Ash.create!()
assert %{first_name_or_bob: "bob"} = assert %{first_name_or_bob: "bob"} =
Author Author
|> Ash.Query.filter(id == ^author.id) |> Ash.Query.filter(id == ^author.id)
|> Ash.Query.load(:first_name_or_bob) |> Ash.Query.load(:first_name_or_bob)
|> Api.read_one!() |> Ash.read_one!()
end end
test "calculations can use the && operator" do test "calculations can use the && operator" do
@ -140,60 +140,60 @@ defmodule AshPostgres.CalculationTest do
first_name: "fred", first_name: "fred",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert %{first_name_and_bob: "bob"} = assert %{first_name_and_bob: "bob"} =
Author Author
|> Ash.Query.filter(id == ^author.id) |> Ash.Query.filter(id == ^author.id)
|> Ash.Query.load(:first_name_and_bob) |> Ash.Query.load(:first_name_and_bob)
|> Api.read_one!() |> Ash.read_one!()
end end
test "calculations can be used in related filters" do test "calculations can be used in related filters" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Api.create!() |> Ash.create!()
post2 = post2 =
Post Post
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
post3 = post3 =
Post Post
|> Ash.Changeset.new(%{title: "title3"}) |> Ash.Changeset.for_create(:create, %{title: "title3"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no_match"}) |> Ash.Changeset.for_create(:create, %{title: "no_match"})
|> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post2, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
post post
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [post2, post3], type: :append_and_remove) |> Ash.Changeset.manage_relationship(:linked_posts, [post2, post3], type: :append_and_remove)
|> Api.update!() |> Ash.update!()
post2 post2
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [post3], type: :append_and_remove) |> Ash.Changeset.manage_relationship(:linked_posts, [post3], type: :append_and_remove)
|> Api.update!() |> Ash.update!()
posts_query = posts_query =
Post Post
@ -202,7 +202,7 @@ defmodule AshPostgres.CalculationTest do
assert %{post: %{c_times_p: 6}} = assert %{post: %{c_times_p: 6}} =
Comment Comment
|> Ash.Query.load(post: posts_query) |> Ash.Query.load(post: posts_query)
|> Api.read!() |> Ash.read!()
|> Enum.filter(&(&1.post.c_times_p == 6)) |> Enum.filter(&(&1.post.c_times_p == 6))
|> Enum.at(0) |> Enum.at(0)
@ -214,20 +214,20 @@ defmodule AshPostgres.CalculationTest do
assert [ assert [
%{post: %{c_times_p: 6, title: "match"}} %{post: %{c_times_p: 6, title: "match"}}
] = Api.read!(query) ] = Ash.read!(query)
post |> Api.load!(:c_times_p) post |> Ash.load!(:c_times_p)
end end
test "concat calculation can be filtered on" do test "concat calculation can be filtered on" do
author = author =
Author Author
|> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) |> Ash.Changeset.for_create(:create, %{first_name: "is", last_name: "match"})
|> Api.create!() |> Ash.create!()
Author Author
|> Ash.Changeset.new(%{first_name: "not", last_name: "match"}) |> Ash.Changeset.for_create(:create, %{first_name: "not", last_name: "match"})
|> Api.create!() |> Ash.create!()
author_id = author.id author_id = author.id
@ -235,60 +235,60 @@ defmodule AshPostgres.CalculationTest do
Author Author
|> Ash.Query.load(:full_name) |> Ash.Query.load(:full_name)
|> Ash.Query.filter(full_name == "is match") |> Ash.Query.filter(full_name == "is match")
|> Api.read_one!() |> Ash.read_one!()
end end
test "calculations that refer to aggregates in comparison expressions can be filtered on" do test "calculations that refer to aggregates in comparison expressions can be filtered on" do
Post Post
|> Ash.Query.load(:has_future_comment) |> Ash.Query.load(:has_future_comment)
|> Api.read!() |> Ash.read!()
end end
test "calculations that refer to aggregates can be authorized" do test "calculations that refer to aggregates can be authorized" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "comment"}) |> Ash.Changeset.for_create(:create, %{title: "comment"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert %{has_future_comment: false} = assert %{has_future_comment: false} =
Post Post
|> Ash.Query.load([:has_future_comment, :latest_comment_created_at]) |> Ash.Query.load([:has_future_comment, :latest_comment_created_at])
|> Ash.Query.for_read(:allow_any, %{}) |> Ash.Query.for_read(:allow_any, %{})
|> Api.read_one!(authorize?: true) |> Ash.read_one!(authorize?: true)
assert %{has_future_comment: true} = assert %{has_future_comment: true} =
Post Post
|> Ash.Query.load([:has_future_comment, :latest_comment_created_at]) |> Ash.Query.load([:has_future_comment, :latest_comment_created_at])
|> Ash.Query.for_read(:allow_any, %{}) |> Ash.Query.for_read(:allow_any, %{})
|> Api.read_one!(authorize?: false) |> Ash.read_one!(authorize?: false)
assert %{has_future_comment: false} = assert %{has_future_comment: false} =
Post Post
|> Ash.Query.for_read(:allow_any, %{}) |> Ash.Query.for_read(:allow_any, %{})
|> Api.read_one!() |> Ash.read_one!()
|> Api.load!([:has_future_comment, :latest_comment_created_at], authorize?: true) |> Ash.load!([:has_future_comment, :latest_comment_created_at], authorize?: true)
assert %{has_future_comment: true} = assert %{has_future_comment: true} =
Post Post
|> Ash.Query.for_read(:allow_any, %{}) |> Ash.Query.for_read(:allow_any, %{})
|> Api.read_one!() |> Ash.read_one!()
|> Api.load!([:has_future_comment, :latest_comment_created_at], authorize?: false) |> Ash.load!([:has_future_comment, :latest_comment_created_at], authorize?: false)
end end
test "conditional calculations can be filtered on" do test "conditional calculations can be filtered on" do
author = author =
Author Author
|> Ash.Changeset.new(%{first_name: "tom"}) |> Ash.Changeset.for_create(:create, %{first_name: "tom"})
|> Api.create!() |> Ash.create!()
Author Author
|> Ash.Changeset.new(%{first_name: "tom", last_name: "holland"}) |> Ash.Changeset.for_create(:create, %{first_name: "tom", last_name: "holland"})
|> Api.create!() |> Ash.create!()
author_id = author.id author_id = author.id
@ -296,45 +296,45 @@ defmodule AshPostgres.CalculationTest do
Author Author
|> Ash.Query.load([:conditional_full_name, :full_name]) |> Ash.Query.load([:conditional_full_name, :full_name])
|> Ash.Query.filter(conditional_full_name == "(none)") |> Ash.Query.filter(conditional_full_name == "(none)")
|> Api.read_one!() |> Ash.read_one!()
end end
test "parameterized calculations can be filtered on" do test "parameterized calculations can be filtered on" do
Author Author
|> Ash.Changeset.new(%{first_name: "tom", last_name: "holland"}) |> Ash.Changeset.for_create(:create, %{first_name: "tom", last_name: "holland"})
|> Api.create!() |> Ash.create!()
assert %{param_full_name: "tom holland"} = assert %{param_full_name: "tom holland"} =
Author Author
|> Ash.Query.load(:param_full_name) |> Ash.Query.load(:param_full_name)
|> Api.read_one!() |> Ash.read_one!()
assert %{param_full_name: "tom~holland"} = assert %{param_full_name: "tom~holland"} =
Author Author
|> Ash.Query.load(param_full_name: [separator: "~"]) |> Ash.Query.load(param_full_name: [separator: "~"])
|> Api.read_one!() |> Ash.read_one!()
assert %{} = assert %{} =
Author Author
|> Ash.Query.filter(param_full_name(separator: "~") == "tom~holland") |> Ash.Query.filter(param_full_name(separator: "~") == "tom~holland")
|> Api.read_one!() |> Ash.read_one!()
end end
test "parameterized related calculations can be filtered on" do test "parameterized related calculations can be filtered on" do
author = author =
Author Author
|> Ash.Changeset.new(%{first_name: "tom", last_name: "holland"}) |> Ash.Changeset.for_create(:create, %{first_name: "tom", last_name: "holland"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert %{title: "match"} = assert %{title: "match"} =
Comment Comment
|> Ash.Query.filter(author.param_full_name(separator: "~") == "tom~holland") |> Ash.Query.filter(author.param_full_name(separator: "~") == "tom~holland")
|> Api.read_one!() |> Ash.read_one!()
assert %{title: "match"} = assert %{title: "match"} =
Comment Comment
@ -342,93 +342,97 @@ defmodule AshPostgres.CalculationTest do
author.param_full_name(separator: "~") == "tom~holland" and author.param_full_name(separator: "~") == "tom~holland" and
author.param_full_name(separator: " ") == "tom holland" author.param_full_name(separator: " ") == "tom holland"
) )
|> Api.read_one!() |> Ash.read_one!()
end end
test "parameterized calculations can be sorted on" do test "parameterized calculations can be sorted on" do
Author Author
|> Ash.Changeset.new(%{first_name: "tom", last_name: "holland"}) |> Ash.Changeset.for_create(:create, %{first_name: "tom", last_name: "holland"})
|> Api.create!() |> Ash.create!()
Author Author
|> Ash.Changeset.new(%{first_name: "abc", last_name: "def"}) |> Ash.Changeset.for_create(:create, %{first_name: "abc", last_name: "def"})
|> Api.create!() |> Ash.create!()
assert [%{first_name: "abc"}, %{first_name: "tom"}] = assert [%{first_name: "abc"}, %{first_name: "tom"}] =
Author Author
|> Ash.Query.sort(param_full_name: [separator: "~"]) |> Ash.Query.sort(param_full_name: [separator: "~"])
|> Api.read!() |> Ash.read!()
end end
test "calculations using if and literal boolean results can run" do test "calculations using if and literal boolean results can run" do
Post Post
|> Ash.Query.load(:was_created_in_the_last_month) |> Ash.Query.load(:was_created_in_the_last_month)
|> Ash.Query.filter(was_created_in_the_last_month == true) |> Ash.Query.filter(was_created_in_the_last_month == true)
|> Api.read!() |> Ash.read!()
end end
test "nested conditional calculations can be loaded" do test "nested conditional calculations can be loaded" do
Author Author
|> Ash.Changeset.new(%{last_name: "holland"}) |> Ash.Changeset.for_create(:create, %{last_name: "holland"})
|> Api.create!() |> Ash.create!()
Author Author
|> Ash.Changeset.new(%{first_name: "tom"}) |> Ash.Changeset.for_create(:create, %{first_name: "tom"})
|> Api.create!() |> Ash.create!()
assert [%{nested_conditional: "No First Name"}, %{nested_conditional: "No Last Name"}] = assert [%{nested_conditional: "No First Name"}, %{nested_conditional: "No Last Name"}] =
Author Author
|> Ash.Query.load(:nested_conditional) |> Ash.Query.load(:nested_conditional)
|> Ash.Query.sort(:nested_conditional) |> Ash.Query.sort(:nested_conditional)
|> Api.read!() |> Ash.read!()
end end
test "calculations load nullable timestamp aggregates compared to a fragment" do test "calculations load nullable timestamp aggregates compared to a fragment" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "bbb", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "aaa", likes: 1, arbitrary_timestamp: DateTime.now!("Etc/UTC")}) |> Ash.Changeset.for_create(:create, %{
title: "aaa",
likes: 1,
arbitrary_timestamp: DateTime.now!("Etc/UTC")
})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "bbb", likes: 1}) |> Ash.Changeset.for_create(:create, %{title: "bbb", likes: 1})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "aaa", likes: 2}) |> Ash.Changeset.for_create(:create, %{title: "aaa", likes: 2})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Query.load([:has_future_arbitrary_timestamp]) |> Ash.Query.load([:has_future_arbitrary_timestamp])
|> Api.read!() |> Ash.read!()
end end
test "loading a calculation loads its dependent loads" do test "loading a calculation loads its dependent loads" do
user = user =
User User
|> Ash.Changeset.for_create(:create, %{is_active: true}) |> Ash.Changeset.for_create(:create, %{is_active: true})
|> Api.create!() |> Ash.create!()
account = account =
Account Account
|> Ash.Changeset.for_create(:create, %{is_active: true}) |> Ash.Changeset.for_create(:create, %{is_active: true})
|> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
|> Api.load!([:active]) |> Ash.load!([:active])
assert account.active assert account.active
end end
@ -442,7 +446,7 @@ defmodule AshPostgres.CalculationTest do
last_name: "Jones", last_name: "Jones",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert %{ assert %{
full_name_with_nils: "Bill Jones", full_name_with_nils: "Bill Jones",
@ -452,7 +456,7 @@ defmodule AshPostgres.CalculationTest do
|> Ash.Query.filter(id == ^author.id) |> Ash.Query.filter(id == ^author.id)
|> Ash.Query.load(:full_name_with_nils) |> Ash.Query.load(:full_name_with_nils)
|> Ash.Query.load(:full_name_with_nils_no_joiner) |> Ash.Query.load(:full_name_with_nils_no_joiner)
|> Api.read_one!() |> Ash.read_one!()
end end
test "with nil value" do test "with nil value" do
@ -462,7 +466,7 @@ defmodule AshPostgres.CalculationTest do
first_name: "Bill", first_name: "Bill",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert %{ assert %{
full_name_with_nils: "Bill", full_name_with_nils: "Bill",
@ -472,23 +476,23 @@ defmodule AshPostgres.CalculationTest do
|> Ash.Query.filter(id == ^author.id) |> Ash.Query.filter(id == ^author.id)
|> Ash.Query.load(:full_name_with_nils) |> Ash.Query.load(:full_name_with_nils)
|> Ash.Query.load(:full_name_with_nils_no_joiner) |> Ash.Query.load(:full_name_with_nils_no_joiner)
|> Api.read_one!() |> Ash.read_one!()
end end
end end
test "arguments with cast_in_query?: false are not cast" do test "arguments with cast_in_query?: false are not cast" do
Post Post
|> Ash.Changeset.new(%{title: "match", score: 42}) |> Ash.Changeset.for_create(:create, %{title: "match", score: 42})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "not", score: 42}) |> Ash.Changeset.for_create(:create, %{title: "not", score: 42})
|> Api.create!() |> Ash.create!()
assert [post] = assert [post] =
Post Post
|> Ash.Query.filter(similarity(search: expr(query(search: "match")))) |> Ash.Query.filter(similarity(search: expr(query(search: "match"))))
|> Api.read!() |> Ash.read!()
assert post.title == "match" assert post.title == "match"
end end
@ -502,7 +506,7 @@ defmodule AshPostgres.CalculationTest do
last_name: "Jones", last_name: "Jones",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert %{ assert %{
split_full_name: ["Bill", "Jones"] split_full_name: ["Bill", "Jones"]
@ -510,7 +514,7 @@ defmodule AshPostgres.CalculationTest do
Author Author
|> Ash.Query.filter(id == ^author.id) |> Ash.Query.filter(id == ^author.id)
|> Ash.Query.load(:split_full_name) |> Ash.Query.load(:split_full_name)
|> Api.read_one!() |> Ash.read_one!()
end end
test "trimming whitespace" do test "trimming whitespace" do
@ -521,7 +525,7 @@ defmodule AshPostgres.CalculationTest do
last_name: "Jones ", last_name: "Jones ",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert %{ assert %{
split_full_name_trim: ["Bill", "Jones"], split_full_name_trim: ["Bill", "Jones"],
@ -530,37 +534,37 @@ defmodule AshPostgres.CalculationTest do
Author Author
|> Ash.Query.filter(id == ^author.id) |> Ash.Query.filter(id == ^author.id)
|> Ash.Query.load([:split_full_name_trim, :split_full_name]) |> Ash.Query.load([:split_full_name_trim, :split_full_name])
|> Api.read_one!() |> Ash.read_one!()
end end
end end
describe "count_nils/1" do describe "count_nils/1" do
test "counts nil values" do test "counts nil values" do
Post Post
|> Ash.Changeset.new(%{list_containing_nils: ["a", nil, "b", nil, "c"]}) |> Ash.Changeset.for_create(:create, %{list_containing_nils: ["a", nil, "b", nil, "c"]})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{list_containing_nils: ["a", nil, "b", "c"]}) |> Ash.Changeset.for_create(:create, %{list_containing_nils: ["a", nil, "b", "c"]})
|> Api.create!() |> Ash.create!()
assert [_] = assert [_] =
Post Post
|> Ash.Query.filter(count_nils(list_containing_nils) == 2) |> Ash.Query.filter(count_nils(list_containing_nils) == 2)
|> Api.read!() |> Ash.read!()
end end
end end
describe "-/1" do describe "-/1" do
test "makes numbers negative" do test "makes numbers negative" do
Post Post
|> Ash.Changeset.new(%{title: "match", score: 42}) |> Ash.Changeset.for_create(:create, %{title: "match", score: 42})
|> Api.create!() |> Ash.create!()
assert [%{negative_score: -42}] = assert [%{negative_score: -42}] =
Post Post
|> Ash.Query.load(:negative_score) |> Ash.Query.load(:negative_score)
|> Api.read!() |> Ash.read!()
end end
end end
@ -568,39 +572,39 @@ defmodule AshPostgres.CalculationTest do
test "maps can reference filtered aggregates" do test "maps can reference filtered aggregates" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "match", score: 42}) |> Ash.Changeset.for_create(:create, %{title: "match", score: 42})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "foo", likes: 2}) |> Ash.Changeset.for_create(:create, %{title: "foo", likes: 2})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "foo", likes: 2}) |> Ash.Changeset.for_create(:create, %{title: "foo", likes: 2})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "bar", likes: 2}) |> Ash.Changeset.for_create(:create, %{title: "bar", likes: 2})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [%{agg_map: %{called_foo: 2, called_bar: 1}}] = assert [%{agg_map: %{called_foo: 2, called_bar: 1}}] =
Post Post
|> Ash.Query.load(:agg_map) |> Ash.Query.load(:agg_map)
|> Api.read!() |> Ash.read!()
end end
test "maps can be constructed" do test "maps can be constructed" do
Post Post
|> Ash.Changeset.new(%{title: "match", score: 42}) |> Ash.Changeset.for_create(:create, %{title: "match", score: 42})
|> Api.create!() |> Ash.create!()
assert [%{score_map: %{negative_score: %{foo: -42}}}] = assert [%{score_map: %{negative_score: %{foo: -42}}}] =
Post Post
|> Ash.Query.load(:score_map) |> Ash.Query.load(:score_map)
|> Api.read!() |> Ash.read!()
end end
end end
@ -613,7 +617,7 @@ defmodule AshPostgres.CalculationTest do
last_name: "Jones ", last_name: "Jones ",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert %{ assert %{
first_name_from_split: "Bill" first_name_from_split: "Bill"
@ -621,15 +625,15 @@ defmodule AshPostgres.CalculationTest do
Author Author
|> Ash.Query.filter(id == ^author.id) |> Ash.Query.filter(id == ^author.id)
|> Ash.Query.load([:first_name_from_split]) |> Ash.Query.load([:first_name_from_split])
|> Api.read_one!() |> Ash.read_one!()
end end
end end
test "dependent calc" do test "dependent calc" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "match", price: 10_024}) |> Ash.Changeset.for_create(:create, %{title: "match", price: 10_024})
|> Api.create!() |> Ash.create!()
Post.get_by_id(post.id, Post.get_by_id(post.id,
query: Post |> Ash.Query.select([:id]) |> Ash.Query.load([:price_string_with_currency_sign]) query: Post |> Ash.Query.select([:id]) |> Ash.Query.load([:price_string_with_currency_sign])
@ -639,10 +643,14 @@ defmodule AshPostgres.CalculationTest do
test "nested get_path works" do test "nested get_path works" do
assert "thing" = assert "thing" =
Post Post
|> Ash.Changeset.new(%{title: "match", price: 10_024, stuff: %{foo: %{bar: "thing"}}}) |> Ash.Changeset.for_create(:create, %{
title: "match",
price: 10_024,
stuff: %{foo: %{bar: "thing"}}
})
|> Ash.Changeset.deselect(:stuff) |> Ash.Changeset.deselect(:stuff)
|> Api.create!() |> Ash.create!()
|> Api.load!(:foo_bar_from_stuff) |> Ash.load!(:foo_bar_from_stuff)
|> Map.get(:foo_bar_from_stuff) |> Map.get(:foo_bar_from_stuff)
end end
@ -654,19 +662,19 @@ defmodule AshPostgres.CalculationTest do
last_name: "Jones", last_name: "Jones",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert %AshPostgres.Test.Money{} = assert %AshPostgres.Test.Money{} =
Post Post
|> Ash.Changeset.new(%{title: "match", price: 10_024}) |> Ash.Changeset.for_create(:create, %{title: "match", price: 10_024})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
|> Api.load!(:calc_returning_json) |> Ash.load!(:calc_returning_json)
|> Map.get(:calc_returning_json) |> Map.get(:calc_returning_json)
assert [%AshPostgres.Test.Money{}] = assert [%AshPostgres.Test.Money{}] =
author author
|> Api.load!(posts: :calc_returning_json) |> Ash.load!(posts: :calc_returning_json)
|> Map.get(:posts) |> Map.get(:posts)
|> Enum.map(&Map.get(&1, :calc_returning_json)) |> Enum.map(&Map.get(&1, :calc_returning_json))
end end
@ -678,7 +686,7 @@ defmodule AshPostgres.CalculationTest do
last_name: "Jones", last_name: "Jones",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert %{calculations: %{length: 9}} = assert %{calculations: %{length: 9}} =
Author Author
@ -687,7 +695,7 @@ defmodule AshPostgres.CalculationTest do
expr(string_length(string_trim(first_name <> last_name <> " "))), expr(string_length(string_trim(first_name <> last_name <> " "))),
:integer :integer
) )
|> Api.read_one!() |> Ash.read_one!()
end end
test "an expression calculation that loads a runtime calculation works" do test "an expression calculation that loads a runtime calculation works" do
@ -697,12 +705,12 @@ defmodule AshPostgres.CalculationTest do
last_name: "Jones", last_name: "Jones",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert [%{expr_referencing_runtime: "Bill Jones Bill Jones"}] = assert [%{expr_referencing_runtime: "Bill Jones Bill Jones"}] =
Author Author
|> Ash.Query.load(:expr_referencing_runtime) |> Ash.Query.load(:expr_referencing_runtime)
|> Api.read!() |> Ash.read!()
end end
test "lazy values are evaluated lazily" do test "lazy values are evaluated lazily" do
@ -712,7 +720,7 @@ defmodule AshPostgres.CalculationTest do
last_name: "Jones", last_name: "Jones",
bio: %{title: "Mr.", bio: "Bones"} bio: %{title: "Mr.", bio: "Bones"}
}) })
|> Api.create!() |> Ash.create!()
assert %{calculations: %{string: "fred"}} = assert %{calculations: %{string: "fred"}} =
Author Author
@ -721,21 +729,21 @@ defmodule AshPostgres.CalculationTest do
expr(lazy({__MODULE__, :fred, []})), expr(lazy({__MODULE__, :fred, []})),
:string :string
) )
|> Api.read_one!() |> Ash.read_one!()
end end
test "exists with a relationship that has a filtered read action works" do test "exists with a relationship that has a filtered read action works" do
post = post =
Post Post
|> Ash.Changeset.for_create(:create, %{}) |> Ash.Changeset.for_create(:create, %{})
|> Api.create!() |> Ash.create!()
post_id = post.id post_id = post.id
assert [%{id: ^post_id}] = assert [%{id: ^post_id}] =
Post Post
|> Ash.Query.filter(has_no_followers) |> Ash.Query.filter(has_no_followers)
|> Api.read!() |> Ash.read!()
end end
def fred do def fred do

View file

@ -7,29 +7,29 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
certification = certification =
AshPostgres.Test.ComplexCalculations.Certification AshPostgres.Test.ComplexCalculations.Certification
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
skill = skill =
AshPostgres.Test.ComplexCalculations.Skill AshPostgres.Test.ComplexCalculations.Skill
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:certification, certification, type: :append) |> Ash.Changeset.manage_relationship(:certification, certification, type: :append)
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
_documentation = _documentation =
AshPostgres.Test.ComplexCalculations.Documentation AshPostgres.Test.ComplexCalculations.Documentation
|> Ash.Changeset.new(%{status: :demonstrated}) |> Ash.Changeset.for_create(:create, %{status: :demonstrated})
|> Ash.Changeset.manage_relationship(:skill, skill, type: :append) |> Ash.Changeset.manage_relationship(:skill, skill, type: :append)
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
skill = skill =
skill skill
|> AshPostgres.Test.ComplexCalculations.Api.load!([:latest_documentation_status]) |> Ash.load!([:latest_documentation_status])
assert skill.latest_documentation_status == :demonstrated assert skill.latest_documentation_status == :demonstrated
certification = certification =
certification certification
|> AshPostgres.Test.ComplexCalculations.Api.load!([ |> Ash.load!([
:count_of_skills :count_of_skills
]) ])
@ -37,7 +37,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
certification = certification =
certification certification
|> AshPostgres.Test.ComplexCalculations.Api.load!([ |> Ash.load!([
:count_of_approved_skills :count_of_approved_skills
]) ])
@ -45,7 +45,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
certification = certification =
certification certification
|> AshPostgres.Test.ComplexCalculations.Api.load!([ |> Ash.load!([
:count_of_documented_skills :count_of_documented_skills
]) ])
@ -53,7 +53,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
certification = certification =
certification certification
|> AshPostgres.Test.ComplexCalculations.Api.load!([ |> Ash.load!([
:count_of_documented_skills, :count_of_documented_skills,
:all_documentation_approved, :all_documentation_approved,
:some_documentation_created :some_documentation_created
@ -66,35 +66,35 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
channel = channel =
AshPostgres.Test.ComplexCalculations.Channel AshPostgres.Test.ComplexCalculations.Channel
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
user_1 = user_1 =
AshPostgres.Test.User AshPostgres.Test.User
|> Ash.Changeset.for_create(:create, %{name: "User 1"}) |> Ash.Changeset.for_create(:create, %{name: "User 1"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
user_2 = user_2 =
AshPostgres.Test.User AshPostgres.Test.User
|> Ash.Changeset.for_create(:create, %{name: "User 2"}) |> Ash.Changeset.for_create(:create, %{name: "User 2"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
channel_member_1 = channel_member_1 =
AshPostgres.Test.ComplexCalculations.ChannelMember AshPostgres.Test.ComplexCalculations.ChannelMember
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:channel, channel, type: :append) |> Ash.Changeset.manage_relationship(:channel, channel, type: :append)
|> Ash.Changeset.manage_relationship(:user, user_1, type: :append) |> Ash.Changeset.manage_relationship(:user, user_1, type: :append)
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
channel_member_2 = channel_member_2 =
AshPostgres.Test.ComplexCalculations.ChannelMember AshPostgres.Test.ComplexCalculations.ChannelMember
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:channel, channel, type: :append) |> Ash.Changeset.manage_relationship(:channel, channel, type: :append)
|> Ash.Changeset.manage_relationship(:user, user_2, type: :append) |> Ash.Changeset.manage_relationship(:user, user_2, type: :append)
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
channel = channel =
channel channel
|> AshPostgres.Test.ComplexCalculations.Api.load!([ |> Ash.load!([
:first_member, :first_member,
:second_member :second_member
]) ])
@ -104,13 +104,13 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
channel = channel =
channel channel
|> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_1) |> Ash.load!(:name, actor: user_1)
assert channel.name == user_1.name assert channel.name == user_1.name
channel = channel =
channel channel
|> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_2) |> Ash.load!(:name, actor: user_2)
assert channel.name == user_2.name assert channel.name == user_2.name
end end
@ -119,31 +119,31 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
dm_channel = dm_channel =
AshPostgres.Test.ComplexCalculations.DMChannel AshPostgres.Test.ComplexCalculations.DMChannel
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
user_1 = user_1 =
AshPostgres.Test.User AshPostgres.Test.User
|> Ash.Changeset.for_create(:create, %{name: "User 1"}) |> Ash.Changeset.for_create(:create, %{name: "User 1"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
user_2 = user_2 =
AshPostgres.Test.User AshPostgres.Test.User
|> Ash.Changeset.for_create(:create, %{name: "User 2"}) |> Ash.Changeset.for_create(:create, %{name: "User 2"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
channel_member_1 = channel_member_1 =
AshPostgres.Test.ComplexCalculations.ChannelMember AshPostgres.Test.ComplexCalculations.ChannelMember
|> Ash.Changeset.for_create(:create, %{channel_id: dm_channel.id, user_id: user_1.id}) |> Ash.Changeset.for_create(:create, %{channel_id: dm_channel.id, user_id: user_1.id})
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
channel_member_2 = channel_member_2 =
AshPostgres.Test.ComplexCalculations.ChannelMember AshPostgres.Test.ComplexCalculations.ChannelMember
|> Ash.Changeset.for_create(:create, %{channel_id: dm_channel.id, user_id: user_2.id}) |> Ash.Changeset.for_create(:create, %{channel_id: dm_channel.id, user_id: user_2.id})
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
dm_channel = dm_channel =
dm_channel dm_channel
|> AshPostgres.Test.ComplexCalculations.Api.load!([ |> Ash.load!([
:first_member, :first_member,
:second_member :second_member
]) ])
@ -153,24 +153,24 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
dm_channel = dm_channel =
dm_channel dm_channel
|> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_1) |> Ash.load!(:name, actor: user_1)
assert dm_channel.name == user_1.name assert dm_channel.name == user_1.name
dm_channel = dm_channel =
dm_channel dm_channel
|> AshPostgres.Test.ComplexCalculations.Api.load!(:name, actor: user_2) |> Ash.load!(:name, actor: user_2)
assert dm_channel.name == user_2.name assert dm_channel.name == user_2.name
channels = channels =
AshPostgres.Test.ComplexCalculations.Channel AshPostgres.Test.ComplexCalculations.Channel
|> Ash.Query.for_read(:read) |> Ash.Query.for_read(:read)
|> AshPostgres.Test.ComplexCalculations.Api.read!() |> Ash.read!()
channels = channels =
channels channels
|> AshPostgres.Test.ComplexCalculations.Api.load!([dm_channel: :name], |> Ash.load!([dm_channel: :name],
actor: user_1 actor: user_1
) )
@ -180,7 +180,7 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
channel = channel =
channel channel
|> AshPostgres.Test.ComplexCalculations.Api.load!([:dm_name, :foo], actor: user_2) |> Ash.load!([:dm_name, :foo], actor: user_2)
assert channel.dm_name == user_2.name assert channel.dm_name == user_2.name
end end
@ -188,92 +188,92 @@ defmodule AshPostgres.Test.ComplexCalculationsTest do
test "calculations with parent filters can be filtered on themselves" do test "calculations with parent filters can be filtered on themselves" do
AshPostgres.Test.ComplexCalculations.DMChannel AshPostgres.Test.ComplexCalculations.DMChannel
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> AshPostgres.Test.ComplexCalculations.Api.create!() |> Ash.create!()
assert [%{foo: "foobar"}] = assert [%{foo: "foobar"}] =
AshPostgres.Test.ComplexCalculations.Channel AshPostgres.Test.ComplexCalculations.Channel
|> Ash.Query.filter(foo == "foobar") |> Ash.Query.filter(foo == "foobar")
|> AshPostgres.Test.ComplexCalculations.Api.read!(load: :foo) |> Ash.read!(load: :foo)
end end
test "calculations with aggregates can be referenced from aggregates" do test "calculations with aggregates can be referenced from aggregates" do
author = author =
AshPostgres.Test.Author AshPostgres.Test.Author
|> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) |> Ash.Changeset.for_create(:create, %{first_name: "is", last_name: "match"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> AshPostgres.Test.Api.create!() |> Ash.create!()
assert [%{author_count_of_posts: 1}] = assert [%{author_count_of_posts: 1}] =
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Query.load(:author_count_of_posts) |> Ash.Query.load(:author_count_of_posts)
|> AshPostgres.Test.Api.read!() |> Ash.read!()
assert [%{author_count_of_posts: 1}] = assert [%{author_count_of_posts: 1}] =
AshPostgres.Test.Post AshPostgres.Test.Post
|> AshPostgres.Test.Api.read!() |> Ash.read!()
|> AshPostgres.Test.Api.load!(:author_count_of_posts) |> Ash.load!(:author_count_of_posts)
assert [_] = assert [_] =
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Query.filter(author_count_of_posts == 1) |> Ash.Query.filter(author_count_of_posts == 1)
|> AshPostgres.Test.Api.read!() |> Ash.read!()
end end
test "calculations can reference aggregates from optimizable first aggregates" do test "calculations can reference aggregates from optimizable first aggregates" do
author = author =
AshPostgres.Test.Author AshPostgres.Test.Author
|> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) |> Ash.Changeset.for_create(:create, %{first_name: "is", last_name: "match"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> AshPostgres.Test.Api.create!() |> Ash.create!()
assert [%{author_count_of_posts_agg: 1}] = assert [%{author_count_of_posts_agg: 1}] =
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Query.load(:author_count_of_posts_agg) |> Ash.Query.load(:author_count_of_posts_agg)
|> AshPostgres.Test.Api.read!() |> Ash.read!()
assert [%{author_count_of_posts_agg: 1}] = assert [%{author_count_of_posts_agg: 1}] =
AshPostgres.Test.Post AshPostgres.Test.Post
|> AshPostgres.Test.Api.read!() |> Ash.read!()
|> AshPostgres.Test.Api.load!(:author_count_of_posts_agg) |> Ash.load!(:author_count_of_posts_agg)
assert [_] = assert [_] =
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Query.filter(author_count_of_posts_agg == 1) |> Ash.Query.filter(author_count_of_posts_agg == 1)
|> AshPostgres.Test.Api.read!() |> Ash.read!()
end end
test "calculations can reference aggregates from non optimizable aggregates" do test "calculations can reference aggregates from non optimizable aggregates" do
author = author =
AshPostgres.Test.Author AshPostgres.Test.Author
|> Ash.Changeset.new(%{first_name: "is", last_name: "match"}) |> Ash.Changeset.for_create(:create, %{first_name: "is", last_name: "match"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> AshPostgres.Test.Api.create!() |> Ash.create!()
assert [%{sum_of_author_count_of_posts: 1}] = assert [%{sum_of_author_count_of_posts: 1}] =
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Query.load(:sum_of_author_count_of_posts) |> Ash.Query.load(:sum_of_author_count_of_posts)
|> AshPostgres.Test.Api.read!() |> Ash.read!()
assert [%{sum_of_author_count_of_posts: 1}] = assert [%{sum_of_author_count_of_posts: 1}] =
AshPostgres.Test.Post AshPostgres.Test.Post
|> AshPostgres.Test.Api.read!() |> Ash.read!()
|> AshPostgres.Test.Api.load!(:sum_of_author_count_of_posts) |> Ash.load!(:sum_of_author_count_of_posts)
assert [_] = assert [_] =
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Query.filter(sum_of_author_count_of_posts == 1) |> Ash.Query.filter(sum_of_author_count_of_posts == 1)
|> AshPostgres.Test.Api.read!() |> Ash.read!()
end end
end end

View file

@ -1,13 +1,13 @@
defmodule AshPostgres.Test.CompositeTypeTest do defmodule AshPostgres.Test.CompositeTypeTest do
use AshPostgres.RepoCase use AshPostgres.RepoCase
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Query require Ash.Query
test "can be cast and stored" do test "can be cast and stored" do
post = post =
Post Post
|> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}})
|> Api.create!() |> Ash.create!()
assert post.composite_point.x == 1 assert post.composite_point.x == 1
assert post.composite_point.y == 2 assert post.composite_point.y == 2
@ -17,22 +17,22 @@ defmodule AshPostgres.Test.CompositeTypeTest do
post = post =
Post Post
|> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}})
|> Api.create!() |> Ash.create!()
post_id = post.id post_id = post.id
assert %{id: ^post_id} = Post |> Ash.Query.filter(composite_point[:x] == 1) |> Api.read_one!() assert %{id: ^post_id} = Post |> Ash.Query.filter(composite_point[:x] == 1) |> Ash.read_one!()
refute Post |> Ash.Query.filter(composite_point[:x] == 2) |> Api.read_one!() refute Post |> Ash.Query.filter(composite_point[:x] == 2) |> Ash.read_one!()
end end
test "composite types can be constructed" do test "composite types can be constructed" do
Post Post
|> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}}) |> Ash.Changeset.for_create(:create, %{title: "locked", composite_point: %{x: 1, y: 2}})
|> Api.create!() |> Ash.create!()
assert %{composite_origin: %{x: 0, y: 0}} = assert %{composite_origin: %{x: 0, y: 0}} =
Post Post
|> Ash.Query.load(:composite_origin) |> Ash.Query.load(:composite_origin)
|> Api.read_one!() |> Ash.read_one!()
end end
end end

View file

@ -1,15 +1,15 @@
defmodule AshPostgres.ConstraintTest do defmodule AshPostgres.ConstraintTest do
@moduledoc false @moduledoc false
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Query require Ash.Query
test "constraint messages are properly raised" do test "constraint messages are properly raised" do
assert_raise Ash.Error.Invalid, ~r/yo, bad price/, fn -> assert_raise Ash.Error.Invalid, ~r/yo, bad price/, fn ->
Post Post
|> Ash.Changeset.new(%{title: "title", price: -1}) |> Ash.Changeset.for_create(:create, %{title: "title", price: -1})
|> Api.create!() |> Ash.create!()
end end
end end
end end

View file

@ -1,24 +1,28 @@
defmodule AshPostgres.Test.CustomIndexTest do defmodule AshPostgres.Test.CustomIndexTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Query require Ash.Query
test "unique constraint errors are properly caught" do test "unique constraint errors are properly caught" do
Post Post
|> Ash.Changeset.new(%{title: "first", uniq_custom_one: "what", uniq_custom_two: "what2"}) |> Ash.Changeset.for_create(:create, %{
|> Api.create!() title: "first",
uniq_custom_one: "what",
uniq_custom_two: "what2"
})
|> Ash.create!()
assert_raise Ash.Error.Invalid, assert_raise Ash.Error.Invalid,
~r/Invalid value provided for uniq_custom_one: dude what the heck/, ~r/Invalid value provided for uniq_custom_one: dude what the heck/,
fn -> fn ->
Post Post
|> Ash.Changeset.new(%{ |> Ash.Changeset.for_create(:create, %{
title: "first", title: "first",
uniq_custom_one: "what", uniq_custom_one: "what",
uniq_custom_two: "what2" uniq_custom_two: "what2"
}) })
|> Api.create!() |> Ash.create!()
end end
end end
end end

View file

@ -1,26 +1,26 @@
defmodule AshPostgres.DistinctTest do defmodule AshPostgres.DistinctTest do
@moduledoc false @moduledoc false
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Query require Ash.Query
setup do setup do
Post Post
|> Ash.Changeset.new(%{title: "title", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "title", score: 1})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "title", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "title", score: 1})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "foo", score: 2}) |> Ash.Changeset.for_create(:create, %{title: "foo", score: 2})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "foo", score: 2}) |> Ash.Changeset.for_create(:create, %{title: "foo", score: 2})
|> Api.create!() |> Ash.create!()
:ok :ok
end end
@ -30,7 +30,7 @@ defmodule AshPostgres.DistinctTest do
Post Post
|> Ash.Query.distinct(:title) |> Ash.Query.distinct(:title)
|> Ash.Query.sort(:title) |> Ash.Query.sort(:title)
|> Api.read!() |> Ash.read!()
assert [%{title: "foo"}, %{title: "title"}] = results assert [%{title: "foo"}, %{title: "title"}] = results
end end
@ -40,7 +40,7 @@ defmodule AshPostgres.DistinctTest do
Post Post
|> Ash.Query.distinct(:title) |> Ash.Query.distinct(:title)
|> Ash.Query.sort(title: :desc) |> Ash.Query.sort(title: :desc)
|> Api.read!() |> Ash.read!()
assert [%{title: "title"}, %{title: "foo"}] = results assert [%{title: "title"}, %{title: "foo"}] = results
end end
@ -51,7 +51,7 @@ defmodule AshPostgres.DistinctTest do
|> Ash.Query.distinct(:title) |> Ash.Query.distinct(:title)
|> Ash.Query.sort(id: :desc) |> Ash.Query.sort(id: :desc)
|> Ash.Query.limit(3) |> Ash.Query.limit(3)
|> Api.read!() |> Ash.read!()
assert [_, _] = results assert [_, _] = results
end end
@ -62,7 +62,7 @@ defmodule AshPostgres.DistinctTest do
|> Ash.Query.distinct(:title) |> Ash.Query.distinct(:title)
|> Ash.Query.sort(id: :desc) |> Ash.Query.sort(id: :desc)
|> Ash.Query.limit(3) |> Ash.Query.limit(3)
|> Api.read!() |> Ash.read!()
assert [_, _] = results assert [_, _] = results
end end
@ -73,7 +73,7 @@ defmodule AshPostgres.DistinctTest do
|> Ash.Query.distinct(:title) |> Ash.Query.distinct(:title)
|> Ash.Query.sort(id: :desc) |> Ash.Query.sort(id: :desc)
|> Ash.Query.limit(1) |> Ash.Query.limit(1)
|> Api.read!() |> Ash.read!()
assert [_] = results assert [_] = results
end end
@ -84,7 +84,7 @@ defmodule AshPostgres.DistinctTest do
|> Ash.Query.distinct(:negative_score) |> Ash.Query.distinct(:negative_score)
|> Ash.Query.sort(:negative_score) |> Ash.Query.sort(:negative_score)
|> Ash.Query.load(:negative_score) |> Ash.Query.load(:negative_score)
|> Api.read!() |> Ash.read!()
assert [ assert [
%{title: "foo", negative_score: -2}, %{title: "foo", negative_score: -2},
@ -96,7 +96,7 @@ defmodule AshPostgres.DistinctTest do
|> Ash.Query.distinct(:negative_score) |> Ash.Query.distinct(:negative_score)
|> Ash.Query.sort(negative_score: :desc) |> Ash.Query.sort(negative_score: :desc)
|> Ash.Query.load(:negative_score) |> Ash.Query.load(:negative_score)
|> Api.read!() |> Ash.read!()
assert [ assert [
%{title: "title", negative_score: -1}, %{title: "title", negative_score: -1},
@ -108,7 +108,7 @@ defmodule AshPostgres.DistinctTest do
|> Ash.Query.distinct(:negative_score) |> Ash.Query.distinct(:negative_score)
|> Ash.Query.sort(:title) |> Ash.Query.sort(:title)
|> Ash.Query.load(:negative_score) |> Ash.Query.load(:negative_score)
|> Api.read!() |> Ash.read!()
assert [ assert [
%{title: "foo", negative_score: -2}, %{title: "foo", negative_score: -2},
@ -118,29 +118,29 @@ defmodule AshPostgres.DistinctTest do
test "distinct, join filters and sort can be combined" do test "distinct, join filters and sort can be combined" do
Post Post
|> Ash.Changeset.new(%{title: "a", score: 2}) |> Ash.Changeset.for_create(:create, %{title: "a", score: 2})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "a", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "a", score: 1})
|> Api.create!() |> Ash.create!()
assert [] = assert [] =
Post Post
|> Ash.Query.distinct(:negative_score) |> Ash.Query.distinct(:negative_score)
|> Ash.Query.filter(author.first_name == "a") |> Ash.Query.filter(author.first_name == "a")
|> Ash.Query.sort(:negative_score) |> Ash.Query.sort(:negative_score)
|> Api.read!() |> Ash.read!()
end end
test "distinct sort is applied" do test "distinct sort is applied" do
Post Post
|> Ash.Changeset.new(%{title: "a", score: 2}) |> Ash.Changeset.for_create(:create, %{title: "a", score: 2})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "a", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "a", score: 1})
|> Api.create!() |> Ash.create!()
results = results =
Post Post
@ -148,7 +148,7 @@ defmodule AshPostgres.DistinctTest do
|> Ash.Query.distinct_sort(:title) |> Ash.Query.distinct_sort(:title)
|> Ash.Query.sort(:negative_score) |> Ash.Query.sort(:negative_score)
|> Ash.Query.load(:negative_score) |> Ash.Query.load(:negative_score)
|> Api.read!() |> Ash.read!()
assert [ assert [
%{title: "a", negative_score: -2}, %{title: "a", negative_score: -2},
@ -161,7 +161,7 @@ defmodule AshPostgres.DistinctTest do
|> Ash.Query.distinct_sort(title: :desc) |> Ash.Query.distinct_sort(title: :desc)
|> Ash.Query.sort(:negative_score) |> Ash.Query.sort(:negative_score)
|> Ash.Query.load(:negative_score) |> Ash.Query.load(:negative_score)
|> Api.read!() |> Ash.read!()
assert [ assert [
%{title: "foo", negative_score: -2}, %{title: "foo", negative_score: -2},
@ -173,7 +173,7 @@ defmodule AshPostgres.DistinctTest do
results = results =
Post Post
|> Ash.Query.distinct(:title) |> Ash.Query.distinct(:title)
|> Api.read!() |> Ash.read!()
assert [_, _] = results assert [_, _] = results
end end

View file

@ -1,34 +1,34 @@
defmodule AshPostgres.EmbeddableResourceTest do defmodule AshPostgres.EmbeddableResourceTest do
@moduledoc false @moduledoc false
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Author, Bio, Post} alias AshPostgres.Test.{Author, Bio, Post}
require Ash.Query require Ash.Query
setup do setup do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
%{post: post} %{post: post}
end end
test "calculations can load json", %{post: post} do test "calculations can load json", %{post: post} do
assert %{calc_returning_json: %AshPostgres.Test.Money{amount: 100, currency: :usd}} = assert %{calc_returning_json: %AshPostgres.Test.Money{amount: 100, currency: :usd}} =
Api.load!(post, :calc_returning_json) Ash.load!(post, :calc_returning_json)
end end
test "embeds with list attributes set to nil are loaded as nil" do test "embeds with list attributes set to nil are loaded as nil" do
post = author =
Author Author
|> Ash.Changeset.new(%{bio: %Bio{list_of_strings: nil}}) |> Ash.Changeset.for_create(:create, %{bio: %Bio{list_of_strings: nil}})
|> Api.create!() |> Ash.create!()
assert is_nil(post.bio.list_of_strings) assert is_nil(author.bio.list_of_strings)
post = Api.reload!(post) author = Ash.reload!(author)
assert is_nil(post.bio.list_of_strings) assert is_nil(author.bio.list_of_strings)
end end
end end

View file

@ -1,13 +1,13 @@
defmodule AshPostgres.EnumTest do defmodule AshPostgres.EnumTest do
@moduledoc false @moduledoc false
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Query require Ash.Query
test "valid values are properly inserted" do test "valid values are properly inserted" do
Post Post
|> Ash.Changeset.new(%{title: "title", status: :open}) |> Ash.Changeset.for_create(:create, %{title: "title", status: :open})
|> Api.create!() |> Ash.create!()
end end
end end

View file

@ -1,28 +1,28 @@
defmodule AshPostgres.ErrorExprTest do defmodule AshPostgres.ErrorExprTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Query require Ash.Query
import Ash.Expr import Ash.Expr
test "exceptions in filters are treated as regular Ash exceptions" do test "exceptions in filters are treated as regular Ash exceptions" do
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn ->
Post Post
|> Ash.Query.filter( |> Ash.Query.filter(
error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10) error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)
) )
|> Api.read!() |> Ash.read!()
end end
end end
test "exceptions in calculations are treated as regular Ash exceptions" do test "exceptions in calculations are treated as regular Ash exceptions" do
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn ->
Post Post
@ -31,15 +31,15 @@ defmodule AshPostgres.ErrorExprTest do
expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)), expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)),
:string :string
) )
|> Api.read!() |> Ash.read!()
|> Enum.map(& &1.calculations) |> Enum.map(& &1.calculations)
end end
end end
test "exceptions in calculations are treated as regular Ash exceptions in transactions" do test "exceptions in calculations are treated as regular Ash exceptions in transactions" do
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn -> assert_raise Ash.Error.Invalid, ~r/this is bad!/, fn ->
AshPostgres.TestRepo.transaction!(fn -> AshPostgres.TestRepo.transaction!(fn ->
@ -49,7 +49,7 @@ defmodule AshPostgres.ErrorExprTest do
expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)), expr(error(Ash.Error.Query.InvalidFilterValue, message: "this is bad!", value: 10)),
:string :string
) )
|> Api.read!() |> Ash.read!()
|> Enum.map(& &1.calculations) |> Enum.map(& &1.calculations)
end) end)
end end

View file

@ -1,7 +1,7 @@
defmodule FilterFieldPolicyTest do defmodule FilterFieldPolicyTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Organization, Post, User} alias AshPostgres.Test.{Organization, Post, User}
require Ash.Query require Ash.Query
@ -9,15 +9,15 @@ defmodule FilterFieldPolicyTest do
organization = organization =
Organization Organization
|> Ash.Changeset.for_create(:create, %{name: "test_org"}) |> Ash.Changeset.for_create(:create, %{name: "test_org"})
|> Api.create!() |> Ash.create!()
User User
|> Ash.Changeset.for_create(:create, %{organization_id: organization.id, name: "foo bar"}) |> Ash.Changeset.for_create(:create, %{organization_id: organization.id, name: "foo bar"})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.for_create(:create, %{organization_id: organization.id}) |> Ash.Changeset.for_create(:create, %{organization_id: organization.id})
|> Api.create!() |> Ash.create!()
filter = Ash.Filter.parse_input!(Post, %{organization: %{name: %{ilike: "%org"}}}) filter = Ash.Filter.parse_input!(Post, %{organization: %{name: %{ilike: "%org"}}})
@ -25,7 +25,7 @@ defmodule FilterFieldPolicyTest do
Post Post
|> Ash.Query.do_filter(filter) |> Ash.Query.do_filter(filter)
|> Ash.Query.for_read(:allow_any) |> Ash.Query.for_read(:allow_any)
|> Api.read!(actor: %{id: "test"}) |> Ash.read!(actor: %{id: "test"})
filter = Ash.Filter.parse_input!(Post, %{organization: %{users: %{name: %{ilike: "%bar"}}}}) filter = Ash.Filter.parse_input!(Post, %{organization: %{users: %{name: %{ilike: "%bar"}}}})
@ -33,6 +33,6 @@ defmodule FilterFieldPolicyTest do
Post Post
|> Ash.Query.do_filter(filter) |> Ash.Query.do_filter(filter)
|> Ash.Query.for_read(:allow_any) |> Ash.Query.for_read(:allow_any)
|> Api.read!(actor: %{id: "test"}) |> Ash.read!(actor: %{id: "test"})
end end
end end

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.Test.LoadTest do defmodule AshPostgres.Test.LoadTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Comment, Post, Record, TempEntity} alias AshPostgres.Test.{Comment, Post, Record, TempEntity}
require Ash.Query require Ash.Query
@ -8,18 +8,18 @@ defmodule AshPostgres.Test.LoadTest do
assert %Post{comments: %Ash.NotLoaded{type: :relationship}} = assert %Post{comments: %Ash.NotLoaded{type: :relationship}} =
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
results = results =
Post Post
|> Ash.Query.load(:comments) |> Ash.Query.load(:comments)
|> Api.read!() |> Ash.read!()
assert [%Post{comments: [%{title: "match"}]}] = results assert [%Post{comments: [%{title: "match"}]}] = results
end end
@ -28,18 +28,18 @@ defmodule AshPostgres.Test.LoadTest do
assert %Comment{post: %Ash.NotLoaded{type: :relationship}} = assert %Comment{post: %Ash.NotLoaded{type: :relationship}} =
comment = comment =
Comment Comment
|> Ash.Changeset.new(%{}) |> Ash.Changeset.for_create(:create, %{})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.Changeset.manage_relationship(:comments, [comment], type: :append_and_remove) |> Ash.Changeset.manage_relationship(:comments, [comment], type: :append_and_remove)
|> Api.create!() |> Ash.create!()
results = results =
Comment Comment
|> Ash.Query.load(:post) |> Ash.Query.load(:post)
|> Api.read!() |> Ash.read!()
assert [%Comment{post: %{title: "match"}}] = results assert [%Comment{post: %{title: "match"}}] = results
end end
@ -47,29 +47,29 @@ defmodule AshPostgres.Test.LoadTest do
test "many_to_many loads work" do test "many_to_many loads work" do
source_post = source_post =
Post Post
|> Ash.Changeset.new(%{title: "source"}) |> Ash.Changeset.for_create(:create, %{title: "source"})
|> Api.create!() |> Ash.create!()
destination_post = destination_post =
Post Post
|> Ash.Changeset.new(%{title: "destination"}) |> Ash.Changeset.for_create(:create, %{title: "destination"})
|> Api.create!() |> Ash.create!()
destination_post2 = destination_post2 =
Post Post
|> Ash.Changeset.new(%{title: "destination"}) |> Ash.Changeset.for_create(:create, %{title: "destination"})
|> Api.create!() |> Ash.create!()
source_post source_post
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2], |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2],
type: :append_and_remove type: :append_and_remove
) )
|> Api.update!() |> Ash.update!()
results = results =
source_post source_post
|> Api.load!(:linked_posts) |> Ash.load!(:linked_posts)
assert %{linked_posts: [%{title: "destination"}, %{title: "destination"}]} = results assert %{linked_posts: [%{title: "destination"}, %{title: "destination"}]} = results
end end
@ -77,29 +77,29 @@ defmodule AshPostgres.Test.LoadTest do
test "many_to_many loads work when nested" do test "many_to_many loads work when nested" do
source_post = source_post =
Post Post
|> Ash.Changeset.new(%{title: "source"}) |> Ash.Changeset.for_create(:create, %{title: "source"})
|> Api.create!() |> Ash.create!()
destination_post = destination_post =
Post Post
|> Ash.Changeset.new(%{title: "destination"}) |> Ash.Changeset.for_create(:create, %{title: "destination"})
|> Api.create!() |> Ash.create!()
source_post source_post
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post], |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post],
type: :append_and_remove type: :append_and_remove
) )
|> Api.update!() |> Ash.update!()
destination_post destination_post
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [source_post], type: :append_and_remove) |> Ash.Changeset.manage_relationship(:linked_posts, [source_post], type: :append_and_remove)
|> Api.update!() |> Ash.update!()
results = results =
source_post source_post
|> Api.load!(linked_posts: :linked_posts) |> Ash.load!(linked_posts: :linked_posts)
assert %{linked_posts: [%{title: "destination", linked_posts: [%{title: "source"}]}]} = assert %{linked_posts: [%{title: "destination", linked_posts: [%{title: "source"}]}]} =
results results
@ -109,75 +109,75 @@ defmodule AshPostgres.Test.LoadTest do
test "parent references are resolved" do test "parent references are resolved" do
post1 = post1 =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
post2 = post2 =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
post2_id = post2.id post2_id = post2.id
post3 = post3 =
Post Post
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Api.create!() |> Ash.create!()
assert [%{posts_with_matching_title: [%{id: ^post2_id}]}] = assert [%{posts_with_matching_title: [%{id: ^post2_id}]}] =
Post Post
|> Ash.Query.load(:posts_with_matching_title) |> Ash.Query.load(:posts_with_matching_title)
|> Ash.Query.filter(id == ^post1.id) |> Ash.Query.filter(id == ^post1.id)
|> Api.read!() |> Ash.read!()
assert [%{posts_with_matching_title: []}] = assert [%{posts_with_matching_title: []}] =
Post Post
|> Ash.Query.load(:posts_with_matching_title) |> Ash.Query.load(:posts_with_matching_title)
|> Ash.Query.filter(id == ^post3.id) |> Ash.Query.filter(id == ^post3.id)
|> Api.read!() |> Ash.read!()
end end
test "parent references work when joining for filters" do test "parent references work when joining for filters" do
%{id: post1_id} = %{id: post1_id} =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
post2 = post2 =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Api.create!() |> Ash.create!()
assert [%{id: ^post1_id}] = assert [%{id: ^post1_id}] =
Post Post
|> Ash.Query.filter(posts_with_matching_title.id == ^post2.id) |> Ash.Query.filter(posts_with_matching_title.id == ^post2.id)
|> Api.read!() |> Ash.read!()
end end
test "lateral join loads (loads with limits or offsets) are supported" do test "lateral join loads (loads with limits or offsets) are supported" do
assert %Post{comments: %Ash.NotLoaded{type: :relationship}} = assert %Post{comments: %Ash.NotLoaded{type: :relationship}} =
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "abc"}) |> Ash.Changeset.for_create(:create, %{title: "abc"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "def"}) |> Ash.Changeset.for_create(:create, %{title: "def"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
comments_query = comments_query =
Comment Comment
@ -187,7 +187,7 @@ defmodule AshPostgres.Test.LoadTest do
results = results =
Post Post
|> Ash.Query.load(comments: comments_query) |> Ash.Query.load(comments: comments_query)
|> Api.read!() |> Ash.read!()
assert [%Post{comments: [%{title: "abc"}]}] = results assert [%Post{comments: [%{title: "abc"}]}] = results
@ -199,7 +199,7 @@ defmodule AshPostgres.Test.LoadTest do
results = results =
Post Post
|> Ash.Query.load(comments: comments_query) |> Ash.Query.load(comments: comments_query)
|> Api.read!() |> Ash.read!()
assert [%Post{comments: [%{title: "def"}]}] = results assert [%Post{comments: [%{title: "def"}]}] = results
@ -211,7 +211,7 @@ defmodule AshPostgres.Test.LoadTest do
results = results =
Post Post
|> Ash.Query.load(comments: comments_query) |> Ash.Query.load(comments: comments_query)
|> Api.read!() |> Ash.read!()
assert [%Post{comments: [%{title: "def"}, %{title: "abc"}]}] = results assert [%Post{comments: [%{title: "def"}, %{title: "abc"}]}] = results
end end
@ -219,25 +219,25 @@ defmodule AshPostgres.Test.LoadTest do
test "loading many to many relationships on records works without loading its join relationship when using code interface" do test "loading many to many relationships on records works without loading its join relationship when using code interface" do
source_post = source_post =
Post Post
|> Ash.Changeset.new(%{title: "source"}) |> Ash.Changeset.for_create(:create, %{title: "source"})
|> Api.create!() |> Ash.create!()
destination_post = destination_post =
Post Post
|> Ash.Changeset.new(%{title: "abc"}) |> Ash.Changeset.for_create(:create, %{title: "abc"})
|> Api.create!() |> Ash.create!()
destination_post2 = destination_post2 =
Post Post
|> Ash.Changeset.new(%{title: "def"}) |> Ash.Changeset.for_create(:create, %{title: "def"})
|> Api.create!() |> Ash.create!()
source_post source_post
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2], |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2],
type: :append_and_remove type: :append_and_remove
) )
|> Api.update!() |> Ash.update!()
assert %{linked_posts: [_, _]} = Post.get_by_id!(source_post.id, load: [:linked_posts]) assert %{linked_posts: [_, _]} = Post.get_by_id!(source_post.id, load: [:linked_posts])
end end
@ -245,25 +245,25 @@ defmodule AshPostgres.Test.LoadTest do
test "lateral join loads with many to many relationships are supported" do test "lateral join loads with many to many relationships are supported" do
source_post = source_post =
Post Post
|> Ash.Changeset.new(%{title: "source"}) |> Ash.Changeset.for_create(:create, %{title: "source"})
|> Api.create!() |> Ash.create!()
destination_post = destination_post =
Post Post
|> Ash.Changeset.new(%{title: "abc"}) |> Ash.Changeset.for_create(:create, %{title: "abc"})
|> Api.create!() |> Ash.create!()
destination_post2 = destination_post2 =
Post Post
|> Ash.Changeset.new(%{title: "def"}) |> Ash.Changeset.for_create(:create, %{title: "def"})
|> Api.create!() |> Ash.create!()
source_post source_post
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2], |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2],
type: :append_and_remove type: :append_and_remove
) )
|> Api.update!() |> Ash.update!()
linked_posts_query = linked_posts_query =
Post Post
@ -272,7 +272,7 @@ defmodule AshPostgres.Test.LoadTest do
results = results =
source_post source_post
|> Api.load!(linked_posts: linked_posts_query) |> Ash.load!(linked_posts: linked_posts_query)
assert %{linked_posts: [%{title: "abc"}]} = results assert %{linked_posts: [%{title: "abc"}]} = results
@ -283,7 +283,7 @@ defmodule AshPostgres.Test.LoadTest do
results = results =
source_post source_post
|> Api.load!(linked_posts: linked_posts_query) |> Ash.load!(linked_posts: linked_posts_query)
assert %{linked_posts: [%{title: "abc"}, %{title: "def"}]} = results assert %{linked_posts: [%{title: "abc"}, %{title: "def"}]} = results
end end
@ -291,25 +291,25 @@ defmodule AshPostgres.Test.LoadTest do
test "lateral join loads with many to many relationships are supported with aggregates" do test "lateral join loads with many to many relationships are supported with aggregates" do
source_post = source_post =
Post Post
|> Ash.Changeset.new(%{title: "source"}) |> Ash.Changeset.for_create(:create, %{title: "source"})
|> Api.create!() |> Ash.create!()
destination_post = destination_post =
Post Post
|> Ash.Changeset.new(%{title: "abc"}) |> Ash.Changeset.for_create(:create, %{title: "abc"})
|> Api.create!() |> Ash.create!()
destination_post2 = destination_post2 =
Post Post
|> Ash.Changeset.new(%{title: "def"}) |> Ash.Changeset.for_create(:create, %{title: "def"})
|> Api.create!() |> Ash.create!()
source_post source_post
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2], |> Ash.Changeset.manage_relationship(:linked_posts, [destination_post, destination_post2],
type: :append_and_remove type: :append_and_remove
) )
|> Api.update!() |> Ash.update!()
linked_posts_query = linked_posts_query =
Post Post
@ -318,7 +318,7 @@ defmodule AshPostgres.Test.LoadTest do
results = results =
source_post source_post
|> Api.load!(linked_posts: linked_posts_query) |> Ash.load!(linked_posts: linked_posts_query)
assert %{linked_posts: [%{title: "abc"}]} = results assert %{linked_posts: [%{title: "abc"}]} = results
@ -330,16 +330,18 @@ defmodule AshPostgres.Test.LoadTest do
results = results =
source_post source_post
|> Api.load!(linked_posts: linked_posts_query) |> Ash.load!(linked_posts: linked_posts_query)
assert %{linked_posts: [%{title: "abc"}, %{title: "def"}]} = results assert %{linked_posts: [%{title: "abc"}, %{title: "def"}]} = results
end end
test "lateral join loads with read action from a custom table and schema" do test "lateral join loads with read action from a custom table and schema" do
record = Record |> Ash.Changeset.new(%{full_name: "name"}) |> Api.create!() record = Record |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!()
temp_entity = TempEntity |> Ash.Changeset.new(%{full_name: "name"}) |> Api.create!()
assert %{entity: entity} = Api.load!(record, :entity) temp_entity =
TempEntity |> Ash.Changeset.for_create(:create, %{full_name: "name"}) |> Ash.create!()
assert %{entity: entity} = Ash.load!(record, :entity)
assert temp_entity.id == entity.id assert temp_entity.id == entity.id
end end

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.Test.LockTest do defmodule AshPostgres.Test.LockTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Query require Ash.Query
setup do setup do
@ -17,7 +17,7 @@ defmodule AshPostgres.Test.LockTest do
Post Post
|> Ash.Changeset.for_create(:create, %{title: "locked"}) |> Ash.Changeset.for_create(:create, %{title: "locked"})
|> Ash.Changeset.set_context(%{data_layer: %{repo: AshPostgres.TestNoSandboxRepo}}) |> Ash.Changeset.set_context(%{data_layer: %{repo: AshPostgres.TestNoSandboxRepo}})
|> Api.create!() |> Ash.create!()
task1 = task1 =
Task.async(fn -> Task.async(fn ->
@ -26,7 +26,7 @@ defmodule AshPostgres.Test.LockTest do
|> Ash.Query.lock("FOR UPDATE NOWAIT") |> Ash.Query.lock("FOR UPDATE NOWAIT")
|> Ash.Query.set_context(%{data_layer: %{repo: AshPostgres.TestNoSandboxRepo}}) |> Ash.Query.set_context(%{data_layer: %{repo: AshPostgres.TestNoSandboxRepo}})
|> Ash.Query.filter(id == ^post.id) |> Ash.Query.filter(id == ^post.id)
|> Api.read!() |> Ash.read!()
:timer.sleep(1000) :timer.sleep(1000)
:ok :ok
@ -43,7 +43,7 @@ defmodule AshPostgres.Test.LockTest do
|> Ash.Query.lock("FOR UPDATE NOWAIT") |> Ash.Query.lock("FOR UPDATE NOWAIT")
|> Ash.Query.set_context(%{data_layer: %{repo: AshPostgres.TestNoSandboxRepo}}) |> Ash.Query.set_context(%{data_layer: %{repo: AshPostgres.TestNoSandboxRepo}})
|> Ash.Query.filter(id == ^post.id) |> Ash.Query.filter(id == ^post.id)
|> Api.read!() |> Ash.read!()
end) end)
rescue rescue
e -> e ->

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.Test.ManualRelationshipsTest do defmodule AshPostgres.Test.ManualRelationshipsTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Comment, Post} alias AshPostgres.Test.{Comment, Post}
require Ash.Query require Ash.Query
@ -8,102 +8,102 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do
test "aggregates can be loaded with no data" do test "aggregates can be loaded with no data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
assert %{count_of_comments_containing_title: 0} = assert %{count_of_comments_containing_title: 0} =
Api.load!(post, :count_of_comments_containing_title) Ash.load!(post, :count_of_comments_containing_title)
end end
test "aggregates can be loaded with data" do test "aggregates can be loaded with data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert %{count_of_comments_containing_title: 1} = assert %{count_of_comments_containing_title: 1} =
Api.load!(post, :count_of_comments_containing_title) Ash.load!(post, :count_of_comments_containing_title)
end end
test "relationships can be filtered on with no data" do test "relationships can be filtered on with no data" do
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
assert [] = assert [] =
Post |> Ash.Query.filter(comments_containing_title.title == "title") |> Api.read!() Post |> Ash.Query.filter(comments_containing_title.title == "title") |> Ash.read!()
end end
test "aggregates can be filtered on with no data" do test "aggregates can be filtered on with no data" do
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
assert [] = Post |> Ash.Query.filter(count_of_comments_containing_title == 1) |> Api.read!() assert [] = Post |> Ash.Query.filter(count_of_comments_containing_title == 1) |> Ash.read!()
end end
test "aggregates can be filtered on with data" do test "aggregates can be filtered on with data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [_] = assert [_] =
Post |> Ash.Query.filter(count_of_comments_containing_title == 1) |> Api.read!() Post |> Ash.Query.filter(count_of_comments_containing_title == 1) |> Ash.read!()
end end
test "relationships can be filtered on with data" do test "relationships can be filtered on with data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [_] = assert [_] =
Post Post
|> Ash.Query.filter(comments_containing_title.title == "title2") |> Ash.Query.filter(comments_containing_title.title == "title2")
|> Api.read!() |> Ash.read!()
end end
end end
@ -111,128 +111,128 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do
test "aggregates can be loaded with no data" do test "aggregates can be loaded with no data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
comment = comment =
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert %{count_of_comments_containing_title: 0} = assert %{count_of_comments_containing_title: 0} =
Api.load!(comment, :count_of_comments_containing_title) Ash.load!(comment, :count_of_comments_containing_title)
end end
test "aggregates can be loaded with data" do test "aggregates can be loaded with data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
comment = comment =
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert %{count_of_comments_containing_title: 1} = assert %{count_of_comments_containing_title: 1} =
Api.load!(comment, :count_of_comments_containing_title) Ash.load!(comment, :count_of_comments_containing_title)
end end
test "aggregates can be filtered on with no data" do test "aggregates can be filtered on with no data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [] = assert [] =
Comment Comment
|> Ash.Query.filter(count_of_comments_containing_title == 1) |> Ash.Query.filter(count_of_comments_containing_title == 1)
|> Api.read!() |> Ash.read!()
end end
test "relationships can be filtered on with no data" do test "relationships can be filtered on with no data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [] = assert [] =
Comment Comment
|> Ash.Query.filter(post.comments_containing_title.title == "title2") |> Ash.Query.filter(post.comments_containing_title.title == "title2")
|> Api.read!() |> Ash.read!()
end end
test "aggregates can be filtered on with data" do test "aggregates can be filtered on with data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [_, _] = assert [_, _] =
Comment Comment
|> Ash.Query.filter(count_of_comments_containing_title == 1) |> Ash.Query.filter(count_of_comments_containing_title == 1)
|> Api.read!() |> Ash.read!()
end end
test "relationships can be filtered on with data" do test "relationships can be filtered on with data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [_, _] = assert [_, _] =
Comment Comment
|> Ash.Query.filter(post.comments_containing_title.title == "title2") |> Ash.Query.filter(post.comments_containing_title.title == "title2")
|> Api.read!() |> Ash.read!()
end end
end end
@ -240,128 +240,128 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do
test "aggregates can be loaded with no data" do test "aggregates can be loaded with no data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
comment = comment =
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert %{posts_for_comments_containing_title: []} = assert %{posts_for_comments_containing_title: []} =
Api.load!(comment, :posts_for_comments_containing_title) Ash.load!(comment, :posts_for_comments_containing_title)
end end
test "aggregates can be loaded with data" do test "aggregates can be loaded with data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
comment = comment =
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert %{posts_for_comments_containing_title: ["title"]} = assert %{posts_for_comments_containing_title: ["title"]} =
Api.load!(comment, :posts_for_comments_containing_title) Ash.load!(comment, :posts_for_comments_containing_title)
end end
test "aggregates can be filtered on with no data" do test "aggregates can be filtered on with no data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [] = assert [] =
Comment Comment
|> Ash.Query.filter("title" in posts_for_comments_containing_title) |> Ash.Query.filter("title" in posts_for_comments_containing_title)
|> Api.read!() |> Ash.read!()
end end
test "relationships can be filtered on with no data" do test "relationships can be filtered on with no data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [] = assert [] =
Comment Comment
|> Ash.Query.filter(post.comments_containing_title.post.title == "title") |> Ash.Query.filter(post.comments_containing_title.post.title == "title")
|> Api.read!() |> Ash.read!()
end end
test "aggregates can be filtered on with data" do test "aggregates can be filtered on with data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [_, _] = assert [_, _] =
Comment Comment
|> Ash.Query.filter(post.comments_containing_title.post.title == "title") |> Ash.Query.filter(post.comments_containing_title.post.title == "title")
|> Api.read!() |> Ash.read!()
end end
test "relationships can be filtered on with data" do test "relationships can be filtered on with data" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "title2"}) |> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "no match"}) |> Ash.Changeset.for_create(:create, %{title: "no match"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [_, _] = assert [_, _] =
Comment Comment
|> Ash.Query.filter(post.comments_containing_title.post.title == "title") |> Ash.Query.filter(post.comments_containing_title.post.title == "title")
|> Api.read!() |> Ash.read!()
end end
end end
end end

View file

@ -4,18 +4,18 @@ defmodule AshPostgres.ManualUpdateTest do
test "Manual update defined in a module to update an attribute" do test "Manual update defined in a module to update an attribute" do
post = post =
AshPostgres.Test.Post AshPostgres.Test.Post
|> Ash.Changeset.new(%{title: "match"}) |> Ash.Changeset.for_create(:create, %{title: "match"})
|> AshPostgres.Test.Api.create!() |> Ash.create!()
AshPostgres.Test.Comment AshPostgres.Test.Comment
|> Ash.Changeset.new(%{title: "_"}) |> Ash.Changeset.for_create(:create, %{title: "_"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> AshPostgres.Test.Api.create!() |> Ash.create!()
post = post =
post post
|> Ash.Changeset.for_update(:manual_update) |> Ash.Changeset.for_update(:manual_update)
|> AshPostgres.Test.Api.update!() |> Ash.update!()
assert post.title == "manual" assert post.title == "manual"

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,18 @@
defmodule AshPostgres.Test.MultitenancyTest do defmodule AshPostgres.Test.MultitenancyTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.MultitenancyTest.{Api, Org, Post, User} alias AshPostgres.MultitenancyTest.{Org, Post, User}
setup do setup do
org1 = org1 =
Org Org
|> Ash.Changeset.new(name: "test1") |> Ash.Changeset.for_create(:create, %{name: "test1"})
|> Api.create!() |> Ash.create!()
org2 = org2 =
Org Org
|> Ash.Changeset.new(name: "test2") |> Ash.Changeset.for_create(:create, %{name: "test2"})
|> Api.create!() |> Ash.create!()
[org1: org1, org2: org2] [org1: org1, org2: org2]
end end
@ -34,17 +34,17 @@ defmodule AshPostgres.Test.MultitenancyTest do
assert [%{id: ^org_id}] = assert [%{id: ^org_id}] =
Org Org
|> Ash.Query.set_tenant(tenant(org1)) |> Ash.Query.set_tenant(tenant(org1))
|> Api.read!() |> Ash.read!()
end end
test "context multitenancy works with policies", %{org1: org1} do test "context multitenancy works with policies", %{org1: org1} do
Post Post
|> Ash.Changeset.new(name: "foo") |> Ash.Changeset.for_create(:create, %{name: "foo"})
|> Ash.Changeset.set_tenant(tenant(org1)) |> Ash.Changeset.set_tenant(tenant(org1))
|> Api.create!() |> Ash.create!()
|> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true) |> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true)
|> Ash.Changeset.set_tenant(tenant(org1)) |> Ash.Changeset.set_tenant(tenant(org1))
|> Api.update!() |> Ash.update!()
end end
test "attribute multitenancy is set on creation" do test "attribute multitenancy is set on creation" do
@ -52,29 +52,29 @@ defmodule AshPostgres.Test.MultitenancyTest do
org = org =
Org Org
|> Ash.Changeset.new(name: "test3") |> Ash.Changeset.for_create(:create, %{name: "test3"})
|> Ash.Changeset.set_tenant("org_#{uuid}") |> Ash.Changeset.set_tenant("org_#{uuid}")
|> Api.create!() |> Ash.create!()
assert org.id == uuid assert org.id == uuid
end end
test "schema multitenancy works", %{org1: org1, org2: org2} do test "schema multitenancy works", %{org1: org1, org2: org2} do
Post Post
|> Ash.Changeset.new(name: "foo") |> Ash.Changeset.for_create(:create, %{name: "foo"})
|> Ash.Changeset.set_tenant(tenant(org1)) |> Ash.Changeset.set_tenant(tenant(org1))
|> Api.create!() |> Ash.create!()
assert [_] = Post |> Ash.Query.set_tenant(tenant(org1)) |> Api.read!() assert [_] = Post |> Ash.Query.set_tenant(tenant(org1)) |> Ash.read!()
assert [] = Post |> Ash.Query.set_tenant(tenant(org2)) |> Api.read!() assert [] = Post |> Ash.Query.set_tenant(tenant(org2)) |> Ash.read!()
end end
test "schema rename on update works", %{org1: org1} do test "schema rename on update works", %{org1: org1} do
new_uuid = Ash.UUID.generate() new_uuid = Ash.UUID.generate()
org1 org1
|> Ash.Changeset.new(id: new_uuid) |> Ash.Changeset.for_update(:update, %{id: new_uuid})
|> Api.update!() |> Ash.update!()
new_tenant = "org_#{new_uuid}" new_tenant = "org_#{new_uuid}"
@ -91,106 +91,106 @@ defmodule AshPostgres.Test.MultitenancyTest do
org = org =
Org Org
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Api.create!() |> Ash.create!()
user = user =
User User
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert Api.load!(user, :org).org.id == org.id assert Ash.load!(user, :org).org.id == org.id
end end
test "loading context multitenant resources from attribute multitenant resources works" do test "loading context multitenant resources from attribute multitenant resources works" do
org = org =
Org Org
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Api.create!() |> Ash.create!()
user1 = user1 =
User User
|> Ash.Changeset.new(%{name: "a"}) |> Ash.Changeset.for_create(:create, %{name: "a"})
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
user2 = user2 =
User User
|> Ash.Changeset.new(%{name: "b"}) |> Ash.Changeset.for_create(:create, %{name: "b"})
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
user1_id = user1.id user1_id = user1.id
user2_id = user2.id user2_id = user2.id
assert [%{id: ^user1_id}, %{id: ^user2_id}] = assert [%{id: ^user1_id}, %{id: ^user2_id}] =
Api.load!(org, users: Ash.Query.sort(User, :name)).users Ash.load!(org, users: Ash.Query.sort(User, :name)).users
end end
test "manage_relationship from context multitenant resource to attribute multitenant resource doesn't raise an error" do test "manage_relationship from context multitenant resource to attribute multitenant resource doesn't raise an error" do
org = Org |> Ash.Changeset.new() |> Api.create!() org = Org |> Ash.Changeset.new() |> Ash.create!()
user = User |> Ash.Changeset.new() |> Api.create!() user = User |> Ash.Changeset.new() |> Ash.create!()
Post Post
|> Ash.Changeset.for_create(:create, %{}, tenant: tenant(org)) |> Ash.Changeset.for_create(:create, %{}, tenant: tenant(org))
|> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
end end
test "loading attribute multitenant resources with limits from context multitenant resources works" do test "loading attribute multitenant resources with limits from context multitenant resources works" do
org = org =
Org Org
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Api.create!() |> Ash.create!()
user = user =
User User
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert Api.load!(user, :org).org.id == org.id assert Ash.load!(user, :org).org.id == org.id
end end
test "loading context multitenant resources with limits from attribute multitenant resources works" do test "loading context multitenant resources with limits from attribute multitenant resources works" do
org = org =
Org Org
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Api.create!() |> Ash.create!()
user1 = user1 =
User User
|> Ash.Changeset.new(%{name: "a"}) |> Ash.Changeset.for_create(:create, %{name: "a"})
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
user2 = user2 =
User User
|> Ash.Changeset.new(%{name: "b"}) |> Ash.Changeset.for_create(:create, %{name: "b"})
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
user1_id = user1.id user1_id = user1.id
user2_id = user2.id user2_id = user2.id
assert [%{id: ^user1_id}, %{id: ^user2_id}] = assert [%{id: ^user1_id}, %{id: ^user2_id}] =
Api.load!(org, users: Ash.Query.sort(Ash.Query.limit(User, 10), :name)).users Ash.load!(org, users: Ash.Query.sort(Ash.Query.limit(User, 10), :name)).users
end end
test "unique constraints are properly scoped", %{org1: org1} do test "unique constraints are properly scoped", %{org1: org1} do
post = post =
Post Post
|> Ash.Changeset.new(%{}) |> Ash.Changeset.for_create(:create, %{})
|> Ash.Changeset.set_tenant(tenant(org1)) |> Ash.Changeset.set_tenant(tenant(org1))
|> Api.create!() |> Ash.create!()
assert_raise Ash.Error.Invalid, assert_raise Ash.Error.Invalid,
~r/Invalid value provided for id: has already been taken/, ~r/Invalid value provided for id: has already been taken/,
fn -> fn ->
Post Post
|> Ash.Changeset.new(%{id: post.id}) |> Ash.Changeset.for_create(:create, %{id: post.id})
|> Ash.Changeset.set_tenant(tenant(org1)) |> Ash.Changeset.set_tenant(tenant(org1))
|> Api.create!() |> Ash.create!()
end end
end end
end end

View file

@ -1,29 +1,29 @@
defmodule AshPostgres.PolymorphismTest do defmodule AshPostgres.PolymorphismTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post, Rating} alias AshPostgres.Test.{Post, Rating}
require Ash.Query require Ash.Query
test "you can create related data" do test "you can create related data" do
Post Post
|> Ash.Changeset.for_create(:create, rating: %{score: 10}) |> Ash.Changeset.for_create(:create, rating: %{score: 10})
|> Api.create!() |> Ash.create!()
assert [%{score: 10}] = assert [%{score: 10}] =
Rating Rating
|> Ash.Query.set_context(%{data_layer: %{table: "post_ratings"}}) |> Ash.Query.set_context(%{data_layer: %{table: "post_ratings"}})
|> Api.read!() |> Ash.read!()
end end
test "you can read related data" do test "you can read related data" do
Post Post
|> Ash.Changeset.for_create(:create, rating: %{score: 10}) |> Ash.Changeset.for_create(:create, rating: %{score: 10})
|> Api.create!() |> Ash.create!()
assert [%{score: 10}] = assert [%{score: 10}] =
Post Post
|> Ash.Query.load(:ratings) |> Ash.Query.load(:ratings)
|> Api.read_one!() |> Ash.read_one!()
|> Map.get(:ratings) |> Map.get(:ratings)
end end
end end

View file

@ -1,16 +1,17 @@
defmodule AshPostgres.Test.PrimaryKeyTest do defmodule AshPostgres.Test.PrimaryKeyTest do
@moduledoc false @moduledoc false
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, IntegerPost, Post, PostView} alias AshPostgres.Test.{IntegerPost, Post, PostView}
require Ash.Query require Ash.Query
test "creates record with integer primary key" do test "creates record with integer primary key" do
assert %IntegerPost{} = IntegerPost |> Ash.Changeset.new(%{title: "title"}) |> Api.create!() assert %IntegerPost{} =
IntegerPost |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.create!()
end end
test "creates record with uuid primary key" do test "creates record with uuid primary key" do
assert %Post{} = Post |> Ash.Changeset.new(%{title: "title"}) |> Api.create!() assert %Post{} = Post |> Ash.Changeset.for_create(:create, %{title: "title"}) |> Ash.create!()
end end
describe "resources without a primary key" do describe "resources without a primary key" do
@ -18,12 +19,12 @@ defmodule AshPostgres.Test.PrimaryKeyTest do
post = post =
Post Post
|> Ash.Changeset.for_action(:create, %{title: "not very interesting"}) |> Ash.Changeset.for_action(:create, %{title: "not very interesting"})
|> Api.create!() |> Ash.create!()
assert {:ok, view} = assert {:ok, view} =
PostView PostView
|> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id}) |> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id})
|> Api.create() |> Ash.create()
assert view.browser == :firefox assert view.browser == :firefox
assert view.post_id == post.id assert view.post_id == post.id
@ -34,14 +35,14 @@ defmodule AshPostgres.Test.PrimaryKeyTest do
post = post =
Post Post
|> Ash.Changeset.for_action(:create, %{title: "not very interesting"}) |> Ash.Changeset.for_action(:create, %{title: "not very interesting"})
|> Api.create!() |> Ash.create!()
expected = expected =
PostView PostView
|> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id}) |> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id})
|> Api.create!() |> Ash.create!()
assert {:ok, [actual]} = Api.read(PostView) assert {:ok, [actual]} = Ash.read(PostView)
assert actual.time == expected.time assert actual.time == expected.time
assert actual.browser == expected.browser assert actual.browser == expected.browser

View file

@ -8,11 +8,12 @@ defmodule AshPostgres.ReferencesTest do
defmodule Org do defmodule Org do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: nil,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
attributes do attributes do
uuid_primary_key(:id, writable?: true) uuid_primary_key(:id, writable?: true)
attribute(:name, :string) attribute(:name, :string, public?: true)
end end
multitenancy do multitenancy do
@ -33,14 +34,15 @@ defmodule AshPostgres.ReferencesTest do
defmodule User do defmodule User do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: nil,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
attributes do attributes do
uuid_primary_key(:id, writable?: true) uuid_primary_key(:id, writable?: true)
attribute(:secondary_id, :uuid) attribute(:secondary_id, :uuid, public?: true)
attribute(:foo_id, :uuid) attribute(:foo_id, :uuid, public?: true)
attribute(:name, :string) attribute(:name, :string, public?: true)
attribute(:org_id, :uuid) attribute(:org_id, :uuid, public?: true)
end end
multitenancy do multitenancy do
@ -49,7 +51,9 @@ defmodule AshPostgres.ReferencesTest do
end end
relationships do relationships do
belongs_to(:org, Org) belongs_to(:org, Org) do
public?(true)
end
end end
postgres do postgres do
@ -66,13 +70,14 @@ defmodule AshPostgres.ReferencesTest do
defmodule UserThing do defmodule UserThing do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: nil,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
attributes do attributes do
attribute(:id, :string, primary_key?: true, allow_nil?: false) attribute(:id, :string, primary_key?: true, allow_nil?: false, public?: true)
attribute(:name, :string) attribute(:name, :string, public?: true)
attribute(:org_id, :uuid) attribute(:org_id, :uuid, public?: true)
attribute(:foo_id, :uuid) attribute(:foo_id, :uuid, public?: true)
end end
multitenancy do multitenancy do

View file

@ -1,7 +1,7 @@
defmodule AshPostgres.RelWithParentFilterTest do defmodule AshPostgres.RelWithParentFilterTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Author} alias AshPostgres.Test.Author
require Ash.Query require Ash.Query
@ -9,11 +9,11 @@ defmodule AshPostgres.RelWithParentFilterTest do
%{id: author_id} = %{id: author_id} =
Author Author
|> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"})
|> Api.create!() |> Ash.create!()
Author Author
|> Ash.Changeset.for_create(:create, %{first_name: "John"}) |> Ash.Changeset.for_create(:create, %{first_name: "John"})
|> Api.create!() |> Ash.create!()
# here we get the expected result of 1 because it is done in the same query # here we get the expected result of 1 because it is done in the same query
assert %{num_of_authors_with_same_first_name: 1} = assert %{num_of_authors_with_same_first_name: 1} =
@ -21,18 +21,18 @@ defmodule AshPostgres.RelWithParentFilterTest do
|> Ash.Query.for_read(:read) |> Ash.Query.for_read(:read)
|> Ash.Query.filter(id == ^author_id) |> Ash.Query.filter(id == ^author_id)
|> Ash.Query.load(:num_of_authors_with_same_first_name) |> Ash.Query.load(:num_of_authors_with_same_first_name)
|> Api.read_one!() |> Ash.read_one!()
end end
test "filter on relationship using parent works as expected when loading relationship" do test "filter on relationship using parent works as expected when loading relationship" do
%{id: author_id} = %{id: author_id} =
Author Author
|> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"})
|> Api.create!() |> Ash.create!()
Author Author
|> Ash.Changeset.for_create(:create, %{first_name: "John"}) |> Ash.Changeset.for_create(:create, %{first_name: "John"})
|> Api.create!() |> Ash.create!()
assert %{authors_with_same_first_name: authors} = assert %{authors_with_same_first_name: authors} =
Author Author
@ -43,7 +43,7 @@ defmodule AshPostgres.RelWithParentFilterTest do
# but when doing that it does a inner lateral join # but when doing that it does a inner lateral join
# instead of using the id from the parent relationship # instead of using the id from the parent relationship
|> Ash.Query.load(:authors_with_same_first_name) |> Ash.Query.load(:authors_with_same_first_name)
|> Api.read_one!() |> Ash.read_one!()
assert length(authors) == 1 assert length(authors) == 1
end end

View file

@ -1,12 +1,12 @@
defmodule AshPostgres.SchemaTest do defmodule AshPostgres.SchemaTest do
@moduledoc false @moduledoc false
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Author, Profile} alias AshPostgres.Test.{Author, Profile}
require Ash.Query require Ash.Query
setup do setup do
[author: Api.create!(Ash.Changeset.for_create(Author, :create, %{}))] [author: Ash.create!(Ash.Changeset.for_create(Author, :create, %{}))]
end end
test "data can be created", %{author: author} do test "data can be created", %{author: author} do
@ -14,16 +14,16 @@ defmodule AshPostgres.SchemaTest do
Profile Profile
|> Ash.Changeset.for_create(:create, %{description: "foo"}) |> Ash.Changeset.for_create(:create, %{description: "foo"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
end end
test "data can be read", %{author: author} do test "data can be read", %{author: author} do
Profile Profile
|> Ash.Changeset.for_create(:create, %{description: "foo"}) |> Ash.Changeset.for_create(:create, %{description: "foo"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [%{description: "foo"}] = Profile |> Api.read!() assert [%{description: "foo"}] = Profile |> Ash.read!()
end end
test "they can be filtered across", %{author: author} do test "they can be filtered across", %{author: author} do
@ -31,33 +31,33 @@ defmodule AshPostgres.SchemaTest do
Profile Profile
|> Ash.Changeset.for_create(:create, %{description: "foo"}) |> Ash.Changeset.for_create(:create, %{description: "foo"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Api.create!(Ash.Changeset.for_create(Author, :create, %{})) Ash.create!(Ash.Changeset.for_create(Author, :create, %{}))
assert [_] = assert [_] =
Author Author
|> Ash.Query.filter(profile.id == ^profile.id) |> Ash.Query.filter(profile.id == ^profile.id)
|> Api.read!() |> Ash.read!()
assert [_] = assert [_] =
Profile Profile
|> Ash.Query.filter(author.id == ^author.id) |> Ash.Query.filter(author.id == ^author.id)
|> Api.read!() |> Ash.read!()
end end
test "aggregates work across schemas", %{author: author} do test "aggregates work across schemas", %{author: author} do
Profile Profile
|> Ash.Changeset.for_create(:create, %{description: "foo"}) |> Ash.Changeset.for_create(:create, %{description: "foo"})
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
assert [%{profile_description: "foo"}] = assert [%{profile_description: "foo"}] =
Author Author
|> Ash.Query.filter(profile_description == "foo") |> Ash.Query.filter(profile_description == "foo")
|> Ash.Query.load(:profile_description) |> Ash.Query.load(:profile_description)
|> Api.read!() |> Ash.read!()
assert %{profile_description: "foo"} = Api.load!(author, :profile_description) assert %{profile_description: "foo"} = Ash.load!(author, :profile_description)
end end
end end

View file

@ -1,15 +1,15 @@
defmodule AshPostgres.SelectTest do defmodule AshPostgres.SelectTest do
@moduledoc false @moduledoc false
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post} alias AshPostgres.Test.Post
require Ash.Query require Ash.Query
test "values not selected in the query are not present in the response" do test "values not selected in the query are not present in the response" do
Post Post
|> Ash.Changeset.new(%{title: "title"}) |> Ash.Changeset.for_create(:create, %{title: "title"})
|> Api.create!() |> Ash.create!()
assert [%{title: nil}] = Api.read!(Ash.Query.select(Post, :id)) assert [%{title: %Ash.NotLoaded{}}] = Ash.read!(Ash.Query.select(Post, :id))
end end
end end

View file

@ -1,30 +1,30 @@
defmodule AshPostgres.SortTest do defmodule AshPostgres.SortTest do
@moduledoc false @moduledoc false
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Comment, Post, PostLink} alias AshPostgres.Test.{Comment, Post, PostLink}
require Ash.Query require Ash.Query
require Ash.Sort require Ash.Sort
test "multi-column sorts work" do test "multi-column sorts work" do
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "bbb", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0})
|> Api.create!() |> Ash.create!()
assert [ assert [
%{title: "aaa", score: 0}, %{title: "aaa", score: 0},
%{title: "aaa", score: 1}, %{title: "aaa", score: 1},
%{title: "bbb"} %{title: "bbb"}
] = ] =
Api.read!( Ash.read!(
Post Post
|> Ash.Query.load(:count_of_comments) |> Ash.Query.load(:count_of_comments)
|> Ash.Query.sort(title: :asc, score: :asc) |> Ash.Query.sort(title: :asc, score: :asc)
@ -34,31 +34,31 @@ defmodule AshPostgres.SortTest do
test "multi-column sorts work on inclusion" do test "multi-column sorts work on inclusion" do
post = post =
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "bbb", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0})
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "aaa", likes: 1}) |> Ash.Changeset.for_create(:create, %{title: "aaa", likes: 1})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "bbb", likes: 1}) |> Ash.Changeset.for_create(:create, %{title: "bbb", likes: 1})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
Comment Comment
|> Ash.Changeset.new(%{title: "aaa", likes: 2}) |> Ash.Changeset.for_create(:create, %{title: "aaa", likes: 2})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove) |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!() |> Ash.create!()
posts = posts =
Post Post
@ -71,7 +71,7 @@ defmodule AshPostgres.SortTest do
|> Ash.Query.limit(1) |> Ash.Query.limit(1)
]) ])
|> Ash.Query.sort([:title, :score]) |> Ash.Query.sort([:title, :score])
|> Api.read!() |> Ash.read!()
assert [ assert [
%{title: "aaa", comments: [%{title: "aaa"}]}, %{title: "aaa", comments: [%{title: "aaa"}]},
@ -82,23 +82,23 @@ defmodule AshPostgres.SortTest do
test "multicolumn sort works with a select statement" do test "multicolumn sort works with a select statement" do
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1})
|> Api.create!() |> Ash.create!()
Post Post
|> Ash.Changeset.new(%{title: "bbb", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0})
|> Api.create!() |> Ash.create!()
assert [ assert [
%{title: "aaa", score: 0}, %{title: "aaa", score: 0},
%{title: "aaa", score: 1}, %{title: "aaa", score: 1},
%{title: "bbb"} %{title: "bbb"}
] = ] =
Api.read!( Ash.read!(
Post Post
|> Ash.Query.sort(title: :asc, score: :asc) |> Ash.Query.sort(title: :asc, score: :asc)
|> Ash.Query.select([:title, :score]) |> Ash.Query.select([:title, :score])
@ -108,43 +108,43 @@ defmodule AshPostgres.SortTest do
test "sorting when joining to a many to many relationship sorts properly" do test "sorting when joining to a many to many relationship sorts properly" do
post1 = post1 =
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Api.create!() |> Ash.create!()
post2 = post2 =
Post Post
|> Ash.Changeset.new(%{title: "bbb", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 1})
|> Api.create!() |> Ash.create!()
post3 = post3 =
Post Post
|> Ash.Changeset.new(%{title: "ccc", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "ccc", score: 0})
|> Api.create!() |> Ash.create!()
PostLink PostLink
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post1, type: :append) |> Ash.Changeset.manage_relationship(:source_post, post1, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post3, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post3, type: :append)
|> Api.create!() |> Ash.create!()
PostLink PostLink
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post2, type: :append) |> Ash.Changeset.manage_relationship(:source_post, post2, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post2, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post2, type: :append)
|> Api.create!() |> Ash.create!()
PostLink PostLink
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post3, type: :append) |> Ash.Changeset.manage_relationship(:source_post, post3, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post1, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post1, type: :append)
|> Api.create!() |> Ash.create!()
assert [ assert [
%{title: "aaa"}, %{title: "aaa"},
%{title: "bbb"}, %{title: "bbb"},
%{title: "ccc"} %{title: "ccc"}
] = ] =
Api.read!( Ash.read!(
Post Post
|> Ash.Query.sort(title: :asc) |> Ash.Query.sort(title: :asc)
|> Ash.Query.filter(linked_posts.title in ["aaa", "bbb", "ccc"]) |> Ash.Query.filter(linked_posts.title in ["aaa", "bbb", "ccc"])
@ -155,7 +155,7 @@ defmodule AshPostgres.SortTest do
%{title: "bbb"}, %{title: "bbb"},
%{title: "aaa"} %{title: "aaa"}
] = ] =
Api.read!( Ash.read!(
Post Post
|> Ash.Query.sort(title: :desc) |> Ash.Query.sort(title: :desc)
|> Ash.Query.filter(linked_posts.title in ["aaa", "bbb", "ccc"] or title == "aaa") |> Ash.Query.filter(linked_posts.title in ["aaa", "bbb", "ccc"] or title == "aaa")
@ -166,7 +166,7 @@ defmodule AshPostgres.SortTest do
%{title: "bbb"}, %{title: "bbb"},
%{title: "aaa"} %{title: "aaa"}
] = ] =
Api.read!( Ash.read!(
Post Post
|> Ash.Query.sort(title: :desc) |> Ash.Query.sort(title: :desc)
|> Ash.Query.filter( |> Ash.Query.filter(
@ -180,48 +180,48 @@ defmodule AshPostgres.SortTest do
Post Post
|> Ash.Query.load(:count_of_comments) |> Ash.Query.load(:count_of_comments)
|> Ash.Query.sort(:c_times_p) |> Ash.Query.sort(:c_times_p)
|> Api.read!() |> Ash.read!()
end end
test "calculations can sort on expressions" do test "calculations can sort on expressions" do
post1 = post1 =
Post Post
|> Ash.Changeset.new(%{title: "aaa", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Api.create!() |> Ash.create!()
post2 = post2 =
Post Post
|> Ash.Changeset.new(%{title: "bbb", score: 1}) |> Ash.Changeset.for_create(:create, %{title: "bbb", score: 1})
|> Api.create!() |> Ash.create!()
post3 = post3 =
Post Post
|> Ash.Changeset.new(%{title: "ccc", score: 0}) |> Ash.Changeset.for_create(:create, %{title: "ccc", score: 0})
|> Api.create!() |> Ash.create!()
PostLink PostLink
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post1, type: :append) |> Ash.Changeset.manage_relationship(:source_post, post1, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post3, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post3, type: :append)
|> Api.create!() |> Ash.create!()
PostLink PostLink
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post2, type: :append) |> Ash.Changeset.manage_relationship(:source_post, post2, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post2, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post2, type: :append)
|> Api.create!() |> Ash.create!()
PostLink PostLink
|> Ash.Changeset.new() |> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post3, type: :append) |> Ash.Changeset.manage_relationship(:source_post, post3, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post1, type: :append) |> Ash.Changeset.manage_relationship(:destination_post, post1, type: :append)
|> Api.create!() |> Ash.create!()
posts_query = posts_query =
Ash.Query.sort(Post, Ash.Sort.expr_sort(source(post_links.state))) Ash.Query.sort(Post, Ash.Sort.expr_sort(source(post_links.state)))
Post Post
|> Ash.Query.load(linked_posts: posts_query) |> Ash.Query.load(linked_posts: posts_query)
|> Api.read!() |> Ash.read!()
end end
end end

View file

@ -1,8 +0,0 @@
defmodule AshPostgres.Test.Api do
@moduledoc false
use Ash.Api
resources do
registry(AshPostgres.Test.Registry)
end
end

View file

@ -1,8 +0,0 @@
defmodule AshPostgres.Test.ComplexCalculations.Api do
@moduledoc false
use Ash.Api
resources do
registry(AshPostgres.Test.ComplexCalculations.Registry)
end
end

View file

@ -0,0 +1,17 @@
defmodule AshPostgres.Test.ComplexCalculations.Domain do
@moduledoc false
use Ash.Domain
resources do
resource(AshPostgres.Test.ComplexCalculations.Certification)
resource(AshPostgres.Test.ComplexCalculations.Skill)
resource(AshPostgres.Test.ComplexCalculations.Documentation)
resource(AshPostgres.Test.ComplexCalculations.Channel)
resource(AshPostgres.Test.ComplexCalculations.DMChannel)
resource(AshPostgres.Test.ComplexCalculations.ChannelMember)
end
authorization do
authorize(:when_requested)
end
end

View file

@ -1,13 +0,0 @@
defmodule AshPostgres.Test.ComplexCalculations.Registry do
@moduledoc false
use Ash.Registry
entries do
entry(AshPostgres.Test.ComplexCalculations.Certification)
entry(AshPostgres.Test.ComplexCalculations.Skill)
entry(AshPostgres.Test.ComplexCalculations.Documentation)
entry(AshPostgres.Test.ComplexCalculations.Channel)
entry(AshPostgres.Test.ComplexCalculations.DMChannel)
entry(AshPostgres.Test.ComplexCalculations.ChannelMember)
end
end

View file

@ -1,8 +1,11 @@
defmodule AshPostgres.Test.ComplexCalculations.Certification do defmodule AshPostgres.Test.ComplexCalculations.Certification do
@moduledoc false @moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
@ -43,6 +46,8 @@ defmodule AshPostgres.Test.ComplexCalculations.Certification do
end end
relationships do relationships do
has_many(:skills, AshPostgres.Test.ComplexCalculations.Skill) has_many(:skills, AshPostgres.Test.ComplexCalculations.Skill) do
public?(true)
end
end end
end end

View file

@ -1,20 +1,22 @@
defmodule AshPostgres.Test.ComplexCalculations.Channel do defmodule AshPostgres.Test.ComplexCalculations.Channel do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer] authorizers: [Ash.Policy.Authorizer]
require Ash.Expr require Ash.Expr
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
create_timestamp(:created_at, private?: false) create_timestamp(:created_at, public?: true)
update_timestamp(:updated_at, private?: false) update_timestamp(:updated_at, public?: true)
end end
postgres do postgres do
@ -23,30 +25,36 @@ defmodule AshPostgres.Test.ComplexCalculations.Channel do
end end
relationships do relationships do
has_many(:channel_members, AshPostgres.Test.ComplexCalculations.ChannelMember) has_many(:channel_members, AshPostgres.Test.ComplexCalculations.ChannelMember) do
public?(true)
end
has_one :first_member, AshPostgres.Test.ComplexCalculations.ChannelMember do has_one :first_member, AshPostgres.Test.ComplexCalculations.ChannelMember do
public?(true)
destination_attribute(:channel_id) destination_attribute(:channel_id)
from_many?(true) from_many?(true)
sort(created_at: :asc) sort(created_at: :asc)
end end
has_one :second_member, AshPostgres.Test.ComplexCalculations.ChannelMember do has_one :second_member, AshPostgres.Test.ComplexCalculations.ChannelMember do
public?(true)
destination_attribute(:channel_id) destination_attribute(:channel_id)
from_many?(true) from_many?(true)
sort(created_at: :desc) sort(created_at: :desc)
end end
has_one :dm_channel, AshPostgres.Test.ComplexCalculations.DMChannel do has_one :dm_channel, AshPostgres.Test.ComplexCalculations.DMChannel do
api(AshPostgres.Test.ComplexCalculations.Api) public?(true)
domain(AshPostgres.Test.ComplexCalculations.Domain)
destination_attribute(:id) destination_attribute(:id)
end end
has_one :dm_channel_with_same_id, AshPostgres.Test.ComplexCalculations.DMChannel do has_one :dm_channel_with_same_id, AshPostgres.Test.ComplexCalculations.DMChannel do
public?(true)
no_attributes?(true) no_attributes?(true)
from_many?(true) from_many?(true)
filter(expr(parent(id) == id)) filter(expr(parent(id) == id))
api(AshPostgres.Test.ComplexCalculations.Api) domain(AshPostgres.Test.ComplexCalculations.Domain)
end end
end end

View file

@ -1,18 +1,21 @@
defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer] authorizers: [Ash.Policy.Authorizer]
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
create_timestamp(:created_at, private?: false) create_timestamp(:created_at, public?: true)
update_timestamp(:updated_at, private?: false) update_timestamp(:updated_at, public?: true)
end end
postgres do postgres do
@ -21,8 +24,8 @@ defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do
end end
relationships do relationships do
belongs_to(:user, AshPostgres.Test.User, api: AshPostgres.Test.Api, attribute_writable?: true) belongs_to(:user, AshPostgres.Test.User, domain: AshPostgres.Test.Domain, public?: true)
belongs_to(:channel, AshPostgres.Test.ComplexCalculations.Channel, attribute_writable?: true) belongs_to(:channel, AshPostgres.Test.ComplexCalculations.Channel, public?: true)
end end
end end

View file

@ -1,20 +1,23 @@
defmodule AshPostgres.Test.ComplexCalculations.DMChannel do defmodule AshPostgres.Test.ComplexCalculations.DMChannel do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer] authorizers: [Ash.Policy.Authorizer]
require Ash.Expr require Ash.Expr
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
create_timestamp(:created_at, private?: false) create_timestamp(:created_at, public?: true)
update_timestamp(:updated_at, private?: false) update_timestamp(:updated_at, public?: true)
end end
postgres do postgres do
@ -24,16 +27,19 @@ defmodule AshPostgres.Test.ComplexCalculations.DMChannel do
relationships do relationships do
has_many :channel_members, AshPostgres.Test.ComplexCalculations.ChannelMember do has_many :channel_members, AshPostgres.Test.ComplexCalculations.ChannelMember do
public?(true)
destination_attribute(:channel_id) destination_attribute(:channel_id)
end end
has_one :first_member, AshPostgres.Test.ComplexCalculations.ChannelMember do has_one :first_member, AshPostgres.Test.ComplexCalculations.ChannelMember do
public?(true)
destination_attribute(:channel_id) destination_attribute(:channel_id)
from_many?(true) from_many?(true)
sort(created_at: :asc) sort(created_at: :asc)
end end
has_one :second_member, AshPostgres.Test.ComplexCalculations.ChannelMember do has_one :second_member, AshPostgres.Test.ComplexCalculations.ChannelMember do
public?(true)
destination_attribute(:channel_id) destination_attribute(:channel_id)
from_many?(true) from_many?(true)
sort(created_at: :desc) sort(created_at: :desc)

View file

@ -1,8 +1,12 @@
defmodule AshPostgres.Test.ComplexCalculations.Documentation do defmodule AshPostgres.Test.ComplexCalculations.Documentation do
@moduledoc false @moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
@ -15,12 +19,13 @@ defmodule AshPostgres.Test.ComplexCalculations.Documentation do
constraints: [ constraints: [
one_of: [:demonstrated, :performed, :approved, :reopened] one_of: [:demonstrated, :performed, :approved, :reopened]
], ],
public?: true,
allow_nil?: false allow_nil?: false
) )
attribute(:documented_at, :utc_datetime_usec) attribute(:documented_at, :utc_datetime_usec, public?: true)
create_timestamp(:inserted_at, private?: false) create_timestamp(:inserted_at, public?: true, writable?: true)
update_timestamp(:updated_at, private?: false) update_timestamp(:updated_at, public?: true, writable?: true)
end end
calculations do calculations do
@ -43,6 +48,8 @@ defmodule AshPostgres.Test.ComplexCalculations.Documentation do
end end
relationships do relationships do
belongs_to(:skill, AshPostgres.Test.ComplexCalculations.Skill) belongs_to(:skill, AshPostgres.Test.ComplexCalculations.Skill) do
public?(true)
end
end end
end end

View file

@ -1,8 +1,12 @@
defmodule AshPostgres.Test.ComplexCalculations.Skill do defmodule AshPostgres.Test.ComplexCalculations.Skill do
@moduledoc false @moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
@ -14,7 +18,7 @@ defmodule AshPostgres.Test.ComplexCalculations.Skill do
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:removed, :boolean, default: false, allow_nil?: false) attribute(:removed, :boolean, default: false, allow_nil?: false, public?: true)
end end
calculations do calculations do
@ -37,13 +41,17 @@ defmodule AshPostgres.Test.ComplexCalculations.Skill do
end end
relationships do relationships do
belongs_to(:certification, AshPostgres.Test.ComplexCalculations.Certification) belongs_to(:certification, AshPostgres.Test.ComplexCalculations.Certification) do
public?(true)
end
has_many :documentations, AshPostgres.Test.ComplexCalculations.Documentation do has_many :documentations, AshPostgres.Test.ComplexCalculations.Documentation do
public?(true)
sort(timestamp: :desc, inserted_at: :desc) sort(timestamp: :desc, inserted_at: :desc)
end end
has_one :latest_documentation, AshPostgres.Test.ComplexCalculations.Documentation do has_one :latest_documentation, AshPostgres.Test.ComplexCalculations.Documentation do
public?(true)
sort(timestamp: :desc, inserted_at: :desc) sort(timestamp: :desc, inserted_at: :desc)
end end
end end

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.Test.Concat do defmodule AshPostgres.Test.Concat do
@moduledoc false @moduledoc false
use Ash.Calculation use Ash.Resource.Calculation
require Ash.Query require Ash.Query
def init(opts) do def init(opts) do
@ -11,16 +11,16 @@ defmodule AshPostgres.Test.Concat do
end end
end end
def expression(opts, %{separator: separator}) do def expression(opts, %{arguments: %{separator: separator}}) do
Enum.reduce(opts[:keys], nil, fn key, expr -> Enum.reduce(opts[:keys], nil, fn key, expr ->
if expr do if expr do
if separator do if separator do
Ash.Query.expr(^expr <> ^separator <> ref(^key)) expr(^expr <> ^separator <> ^ref(key))
else else
Ash.Query.expr(^expr <> ref(^key)) expr(^expr <> ^ref(key))
end end
else else
Ash.Query.expr(ref(^key)) expr(^ref(key))
end end
end) end)
end end

27
test/support/domain.ex Normal file
View file

@ -0,0 +1,27 @@
defmodule AshPostgres.Test.Domain do
@moduledoc false
use Ash.Domain
resources do
resource(AshPostgres.Test.Post)
resource(AshPostgres.Test.Comment)
resource(AshPostgres.Test.IntegerPost)
resource(AshPostgres.Test.Rating)
resource(AshPostgres.Test.PostLink)
resource(AshPostgres.Test.PostView)
resource(AshPostgres.Test.Author)
resource(AshPostgres.Test.Profile)
resource(AshPostgres.Test.User)
resource(AshPostgres.Test.Account)
resource(AshPostgres.Test.Organization)
resource(AshPostgres.Test.Manager)
resource(AshPostgres.Test.Entity)
resource(AshPostgres.Test.TempEntity)
resource(AshPostgres.Test.Record)
resource(AshPostgres.Test.PostFollower)
end
authorization do
authorize(:when_requested)
end
end

View file

@ -1,8 +0,0 @@
defmodule AshPostgres.MultitenancyTest.Api do
@moduledoc false
use Ash.Api
resources do
registry(AshPostgres.MultitenancyTest.Registry)
end
end

View file

@ -0,0 +1,10 @@
defmodule AshPostgres.MultitenancyTest.Domain do
@moduledoc false
use Ash.Domain
resources do
resource(AshPostgres.MultitenancyTest.Org)
resource(AshPostgres.MultitenancyTest.User)
resource(AshPostgres.MultitenancyTest.Post)
end
end

View file

@ -1,10 +0,0 @@
defmodule AshPostgres.MultitenancyTest.Registry do
@moduledoc false
use Ash.Registry
entries do
entry(AshPostgres.MultitenancyTest.Org)
entry(AshPostgres.MultitenancyTest.User)
entry(AshPostgres.MultitenancyTest.Post)
end
end

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.MultitenancyTest.Org do defmodule AshPostgres.MultitenancyTest.Org do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.MultitenancyTest.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
identities do identities do
@ -9,10 +10,12 @@ defmodule AshPostgres.MultitenancyTest.Org do
attributes do attributes do
uuid_primary_key(:id, writable?: true) uuid_primary_key(:id, writable?: true)
attribute(:name, :string) attribute(:name, :string, public?: true)
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
@ -33,8 +36,15 @@ defmodule AshPostgres.MultitenancyTest.Org do
end end
relationships do relationships do
has_many(:posts, AshPostgres.MultitenancyTest.Post, destination_attribute: :org_id) has_many(:posts, AshPostgres.MultitenancyTest.Post,
has_many(:users, AshPostgres.MultitenancyTest.User, destination_attribute: :org_id) destination_attribute: :org_id,
public?: true
)
has_many(:users, AshPostgres.MultitenancyTest.User,
destination_attribute: :org_id,
public?: true
)
end end
def tenant("org_" <> tenant) do def tenant("org_" <> tenant) do

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.MultitenancyTest.Post do defmodule AshPostgres.MultitenancyTest.Post do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.MultitenancyTest.Domain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer] authorizers: [Ash.Policy.Authorizer]
@ -17,10 +18,12 @@ defmodule AshPostgres.MultitenancyTest.Post do
attributes do attributes do
uuid_primary_key(:id, writable?: true) uuid_primary_key(:id, writable?: true)
attribute(:name, :string) attribute(:name, :string, public?: true)
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
update(:update_with_policy) update(:update_with_policy)
@ -38,8 +41,14 @@ defmodule AshPostgres.MultitenancyTest.Post do
end end
relationships do relationships do
belongs_to(:org, AshPostgres.MultitenancyTest.Org) belongs_to(:org, AshPostgres.MultitenancyTest.Org) do
belongs_to(:user, AshPostgres.MultitenancyTest.User) public?(true)
has_one(:self, __MODULE__, destination_attribute: :id, source_attribute: :id) end
belongs_to(:user, AshPostgres.MultitenancyTest.User) do
public?(true)
end
has_one(:self, __MODULE__, destination_attribute: :id, source_attribute: :id, public?: true)
end end
end end

View file

@ -1,12 +1,13 @@
defmodule AshPostgres.MultitenancyTest.User do defmodule AshPostgres.MultitenancyTest.User do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.MultitenancyTest.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
attributes do attributes do
uuid_primary_key(:id, writable?: true) uuid_primary_key(:id, writable?: true)
attribute(:name, :string) attribute(:name, :string, public?: true)
attribute(:org_id, :uuid) attribute(:org_id, :uuid, public?: true)
end end
postgres do postgres do
@ -15,6 +16,8 @@ defmodule AshPostgres.MultitenancyTest.User do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
@ -28,7 +31,9 @@ defmodule AshPostgres.MultitenancyTest.User do
end end
relationships do relationships do
belongs_to(:org, AshPostgres.MultitenancyTest.Org) belongs_to(:org, AshPostgres.MultitenancyTest.Org) do
public?(true)
end
end end
def parse_tenant("org_" <> id), do: id def parse_tenant("org_" <> id), do: id

View file

@ -1,23 +0,0 @@
defmodule AshPostgres.Test.Registry do
@moduledoc false
use Ash.Registry
entries do
entry(AshPostgres.Test.Post)
entry(AshPostgres.Test.Comment)
entry(AshPostgres.Test.IntegerPost)
entry(AshPostgres.Test.Rating)
entry(AshPostgres.Test.PostLink)
entry(AshPostgres.Test.PostView)
entry(AshPostgres.Test.Author)
entry(AshPostgres.Test.Profile)
entry(AshPostgres.Test.User)
entry(AshPostgres.Test.Account)
entry(AshPostgres.Test.Organization)
entry(AshPostgres.Test.Manager)
entry(AshPostgres.Test.Entity)
entry(AshPostgres.Test.TempEntity)
entry(AshPostgres.Test.Record)
entry(AshPostgres.Test.PostFollower)
end
end

View file

@ -13,7 +13,7 @@ defmodule AshPostgres.Test.Post.CommentsContainingTitle do
query query
|> Ash.Query.filter(post_id in ^post_ids) |> Ash.Query.filter(post_id in ^post_ids)
|> Ash.Query.filter(contains(title, post.title)) |> Ash.Query.filter(contains(title, post.title))
|> AshPostgres.Test.Api.read!(actor: actor, authorize?: authorize?) |> Ash.read!(actor: actor, authorize?: authorize?)
|> Enum.group_by(& &1.post_id)} |> Enum.group_by(& &1.post_id)}
end end

View file

@ -1,8 +1,12 @@
defmodule AshPostgres.Test.Account do defmodule AshPostgres.Test.Account do
@moduledoc false @moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
@ -12,7 +16,7 @@ defmodule AshPostgres.Test.Account do
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:is_active, :boolean) attribute(:is_active, :boolean, public?: true)
end end
calculations do calculations do
@ -30,6 +34,8 @@ defmodule AshPostgres.Test.Account do
end end
relationships do relationships do
belongs_to(:user, AshPostgres.Test.User) belongs_to(:user, AshPostgres.Test.User) do
public?(true)
end
end end
end end

View file

@ -1,11 +1,12 @@
defmodule AshPostgres.Test.Author do defmodule AshPostgres.Test.Author do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
defmodule RuntimeFullName do defmodule RuntimeFullName do
@moduledoc false @moduledoc false
use Ash.Calculation use Ash.Resource.Calculation
def calculate(records, _, _) do def calculate(records, _, _) do
Enum.map(records, fn record -> Enum.map(records, fn record ->
@ -21,21 +22,29 @@ defmodule AshPostgres.Test.Author do
attributes do attributes do
uuid_primary_key(:id, writable?: true) uuid_primary_key(:id, writable?: true)
attribute(:first_name, :string) attribute(:first_name, :string, public?: true)
attribute(:last_name, :string) attribute(:last_name, :string, public?: true)
attribute(:bio, AshPostgres.Test.Bio) attribute(:bio, AshPostgres.Test.Bio, public?: true)
attribute(:badges, {:array, :atom}) attribute(:badges, {:array, :atom}, public?: true)
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
relationships do relationships do
has_one(:profile, AshPostgres.Test.Profile) has_one(:profile, AshPostgres.Test.Profile) do
has_many(:posts, AshPostgres.Test.Post) public?(true)
end
has_many(:posts, AshPostgres.Test.Post) do
public?(true)
end
has_many :authors_with_same_first_name, __MODULE__ do has_many :authors_with_same_first_name, __MODULE__ do
public?(true)
source_attribute(:first_name) source_attribute(:first_name)
destination_attribute(:first_name) destination_attribute(:first_name)
filter(expr(parent(id) != id)) filter(expr(parent(id) != id))

View file

@ -3,15 +3,18 @@ defmodule AshPostgres.Test.Bio do
use Ash.Resource, data_layer: :embedded use Ash.Resource, data_layer: :embedded
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
attributes do attributes do
attribute(:title, :string) attribute(:title, :string, public?: true)
attribute(:bio, :string) attribute(:bio, :string, public?: true)
attribute(:years_of_experience, :integer) attribute(:years_of_experience, :integer, public?: true)
attribute :list_of_strings, {:array, :string} do attribute :list_of_strings, {:array, :string} do
public?(true)
allow_nil?(true) allow_nil?(true)
default(nil) default(nil)
end end

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.Test.Comment do defmodule AshPostgres.Test.Comment do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [ authorizers: [
Ash.Policy.Authorizer Ash.Policy.Authorizer
@ -23,6 +24,7 @@ defmodule AshPostgres.Test.Comment do
end end
actions do actions do
default_accept(:*)
defaults([:read, :update, :destroy]) defaults([:read, :update, :destroy])
create :create do create :create do
@ -35,10 +37,10 @@ defmodule AshPostgres.Test.Comment do
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:title, :string) attribute(:title, :string, public?: true)
attribute(:likes, :integer) attribute(:likes, :integer, public?: true)
attribute(:arbitrary_timestamp, :utc_datetime_usec) attribute(:arbitrary_timestamp, :utc_datetime_usec, public?: true)
create_timestamp(:created_at, writable?: true) create_timestamp(:created_at, writable?: true, public?: true)
end end
aggregates do aggregates do
@ -49,15 +51,22 @@ defmodule AshPostgres.Test.Comment do
end end
relationships do relationships do
belongs_to(:post, AshPostgres.Test.Post) belongs_to(:post, AshPostgres.Test.Post) do
belongs_to(:author, AshPostgres.Test.Author) public?(true)
end
belongs_to(:author, AshPostgres.Test.Author) do
public?(true)
end
has_many(:ratings, AshPostgres.Test.Rating, has_many(:ratings, AshPostgres.Test.Rating,
public?: true,
destination_attribute: :resource_id, destination_attribute: :resource_id,
relationship_context: %{data_layer: %{table: "comment_ratings"}} relationship_context: %{data_layer: %{table: "comment_ratings"}}
) )
has_many(:popular_ratings, AshPostgres.Test.Rating, has_many(:popular_ratings, AshPostgres.Test.Rating,
public?: true,
destination_attribute: :resource_id, destination_attribute: :resource_id,
relationship_context: %{data_layer: %{table: "comment_ratings"}}, relationship_context: %{data_layer: %{table: "comment_ratings"}},
filter: expr(score > 5) filter: expr(score > 5)

View file

@ -2,14 +2,15 @@ defmodule AshPostgres.Test.Entity do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:full_name, :string, allow_nil?: false) attribute(:full_name, :string, allow_nil?: false, public?: true)
timestamps(private?: false) timestamps(public?: true)
end end
postgres do postgres do
@ -18,6 +19,8 @@ defmodule AshPostgres.Test.Entity do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read]) defaults([:create, :read])
read :read_from_temp do read :read_from_temp do

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.Test.IntegerPost do defmodule AshPostgres.Test.IntegerPost do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
postgres do postgres do
@ -9,11 +10,13 @@ defmodule AshPostgres.Test.IntegerPost do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
attributes do attributes do
integer_primary_key(:id) integer_primary_key(:id)
attribute(:title, :string) attribute(:title, :string, public?: true)
end end
end end

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.Test.Manager do defmodule AshPostgres.Test.Manager do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
postgres do postgres do
@ -9,6 +10,8 @@ defmodule AshPostgres.Test.Manager do
end end
actions do actions do
default_accept(:*)
defaults([:read, :update, :destroy]) defaults([:read, :update, :destroy])
create :create do create :create do
@ -25,14 +28,15 @@ defmodule AshPostgres.Test.Manager do
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:name, :string) attribute(:name, :string, public?: true)
attribute(:code, :string, allow_nil?: false) attribute(:code, :string, allow_nil?: false, public?: true)
attribute(:must_be_present, :string, allow_nil?: false) attribute(:must_be_present, :string, allow_nil?: false, public?: true)
attribute(:role, :string) attribute(:role, :string, public?: true)
end end
relationships do relationships do
belongs_to :organization, AshPostgres.Test.Organization do belongs_to :organization, AshPostgres.Test.Organization do
public?(true)
attribute_writable?(true) attribute_writable?(true)
end end
end end

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.Test.Organization do defmodule AshPostgres.Test.Organization do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [ authorizers: [
Ash.Policy.Authorizer Ash.Policy.Authorizer
@ -24,17 +25,27 @@ defmodule AshPostgres.Test.Organization do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
attributes do attributes do
uuid_primary_key(:id, writable?: true) uuid_primary_key(:id, writable?: true)
attribute(:name, :string) attribute(:name, :string, public?: true)
end end
relationships do relationships do
has_many(:users, AshPostgres.Test.User) has_many(:users, AshPostgres.Test.User) do
has_many(:posts, AshPostgres.Test.Post) public?(true)
has_many(:managers, AshPostgres.Test.Manager) end
has_many(:posts, AshPostgres.Test.Post) do
public?(true)
end
has_many(:managers, AshPostgres.Test.Manager) do
public?(true)
end
end end
end end

View file

@ -16,6 +16,7 @@ end
defmodule AshPostgres.Test.Post do defmodule AshPostgres.Test.Post do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [ authorizers: [
Ash.Policy.Authorizer Ash.Policy.Authorizer
@ -73,7 +74,14 @@ defmodule AshPostgres.Test.Post do
end end
actions do actions do
defaults([:update, :destroy]) default_accept(:*)
defaults([:destroy])
update :update do
primary?(true)
require_atomic?(false)
end
read :title_is_foo do read :title_is_foo do
filter(expr(title == "foo")) filter(expr(title == "foo"))
@ -113,6 +121,7 @@ defmodule AshPostgres.Test.Post do
end end
update :manual_update do update :manual_update do
require_atomic?(false)
manual(AshPostgres.Test.Post.ManualUpdate) manual(AshPostgres.Test.Post.ManualUpdate)
end end
end end
@ -125,83 +134,103 @@ defmodule AshPostgres.Test.Post do
uuid_primary_key(:id, writable?: true) uuid_primary_key(:id, writable?: true)
attribute(:title, :string) do attribute(:title, :string) do
public?(true)
source(:title_column) source(:title_column)
end end
attribute(:score, :integer) attribute(:score, :integer, public?: true)
attribute(:public, :boolean) attribute(:public, :boolean, public?: true)
attribute(:category, :ci_string) attribute(:category, :ci_string, public?: true)
attribute(:type, :atom, default: :sponsored, private?: true, writable?: false) attribute(:type, :atom, default: :sponsored, writable?: false, public?: false)
attribute(:price, :integer) attribute(:price, :integer, public?: true)
attribute(:decimal, :decimal, default: Decimal.new(0)) attribute(:decimal, :decimal, default: Decimal.new(0), public?: true)
attribute(:status, AshPostgres.Test.Types.Status) attribute(:status, AshPostgres.Test.Types.Status, public?: true)
attribute(:status_enum, AshPostgres.Test.Types.StatusEnum) attribute(:status_enum, AshPostgres.Test.Types.StatusEnum, public?: true)
attribute(:status_enum_no_cast, AshPostgres.Test.Types.StatusEnumNoCast, source: :status_enum)
attribute(:point, AshPostgres.Test.Point) attribute(:status_enum_no_cast, AshPostgres.Test.Types.StatusEnumNoCast,
attribute(:composite_point, AshPostgres.Test.CompositePoint) source: :status_enum,
attribute(:stuff, :map) public?: true
attribute(:list_of_stuff, {:array, :map}) )
attribute(:uniq_one, :string)
attribute(:uniq_two, :string) attribute(:point, AshPostgres.Test.Point, public?: true)
attribute(:uniq_custom_one, :string) attribute(:composite_point, AshPostgres.Test.CompositePoint, public?: true)
attribute(:uniq_custom_two, :string) attribute(:stuff, :map, public?: true)
attribute(:list_of_stuff, {:array, :map}, public?: true)
attribute(:uniq_one, :string, public?: true)
attribute(:uniq_two, :string, public?: true)
attribute(:uniq_custom_one, :string, public?: true)
attribute(:uniq_custom_two, :string, public?: true)
attribute :list_containing_nils, {:array, :string} do attribute :list_containing_nils, {:array, :string} do
public?(true)
constraints(nil_items?: true) constraints(nil_items?: true)
end end
create_timestamp(:created_at) create_timestamp(:created_at, writable?: true, public?: true)
update_timestamp(:updated_at) update_timestamp(:updated_at, writable?: true, public?: true)
end end
code_interface do code_interface do
define_for(AshPostgres.Test.Api) define(:create, args: [:title])
define(:get_by_id, action: :read, get_by: [:id]) define(:get_by_id, action: :read, get_by: [:id])
define(:increment_score, args: [{:optional, :amount}]) define(:increment_score, args: [{:optional, :amount}])
define(:destroy)
define(:bulk_create, bulk?: true, action: :create)
end end
relationships do relationships do
belongs_to :organization, AshPostgres.Test.Organization do belongs_to :organization, AshPostgres.Test.Organization do
public?(true)
attribute_writable?(true) attribute_writable?(true)
end end
belongs_to(:author, AshPostgres.Test.Author) belongs_to(:author, AshPostgres.Test.Author) do
public?(true)
end
has_many :posts_with_matching_title, __MODULE__ do has_many :posts_with_matching_title, __MODULE__ do
public?(true)
no_attributes?(true) no_attributes?(true)
filter(expr(parent(title) == title and parent(id) != id)) filter(expr(parent(title) == title and parent(id) != id))
end end
has_many(:comments, AshPostgres.Test.Comment, destination_attribute: :post_id) has_many(:comments, AshPostgres.Test.Comment, destination_attribute: :post_id, public?: true)
has_many :comments_matching_post_title, AshPostgres.Test.Comment do has_many :comments_matching_post_title, AshPostgres.Test.Comment do
public?(true)
filter(expr(title == parent_expr(title))) filter(expr(title == parent_expr(title)))
end end
has_many :popular_comments, AshPostgres.Test.Comment do has_many :popular_comments, AshPostgres.Test.Comment do
public?(true)
destination_attribute(:post_id) destination_attribute(:post_id)
filter(expr(likes > 10)) filter(expr(likes > 10))
end end
has_many :comments_containing_title, AshPostgres.Test.Comment do has_many :comments_containing_title, AshPostgres.Test.Comment do
public?(true)
manual(AshPostgres.Test.Post.CommentsContainingTitle) manual(AshPostgres.Test.Post.CommentsContainingTitle)
end end
has_many :comments_with_high_rating, AshPostgres.Test.Comment do has_many :comments_with_high_rating, AshPostgres.Test.Comment do
public?(true)
filter(expr(ratings.score > 5)) filter(expr(ratings.score > 5))
end end
has_many(:ratings, AshPostgres.Test.Rating, has_many(:ratings, AshPostgres.Test.Rating,
public?: true,
destination_attribute: :resource_id, destination_attribute: :resource_id,
relationship_context: %{data_layer: %{table: "post_ratings"}} relationship_context: %{data_layer: %{table: "post_ratings"}}
) )
has_many(:post_links, AshPostgres.Test.PostLink, has_many(:post_links, AshPostgres.Test.PostLink,
public?: true,
destination_attribute: :source_post_id, destination_attribute: :source_post_id,
filter: [state: :active] filter: [state: :active]
) )
many_to_many(:linked_posts, __MODULE__, many_to_many(:linked_posts, __MODULE__,
public?: true,
through: AshPostgres.Test.PostLink, through: AshPostgres.Test.PostLink,
join_relationship: :post_links, join_relationship: :post_links,
source_attribute_on_join_resource: :source_post_id, source_attribute_on_join_resource: :source_post_id,
@ -209,13 +238,16 @@ defmodule AshPostgres.Test.Post do
) )
many_to_many(:followers, AshPostgres.Test.User, many_to_many(:followers, AshPostgres.Test.User,
public?: true,
through: AshPostgres.Test.PostFollower, through: AshPostgres.Test.PostFollower,
source_attribute_on_join_resource: :post_id, source_attribute_on_join_resource: :post_id,
destination_attribute_on_join_resource: :follower_id, destination_attribute_on_join_resource: :follower_id,
read_action: :active read_action: :active
) )
has_many(:views, AshPostgres.Test.PostView) has_many(:views, AshPostgres.Test.PostView) do
public?(true)
end
end end
validations do validations do
@ -483,10 +515,10 @@ end
defmodule CalculatePostPriceString do defmodule CalculatePostPriceString do
@moduledoc false @moduledoc false
use Ash.Calculation use Ash.Resource.Calculation
@impl true @impl true
def select(_, _, _), do: [:price] def load(_, _, _), do: [:price]
@impl true @impl true
def calculate(records, _, _) do def calculate(records, _, _) do
@ -500,7 +532,7 @@ end
defmodule CalculatePostPriceStringWithSymbol do defmodule CalculatePostPriceStringWithSymbol do
@moduledoc false @moduledoc false
use Ash.Calculation use Ash.Resource.Calculation
@impl true @impl true
def load(_, _, _), do: [:price_string] def load(_, _, _), do: [:price_string]
@ -524,7 +556,7 @@ defmodule AshPostgres.Test.Post.ManualUpdate do
|> Ash.Changeset.for_update(:update, changeset.attributes) |> Ash.Changeset.for_update(:update, changeset.attributes)
|> Ash.Changeset.force_change_attribute(:title, "manual") |> Ash.Changeset.force_change_attribute(:title, "manual")
|> Ash.Changeset.load(:comments) |> Ash.Changeset.load(:comments)
|> AshPostgres.Test.Api.update!() |> Ash.update!()
} }
end end
end end

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.Test.PostFollower do defmodule AshPostgres.Test.PostFollower do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
postgres do postgres do
@ -9,6 +10,8 @@ defmodule AshPostgres.Test.PostFollower do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
@ -18,10 +21,12 @@ defmodule AshPostgres.Test.PostFollower do
relationships do relationships do
belongs_to :post, AshPostgres.Test.Post do belongs_to :post, AshPostgres.Test.Post do
public?(true)
allow_nil?(false) allow_nil?(false)
end end
belongs_to :follower, AshPostgres.Test.User do belongs_to :follower, AshPostgres.Test.User do
public?(true)
allow_nil?(false) allow_nil?(false)
end end
end end

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.Test.PostLink do defmodule AshPostgres.Test.PostLink do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
postgres do postgres do
@ -9,6 +10,8 @@ defmodule AshPostgres.Test.PostLink do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
@ -18,6 +21,7 @@ defmodule AshPostgres.Test.PostLink do
attributes do attributes do
attribute :state, :atom do attribute :state, :atom do
public?(true)
constraints(one_of: [:active, :archived]) constraints(one_of: [:active, :archived])
default(:active) default(:active)
end end
@ -25,11 +29,13 @@ defmodule AshPostgres.Test.PostLink do
relationships do relationships do
belongs_to :source_post, AshPostgres.Test.Post do belongs_to :source_post, AshPostgres.Test.Post do
public?(true)
allow_nil?(false) allow_nil?(false)
primary_key?(true) primary_key?(true)
end end
belongs_to :destination_post, AshPostgres.Test.Post do belongs_to :destination_post, AshPostgres.Test.Post do
public?(true)
allow_nil?(false) allow_nil?(false)
primary_key?(true) primary_key?(true)
end end

View file

@ -1,18 +1,23 @@
defmodule AshPostgres.Test.PostView do defmodule AshPostgres.Test.PostView do
@moduledoc false @moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer
actions do actions do
default_accept(:*)
defaults([:create, :read]) defaults([:create, :read])
end end
attributes do attributes do
create_timestamp(:time) create_timestamp(:time)
attribute(:browser, :atom, constraints: [one_of: [:firefox, :chrome, :edge]]) attribute(:browser, :atom, constraints: [one_of: [:firefox, :chrome, :edge]], public?: true)
end end
relationships do relationships do
belongs_to :post, AshPostgres.Test.Post do belongs_to :post, AshPostgres.Test.Post do
public?(true)
allow_nil?(false) allow_nil?(false)
attribute_writable?(true) attribute_writable?(true)
end end

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.Test.Profile do defmodule AshPostgres.Test.Profile do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
postgres do postgres do
@ -11,14 +12,18 @@ defmodule AshPostgres.Test.Profile do
attributes do attributes do
uuid_primary_key(:id, writable?: true) uuid_primary_key(:id, writable?: true)
attribute(:description, :string) attribute(:description, :string, public?: true)
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
relationships do relationships do
belongs_to(:author, AshPostgres.Test.Author) belongs_to(:author, AshPostgres.Test.Author) do
public?(true)
end
end end
end end

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.Test.Rating do defmodule AshPostgres.Test.Rating do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
postgres do postgres do
@ -9,12 +10,14 @@ defmodule AshPostgres.Test.Rating do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:score, :integer) attribute(:score, :integer, public?: true)
attribute(:resource_id, :uuid) attribute(:resource_id, :uuid, public?: true)
end end
end end

View file

@ -2,20 +2,22 @@ defmodule AshPostgres.Test.Record do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer data_layer: AshPostgres.DataLayer
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:full_name, :string, allow_nil?: false) attribute(:full_name, :string, allow_nil?: false, public?: true)
timestamps(private?: false) timestamps(public?: true)
end end
relationships do relationships do
alias AshPostgres.Test.Entity alias AshPostgres.Test.Entity
has_one :entity, Entity do has_one :entity, Entity do
public?(true)
no_attributes?(true) no_attributes?(true)
read_action(:read_from_temp) read_action(:read_from_temp)
@ -30,6 +32,8 @@ defmodule AshPostgres.Test.Record do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read]) defaults([:create, :read])
end end
end end

View file

@ -3,6 +3,7 @@ defmodule AshPostgres.Test.Subquery.Access do
alias AshPostgres.Test.Subquery.Parent alias AshPostgres.Test.Subquery.Parent
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Subquery.ParentDomain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [ authorizers: [
Ash.Policy.Authorizer Ash.Policy.Authorizer
@ -17,19 +18,19 @@ defmodule AshPostgres.Test.Subquery.Access do
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:parent_id, :uuid) attribute(:parent_id, :uuid, public?: true)
attribute(:email, :string) attribute(:email, :string, public?: true)
end end
code_interface do code_interface do
define_for(AshPostgres.Test.Subquery.ParentApi)
define(:create) define(:create)
define(:read) define(:read)
end end
relationships do relationships do
belongs_to(:parent, Parent) belongs_to(:parent, Parent) do
public?(true)
end
end end
policies do policies do
@ -39,6 +40,8 @@ defmodule AshPostgres.Test.Subquery.Access do
end end
actions do actions do
default_accept(:*)
defaults([:create, :update, :destroy]) defaults([:create, :update, :destroy])
read :read do read :read do

View file

@ -3,6 +3,7 @@ defmodule AshPostgres.Test.Subquery.Child do
alias AshPostgres.Test.Subquery.Through alias AshPostgres.Test.Subquery.Through
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Subquery.ChildDomain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [ authorizers: [
Ash.Policy.Authorizer Ash.Policy.Authorizer
@ -15,18 +16,17 @@ defmodule AshPostgres.Test.Subquery.Child do
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:state, :string) attribute(:state, :string, public?: true)
end end
code_interface do code_interface do
define_for(AshPostgres.Test.Subquery.ChildApi)
define(:create) define(:create)
define(:read) define(:read)
end end
relationships do relationships do
has_many :throughs, Through do has_many :throughs, Through do
public?(true)
source_attribute(:id) source_attribute(:id)
destination_attribute(:child_id) destination_attribute(:child_id)
end end
@ -48,6 +48,8 @@ defmodule AshPostgres.Test.Subquery.Child do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
end end

View file

@ -1,11 +1,15 @@
defmodule AshPostgres.Test.Subquery.ChildApi do defmodule AshPostgres.Test.Subquery.ChildDomain do
@moduledoc false @moduledoc false
alias AshPostgres.Test.Subquery.Child alias AshPostgres.Test.Subquery.Child
alias AshPostgres.Test.Subquery.Through alias AshPostgres.Test.Subquery.Through
use Ash.Api use Ash.Domain
resources do resources do
resource(Child) resource(Child)
resource(Through) resource(Through)
end end
authorization do
authorize(:when_requested)
end
end end

View file

@ -1,6 +1,7 @@
defmodule AshPostgres.Test.Subquery.Parent do defmodule AshPostgres.Test.Subquery.Parent do
@moduledoc false @moduledoc false
use Ash.Resource, use Ash.Resource,
domain: AshPostgres.Test.Subquery.ParentDomain,
data_layer: AshPostgres.DataLayer, data_layer: AshPostgres.DataLayer,
authorizers: [ authorizers: [
Ash.Policy.Authorizer Ash.Policy.Authorizer
@ -15,22 +16,25 @@ defmodule AshPostgres.Test.Subquery.Parent do
attributes do attributes do
uuid_primary_key(:id) uuid_primary_key(:id)
attribute(:owner_email, :string) attribute(:owner_email, :string, public?: true)
attribute(:other_owner_email, :string) attribute(:other_owner_email, :string, public?: true)
attribute(:visible, :boolean) attribute(:visible, :boolean, public?: true)
end end
relationships do relationships do
many_to_many :children, Child do many_to_many :children, Child do
public?(true)
through(Through) through(Through)
source_attribute(:id) source_attribute(:id)
source_attribute_on_join_resource(:parent_id) source_attribute_on_join_resource(:parent_id)
destination_attribute(:id) destination_attribute(:id)
destination_attribute_on_join_resource(:child_id) destination_attribute_on_join_resource(:child_id)
api(AshPostgres.Test.Subquery.ChildApi) domain(AshPostgres.Test.Subquery.ChildDomain)
end end
has_many(:accesses, Access) has_many(:accesses, Access) do
public?(true)
end
end end
policies do policies do
@ -48,8 +52,6 @@ defmodule AshPostgres.Test.Subquery.Parent do
end end
code_interface do code_interface do
define_for(AshPostgres.Test.Subquery.ParentApi)
define(:create) define(:create)
define(:read) define(:read)
@ -57,6 +59,8 @@ defmodule AshPostgres.Test.Subquery.Parent do
end end
actions do actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy]) defaults([:create, :read, :update, :destroy])
end end
end end

Some files were not shown because too many files have changed in this diff Show more