Search code examples
elixirprotocolsphoenix-frameworkenumerable

protocol Enumerable not implemented for {:ok, ["Massimo Dutti", "Sneakers laag", "38", "Black", "99.95"]} of type Tuple


for a schoolproject we have to make a webshop in elixir that can create new products by uploading a csv file. We tried implementing this by following How to import users from csv file with elixir/phoenix? but we always get an error (see title)
Can anybody help us out? Here is our code:
Form

<%= form_for @changeset, @action, [multipart: true], fn f -> %>
 <div class="form-group">
  <label>File</label>
  <%= file_input f, :file, class: "form-control" %>
 </div>

 <div class="form-group">
  <%= submit "Submit", class: "btn btn-primary" %>
 </div>
<% end %>

Render

<%= render "bulkform.html", changeset: @changeset, action: Routes.product_path(@conn, :createBulk) %>

Routes

post "/productsBulk", ProductController, :createBulk

Schema

schema "products" do
 field :color, :string
 field :size, :string
 field :description, :string
 field :price, :decimal
 field :title, :string

 timestamps()
end

@doc false
def changeset(product, attrs) do
product
 |> cast(attrs, [:title, :description, :size, :color, :price, :stock])
 |> validate_required([:title, :description, :size, :color, :price, :stock])
 |> unique_constraint(:title, name: :unique_products_index, message:
 "Title already in use.")
end

Controller

def createBulk(conn, %{"product" => product_params}) do
 product_params["file"].path
 |> File.stream!()
 |> CSV.decode
 |> Enum.each(fn(product) -> Product.changeset(%Product{}, %{title: Enum.at(product, 0), description: 
 Enum.at(product, 1), size: Enum.at(product, 2), color: Enum.at(product, 3), price: Enum.at(product, 4)})
 |> Repo.insert() end)
 conn
 |> put_flash(:info, "Imported")
 |> redirect(to: Routes.product_path(conn, :overview))
end

Solution

  • It would be more helpful if you included your error message in its entirety, and if you could show us exactly the lines that were mentioned in the error message.

    However, as Aleksei has already pointed out, I suspect your problem is that you are passing a tuple to Enum.each/1 when you should be passing a list. In other words:

    Enum.each({:ok, [1, 2, 3]}, fn x -> IO.puts(x) end)
    ** (Protocol.UndefinedError) protocol Enumerable not implemented for ...
    

    whereas this works fine:

    Enum.each([1, 2, 3], fn x -> IO.puts(x) end)
    

    So the output from CSV.decode cannot be piped directly into an Enum function because it returns a tuple, but using CSV.decode! instead should return the raw list and your code should work.