In this application phoenix is used as an API, and the front end is React Redux. My first attempt was using FormData, which was not successful because apparently it’s only suited to POST requests. I’m trying to stick with CRUD here by uploading an image as part of a PATCH request using isomorphic-fetch. My next attempt gets me closer, by saving the file to a tmp location via URL.createObjectURl:
handleImageChange(e) {
e.preventDefault();
const reader = new FileReader();
const file = e.target.files[0];
const src = window.URL.createObjectURL(file)
reader.onloadend = () => {
this.setState({
imagePreviewUrl: reader.result
});
this.setState({
photo: {
filename: file.name,
content_type: file.type,
path: src
}
});
}
reader.readAsDataURL(file)
}
However, sending the temporary file path results in an invalid_file_path error in phoenix when attempting to save using iex:
Photo.store("blob:http://localhost:4000/7252ed69-bc36-4a1d-a7c5-ca8c0ae4ec5e")
# => {:error, :invalid_file_path}
The other method I’ve tried is modifying the changeset to use the photo_params, and cast_attachments in the model…
The params:
changes: %{photo: %{"content_type" => "image/jpeg",
"filename" => "welcome-bg.jpg",
"path" => "blob:http://localhost:4000/d31be00a-f88d-4a9f-b7d2-51f804726927"}},
errors: [], data: #Knitwhiz.Design<>, valid?: true>
Controller update action:
def update(conn, %{"id" => id, "design" => design_params, "photo" => photo_params}) do
design = Repo.get!(Design, id)
changeset = Design.changeset(design, design_params)
|> Ecto.Changeset.change
|> Ecto.Changeset.put_change(:photo, photo_params)
case Repo.update(changeset) do
{:ok, design} ->
render(conn, "show.json", design: design)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(Knitwhiz.ChangesetView, "error.json", changeset: changeset)
end
end
The model:
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:name :special_instructions])
|> cast_attachments(params, [:photo])
|> validate_required([:name])
end
The error (for this case!):
[error] #PID<0.4409.0> running Knitwhiz.Endpoint terminated
Server: localhost:4000 (http)
Request: PUT /api/v1/designs/1
** (exit) an exception was raised:
** (FunctionClauseError) no function clause matching in Arc.Ecto.Type.dump/2
(arc_ecto) lib/arc_ecto/type.ex:39: Arc.Ecto.Type.dump(Knitwhiz.Photo, %{"content_type" => "image/jpeg", "filename" => "welcome-bg.jpg", "path" => "blob:http://localhost:4000/d31be00a-f88d-4a9f-b7d2-51f804726927"})
(ecto) lib/ecto/type.ex:662: Ecto.Type.process_dumpers/3
(ecto) lib/ecto/repo/schema.ex:687: Ecto.Repo.Schema.dump_field!/6
(ecto) lib/ecto/repo/schema.ex:700: anonymous fn/6 in Ecto.Repo.Schema.dump_fields!/5
(stdlib) lists.erl:1263: :lists.foldl/3
(ecto) lib/ecto/repo/schema.ex:698: Ecto.Repo.Schema.dump_fields!/5
(ecto) lib/ecto/repo/schema.ex:647: Ecto.Repo.Schema.dump_changes!/6
(ecto) lib/ecto/repo/schema.ex:258: anonymous fn/13 in Ecto.Repo.Schema.do_update/4
(ecto) lib/ecto/repo/schema.ex:676: anonymous fn/3 in Ecto.Repo.Schema.wrap_in_transaction/6
(ecto) lib/ecto/adapters/sql.ex:615: anonymous fn/3 in Ecto.Adapters.SQL.do_transaction/3
(db_connection) lib/db_connection.ex:1274: DBConnection.transaction_run/4
(db_connection) lib/db_connection.ex:1198: DBConnection.run_begin/3
(db_connection) lib/db_connection.ex:789: DBConnection.transaction/3
(knitwhiz) web/controllers/api/v1/design_controller.ex:48: Knitwhiz.DesignController.update/2
(knitwhiz) web/controllers/api/v1/design_controller.ex:1: Knitwhiz.DesignController.action/2
(knitwhiz) web/controllers/api/v1/design_controller.ex:1: Knitwhiz.DesignController.phoenix_controller_pipeline/2
(knitwhiz) lib/knitwhiz/endpoint.ex:1: Knitwhiz.Endpoint.instrument/4
(knitwhiz) lib/phoenix/router.ex:261: Knitwhiz.Router.dispatch/2
(knitwhiz) web/router.ex:1: Knitwhiz.Router.do_call/2
(knitwhiz) lib/knitwhiz/endpoint.ex:1: Knitwhiz.Endpoint.phoenix_pipeline/1
I’m beginning to think it would just be simpler to load images directly to S3, without using arc or arc_ecto. Suggestions, comments, opinions?