ash/test/resource/upsert_and_load_children_test.exs

165 lines
3.9 KiB
Elixir
Raw Normal View History

defmodule Ash.Test.Resource.UpsertAndLoadChildrenTest do
@moduledoc false
use ExUnit.Case, async: true
defmodule ProductCatalog do
@moduledoc """
An example API which could represent a Product Catalog bounded context
in an ECommerce or PIM system.
The example models a situation where a parent resource acts as a consistency boundary
for the child resource. In this case the product maintains consistency rules for
its set of child variants. i.e. any changes to the set of variants must be performed through the product.
In the language of Domain Driven Design, the Product resource is acting as the "Aggregate Root" Entity type.
The module LazyLoadVariants represents the most simple example of where
we might want to add a validation to the product that lazy loads the set of variants
to ensure that a consistency condition is met.
"""
use Ash.Api,
extensions: []
resources do
registry(ProductCatalog.Registry)
end
end
defmodule LazyLoadVariants do
@moduledoc false
use Ash.Resource.Change
alias ProductCatalog
def change(changeset, _opts, _context) do
Ash.Changeset.after_action(changeset, fn _changeset, record ->
record
|> ProductCatalog.load(:variants, lazy?: true)
# !ATTN! Loading the variants seems to affect the upsert behaviour.
# i.e. Uncommenting the line below fixes the upsert error.
# {:ok, record}
end)
end
end
defmodule ProductCatalog.Product do
@moduledoc false
alias ProductCatalog.Variant, warn: false
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
extensions: []
code_interface do
define_for(ProductCatalog)
define(:create)
define(:upsert_variants, args: [:variants])
end
changes do
change(LazyLoadVariants)
end
actions do
defaults([:create, :update, :read, :destroy])
update :upsert_variants do
transaction?(true)
argument :variants, {:array, :map} do
allow_nil?(false)
end
change(
manage_relationship(:variants, :variants,
on_lookup: :ignore,
on_no_match: :create,
on_match: :update,
use_identities: [:_primary_key, :sku]
)
)
end
end
attributes do
uuid_primary_key(:id)
end
relationships do
has_many :variants, ProductCatalog.Variant do
destination_attribute(:product_id)
end
end
end
defmodule ProductCatalog.Variant do
@moduledoc false
alias ProductCatalog.Product, warn: false
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
extensions: []
identities do
identity(:sku, [:sku], pre_check_with: ProductCatalog)
end
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
attribute :product_id, :uuid do
allow_nil?(false)
end
attribute :sku, :string do
allow_nil?(false)
end
attribute(:color, :string)
end
relationships do
belongs_to :product, ProductCatalog.Product do
end
end
end
defmodule ProductCatalog.Registry do
@moduledoc false
use Ash.Registry,
extensions: [
Ash.Registry.ResourceValidations
]
entries do
entry(ProductCatalog.Product)
entry(ProductCatalog.Variant)
end
end
alias ProductCatalog.Product
describe "upsert and load children" do
test "Product.upsert_variants" do
{:ok, p} = Product.create(%{})
v = %{sku: "SOME_SKU", color: "Red"}
v_updated = %{sku: v.sku, color: "Blue"}
%{variants: [expected]} = Product.upsert_variants!(p, [v])
assert expected.color == v.color
# v_1 gets matched by sku and then updated
%{variants: [expected_updated]} = Product.upsert_variants!(p, [v_updated])
assert expected_updated.color == v_updated.color
end
end
end