How to create a product with many_to_many tags?

Setup:
defmodule App.Shop.Product do
[...]
relationships do
many_to_many :tags, App.Shop.Tag do
through App.Shop.ProductTag
source_attribute_on_join_resource :product_id
destination_attribute_on_join_resource :tag_id
end
end

actions do
defaults [:create, :read]
end

code_interface do
define_for App.Shop
define :create
define :read
define :by_name, get_by: [:name], action: :read
end
end
defmodule App.Shop.Product do
[...]
relationships do
many_to_many :tags, App.Shop.Tag do
through App.Shop.ProductTag
source_attribute_on_join_resource :product_id
destination_attribute_on_join_resource :tag_id
end
end

actions do
defaults [:create, :read]
end

code_interface do
define_for App.Shop
define :create
define :read
define :by_name, get_by: [:name], action: :read
end
end
defmodule App.Shop.Tag do
[...]

relationships do
many_to_many :products, App.Shop.Product do
through App.Shop.ProductTag
source_attribute_on_join_resource :tag_id
destination_attribute_on_join_resource :product_id
end
end

actions do
defaults [:create, :read]
end

code_interface do
define_for App.Shop
define :create
define :read
define :by_name, get_by: [:name], action: :read
end
end
defmodule App.Shop.Tag do
[...]

relationships do
many_to_many :products, App.Shop.Product do
through App.Shop.ProductTag
source_attribute_on_join_resource :tag_id
destination_attribute_on_join_resource :product_id
end
end

actions do
defaults [:create, :read]
end

code_interface do
define_for App.Shop
define :create
define :read
define :by_name, get_by: [:name], action: :read
end
end
defmodule App.Shop.ProductTag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

relationships do
belongs_to :product, App.Shop.Product do
primary_key? true
allow_nil? false
end

belongs_to :tag, App.Shop.Tag do
primary_key? true
allow_nil? false
end
end
end
defmodule App.Shop.ProductTag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

relationships do
belongs_to :product, App.Shop.Product do
primary_key? true
allow_nil? false
end

belongs_to :tag, App.Shop.Tag do
primary_key? true
allow_nil? false
end
end
end
The iex preparation:
iex(1)> good_deal_tag = App.Shop.Tag.create!(%{name: "Good deal"})
iex(2)> yellow_tag = App.Shop.Tag.create!(%{name: "Yellow"})
iex(1)> good_deal_tag = App.Shop.Tag.create!(%{name: "Good deal"})
iex(2)> yellow_tag = App.Shop.Tag.create!(%{name: "Yellow"})
Question: How can I create a new product which has the two tags good_deal_tag and yellow_tag? Something like:
iex(3) App.Shop.Product.create!(%{name: "Banana", tags: [good_deal_tag, yellow_tag]})
iex(3) App.Shop.Product.create!(%{name: "Banana", tags: [good_deal_tag, yellow_tag]})
10 Replies
ZachDaniel
ZachDaniel2y ago
Should the tags already exist? Or should it create tags if they don't exist yet? If the former:
argument :tags, {:array, :map}

change manage_relationship(:tags, type: :append_and_remove}
argument :tags, {:array, :map}

change manage_relationship(:tags, type: :append_and_remove}
that would delete/create join rows accordingly
Stefan Wintermeyer
Is the later possible too?
ZachDaniel
ZachDaniel2y ago
yep!
Stefan Wintermeyer
Where exactly do I have to put this code?
argument :tags, {:array, :map}
change manage_relationship(:tags, type: :append_and_remove}
argument :tags, {:array, :map}
change manage_relationship(:tags, type: :append_and_remove}
ZachDaniel
ZachDaniel2y ago
That would go in the action
create :create do
argument :tags, {:array, :map}

change manage_relationship(:tags, type: :append_and_remove, on_no_match: :create}
end
create :create do
argument :tags, {:array, :map}

change manage_relationship(:tags, type: :append_and_remove, on_no_match: :create}
end
Stefan Wintermeyer
actions do
defaults [:read, :update, :destroy]

create :create do
argument :tags, {:array, :map}
change manage_relationship(:tags, type: :append_and_remove, on_no_match: :create)
end
end
actions do
defaults [:read, :update, :destroy]

create :create do
argument :tags, {:array, :map}
change manage_relationship(:tags, type: :append_and_remove, on_no_match: :create)
end
end
This raises an error:
$ iex -S mix
Compiling 2 files (.ex)
** (EXIT from #PID<0.98.0>) an exception was raised:
** (Spark.Error.DslError) [App.Shop.Product]
actions -> create -> create -> change -> manage_relationship -> tags:
The following error was raised when validating options provided to manage_relationship.

** (RuntimeError) Required primary create action for App.Shop.ProductTag.
[...]
$ iex -S mix
Compiling 2 files (.ex)
** (EXIT from #PID<0.98.0>) an exception was raised:
** (Spark.Error.DslError) [App.Shop.Product]
actions -> create -> create -> change -> manage_relationship -> tags:
The following error was raised when validating options provided to manage_relationship.

** (RuntimeError) Required primary create action for App.Shop.ProductTag.
[...]
ZachDaniel
ZachDaniel2y ago
Yes, managing relationships uses actions on the target resources and by default, it uses the primary actions there aren't any actions on ProductTag a far as I can tell
Stefan Wintermeyer
Here's the current code.
defmodule App.Shop.Tag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

attributes do
uuid_primary_key :id
attribute :name, :string
end

relationships do
many_to_many :products, App.Shop.Product do
through App.Shop.ProductTag
source_attribute_on_join_resource :tag_id
destination_attribute_on_join_resource :product_id
end
end

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

create :create do
primary? true
argument :products, {:array, :map}
change manage_relationship(:products, type: :append_and_remove, on_no_match: :create)
end
end

code_interface do
define_for App.Shop
define :create
define :read
define :by_id, get_by: [:id], action: :read
define :by_name, get_by: [:name], action: :read
define :update
define :destroy
end
end
defmodule App.Shop.Tag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

attributes do
uuid_primary_key :id
attribute :name, :string
end

relationships do
many_to_many :products, App.Shop.Product do
through App.Shop.ProductTag
source_attribute_on_join_resource :tag_id
destination_attribute_on_join_resource :product_id
end
end

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

create :create do
primary? true
argument :products, {:array, :map}
change manage_relationship(:products, type: :append_and_remove, on_no_match: :create)
end
end

code_interface do
define_for App.Shop
define :create
define :read
define :by_id, get_by: [:id], action: :read
define :by_name, get_by: [:name], action: :read
define :update
define :destroy
end
end
defmodule App.Shop.Product do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

attributes do
uuid_primary_key :id
attribute :name, :string
attribute :price, :decimal
end

relationships do
many_to_many :tags, App.Shop.Tag do
through App.Shop.ProductTag
source_attribute_on_join_resource :product_id
destination_attribute_on_join_resource :tag_id
end
end

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

create :create do
primary? true
argument :tags, {:array, :map}
change manage_relationship(:tags, type: :append_and_remove, on_no_match: :create)
end
end

code_interface do
define_for App.Shop
define :create
define :read
define :by_id, get_by: [:id], action: :read
define :by_name, get_by: [:name], action: :read
define :update
define :destroy
end
end
defmodule App.Shop.Product do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

attributes do
uuid_primary_key :id
attribute :name, :string
attribute :price, :decimal
end

relationships do
many_to_many :tags, App.Shop.Tag do
through App.Shop.ProductTag
source_attribute_on_join_resource :product_id
destination_attribute_on_join_resource :tag_id
end
end

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

create :create do
primary? true
argument :tags, {:array, :map}
change manage_relationship(:tags, type: :append_and_remove, on_no_match: :create)
end
end

code_interface do
define_for App.Shop
define :create
define :read
define :by_id, get_by: [:id], action: :read
define :by_name, get_by: [:name], action: :read
define :update
define :destroy
end
end
defmodule App.Shop.ProductTag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

relationships do
belongs_to :product, App.Shop.Product do
primary_key? true
allow_nil? false
end

belongs_to :tag, App.Shop.Tag do
primary_key? true
allow_nil? false
end
end
end
defmodule App.Shop.ProductTag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

relationships do
belongs_to :product, App.Shop.Product do
primary_key? true
allow_nil? false
end

belongs_to :tag, App.Shop.Tag do
primary_key? true
allow_nil? false
end
end
end
That results in this error:
$ iex -S mix
Compiling 2 files (.ex)
** (EXIT from #PID<0.98.0>) an exception was raised:
** (Spark.Error.DslError) [App.Shop.Product]
actions -> create -> create -> change -> manage_relationship -> tags:
The following error was raised when validating options provided to manage_relationship.

** (RuntimeError) Required primary create action for App.Shop.ProductTag.
(ash 2.14.17) lib/ash/resource/info.ex:493: Ash.Resource.Info.primary_action!/2
...
$ iex -S mix
Compiling 2 files (.ex)
** (EXIT from #PID<0.98.0>) an exception was raised:
** (Spark.Error.DslError) [App.Shop.Product]
actions -> create -> create -> change -> manage_relationship -> tags:
The following error was raised when validating options provided to manage_relationship.

** (RuntimeError) Required primary create action for App.Shop.ProductTag.
(ash 2.14.17) lib/ash/resource/info.ex:493: Ash.Resource.Info.primary_action!/2
...
ZachDaniel
ZachDaniel2y ago
defmodule App.Shop.ProductTag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

# actions required for the join resource

relationships do
belongs_to :product, App.Shop.Product do
primary_key? true
allow_nil? false
end

belongs_to :tag, App.Shop.Tag do
primary_key? true
allow_nil? false
end
end
end
defmodule App.Shop.ProductTag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

# actions required for the join resource

relationships do
belongs_to :product, App.Shop.Product do
primary_key? true
allow_nil? false
end

belongs_to :tag, App.Shop.Tag do
primary_key? true
allow_nil? false
end
end
end
Stefan Wintermeyer
Thanks! Solution for the archive:
defmodule App.Shop.ProductTag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

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

relationships do
belongs_to :product, App.Shop.Product do
primary_key? true
allow_nil? false
end

belongs_to :tag, App.Shop.Tag do
primary_key? true
allow_nil? false
end
end
end
defmodule App.Shop.ProductTag do
use Ash.Resource, data_layer: Ash.DataLayer.Ets

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

relationships do
belongs_to :product, App.Shop.Product do
primary_key? true
allow_nil? false
end

belongs_to :tag, App.Shop.Tag do
primary_key? true
allow_nil? false
end
end
end
iex(1)> good_deal_tag = App.Shop.Tag.create!(%{name: "Good deal"})
iex(2)> yellow_tag = App.Shop.Tag.create!(%{name: "Yellow"})
iex(3)> App.Shop.Product.create!(%{name: "Banana", tags: [good_deal_tag, yellow_tag]})
iex(4)> App.Shop.Product.by_name!("Banana", load: [:tags])
#App.Shop.Product<
tags: [
#App.Shop.Tag<
products: #Ash.NotLoaded<:relationship>,
products_join_assoc: #Ash.NotLoaded<:relationship>,
__meta__: #Ecto.Schema.Metadata<:loaded>,
id: "82b7e8af-69b9-4f35-b32a-0b6b2bed1d15",
name: "Good deal",
aggregates: %{},
calculations: %{},
...
>,
#App.Shop.Tag<
products: #Ash.NotLoaded<:relationship>,
products_join_assoc: #Ash.NotLoaded<:relationship>,
__meta__: #Ecto.Schema.Metadata<:loaded>,
id: "d04aa5ef-195e-4dd8-9c5a-5c73e6f44afe",
name: "Yellow",
aggregates: %{},
calculations: %{},
...
>
],
...
>
iex(1)> good_deal_tag = App.Shop.Tag.create!(%{name: "Good deal"})
iex(2)> yellow_tag = App.Shop.Tag.create!(%{name: "Yellow"})
iex(3)> App.Shop.Product.create!(%{name: "Banana", tags: [good_deal_tag, yellow_tag]})
iex(4)> App.Shop.Product.by_name!("Banana", load: [:tags])
#App.Shop.Product<
tags: [
#App.Shop.Tag<
products: #Ash.NotLoaded<:relationship>,
products_join_assoc: #Ash.NotLoaded<:relationship>,
__meta__: #Ecto.Schema.Metadata<:loaded>,
id: "82b7e8af-69b9-4f35-b32a-0b6b2bed1d15",
name: "Good deal",
aggregates: %{},
calculations: %{},
...
>,
#App.Shop.Tag<
products: #Ash.NotLoaded<:relationship>,
products_join_assoc: #Ash.NotLoaded<:relationship>,
__meta__: #Ecto.Schema.Metadata<:loaded>,
id: "d04aa5ef-195e-4dd8-9c5a-5c73e6f44afe",
name: "Yellow",
aggregates: %{},
calculations: %{},
...
>
],
...
>

Did you find this page helpful?