Hello folks! I am trying to upload multiple files at once using Arc. I face such error: protocol Enumerable not implemented for %Plug.Upload
when i try to create a product with attached file/s. Any idea what I am doing wrong here?
My schemas:
schema "products" do
field :colour, :string
field :description, :string
field :name, :string
field :price, :decimal
field :product_code, :string
field :release_year, :integer
belongs_to :category, Jordaniva.Categories.Category
belongs_to :subcategory, Jordaniva.Categories.Subcategory
belongs_to :type, Jordaniva.Inventory.Type
has_many :items, Jordaniva.Inventory.Item
has_many :photos, Jordaniva.Gallery.Photo
timestamps()
end
@doc false
def changeset(struct, attrs \\ %{}) do
struct
|> cast(attrs, [:name, :description, :price, :colour, :product_code, :release_year, :category_id, :subcategory_id, :type_id])
|> cast_assoc(:items)
|> cast_assoc(:photos)
|> validate_required([:name, :description, :price, :colour, :product_code, :release_year, :category_id, :subcategory_id, :type_id])
end
schema "photos" do
field :photo, Jordaniva.Photo.Type
field :uuid, :string
belongs_to :product, Jordaniva.Inventory.Product
timestamps()
end
@doc false
def changeset(photo, attrs) do
photo
|> Map.update(:uuid, Ecto.UUID.generate, fn val -> val || Ecto.UUID.generate end)
|> cast_attachments(attrs, [:photo])
|> validate_required([:photo])
end
Product controller (new/create):
def new(conn, _params) do
changeset = Product.changeset(%Product{items: [%Item{}, %Item{}]})
categories = Repo.all(Category) |> Enum.map(&{&1.name, &1.id})
subcategories = Repo.all(Subcategory) |> Enum.map(&{&1.name, &1.id})
render(conn, "new.html", changeset: changeset, categories: categories, subcategories: subcategories)
end
def create(conn, %{"photos" => product_params}) do
case Inventory.create_product(product_params) do
{:ok, product} ->
conn
|> put_flash(:info, "Product created successfully.")
|> redirect(to: Routes.product_path(conn, :show, product))
{:error, %Ecto.Changeset{} = changeset} ->
categories = Repo.all(Category) |> Enum.map(&{&1.name, &1.id})
subcategories = Repo.all(Subcategory) |> Enum.map(&{&1.name, &1.id})
render(conn, "new.html", changeset: changeset, categories: categories, subcategories: subcategories)
end
end
Photo controller:
def new(conn, _params) do
changeset = Gallery.change_photo(%Photo{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"product_id" => product_id, "photo" => photo_params}) do
product = Jordaniva.Inventory.get_product!(product_id)
case Gallery.create_photo(photo_params) do
{:ok, _photo} ->
conn
|> put_flash(:info, "Photo created successfully.")
|> redirect(to: Routes.photo_path(conn, :index))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
Gallery.ex
def create_photo(%Product{} = product, attrs \\ %{}) do
%Photo{}
|> Photo.changeset(attrs)
|> Ecto.Changeset.put_assoc(:product, product)
|> Repo.insert()
end
def create_photos(%Product{} = product, attrs \\ %{}) do
Enum.each attrs["photos"], fn p ->
create_photo(product, %{"photo" => p})
end
end
end
Uploaders/photo.ex
defmodule Jordaniva.Photo do
use Arc.Definition
use Arc.Ecto.Definition
@extension_whitelist ~w(.jpg .jpeg .gif .png)
@versions [:primary]
@acl :public_read
def default_url(:primary, _movie) do
"http://placehold.it/350x200"
end
# To add a thumbnail version:
@versions [:original, :thumb]
# Whitelist file extensions:
def validate({file, _}) do
file_extension = file.file_name |> Path.extname() |> String.downcase()
Enum.member?(@extension_whitelist, file_extension)
end
# Define a thumbnail transformation:
def transform(:thumb, _) do
{:convert, "-strip -thumbnail 150x150^ -gravity center -extent 150x150"}
end
# Override the persisted filenames:
def filename(version, {file, scope}) do
"#{scope.uuid}_#{version}"
end
# Override the storage directory:
def storage_dir(version, {file, scope}) do
"uploads/photos/"
end
end
And full error:
[info] POST /products
[debug] Processing with JordanivaWeb.ProductController.create/2
Parameters: %{"_csrf_token" => "DQAuSi4VWwZsAgQxSltSVwYCMSkSTnR2_b_ggd30Us4dz-jesRTyb897", "photos" => %{"category_id" => "1", "colour" => "test66", "description" => "test66", "name" => "test66", "photos" => [%Plug.Upload{content_type: "image/png", filename: "Zrzut ekranu 2020-07-20 o 22.46.06.png", path: "/var/folders/1t/q27498654m376jz6fl02fqd80000gn/T//plug-1597/multipart-1597933231-847704270337599-3"}], "price" => "666", "product_code" => "test66", "release_year" => "2004", "subcategory_id" => "1", "type_id" => "1"}}
Pipelines: [:browser]
[debug] QUERY OK source="types" db=0.3ms idle=1764.5ms
SELECT t0."id", t0."name", t0."inserted_at", t0."updated_at" FROM "types" AS t0 ORDER BY t0."name" []
[info] Sent 500 in 32ms
[error] #PID<0.1214.0> running JordanivaWeb.Endpoint (connection #PID<0.1213.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: POST /products
** (exit) an exception was raised:
** (Protocol.UndefinedError) protocol Enumerable not implemented for %Plug.Upload{content_type: "image/png", filename: "Zrzut ekranu 2020-07-20 o 22.46.06.png", path: "/var/folders/1t/q27498654m376jz6fl02fqd80000gn/T//plug-1597/multipart-1597933231-847704270337599-3"} of type Plug.Upload (a struct). This protocol is implemented for the following type(s): Ecto.Adapters.SQL.Stream, Postgrex.Stream, DBConnection.Stream, DBConnection.PrepareStream, HashSet, Range, Map, Function, List, Stream, Date.Range, HashDict, GenEvent.Stream, MapSet, File.Stream, IO.Stream
(elixir 1.10.4) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir 1.10.4) lib/enum.ex:141: Enumerable.reduce/3
(elixir 1.10.4) lib/enum.ex:3383: Enum.reduce/3
(arc_ecto 0.11.3) lib/arc_ecto/schema.ex:58: Arc.Ecto.Schema.convert_params_to_binary/1
(jordaniva 0.1.0) lib/jordaniva/gallery/photo.ex:19: Jordaniva.Gallery.Photo.changeset/2
(ecto 3.4.5) lib/ecto/changeset.ex:829: anonymous fn/4 in Ecto.Changeset.on_cast_default/2
(ecto 3.4.5) lib/ecto/changeset/relation.ex:128: Ecto.Changeset.Relation.do_cast/6
(ecto 3.4.5) lib/ecto/changeset/relation.ex:338: Ecto.Changeset.Relation.map_changes/9
(ecto 3.4.5) lib/ecto/changeset/relation.ex:112: Ecto.Changeset.Relation.cast/5
(ecto 3.4.5) lib/ecto/changeset.ex:800: Ecto.Changeset.cast_relation/4
(jordaniva 0.1.0) lib/jordaniva/inventory/product.ex:29: Jordaniva.Inventory.Product.changeset/2
(jordaniva 0.1.0) lib/jordaniva/inventory.ex:67: Jordaniva.Inventory.create_product/1
(jordaniva 0.1.0) lib/jordaniva_web/controllers/product_controller.ex:28: JordanivaWeb.ProductController.create/2
(jordaniva 0.1.0) lib/jordaniva_web/controllers/product_controller.ex:1: JordanivaWeb.ProductController.action/2
(jordaniva 0.1.0) lib/jordaniva_web/controllers/product_controller.ex:1: JordanivaWeb.ProductController.phoenix_controller_pipeline/2
(phoenix 1.5.4) lib/phoenix/router.ex:352: Phoenix.Router.__call__/2
(jordaniva 0.1.0) lib/jordaniva_web/endpoint.ex:1: JordanivaWeb.Endpoint.plug_builder_call/2
(jordaniva 0.1.0) lib/plug/debugger.ex:132: JordanivaWeb.Endpoint."call (overridable 3)"/2
(jordaniva 0.1.0) lib/jordaniva_web/endpoint.ex:1: JordanivaWeb.Endpoint.call/2
(phoenix 1.5.4) lib/phoenix/endpoint/cowboy2_handler.ex:65: Phoenix.Endpoint.Cowboy2Handler.init/4