Hi all!
Trying to follow along with this and hitting an error I cant figure out. When we first get to testing %Plug.Upload{}
in IEx
after implementing the create_upload_from_plug_upload
fn, I’m getting a function clause error.
If I understand the logs, apparently I’m giving a nil
value to Upload.local_path()
. I’m not sure how to rectify this, as I can’t figure out how I’m giving a nil
value. Is my upload.id
value nil
for File.cp/2
? I don’t understand how if so.
I’ve followed the tutorial up to this point without any issues and my code is identical. Thanks for any help!
Here is the full error -
<code>iex(3)> Documents.create_upload_from_plug_upload(upload)
[debug] QUERY OK db=13.9ms idle=487.9ms
begin []
↳ :erl_eval.do_apply/7, at: erl_eval.erl:744
[debug] QUERY OK db=2.9ms
INSERT INTO "uploads" ("content_type","filename","hash","size","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5,$6) RETURNING "id" ["image/png", "phoenix.png", "07aa9b01595fe10fd4e5ceb6cc67ba186ef8ce91e5a5cba47166f7d8498d7852", 13900, ~N[2022-11-16 07:26:37], ~N[2022-11-16 07:26:37]]
↳ anonymous fn/4 in Woof.Documents.create_upload_from_plug_upload/1, at: lib/woof/documents.ex:25
[debug] QUERY OK db=0.3ms
rollback []
↳ :erl_eval.do_apply/7, at: erl_eval.erl:744
** (FunctionClauseError) no function clause matching in IO.chardata_to_string/1
The following arguments were given to IO.chardata_to_string/1:
# 1
nil
Attempted function clauses (showing 2 out of 2):
def chardata_to_string(string) when is_binary(string)
def chardata_to_string(list) when is_list(list)
(elixir 1.13.4) IO.chardata_to_string/1
(elixir 1.13.4) lib/path.ex:538: Path.join/2
(elixir 1.13.4) lib/path.ex:509: Path.join/1
(woof 0.1.0) lib/woof/documents.ex:26: anonymous fn/4 in Woof.Documents.create_upload_from_plug_upload/1
(ecto_sql 3.9.0) lib/ecto/adapters/sql.ex:1190: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
(db_connection 2.4.2) lib/db_connection.ex:1562: DBConnection.run_transaction/4</code>
And my relevant code-
lib.woof.documents.ex
defmodule Woof.Documents do
import Ecto.Query, warn: false
alias Woof.Repo
alias Woof.Documents.Upload
def create_upload_from_plug_upload(%Plug.Upload{
filename: filename,
path: tmp_path,
content_type: content_type
}) do
hash =
File.stream!(tmp_path, [], 2048)
|> Upload.sha256()
with {:ok, %File.Stat{size: size}} <- File.stat(tmp_path),
{:ok, upload} <-
%Upload{}
|> Upload.changeset(%{
filename: filename,
content_type: content_type,
hash: hash,
size: size
})
|> Repo.insert(),
:ok <-
File.cp(
tmp_path,
Upload.local_path(upload.id, filename)
) do
{:ok, upload}
else
{:error, reason} = error -> error
end
end
end
lib.woof.documents.upload.ex
defmodule Woof.Documents.Upload do
use Ecto.Schema
import Ecto.Changeset
schema "uploads" do
field :content_type, :string
field :filename, :string
field :hash, :string
field :size, :integer
timestamps()
end
@doc false
def changeset(upload, attrs) do
upload
|> cast(attrs, [:filename, :size, :content_type, :hash])
|> validate_required([:filename, :size, :content_type, :hash])
|> validate_number(:size, greater_than: 0)
|> validate_length(:hash, is: 64)
end
def sha256(chunks_enum) do
chunks_enum
|> Enum.reduce(
:crypto.hash_init(:sha256),
&:crypto.hash_update(&2, &1)
)
|> :crypto.hash_final()
|> Base.encode16()
|> String.downcase()
end
def upload_directory do
Application.get_env(:woof, :uploads_directory)
end
def local_path(id, filename) do
[upload_directory(), "#{id}-#{filename}"]
|> Path.join()
end
end