Hello there,
I’m new to Elixir and I’m currently having trouble with:
I need to infer values from a form’s fields and put those values into a changeset. What makes it harder is that I’m using embedded schemas.
Let’s say I have an “Invoice Item” entity. The invoice item has country specific fields according to its emission country.
My schema ‘invoice_item’ is as follows (simplified):
schema "purchase_invoice_items" do
field(:quantity, :integer)
field(:unit_price, Money.Ecto.Type)
embeds_one(
:purchase_invoice_item_country_fields,
PurchaseInvoiceItemCountryFields,
on_replace: :update
)
belongs_to(:invoice, Invoice)
end
In country fields, there are different fields according to countries’ specific tax system.
In my invoice_item changeset, I have changesets for updating and creation. These changesets call for specific country changesets to cast changes for the embedded entity country_fields:
def create_changeset(attrs, %Invoice{} = invoice, %Org{} = org) do
%InvoiceItem{}
|> cast(attrs, [:quantity, :unit_price, :provider_product_id])
|> cast_creation_country_fields(invoice.country, attrs, org)
|> put_change(:invoice_id, invoice.id)
|> validate_required([
:quantity,
:unit_price,
:invoice_id,
:provider_product_id,
:purchase_invoice_item_country_fields
])
|> validate_number(:quantity, greater_than: 0)
|> validate_invoice_status(invoice, "pending")
end
@doc false
def update_changeset(%InvoiceItem{} = invoice_item, %Org{} = org, attrs) do
invoice_item
|> cast(attrs, [:quantity, :unit_price])
|> cast_update_country_fields(
Purchase.get_invoice!(invoice_item.invoice_id, org).country,
attrs,
org,
invoice_item
)
|> validate_required([:quantity, :unit_price, :purchase_invoice_item_country_fields])
|> validate_number(:quantity, greater_than: 0)
|> validate_invoice_status("pending")
end
Ok so, within my purchase_invoice_item_country_fields, I have my specific changeset for each country, where each country has its changeset for country specific field validation:
def brazil_changeset(%PurchaseInvoiceItemCountryFields{} = fields, attrs) do
|> cast(attrs, [
:codigo_prod_sistema,
:unidade,
:quantidade,
:valor_unitario
])
|> validate_required([
:codigo_prod_sistema,
:unidade,
:quantidade,
:valor_unitario
])
end
This changeset is called by cast_creation_country_fields and cast_update_country_fields on their upper changesets. I will show cast_creation_country_fields as an example of what I am trying to do when inferring some field values to others:
defp cast_creation_country_fields(%Ecto.Changeset{} = changeset, country, attrs, %Org{} = org)
when country == "Brazil" do
# attrs["provider_product_id"] will usually be nil on new action,
# but defined on create action
float_unit_price = normalize_unit_price(attrs["unit_price"], nil)
if attrs["provider_product_id"] && attrs["provider_product_id"] != "" do
provider_product =
Purchase.get_provider_product!(attrs["provider_product_id"], org, preload: [:product])
changeset
|> cast_embed(
:purchase_invoice_item_country_fields,
with: &PurchaseInvoiceItemCountryFields.brazil_changeset/2
)
|> put_embed(:purchase_invoice_item_country_fields, %{
quantidade: attrs["quantity"],
valor_unitario: float_unit_price,
codigo_prod_sistema: provider_product.product.id,
unidade: provider_product.product.measure_unit
})
else
changeset
|> cast_embed(
:purchase_invoice_item_country_fields,
with: &PurchaseInvoiceItemCountryFields.brazil_changeset/2
)
|> put_embed(:purchase_invoice_item_country_fields, %{
quantidade: attrs["quantity"],
valor_unitario: float_unit_price,
})
end
end
Problems I get when doing this include:
- Sometimes attrs are not loaded with any relevant values, thus yielding blank country fields and invalid changesets.
- Sometimes provider_product doesn’t seem to load properly (It will basically never load upon creation, only update, since a fresh invoice item won’t have any product tied to it) making the application rely on attrs and going back to the first problem)
- **Invoice_item unit_price field uses Money type (**Ecto.Money.Type). It seems like Money Type doesn’t work with embedded schemas (somebody correct me if I’m wrong, since it would solve a big part of my problem). This problem forces me to use integer as a representation for fields like ‘valor_unitario’ (unit_price in Brazil’s tax system). Sometimes it doesn’t parse appropriately to integer generating status 500.
I know it’s a lot to process, but thanks in advance to anyone who has ideas or suggestions!