Phoenix error when using decimal fields in forms - how to fix?

I have been trying to figure this out for a few hours now.

The problem
When i use a decimal field in a phoenix for, i see this message
“lists in Phoenix.HTML and templates may only contain integers representing bytes, binaries or other lists, got invalid entry: #Decimal<12>”

I have a an ecto query that returns a decimal field. The query looks like this

    query = from o in OrderSupplies, 
            join: s in Supply, where: s.id == o.supply_id,                    
            select: [ o.id, s.product_name, o.supply_id, o.qty_ordered, o.price], 
            where: o.order_id == ^id, 
            order_by: [desc: o.inserted_at]                

o.price is the decimal field. The data base is mysql.

I am trying to display the price field in an index page in this way

<%= supply.price)) %>

I also tried these two variations. It didn’t work :frowning:
<%

<%= Decimal.to_string(Decimal.new(supply.price)) %> %>
<% <%= Float.to_string(supply.price) %> %>

I am not sure what else to try. Can anyone see what i am doing wrong?

Thanks

The to string should work but why do you have two pairs of <% around it? A single one with the equal sign should be sufficient…

If removing the outer ones does not help, please give us a bit more context, your error message tells something about “for” which I do not see in your snippets.

It looks like you are trying to interpolate a Decimal struct from the Decimal module, but phoenix is saying that you can only interpolate “bytes, binaries, or other lists”.

I’m assuming it is the Decimal module, in which case it implements the to_string protocol , so you should be able to do:

<%= to_string supply.price %>

EDIT: I just realized that your Decimal.to_string(Decimal.new(supply.price)) should effectively be the same thing, so maybe that isn’t it.

EDIT2: The error specifically says “lists in Phoenix.HTML and templates”, so maybe your query returned a list with a single item. You might try <%= supply.price |> Lists.first |> to_string %> and see if that works. If that does then you probably want to extract the item from the lists before passing it to the template, just to keep the template concerned with only printing values.

@NobbZ: The extra <% was a typo, i missed removing it when i created the post. Same with this line just below it… “<% <%= Float.to_string(supply.price) %> %>” i forgot to remove it as well. Sorry for the confusion.

@kylethebaker: You are correct, the column value returned from the ecto query seems to be a decimal structure. And no, this did not work either ‘<%= supply.price |> Lists.first |> to_string %>’

Here is some more context: I am trying to display a money value in a phoenix form with a value from the database which happens to be a decimal. So it appears Ecto is doing what it is supposed to do.

Here is gist showing the relevant code - https://gist.github.com/skota/65eeeff0eec38622996dda57021ae9f2

I googled again after i posted this question and see that the general wisdom is to use a bigint instead of decimal and store currency as cents. So perhaps i should switch to using bigint? Your thoughts?

Thanks

Ha! theres my for. Where does @order_supplies come from? Is this really a list or an Ecto-Result?

Opps…just updated the gist with the controller method that creates @order_supplies. Yes this is an ecto result

thanks

EDIT
Here is a screenshot showing ecto result from iex

I had a typo in my reply, it should be List.first instead of Lists.first. Ecto.all() returns a list, and the error message indicates that it is trying to interpolate of a list of Decimal schemas. Give this another shot now that it’s corrected:

<%= order_supply.price |> List.first |> to_string %>

Okay, your problem is not the stringification of the Decimal in the table. Its your single line of <%= @orders_supplies %>, removing it or properly converting it into a string (inspect as a start?) should be sufficient.


Explanation:

You gave it a list, simplified: [[64, "String", #Decimal<12>]], which is recursively tried to get interpreted as io_data. The number 64 is interpreted as Unicode (which represents the @), the string is interpreted directly, and Decimal<12> fails.


PS: Also instead of pasting screenshots, it would be nice if you could just copy and paste the output. Its much nicer to read, as it adheres to the colorschema of the board and my user-CSS, I had a hard time to actually read the screenshot as the colors are not what I consider an optimum.

2 Likes

Thanks @kylethebaker and @NobbZ

So were you able to solve this? Whom of us was right?

@nobbz, sorry no i still don’t have it working, but your explanation made sense and now i am trying to figure how to convert this struct to a string.

i haven’t used elixir of long, so it is possible that there are simpler methods to stringify a decimal struct i am not aware of yet…:frowning:

@kylethebaker: no luck with this as well - <%= order_supply.price |> List.first |> to_string %>

I think @NobbZ is right, but I think I’m seeing another error here. In the ecto query you are selecting into a list, but what you really want is an OrderSupply struct I think. You can see both in the code and the screenshot that it’s returning a nested list:

 select: [ o.id, s.product_name, o.supply_id, o.qty_ordered, o.price]

So inside of the for, order_supply is actually a list and not a struct, which won’t work. I think it should be:

 select: %{ o.id, s.product_name, o.supply_id, o.qty_ordered, o.price }

And then you probably want:

Repo.all(query) |> Enum.map(fn(order_supply) -> struct(OrderSupplies, order_supply) end)

Have you removed the line I mentioned for a test at least?

Yes i did…didn’t seem to work…
<% for order_supply <- @orders_supplies do %>

To make sure we are on the same table. I’m talking about line 75 of your gist, rev2. Which line did you remove?

I removed line 75, I added as a debug step.

and i changed line 78 to this …<% for order_supply <- @orders_supplies do %>

And i now see this error - Argument error

“Called with 3 arguments
[37, “test product”, 33, 20, #Decimal<12>]
:product_name
[]”

@kylethebaker: Is this line converting the list to a struct?
Enum.map(fn(order_supply) -> struct(OrderSupplies, order_supply) end)

Yeah, thats because of the list vs struct thing @kylethebaker mentioned.

Yes, but you first need to change your query so that Ecto returns the results inside of a map instead of a list. Notice the %{} instead of []:

select: %{ o.id, s.product_name, o.supply_id, o.qty_ordered, o.price }

Then you pass your query to Repo.all() which gives you a list of maps. Then you need to convert each one of those maps to your OrderSupply struct. Enum.map is one way to do that (calling struct(module, map) will create a struct from a map for that module).

query 
|> Repo.all
|> Enum.map(fn(order_supply) -> struct(OrderSupplies, order_supply) end)

You can also do it this way which I find more succinct:

for order <- Repo.all(query), do: struct(OrderSupplies, order)

I’m using OrderSupplies here for the module name, but you may need to alias it or use the fully qualified name so that its in scope.

@kylethebaker i tried this by changing the ecto select clause to a map.
select: %{ o.id, s.product_name, o.supply_id, o.qty_ordered, o.price},

When i started mix i see a compilation error with this message

** (FunctionClauseError) no function clause matching in anonymous fn/2 in Ecto.Query.Builder.Select.escape_pairs/4

The following arguments were given to anonymous fn/2 in Ecto.Query.Builder.Select.escape_pairs/4:

    # 1
    {{:., [line: 335], [{:o, [line: 335], nil}, :id]}, [line: 335], []}

    # 2
    {%{}, %{}}

lib/ecto/query/builder/select.ex:86: anonymous fn/2 in Ecto.Query.Builder.Select.escape_pairs/4

so i guess i need to learn more about ecto queries and maps to make your example work for me. I decided to use this as a learning exercise for the weekend and for now switch from using decimal to bigint.

Many thanks to you and @nobbz for being patient and super helpful. I learned something new today about ecto and elixir maps.

cheers

1 Like