У меня есть следующий сценарий:
Я храню товар и его варианты (has_many
) в базе данных. Затем я отправляю Product и его варианты в API одним запросом:
payload = %Product{
# ...
variants: [
%Variant{
# ...
option1: "Black",
option2: "64GB",
},
%Variant{
# ...
option1: "Silver",
option2: "64GB",
}
],
vendor: "Apple"
}
Когда я получаю ответ, мне нужно обновить продукт и его варианты в моей базе данных. Ответ:
%Shopify.Product{
# ...
created_at: "2018-04-26T12:54:33-04:00",
variants: [
%Shopify.Variant{
# ...
created_at: "2018-04-26T12:54:33-04:00",
option1: "Black",
option2: "64GB",
title: "Black / 64GB",
},
%Shopify.Variant{
# ...
created_at: "2018-04-26T12:54:33-04:00",
option1: "Silver",
option2: "64GB",
title: "Silver / 64GB",
}
],
}
Когда я пытаюсь обновить связанные варианты, используя cast_assoc
, ecto удаляет имеющиеся у меня записи вариантов в базе данных и вставляет новые из полезной нагрузки, которые совпадают.
Я знаю, это потому, что on_replace: :delete
, но если я не установлю :on_replace
, Экто будет жаловаться, что не знает, что делать на замену. Но почему заменить?
Мне нужно обновить связанные записи. Я попытался внедрить идентификаторы вариантов из базы данных в ответ, который я использую в качестве атрибутов для обновления, поэтому Ecto может связать варианты из ответа с теми, что есть в базе данных.
Борьба с этим весь день. Можно ли вообще обновить связанные записи? Чего мне не хватает?
Вот моя схема:
defmodule Product do
# ...
has_many(:variants, Variant, on_replace: :delete)
# ...
def changeset_from_response(product, response) do
attrs =
response
|> Utils.map_from_struct_deep()
|> put_variant_ids(product)
product
|> cast(attrs, @castable_attrs)
# ...
# HERE **********
|> cast_assoc(:variants, with: &Variant.changeset_from_response/2)
# HERE **********
end
end
defmodule Variant do
# ...
belongs_to(:product, Product)
# ...
def changeset_from_response(variant, response) do
attrs =
response
|> Utils.map_from_struct_deep()
variant
|> cast(attrs, @castable_attrs ++ [:id, :product_id])
# ...
end
end
И упрощенный код, который должен выполнять работу:
product = Repo.get(Product, id) |> Repo.preload(:variants)
payload = # product to payload
shopify_product = Shopify.Product.create(payload)
changeset = Product.changeset_from_response(product, shopify_product)
iex> changeset
#Ecto.Changeset<
action: nil,
changes: %{
# ...
shopify_created_at: ~N[2018-04-26 12:54:33],
shopify_id: "1333797453908",
variants: [
#Ecto.Changeset<action: :replace, changes: %{}, errors: [],
data: #ProdSync.Data.Variant<>, valid?: true>,
#Ecto.Changeset<action: :replace, changes: %{}, errors: [],
data: #ProdSync.Data.Variant<>, valid?: true>,
***** HERE IT DELETES VARIANTS AND INSERTS NEW ONES ********
#Ecto.Changeset<
action: :insert,
changes: %{
id: 26,
option1: "Black",
option2: "64GB",
title: "Black / 64GB",
},
errors: [],
data: #ProdSync.Data.Variant<>,
valid?: true
>,
#Ecto.Changeset<
action: :insert,
changes: %{
id: 27,
option1: "Silver",
option2: "64GB",
title: "Silver / 64GB",
},
errors: [],
data: #ProdSync.Data.Variant<>,
valid?: true
>,
]
},
errors: [],
data: #ProdSync.Data.Product<>,
valid?: true
>