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

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
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

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews