NatassiaTavares

NatassiaTavares

I’m testing the product_service for my API and I’m getting this error:

** (FunctionClauseError) no function clause matching in Ecto.Changeset.cast/4

when attempting to create or update an object.

Here’s the code:
product_service_test.exs

defmodule Warehouse.ProductServiceTest do
  use ExUnit.Case, async: true
  import Ecto.Query, warn: false
  import Morphix
  alias Warehouse.ProductService
  alias Warehouse.Repo

  setup do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Warehouse.Repo)
  end

  describe " ProductService.create_or_update_product " do
    test 'creates a product' do
      {:ok, atomized_product} = Morphix.atomorphiform(product())
      ProductService.create_or_update_product(atomized_product)
      assert Repo.one(from p in "products", select: count(p.id)) == 1
    end

    test 'updates a product' do
      {:ok, product_created} = ProductService.create_or_update_product(product())

      product_changed = Map.replace!(product(), :title, "new title")
      {:ok, product_updated} = ProductService.create_or_update_product(product_changed)
      
      assert product_updated.id == product_created.id
      assert product_updated.title == "new title"
    end
  end
end

product_service.ex

defmodule Warehouse.ProductService do
	alias Warehouse.ProductRepo
  alias Warehouse.Product

  def list_products(store_id) do
    ProductRepo.list_products(store_id)
  end

  def get_product!(id) do
    ProductRepo.get_product!(id)
  end

  def create_product(attrs \\ %{}) do
    ProductRepo.create_product(attrs)
  end

  def get_product_by_sku(sku, store_id) do
    ProductRepo.get_product_by_sku(sku, store_id)
  end

  def create_or_update_product(product_to_update \\ %{}) do
    product = get_product_by_external_code(product_to_update)
    if product != nil do
      valid_product_to_update = prepare_changeset(product_to_update)
      ProductRepo.update_product(product, valid_product_to_update)
    else
      ProductRepo.create_product(product_to_update)
    end
  end
  
  defp prepare_changeset(new_product) do
    new_product
    |> Map.delete(:sku)
    |> Map.delete(:gtin)
  end
end

product_repo.ex

defmodule Warehouse.ProductRepo do
  import Ecto.Query, warn: false
  alias Warehouse.Repo
  alias Warehouse.Product

  def list_products(store_id) do
    Repo.all from p in Product, preload: [:images, :videos, :listings], where: p.store_id == ^store_id
  end

  def get_product!(id) do 
    Repo.get!(Product, id)
    |> Repo.preload([:images, :videos, :provider])
  end

  def create_product(attrs \\ %{}) do
    %Product{}
    |> Product.changeset(attrs)
    |> Repo.insert()
  end

  def update_product(product, new_product) do    
    product
    |> Product.changeset(new_product)
    |> Repo.update()
  end
end

product.ex

defmodule Warehouse.Product do
  use Ecto.Schema
  import Ecto.Changeset
  import EctoEnum

  defenum ConditionEnum, :condition, [:new, :used, :refurbished]

  @serializables [:id, :store_id, :title, :condition, :price, :promotional_price, :available_quantity,
    :sku, :currency, :gtin, :short_description, :description, :height, 
    :lenght, :width, :weight, :condition, :provider_id]

  @derive { Jason.Encoder, only:  Enum.concat(@serializables, [:images, :listings]) }

  schema "products" do
    timestamps()

    belongs_to :provider, Warehouse.Provider
    
    has_many :images, Warehouse.Image, on_replace: :delete
    has_many :videos,  Warehouse.Video, on_replace: :delete
    has_many :listings, Warehouse.Listing, on_replace: :delete

    field :store_id, :integer
    field :title, :string
    field :price, :decimal
    field :promotional_price, :decimal
    field :available_quantity, :integer
    field :sku, :string
    field :currency, :string
    field :gtin, :string
    field :short_description, :string
    field :description, :string
    field :height, :integer
    field :lenght, :integer
    field :width, :integer
    field :weight, :integer
    field :condition, :string
  end

  @doc false
  def changeset(product, attrs \\ %{}) do
    product
    |> cast(attrs, @serializables)
    |> cast_assoc(:images)
    |> cast_assoc(:videos)
    |> cast_assoc(:listings)
    |> validate_required([:store_id])
  end
end

The full error message:

 1) test  ProductService.create_or_update_product  updates a product (Warehouse.ProductServiceTest)
     test/warehouse/product_service_test.exs:19
     ** (FunctionClauseError) no function clause matching in Ecto.Changeset.cast/4

     The following arguments were given to Ecto.Changeset.cast/4:

         # 1
         %{"available_quantity" => 2, "condition" => "new", "currency" => "BRL", "description" => "description", "gtin" => "s", "height" => 2, "images" => [%{"url" => "some_url"}], "lenght" => 2, "listings" => [%{"external_code" => "some_code", "sales_channel_id" => 1}], "price" => 2.3, "short_description" => "a", "sku" => "somesku", "store_id" => 3, "title" => "some title", "unity" => "KG", "videos" => [%{"url" => "some_url"}], "width" => 2}

         # 2
         %{"available_quantity" => 2, "condition" => "new", "currency" => "BRL", "description" => "description", "gtin" => "s", "height" => 2, "images" => [%{"url" => "some_url"}], "lenght" => 2, "listings" => [%{"external_code" => "some_code", "sales_channel_id" => 1}], "price" => 2.3, "short_description" => "a", "sku" => "somesku", "store_id" => 3, "title" => "some title", "unity" => "KG", "videos" => [%{"url" => "some_url"}], "width" => 2}

         # 3
         [:id, :store_id, :title, :condition, :price, :promotional_price, :available_quantity, :sku, :currency, :gtin, :short_description, :description, :height, :lenght, :width, :weight, :condition, :provider_id]

         # 4
         []

     Attempted function clauses (showing 5 out of 5):

         def cast(_data, %{__struct__: _} = params, _permitted, _opts)
         def cast({data, types}, params, permitted, opts) when is_map(data)
         def cast(%Ecto.Changeset{types: nil}, _params, _permitted, _opts)
         def cast(%Ecto.Changeset{changes: changes, data: data, types: types, empty_values: empty_values} = changeset, params, permitted, opts)
         def cast(%{__struct__: module} = data, params, permitted, opts)

     code: {:ok, product_created} = ProductService.create_or_update_product(product())
     stacktrace:
       (ecto) lib/ecto/changeset.ex:463: Ecto.Changeset.cast/4
       (warehouse) lib/warehouse/models/product.ex:43: Warehouse.Product.changeset/2
       (warehouse) lib/warehouse/repositories/product_repo.ex:24: Warehouse.ProductRepo.update_product/2
       test/warehouse/product_service_test.exs:20: (test)

Seems like Elixir is matching a Ecto.Changeset.cast/3 into a Ecto.Changeset.cast/4 adding an empty array as the 4th parameter. This is happening in every test with ecto, is working fine in the API, I have no idea what is causing this.

Showing Posts 1 to 1

idi527

idi527

:waving_hand:

Seems like Elixir is matching a Ecto.Changeset.cast/3 into a Ecto.Changeset.cast/4 adding an empty array as the 4th parameter. This is happening in every test with ecto, is working fine in the API, I have no idea what is causing this.

I think the problem is with the first argument, actually. It should probably be a product struct instead of plain map in your case.

What does get_product_by_external_code/1 return? Since ProductRepo.update_product(product, valid_product_to_update) since to be getting two maps according to the stacktrace?

— All posts loaded —

Where Next? Top

Trending in Questions Top

water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Alvinkariuki
How Can I Optimise Compile Time Dependencies I have been building an elixir application for about 2 years now. Many modules and files ha...
New

Other Trending Topics Top

budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews