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
about: Suggest an idea for this project
title: ''
title: ""
labels: enhancement, needs review
assignees: ''
assignees: ""
---
**Is your feature request related to a problem? Please describe.**
@ -29,7 +28,7 @@ For example
Or
```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**

View file

@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
postgres-version: ["10", "12", "14", "16"]
postgres-version: ["14", "15", "16"]
uses: ash-project/ash/.github/workflows/ash-ci.yml@main
with:
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
defmodule MyApp.SomeResource do
use Ash.Resource, data_layer: AshPostgres.DataLayer
use Ash.Resource, domain: MyDomain, data_layer: AshPostgres.DataLayer
postgres do
repo MyApp.Repo

View file

@ -1,4 +1,4 @@
alias AshPostgres.Test.{Api, Post}
alias AshPostgres.Test.{Domain, Post}
ten_rows =
1..10
@ -25,7 +25,7 @@ hundred_thousand_rows =
end)
# 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,
max_concurrency: 2
)
@ -39,13 +39,13 @@ batch_size = 200
Benchee.run(
%{
"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,
transaction: false
)
end,
"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,
transaction: false,
assume_casted?: true
@ -62,7 +62,7 @@ Benchee.run(
input
|> Stream.chunk_every(batch_size)
|> 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
)
end, max_concurrency: max_concurrency, timeout: :infinity)
@ -72,7 +72,7 @@ Benchee.run(
input
|> Stream.chunk_every(batch_size)
|> 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,
assume_casted?: true
)
@ -80,14 +80,14 @@ Benchee.run(
|> Stream.run()
end,
"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,
max_concurrency: max_concurrency,
batch_size: batch_size
)
end,
"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,
assume_casted?: true,
max_concurrency: max_concurrency,

View file

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

View file

@ -38,7 +38,7 @@ defmodule Helpdesk.Support.Ticket.Relationships.TicketsAboveThreshold do
|> Enum.group_by(& &1.representative_id)}
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}`

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:
`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`
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
defp repos do
apis()
|> Enum.flat_map(fn api ->
api
|> Ash.Api.Info.resources()
domains()
|> Enum.flat_map(fn domain ->
domain
|> Ash.Domain.Info.resources()
|> Enum.map(&AshPostgres.DataLayer.Info.repo/1)
end)
|> Enum.uniq()
end
defp apis do
Application.fetch_env!(@app, :ash_apis)
defp domains do
Application.fetch_env!(@app, :ash_domains)
end
defp load_app do

View file

@ -5,6 +5,7 @@ To support leveraging the same resource backed by multiple tables (useful for th
```elixir
defmodule MyApp.Reaction do
use Ash.Resource,
domain: MyDomain,
data_layer: AshPostgres.DataLayer
postgres do
@ -12,9 +13,9 @@ defmodule MyApp.Reaction do
end
attributes do
attribute(:resource_id, :uuid)
attribute :resource_id, :uuid, public?: true
end
...
end
```
@ -24,6 +25,7 @@ Then, in your related resources, you set the table context like so:
```elixir
defmodule MyApp.Post do
use Ash.Resource,
domain: MyDomain,
data_layer: AshPostgres.DataLayer
...
@ -37,6 +39,7 @@ end
defmodule MyApp.Comment do
use Ash.Resource,
domain: MyDomain,
data_layer: AshPostgres.DataLayer
...
@ -61,6 +64,7 @@ For example:
```elixir
defmodule MyApp.Reaction do
# ...
actions do
read :for_comments do
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
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
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`.

View file

@ -68,7 +68,7 @@ import Config
# This should already have been added in the first
# getting started guide
config :helpdesk,
ash_apis: [Helpdesk.Support]
ash_domains: [Helpdesk.Support]
config :helpdesk,
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
use Ash.Resource,
domain: Helpdesk.Support,
data_layer: AshPostgres.DataLayer
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
use Ash.Resource,
domain: Helpdesk.Support,
data_layer: AshPostgres.DataLayer
postgres do

View file

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

View file

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

View file

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

View file

@ -14,6 +14,21 @@ defmodule AshPostgres.DataLayer.Info do
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"
def table(resource) do
Extension.get_opt(resource, [:postgres], :table, nil, true)

View file

@ -14,6 +14,7 @@ defmodule AshPostgres.Expr do
DateAdd,
DateTimeAdd,
Error,
Fragment,
FromNow,
GetPath,
If,
@ -21,6 +22,7 @@ defmodule AshPostgres.Expr do
Length,
Now,
Round,
StringDowncase,
StringJoin,
StringLength,
StringSplit,
@ -29,7 +31,7 @@ defmodule AshPostgres.Expr do
Type
}
alias AshPostgres.Functions.{Fragment, ILike, Like, TrigramSimilarity, VectorCosineDistance}
alias AshPostgres.Functions.{ILike, Like, TrigramSimilarity, VectorCosineDistance}
require Ecto.Query
@ -725,6 +727,27 @@ defmodule AshPostgres.Expr do
)
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(
query,
%StringTrim{arguments: [value], embedded?: pred_embedded?},
@ -1074,10 +1097,11 @@ defmodule AshPostgres.Expr do
expression =
Ash.Actions.Read.add_calc_context_to_filter(
expression,
calculation.context[:actor],
calculation.context[:authorize?],
calculation.context[:tenant],
calculation.context[:tracer]
calculation.context.actor,
calculation.context.authorize?,
calculation.context.tenant,
calculation.context.tracer,
nil
)
do_dynamic_expr(
@ -1190,7 +1214,8 @@ defmodule AshPostgres.Expr do
aggregate.context[:actor],
aggregate.context[:authorize?],
aggregate.context[:tenant],
aggregate.context[:tracer]
aggregate.context[:tracer],
nil
)
{value, acc} = do_dynamic_expr(query, ref, query.__ash_bindings__, false, acc)
@ -1452,10 +1477,10 @@ defmodule AshPostgres.Expr do
end
{encoded, acc} =
if Ash.Filter.TemplateHelpers.expr?(input) do
if Ash.Expr.expr?(input) do
frag_parts =
Enum.flat_map(input, fn {key, value} ->
if Ash.Filter.TemplateHelpers.expr?(value) do
if Ash.Expr.expr?(value) do
[
expr: to_string(key),
raw: "::text, ",
@ -1720,7 +1745,7 @@ defmodule AshPostgres.Expr do
when is_map(value) and not is_struct(value) do
if bindings[:location] == :update &&
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
elements =
value
@ -1770,7 +1795,7 @@ defmodule AshPostgres.Expr do
if other && is_atom(other) && !is_boolean(other) do
{to_string(other), acc}
else
if Ash.Filter.TemplateHelpers.expr?(other) do
if Ash.Expr.expr?(other) do
if is_list(other) do
list_expr(query, other, bindings, true, acc, type)
else
@ -1801,7 +1826,7 @@ defmodule AshPostgres.Expr do
end
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
list_expr(query, value, bindings, false, acc, type)
else
@ -2033,7 +2058,7 @@ defmodule AshPostgres.Expr do
defp list_expr(query, value, bindings, embedded?, acc, type) do
if !Enum.empty?(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
type =
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,
drop_columns: false
def generate(apis, opts \\ []) do
apis = List.wrap(apis)
def generate(domains, opts \\ []) do
domains = List.wrap(domains)
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} =
all_resources
@ -60,8 +60,8 @@ defmodule AshPostgres.MigrationGenerator do
Does not support everything supported by the migration generator.
"""
def take_snapshots(api, repo, only_resources \\ nil) do
all_resources = api |> Ash.Api.Info.resources() |> Enum.uniq()
def take_snapshots(domain, repo, only_resources \\ nil) do
all_resources = domain |> Ash.Domain.Info.resources() |> Enum.uniq()
all_resources
|> Enum.filter(fn resource ->

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.MixHelpers do
@moduledoc false
def apis!(opts, args) do
def domains!(opts, args) do
apps =
if apps_paths = Mix.Project.apps_paths() do
apps_paths |> Map.keys() |> Enum.sort()
@ -8,46 +8,46 @@ defmodule AshPostgres.MixHelpers do
[Mix.Project.config()[:app]]
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 =
if opts[:apis] && opts[:apis] != "" do
opts[:apis]
domains =
if opts[:domains] && opts[:domains] != "" do
opts[:domains]
|> Kernel.||("")
|> String.split(",")
|> Enum.flat_map(fn
"" ->
[]
api ->
[Module.concat([api])]
domain ->
[Module.concat([domain])]
end)
else
configured_apis
configure_domains
end
apis
domains
|> Enum.map(&ensure_compiled(&1, args))
|> 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 ->
apis
domains ->
domains
end
end
def repos!(opts, args) do
apis = apis!(opts, args)
domains = domains!(opts, args)
resources =
apis
|> Enum.flat_map(&Ash.Api.Info.resources/1)
domains
|> Enum.flat_map(&Ash.Domain.Info.resources/1)
|> Enum.filter(&(Ash.DataLayer.data_layer(&1) == AshPostgres.DataLayer))
|> case do
[] ->
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`.
"""
@ -64,7 +64,7 @@ defmodule AshPostgres.MixHelpers do
|> case do
[] ->
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.
@ -98,7 +98,7 @@ defmodule AshPostgres.MixHelpers do
end
end
defp ensure_compiled(api, args) do
defp ensure_compiled(domain, args) do
if Code.ensure_loaded?(Mix.Tasks.App.Config) do
Mix.Task.run("app.config", args)
else
@ -106,18 +106,18 @@ defmodule AshPostgres.MixHelpers do
"--no-compile" not in args && Mix.Task.run("compile", args)
end
case Code.ensure_compiled(api) do
case Code.ensure_compiled(domain) do
{:module, _} ->
api
|> Ash.Api.Info.resources()
domain
|> Ash.Domain.Info.resources()
|> Enum.each(&Code.ensure_compiled/1)
# TODO: We shouldn't need to make sure that the resources are compiled
api
domain
{:error, error} ->
Mix.raise("Could not load #{inspect(api)}, error: #{inspect(error)}. ")
Mix.raise("Could not load #{inspect(domain)}, error: #{inspect(error)}. ")
end
end

View file

@ -5,7 +5,7 @@ defmodule Mix.Tasks.AshPostgres.Create do
@switches [
quiet: :boolean,
apis: :string,
domains: :string,
no_compile: :boolean,
no_deps_check: :boolean
]
@ -15,16 +15,16 @@ defmodule Mix.Tasks.AshPostgres.Create do
]
@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
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
* `--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
* `--no-compile` - 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)]
end)
rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--apis")
rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--domains")
Mix.Task.reenable("ecto.create")
Mix.Task.run("ecto.create", repo_args ++ rest_opts)

View file

@ -1,7 +1,7 @@
defmodule Mix.Tasks.AshPostgres.Drop do
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]
@aliases [
@ -13,7 +13,7 @@ defmodule Mix.Tasks.AshPostgres.Drop do
force: :boolean,
force_drop: :boolean,
quiet: :boolean,
apis: :string,
domains: :string,
no_compile: :boolean,
no_deps_check: :boolean
]
@ -24,11 +24,11 @@ defmodule Mix.Tasks.AshPostgres.Drop do
## Examples
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
* `--apis` - the apis who's repos should be dropped
* `--domains` - the domains who's repos should be dropped
* `-q`, `--quiet` - run the command quietly
* `-f`, `--force` - do not ask for confirmation when dropping the database.
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)]
end)
rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--apis")
rest_opts = AshPostgres.MixHelpers.delete_arg(args, "--domains")
Mix.Task.reenable("ecto.drop")
Mix.Task.run("ecto.drop", repo_args ++ rest_opts)

View file

@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do
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"
* `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`
@ -87,7 +87,7 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do
{opts, _} =
OptionParser.parse!(args,
strict: [
apis: :string,
domains: :string,
snapshot_path: :string,
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
@ -119,6 +119,6 @@ defmodule Mix.Tasks.AshPostgres.GenerateMigrations do
""")
end
AshPostgres.MigrationGenerator.generate(apis, opts)
AshPostgres.MigrationGenerator.generate(domains, opts)
end
end

View file

@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
import AshPostgres.MixHelpers,
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 [
n: :step
@ -20,7 +20,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
pool_size: :integer,
log_sql: :boolean,
strict_version_order: :boolean,
apis: :string,
domains: :string,
no_compile: :boolean,
no_deps_check: :boolean,
migrations_path: :keep,
@ -42,7 +42,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
specific version number, supply `--to version_number`. To migrate a
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
migrate/roll them back to different points, you will need to use the
ecto specific task, `mix ecto.migrate` and provide your repo name.
@ -53,7 +53,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
## Examples
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 --step 3
@ -62,7 +62,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
## 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
@ -107,7 +107,7 @@ defmodule Mix.Tasks.AshPostgres.Migrate do
rest_opts =
args
|> AshPostgres.MixHelpers.delete_arg("--apis")
|> AshPostgres.MixHelpers.delete_arg("--domains")
|> AshPostgres.MixHelpers.delete_arg("--migrations-path")
|> AshPostgres.MixHelpers.delete_flag("--tenants")
|> AshPostgres.MixHelpers.delete_flag("--only-tenants")

View file

@ -4,7 +4,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do
import AshPostgres.MixHelpers,
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 """
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
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
migrate/roll them back to different points, you will need to use the
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
## 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
* `--step` / `-n` - revert n number of applied migrations
* `--to` / `-v` - revert all migrations down to and including version
@ -66,7 +66,7 @@ defmodule Mix.Tasks.AshPostgres.Rollback do
rest_opts =
args
|> AshPostgres.MixHelpers.delete_arg("--apis")
|> AshPostgres.MixHelpers.delete_arg("--domains")
|> AshPostgres.MixHelpers.delete_arg("--migrations-path")
|> AshPostgres.MixHelpers.delete_flag("--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"
@callback installed_extensions() :: [String.t() | module()]
@doc "Configure the version of postgres that is being used."
@callback pg_version() :: Version.t()
@doc """
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,
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
@ -66,6 +68,7 @@ defmodule AshPostgres.Repo do
@callback migrations_path() :: String.t() | nil
@doc "The default prefix(postgres schema) to use when building queries"
@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"
@callback override_migration_type(atom) :: atom
@ -88,7 +91,6 @@ defmodule AshPostgres.Repo do
def migrations_path, do: nil
def default_prefix, do: "public"
def override_migration_type(type), do: type
def min_pg_version, do: 10
def transaction!(fun) do
case fun.() do
@ -224,8 +226,7 @@ defmodule AshPostgres.Repo do
all_tenants: 0,
tenant_migrations_path: 0,
default_prefix: 0,
override_migration_type: 1,
min_pg_version: 0
override_migration_type: 1
end
end
end

View file

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

View file

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

View file

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

View file

@ -1,23 +1,21 @@
defmodule AshPostgres.Transformers.ValidateReferences do
defmodule AshPostgres.Verifiers.ValidateReferences do
@moduledoc false
use Spark.Dsl.Transformer
alias Spark.Dsl.Transformer
use Spark.Dsl.Verifier
alias Spark.Dsl.Verifier
def after_compile?, do: true
def transform(dsl) do
def verify(dsl) do
dsl
|> AshPostgres.DataLayer.Info.references()
|> Enum.each(fn reference ->
unless Ash.Resource.Info.relationship(dsl, reference.relationship) do
raise Spark.Error.DslError,
path: [:postgres, :references, reference.relationship],
module: Transformer.get_persisted(dsl, :module),
module: Verifier.get_persisted(dsl, :module),
message:
"Found reference configuration for relationship `#{reference.relationship}`, but no such relationship exists"
end
end)
{:ok, dsl}
:ok
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
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, []}
]
end
@ -140,7 +140,6 @@ defmodule AshPostgres.MixProject do
EctoMigrationDefault
],
Expressions: [
AshPostgres.Functions.Fragment,
AshPostgres.Functions.TrigramSimilarity,
AshPostgres.Functions.ILike,
AshPostgres.Functions.Like,
@ -157,7 +156,10 @@ defmodule AshPostgres.MixProject do
{:ecto, "~> 3.9"},
{:jason, "~> 1.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]},
{:git_ops, "~> 2.5", only: [:dev, :test]},
{: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"},
"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"},
@ -8,11 +8,9 @@
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"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"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
"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_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"},
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
"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_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"},
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
"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"},
"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"},
"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"},
"stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"},
"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
AshPostgres.Test.Post
|> Ash.Changeset.new(%{title: "title"})
|> AshPostgres.Test.Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
assert_receive %{
type: :create,
@ -15,8 +15,8 @@ defmodule AshPostgresTest do
test "filter policies are applied" do
post =
AshPostgres.Test.Post
|> Ash.Changeset.new(%{title: "good"})
|> AshPostgres.Test.Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "good"})
|> Ash.create!()
assert_raise Ash.Error.Forbidden, fn ->
post
@ -24,13 +24,13 @@ defmodule AshPostgresTest do
authorize?: true,
actor: %{id: Ash.UUID.generate()}
)
|> AshPostgres.Test.Api.update!()
|> Ash.update!()
|> Map.get(:title)
end
post
|> Ash.Changeset.for_update(:update, %{title: "okay"}, authorize?: true)
|> AshPostgres.Test.Api.update!()
|> Ash.update!()
|> Map.get(:title)
end
end

View file

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

View file

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

View file

@ -1,50 +1,50 @@
defmodule AshPostgres.BulkDestroyTest do
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post}
alias AshPostgres.Test.Post
require Ash.Expr
require Ash.Query
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
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
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
|> Ash.Query.filter(title == "fred")
|> Api.bulk_destroy!(:update, %{})
|> Ash.bulk_destroy!(:update, %{})
# 😢 sad
assert [%{title: "george"}] = Api.read!(Post)
assert [%{title: "george"}] = Ash.read!(Post)
end
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
|> 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
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
|> Api.read!()
|> Api.bulk_destroy!(:destroy, %{})
|> Ash.read!()
|> Ash.bulk_destroy!(:destroy, %{}, strategy: :stream, return_errors?: true)
assert [] = Api.read!(Post)
assert [] = Ash.read!(Post)
end
end

View file

@ -1,30 +1,30 @@
defmodule AshPostgres.BulkUpdateTest do
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post}
alias AshPostgres.Test.Post
require Ash.Expr
require Ash.Query
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
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")}
)
posts = Api.read!(Post)
posts = Ash.read!(Post)
assert Enum.all?(posts, &String.ends_with?(&1.title, "_stuff"))
end
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
|> Api.bulk_update!(
|> Ash.bulk_update!(
:update,
%{list_of_stuff: [%{a: 1}]},
return_records?: true,
@ -36,25 +36,25 @@ defmodule AshPostgres.BulkUpdateTest do
test "a map can be given as input on a regular update" do
%{records: [post | _]} =
Api.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create,
Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create,
return_records?: true
)
post
|> Ash.Changeset.for_update(:update, %{list_of_stuff: [%{a: [:a, :b]}, %{a: [:c, :d]}]})
|> Api.update!()
|> Ash.update!()
end
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
|> 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 =
Post
|> Api.read!()
|> Ash.read!()
|> Enum.map(& &1.title)
|> Enum.sort()
@ -62,15 +62,15 @@ defmodule AshPostgres.BulkUpdateTest do
end
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
|> 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 =
Post
|> Api.read!()
|> Ash.read!()
|> Enum.map(& &1.title)
|> Enum.sort()
@ -78,18 +78,19 @@ defmodule AshPostgres.BulkUpdateTest do
end
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
|> Api.read!()
|> Api.bulk_update!(:update, %{},
|> Ash.read!()
|> Ash.bulk_update!(:update, %{},
atomic_update: %{title: Ash.Expr.expr(title <> "_stuff")},
return_records?: true
return_records?: true,
strategy: [:stream]
)
titles =
Post
|> Api.read!()
|> Ash.read!()
|> Enum.map(& &1.title)
|> Enum.sort()
@ -97,14 +98,17 @@ defmodule AshPostgres.BulkUpdateTest do
end
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
|> Ash.Query.for_read(:paginated, authorize?: true)
|> Api.bulk_update!(:requires_initial_data, %{},
authorize?: true,
allow_stream_with: :full_read,
authorize_query?: false
)
assert_raise Ash.Error.Invalid, ~r/had no matching bulk strategy that could be used/, fn ->
Post
|> Ash.Query.for_read(:paginated, authorize?: true)
|> Ash.bulk_update!(:requires_initial_data, %{},
authorize?: true,
allow_stream_with: :full_read,
authorize_query?: false,
return_errors?: true
)
end
end
end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.Test.ManualRelationshipsTest do
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Comment, Post}
alias AshPostgres.Test.{Comment, Post}
require Ash.Query
@ -8,102 +8,102 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do
test "aggregates can be loaded with no data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
assert %{count_of_comments_containing_title: 0} =
Api.load!(post, :count_of_comments_containing_title)
Ash.load!(post, :count_of_comments_containing_title)
end
test "aggregates can be loaded with data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert %{count_of_comments_containing_title: 1} =
Api.load!(post, :count_of_comments_containing_title)
Ash.load!(post, :count_of_comments_containing_title)
end
test "relationships can be filtered on with no data" do
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
assert [] =
Post |> Ash.Query.filter(comments_containing_title.title == "title") |> Api.read!()
Post |> Ash.Query.filter(comments_containing_title.title == "title") |> Ash.read!()
end
test "aggregates can be filtered on with no data" do
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> 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
test "aggregates can be filtered on with data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
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
test "relationships can be filtered on with data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert [_] =
Post
|> Ash.Query.filter(comments_containing_title.title == "title2")
|> Api.read!()
|> Ash.read!()
end
end
@ -111,128 +111,128 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do
test "aggregates can be loaded with no data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> 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)
|> Api.create!()
|> Ash.create!()
assert %{count_of_comments_containing_title: 0} =
Api.load!(comment, :count_of_comments_containing_title)
Ash.load!(comment, :count_of_comments_containing_title)
end
test "aggregates can be loaded with data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> 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)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert %{count_of_comments_containing_title: 1} =
Api.load!(comment, :count_of_comments_containing_title)
Ash.load!(comment, :count_of_comments_containing_title)
end
test "aggregates can be filtered on with no data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert [] =
Comment
|> Ash.Query.filter(count_of_comments_containing_title == 1)
|> Api.read!()
|> Ash.read!()
end
test "relationships can be filtered on with no data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert [] =
Comment
|> Ash.Query.filter(post.comments_containing_title.title == "title2")
|> Api.read!()
|> Ash.read!()
end
test "aggregates can be filtered on with data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert [_, _] =
Comment
|> Ash.Query.filter(count_of_comments_containing_title == 1)
|> Api.read!()
|> Ash.read!()
end
test "relationships can be filtered on with data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert [_, _] =
Comment
|> Ash.Query.filter(post.comments_containing_title.title == "title2")
|> Api.read!()
|> Ash.read!()
end
end
@ -240,128 +240,128 @@ defmodule AshPostgres.Test.ManualRelationshipsTest do
test "aggregates can be loaded with no data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> 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)
|> Api.create!()
|> Ash.create!()
assert %{posts_for_comments_containing_title: []} =
Api.load!(comment, :posts_for_comments_containing_title)
Ash.load!(comment, :posts_for_comments_containing_title)
end
test "aggregates can be loaded with data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> 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)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert %{posts_for_comments_containing_title: ["title"]} =
Api.load!(comment, :posts_for_comments_containing_title)
Ash.load!(comment, :posts_for_comments_containing_title)
end
test "aggregates can be filtered on with no data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert [] =
Comment
|> Ash.Query.filter("title" in posts_for_comments_containing_title)
|> Api.read!()
|> Ash.read!()
end
test "relationships can be filtered on with no data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert [] =
Comment
|> Ash.Query.filter(post.comments_containing_title.post.title == "title")
|> Api.read!()
|> Ash.read!()
end
test "aggregates can be filtered on with data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert [_, _] =
Comment
|> Ash.Query.filter(post.comments_containing_title.post.title == "title")
|> Api.read!()
|> Ash.read!()
end
test "relationships can be filtered on with data" do
post =
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.create!()
Comment
|> Ash.Changeset.new(%{title: "title2"})
|> Ash.Changeset.for_create(:create, %{title: "title2"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
assert [_, _] =
Comment
|> Ash.Query.filter(post.comments_containing_title.post.title == "title")
|> Api.read!()
|> Ash.read!()
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
post =
AshPostgres.Test.Post
|> Ash.Changeset.new(%{title: "match"})
|> AshPostgres.Test.Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "match"})
|> Ash.create!()
AshPostgres.Test.Comment
|> Ash.Changeset.new(%{title: "_"})
|> Ash.Changeset.for_create(:create, %{title: "_"})
|> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
|> AshPostgres.Test.Api.create!()
|> Ash.create!()
post =
post
|> Ash.Changeset.for_update(:manual_update)
|> AshPostgres.Test.Api.update!()
|> Ash.update!()
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
use AshPostgres.RepoCase, async: false
alias AshPostgres.MultitenancyTest.{Api, Org, Post, User}
alias AshPostgres.MultitenancyTest.{Org, Post, User}
setup do
org1 =
Org
|> Ash.Changeset.new(name: "test1")
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{name: "test1"})
|> Ash.create!()
org2 =
Org
|> Ash.Changeset.new(name: "test2")
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{name: "test2"})
|> Ash.create!()
[org1: org1, org2: org2]
end
@ -34,17 +34,17 @@ defmodule AshPostgres.Test.MultitenancyTest do
assert [%{id: ^org_id}] =
Org
|> Ash.Query.set_tenant(tenant(org1))
|> Api.read!()
|> Ash.read!()
end
test "context multitenancy works with policies", %{org1: org1} do
Post
|> Ash.Changeset.new(name: "foo")
|> Ash.Changeset.for_create(:create, %{name: "foo"})
|> Ash.Changeset.set_tenant(tenant(org1))
|> Api.create!()
|> Ash.create!()
|> Ash.Changeset.for_update(:update_with_policy, %{}, authorize?: true)
|> Ash.Changeset.set_tenant(tenant(org1))
|> Api.update!()
|> Ash.update!()
end
test "attribute multitenancy is set on creation" do
@ -52,29 +52,29 @@ defmodule AshPostgres.Test.MultitenancyTest do
org =
Org
|> Ash.Changeset.new(name: "test3")
|> Ash.Changeset.for_create(:create, %{name: "test3"})
|> Ash.Changeset.set_tenant("org_#{uuid}")
|> Api.create!()
|> Ash.create!()
assert org.id == uuid
end
test "schema multitenancy works", %{org1: org1, org2: org2} do
Post
|> Ash.Changeset.new(name: "foo")
|> Ash.Changeset.for_create(:create, %{name: "foo"})
|> 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(org2)) |> Api.read!()
assert [_] = Post |> Ash.Query.set_tenant(tenant(org1)) |> Ash.read!()
assert [] = Post |> Ash.Query.set_tenant(tenant(org2)) |> Ash.read!()
end
test "schema rename on update works", %{org1: org1} do
new_uuid = Ash.UUID.generate()
org1
|> Ash.Changeset.new(id: new_uuid)
|> Api.update!()
|> Ash.Changeset.for_update(:update, %{id: new_uuid})
|> Ash.update!()
new_tenant = "org_#{new_uuid}"
@ -91,106 +91,106 @@ defmodule AshPostgres.Test.MultitenancyTest do
org =
Org
|> Ash.Changeset.new()
|> Api.create!()
|> Ash.create!()
user =
User
|> Ash.Changeset.new()
|> 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
test "loading context multitenant resources from attribute multitenant resources works" do
org =
Org
|> Ash.Changeset.new()
|> Api.create!()
|> Ash.create!()
user1 =
User
|> Ash.Changeset.new(%{name: "a"})
|> Ash.Changeset.for_create(:create, %{name: "a"})
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
user2 =
User
|> Ash.Changeset.new(%{name: "b"})
|> Ash.Changeset.for_create(:create, %{name: "b"})
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
user1_id = user1.id
user2_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
test "manage_relationship from context multitenant resource to attribute multitenant resource doesn't raise an error" do
org = Org |> Ash.Changeset.new() |> Api.create!()
user = User |> Ash.Changeset.new() |> Api.create!()
org = Org |> Ash.Changeset.new() |> Ash.create!()
user = User |> Ash.Changeset.new() |> Ash.create!()
Post
|> Ash.Changeset.for_create(:create, %{}, tenant: tenant(org))
|> Ash.Changeset.manage_relationship(:user, user, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
end
test "loading attribute multitenant resources with limits from context multitenant resources works" do
org =
Org
|> Ash.Changeset.new()
|> Api.create!()
|> Ash.create!()
user =
User
|> Ash.Changeset.new()
|> 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
test "loading context multitenant resources with limits from attribute multitenant resources works" do
org =
Org
|> Ash.Changeset.new()
|> Api.create!()
|> Ash.create!()
user1 =
User
|> Ash.Changeset.new(%{name: "a"})
|> Ash.Changeset.for_create(:create, %{name: "a"})
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
user2 =
User
|> Ash.Changeset.new(%{name: "b"})
|> Ash.Changeset.for_create(:create, %{name: "b"})
|> Ash.Changeset.manage_relationship(:org, org, type: :append_and_remove)
|> Api.create!()
|> Ash.create!()
user1_id = user1.id
user2_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
test "unique constraints are properly scoped", %{org1: org1} do
post =
Post
|> Ash.Changeset.new(%{})
|> Ash.Changeset.for_create(:create, %{})
|> Ash.Changeset.set_tenant(tenant(org1))
|> Api.create!()
|> Ash.create!()
assert_raise Ash.Error.Invalid,
~r/Invalid value provided for id: has already been taken/,
fn ->
Post
|> Ash.Changeset.new(%{id: post.id})
|> Ash.Changeset.for_create(:create, %{id: post.id})
|> Ash.Changeset.set_tenant(tenant(org1))
|> Api.create!()
|> Ash.create!()
end
end
end

View file

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

View file

@ -1,16 +1,17 @@
defmodule AshPostgres.Test.PrimaryKeyTest do
@moduledoc false
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, IntegerPost, Post, PostView}
alias AshPostgres.Test.{IntegerPost, Post, PostView}
require Ash.Query
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
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
describe "resources without a primary key" do
@ -18,12 +19,12 @@ defmodule AshPostgres.Test.PrimaryKeyTest do
post =
Post
|> Ash.Changeset.for_action(:create, %{title: "not very interesting"})
|> Api.create!()
|> Ash.create!()
assert {:ok, view} =
PostView
|> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id})
|> Api.create()
|> Ash.create()
assert view.browser == :firefox
assert view.post_id == post.id
@ -34,14 +35,14 @@ defmodule AshPostgres.Test.PrimaryKeyTest do
post =
Post
|> Ash.Changeset.for_action(:create, %{title: "not very interesting"})
|> Api.create!()
|> Ash.create!()
expected =
PostView
|> 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.browser == expected.browser

View file

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

View file

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

View file

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

View file

@ -1,15 +1,15 @@
defmodule AshPostgres.SelectTest do
@moduledoc false
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Post}
alias AshPostgres.Test.Post
require Ash.Query
test "values not selected in the query are not present in the response" do
Post
|> Ash.Changeset.new(%{title: "title"})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "title"})
|> 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

View file

@ -1,30 +1,30 @@
defmodule AshPostgres.SortTest do
@moduledoc false
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Comment, Post, PostLink}
alias AshPostgres.Test.{Comment, Post, PostLink}
require Ash.Query
require Ash.Sort
test "multi-column sorts work" do
Post
|> Ash.Changeset.new(%{title: "aaa", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Ash.create!()
Post
|> Ash.Changeset.new(%{title: "aaa", score: 1})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1})
|> Ash.create!()
Post
|> Ash.Changeset.new(%{title: "bbb", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0})
|> Ash.create!()
assert [
%{title: "aaa", score: 0},
%{title: "aaa", score: 1},
%{title: "bbb"}
] =
Api.read!(
Ash.read!(
Post
|> Ash.Query.load(:count_of_comments)
|> Ash.Query.sort(title: :asc, score: :asc)
@ -34,31 +34,31 @@ defmodule AshPostgres.SortTest do
test "multi-column sorts work on inclusion" do
post =
Post
|> Ash.Changeset.new(%{title: "aaa", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Ash.create!()
Post
|> Ash.Changeset.new(%{title: "aaa", score: 1})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1})
|> Ash.create!()
Post
|> Ash.Changeset.new(%{title: "bbb", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0})
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
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)
|> Api.create!()
|> Ash.create!()
posts =
Post
@ -71,7 +71,7 @@ defmodule AshPostgres.SortTest do
|> Ash.Query.limit(1)
])
|> Ash.Query.sort([:title, :score])
|> Api.read!()
|> Ash.read!()
assert [
%{title: "aaa", comments: [%{title: "aaa"}]},
@ -82,23 +82,23 @@ defmodule AshPostgres.SortTest do
test "multicolumn sort works with a select statement" do
Post
|> Ash.Changeset.new(%{title: "aaa", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Ash.create!()
Post
|> Ash.Changeset.new(%{title: "aaa", score: 1})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "aaa", score: 1})
|> Ash.create!()
Post
|> Ash.Changeset.new(%{title: "bbb", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "bbb", score: 0})
|> Ash.create!()
assert [
%{title: "aaa", score: 0},
%{title: "aaa", score: 1},
%{title: "bbb"}
] =
Api.read!(
Ash.read!(
Post
|> Ash.Query.sort(title: :asc, score: :asc)
|> 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
post1 =
Post
|> Ash.Changeset.new(%{title: "aaa", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Ash.create!()
post2 =
Post
|> Ash.Changeset.new(%{title: "bbb", score: 1})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "bbb", score: 1})
|> Ash.create!()
post3 =
Post
|> Ash.Changeset.new(%{title: "ccc", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "ccc", score: 0})
|> Ash.create!()
PostLink
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post1, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post3, type: :append)
|> Api.create!()
|> Ash.create!()
PostLink
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post2, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post2, type: :append)
|> Api.create!()
|> Ash.create!()
PostLink
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post3, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post1, type: :append)
|> Api.create!()
|> Ash.create!()
assert [
%{title: "aaa"},
%{title: "bbb"},
%{title: "ccc"}
] =
Api.read!(
Ash.read!(
Post
|> Ash.Query.sort(title: :asc)
|> Ash.Query.filter(linked_posts.title in ["aaa", "bbb", "ccc"])
@ -155,7 +155,7 @@ defmodule AshPostgres.SortTest do
%{title: "bbb"},
%{title: "aaa"}
] =
Api.read!(
Ash.read!(
Post
|> Ash.Query.sort(title: :desc)
|> Ash.Query.filter(linked_posts.title in ["aaa", "bbb", "ccc"] or title == "aaa")
@ -166,7 +166,7 @@ defmodule AshPostgres.SortTest do
%{title: "bbb"},
%{title: "aaa"}
] =
Api.read!(
Ash.read!(
Post
|> Ash.Query.sort(title: :desc)
|> Ash.Query.filter(
@ -180,48 +180,48 @@ defmodule AshPostgres.SortTest do
Post
|> Ash.Query.load(:count_of_comments)
|> Ash.Query.sort(:c_times_p)
|> Api.read!()
|> Ash.read!()
end
test "calculations can sort on expressions" do
post1 =
Post
|> Ash.Changeset.new(%{title: "aaa", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "aaa", score: 0})
|> Ash.create!()
post2 =
Post
|> Ash.Changeset.new(%{title: "bbb", score: 1})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "bbb", score: 1})
|> Ash.create!()
post3 =
Post
|> Ash.Changeset.new(%{title: "ccc", score: 0})
|> Api.create!()
|> Ash.Changeset.for_create(:create, %{title: "ccc", score: 0})
|> Ash.create!()
PostLink
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post1, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post3, type: :append)
|> Api.create!()
|> Ash.create!()
PostLink
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post2, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post2, type: :append)
|> Api.create!()
|> Ash.create!()
PostLink
|> Ash.Changeset.new()
|> Ash.Changeset.manage_relationship(:source_post, post3, type: :append)
|> Ash.Changeset.manage_relationship(:destination_post, post1, type: :append)
|> Api.create!()
|> Ash.create!()
posts_query =
Ash.Query.sort(Post, Ash.Sort.expr_sort(source(post_links.state)))
Post
|> Ash.Query.load(linked_posts: posts_query)
|> Api.read!()
|> Ash.read!()
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
@moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer
use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer
actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy])
end
@ -43,6 +46,8 @@ defmodule AshPostgres.Test.ComplexCalculations.Certification do
end
relationships do
has_many(:skills, AshPostgres.Test.ComplexCalculations.Skill)
has_many(:skills, AshPostgres.Test.ComplexCalculations.Skill) do
public?(true)
end
end
end

View file

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

View file

@ -1,18 +1,21 @@
defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do
@moduledoc false
use Ash.Resource,
domain: AshPostgres.Test.ComplexCalculations.Domain,
data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer]
actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
create_timestamp(:created_at, private?: false)
update_timestamp(:updated_at, private?: false)
create_timestamp(:created_at, public?: true)
update_timestamp(:updated_at, public?: true)
end
postgres do
@ -21,8 +24,8 @@ defmodule AshPostgres.Test.ComplexCalculations.ChannelMember do
end
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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.Test.Concat do
@moduledoc false
use Ash.Calculation
use Ash.Resource.Calculation
require Ash.Query
def init(opts) do
@ -11,16 +11,16 @@ defmodule AshPostgres.Test.Concat do
end
end
def expression(opts, %{separator: separator}) do
def expression(opts, %{arguments: %{separator: separator}}) do
Enum.reduce(opts[:keys], nil, fn key, expr ->
if expr do
if separator do
Ash.Query.expr(^expr <> ^separator <> ref(^key))
expr(^expr <> ^separator <> ^ref(key))
else
Ash.Query.expr(^expr <> ref(^key))
expr(^expr <> ^ref(key))
end
else
Ash.Query.expr(ref(^key))
expr(^ref(key))
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
@moduledoc false
use Ash.Resource,
domain: AshPostgres.MultitenancyTest.Domain,
data_layer: AshPostgres.DataLayer
identities do
@ -9,10 +10,12 @@ defmodule AshPostgres.MultitenancyTest.Org do
attributes do
uuid_primary_key(:id, writable?: true)
attribute(:name, :string)
attribute(:name, :string, public?: true)
end
actions do
default_accept(:*)
defaults([:create, :read, :update, :destroy])
end
@ -33,8 +36,15 @@ defmodule AshPostgres.MultitenancyTest.Org do
end
relationships do
has_many(:posts, AshPostgres.MultitenancyTest.Post, destination_attribute: :org_id)
has_many(:users, AshPostgres.MultitenancyTest.User, destination_attribute: :org_id)
has_many(:posts, AshPostgres.MultitenancyTest.Post,
destination_attribute: :org_id,
public?: true
)
has_many(:users, AshPostgres.MultitenancyTest.User,
destination_attribute: :org_id,
public?: true
)
end
def tenant("org_" <> tenant) do

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,14 +2,15 @@ defmodule AshPostgres.Test.Entity do
@moduledoc false
use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer
attributes do
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
postgres do
@ -18,6 +19,8 @@ defmodule AshPostgres.Test.Entity do
end
actions do
default_accept(:*)
defaults([:create, :read])
read :read_from_temp do

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,20 +2,22 @@ defmodule AshPostgres.Test.Record do
@moduledoc false
use Ash.Resource,
domain: AshPostgres.Test.Domain,
data_layer: AshPostgres.DataLayer
attributes do
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
relationships do
alias AshPostgres.Test.Entity
has_one :entity, Entity do
public?(true)
no_attributes?(true)
read_action(:read_from_temp)
@ -30,6 +32,8 @@ defmodule AshPostgres.Test.Record do
end
actions do
default_accept(:*)
defaults([:create, :read])
end
end

View file

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

View file

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

View file

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

View file

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

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