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
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.