Replacing a map's key and value (and a couple of other questions)

A few noob questions here - sorry. (I am making my way through the Udemy course and I also bought the Programming Phoenix and Elixir books, but it would be satisfying if I could get this JSON outputting as intended in the meantime and understand how it’s done.)

Query example:

/api/banner_price?material_id=2&width=1000&height=2200

Controller:

defmodule AppName.Api.BannerPriceController do
  use AppName.Web, :controller

  alias AppName.Product.Material

  defp unit_mm(x) do x <> "mm" end

  defp calculate_price(price_sq_m, width, height) do
    Money.multiply(price_sq_m, width * height / 1000000)
  end

  def show(conn, %{"material_id" => material_id, "width" => width, "height" => height}) do
    material = Repo.get!(Material, material_id)
    |> Map.take([:id, :price_sq_m, :name])

    # |> Access.get_and_update()
    # put_in(material, [:price_sq_m], material.price_sq_m |> Money.to_string(symbol: true))

    price = calculate_price(material.price_sq_m, String.to_integer(width), String.to_integer(height))
    |> Money.to_string(symbol: true)

    dims = Enum.map([width, height], fn(x) -> unit_mm(x) end)

    render(conn, "banner_price.json", %{dims: dims, material: material, price: price})
  end
end

View:

defmodule AppName.Api.BannerPriceView do
  use AppName.Web, :view

  def render("banner_price.json", %{dims: dims, material: material, price: price}) do
    %{type: "banner prices", material: material, price: price, dimensions: dims}
  end
end

JSON result:

{
  "type": "banner prices",
  "price": "£23.10",
  "material": {
    "price_sq_m": {
      "currency": "GBP",
      "amount": 1050
    },
    "name": "440gsm PVC",
    "id": 2
  },
  "dimensions": [
    "1000mm",
    "2200mm"
  ]
}

My questions:

  1. Is using String.to_integer() like this an appropriate way to use number values in URL query parameters?
  2. How can I replace the price_sq_m part of the material part of the JSON with "Price per square metre": "£10.50"? (I believe the ‘£10.50’ itself can be achieved with Money.to_string(material.price_sq_m, symbol: true), but I’m struggling to replace the relevant key and value.)
  3. How can I wrap all of the JSON in a containing ‘data’ attribute/containing “node”? I think this will involve adding another render function to the view, but I’ve been failing hard when it comes to that.

(If you see any issues or improvements that can be made with any of the code I’d be glad to hear them, too, of course. :relaxed: )

Thanks.

That’ll work!

You’d need to modify material. Something like:

price = material.price_sq_m
  |> Kernel./(100)
  |> Money.to_string(symbol: true)

material = Map.drop(material, [:price_sq_m])
material = Map.put(material, :price, price) 

Maybe you can just update the existing render? Something like:

def render("banner_price.json", %{dims: dims, material: material, price: price}) do
  data = %{type: "banner prices", material: material, price: price, dimensions: dims}
  %{data: data} 
end
1 Like

Many thanks. All working now. :slight_smile:

# Format material price per square metre
material_price_sq_m_fmt = material.price_sq_m
|> format_money
material = Map.drop(material, [:price_sq_m])
|> Map.put("price per square metre", material_price_sq_m_fmt)