Noob question: How might this code be improved?

Hi, I am teaching myself FP by porting a Rails 6 shopping app to Phoenix. Loving it so far!

I am muddling my way through the coding of an action to transform a list of products from the database, by adding a field (quantity added to the shopping cart) to each product.

Here’s what I’ve come up with so far:

  def show(conn, _params) do

    # TODO: create or find a 'cart'

    products = Products.list_products() |> Enum.map(&product_with_quantity(cart, &1))

    render(conn, "show.html", products: products, body_class: body_class)
  end

  defp product_with_quantity(%Carts.Cart{} = cart, %Products.Product{} = product) do
    Product.as_map(product)
    |> Map.put(:quantity_in_cart, Carts.count_cart_product_items(cart, product))
  end

You’ll see that I have some code that converts a product’s Ecto struct into a map so that the cart quantity can be put into it. ‘as_map’ is a function in the Product schema module, which simply converts a struct to a map.

Ideally, I would like to do the mapping through a pipeline step and avoid the need for the private function, along the lines of:

Products.list_products()
|> Product.as_map()
|> Map.put(:quantity_in_cart, Carts.count_cart_product_items(cart, &1)

But this approach doesn’t seem to be possible due to my context function ‘count_cart_product_items’ expecting to receive a product struct, not a map. I realise that I could change the context function to expect a map instead of a struct but this would be inconsistent with the other context functions.

Would appreciate thoughts on how I might improve this code.

Thanks!
Lee.

1 Like

Hi, and welcome!

not sure I completely understand, but I would not touch/merge the data - I would simplify and add the cart data, then in the view I would do a conditional

eg
render(conn, "show.html", products: products, body_class: body_class, cart: cart)

and in the view, something like: (change it to eex)

for product <- products do
  ..
  if y = Enum.find(cart, fn cart_item -> cart_item.product_id == product.id end), do: "prod #{product.id} in cart with id #{y.id}", else: "not in cart"
  ..
end
1 Like

Thanks!

The view is a React app responsible for listing all products along with cart summary information (total number of items in cart, total price, next delivery slot).

Each product in the listing has ‘Add to cart’ and ‘Remove from cart’ buttons and a number indicating how many times the product has been added to the cart.

As currently designed, the React app just expects to receive a list of products and for each product to include the quantity added to the cart, and this is why I set out to transform the product data in the controller before passing to React.

I wouldn’t change the product struct but maybe make a new map in the controller with

%{product: product, quantity: quantity}

And then in the view change the data structure to the structure that the frontend app is expecting

Thanks. I’ve changed the controller code to:

products = Products.list_products() |> Enum.map(& %{product: &1, quantity: Carts.count_cart_product_items(cart, &1)} )

I’ll just pass ‘products’ through to the React component and change the React code.

Appreciate your and outlog’s help with this.

2 Likes