Understanding "manage_relationship"

So I have a many_to_many relationship between the following resources. Reactant
defmodule Flame.App.Resources.Reactant do
...
attributes do
...
attribute :identity, :string do
allow_nil? false
constraints trim?: false, max_length: 20, min_length: 4, allow_empty?: false
end
...
end

actions do
defaults [:create, :read, :update, :destroy]

create :define do
upsert? true

argument :source_definitions, {:array, :map}

change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_no_match: :create,
on_match: :update,
on_missing: :unrelate
)
end

update :redefine do
argument :source_definitions, {:array, :map}

change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_match: :update,
on_no_match: :create,
on_missing: :unrelate
)
end
end

relationships do
many_to_many :sources, Flame.App.Resources.Source do
through Flame.App.Resources.ReactantSource
source_attribute_on_join_resource :reactant_id
destination_attribute_on_join_resource :source_id
end
end
...
end
defmodule Flame.App.Resources.Reactant do
...
attributes do
...
attribute :identity, :string do
allow_nil? false
constraints trim?: false, max_length: 20, min_length: 4, allow_empty?: false
end
...
end

actions do
defaults [:create, :read, :update, :destroy]

create :define do
upsert? true

argument :source_definitions, {:array, :map}

change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_no_match: :create,
on_match: :update,
on_missing: :unrelate
)
end

update :redefine do
argument :source_definitions, {:array, :map}

change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_match: :update,
on_no_match: :create,
on_missing: :unrelate
)
end
end

relationships do
many_to_many :sources, Flame.App.Resources.Source do
through Flame.App.Resources.ReactantSource
source_attribute_on_join_resource :reactant_id
destination_attribute_on_join_resource :source_id
end
end
...
end
Source
defmodule Flame.App.Resources.Source do
...
attributes do
uuid_primary_key :id

attribute :description, :string do
allow_nil? false
end

create_timestamp :created_at
update_timestamp :updated_at
end

actions do
defaults [:create, :read, :update, :destroy]
end

identities do
identity :unique_description, [:description]
end
...
end
defmodule Flame.App.Resources.Source do
...
attributes do
uuid_primary_key :id

attribute :description, :string do
allow_nil? false
end

create_timestamp :created_at
update_timestamp :updated_at
end

actions do
defaults [:create, :read, :update, :destroy]
end

identities do
identity :unique_description, [:description]
end
...
end
3 Replies
morfertaw
morfertawOP2y ago
ReactantSource
defmodule Flame.App.Resources.ReactantSource do
...
actions do
defaults [:create, :read, :update, :destroy]
end

relationships do
belongs_to :reactant, Flame.App.Resources.Reactant, primary_key?: true, allow_nil?: false
belongs_to :source, Flame.App.Resources.Source, primary_key?: true, allow_nil?: false
end
...
end
defmodule Flame.App.Resources.ReactantSource do
...
actions do
defaults [:create, :read, :update, :destroy]
end

relationships do
belongs_to :reactant, Flame.App.Resources.Reactant, primary_key?: true, allow_nil?: false
belongs_to :source, Flame.App.Resources.Source, primary_key?: true, allow_nil?: false
end
...
end
If I create a new Reactant as follows.
{:ok, reactant} = Flame.App.Resources.Reactant
|> Ash.Changeset.new(%{identity: "test_reactant"})
|> Ash.Changeset.for_create(:define, %{
source_definitions: [%{description: "earth"}, %{description: "space"}]
})
|> Flame.App.create()
{:ok, reactant} = Flame.App.Resources.Reactant
|> Ash.Changeset.new(%{identity: "test_reactant"})
|> Ash.Changeset.for_create(:define, %{
source_definitions: [%{description: "earth"}, %{description: "space"}]
})
|> Flame.App.create()
It works and creates a Reactant while creating and relating two Sources. If I then update the reactant as follows.
reactant =
reactant
|> Ash.Changeset.for_update(:redefine, %{source_definitions: nil})
|> Flame.App.update()
reactant =
reactant
|> Ash.Changeset.for_update(:redefine, %{source_definitions: nil})
|> Flame.App.update()
This also works unrelating both Sources without destroying the Sources from their table. Then if I update the reactant as below.
reactant =
reactant
|> Ash.Changeset.for_update(:redefine, %{source_definitions: [%{description: "earth"}] })
|> Flame.App.update()
reactant =
reactant
|> Ash.Changeset.for_update(:redefine, %{source_definitions: [%{description: "earth"}] })
|> Flame.App.update()
This works as expected relating the existing Source from its table without creating a new Source. But the following two updates fail after this update.
reactant =
reactant
|> Ash.Changeset.for_update(:redefine, %{source_definitions: [%{description: "earth"}, %{description: "mars"}] })
|> Flame.App.update()
# OR
reactant =
reactant
|> Ash.Changeset.for_update(:redefine, %{source_definitions: [%{description: "earth"}, %{description: "space"}] })
|> Flame.App.update()
reactant =
reactant
|> Ash.Changeset.for_update(:redefine, %{source_definitions: [%{description: "earth"}, %{description: "mars"}] })
|> Flame.App.update()
# OR
reactant =
reactant
|> Ash.Changeset.for_update(:redefine, %{source_definitions: [%{description: "earth"}, %{description: "space"}] })
|> Flame.App.update()
The error suggests its trying to create an entry in the join resource ReactantSource which already exists.
ZachDaniel
ZachDaniel2y ago
change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_match: :update,
on_no_match: :create,
on_missing: :unrelate,
use_identities: [:the_identity_on_description]
)
change manage_relationship(:source_definitions, :sources,
on_lookup: :relate,
on_match: :update,
on_no_match: :create,
on_missing: :unrelate,
use_identities: [:the_identity_on_description]
)
I believe what you need the the use_identities option so it knows to look it up by certain keys by default it only looks for priamry key matches
morfertaw
morfertawOP2y ago
It works as expected now! I had incorrectly assumed that, by default, it would use all identities and if any matched, trigger the on_match.

Did you find this page helpful?