Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uploading to S3 while model is being created #66

Open
lekevicius opened this issue Feb 26, 2017 · 5 comments
Open

Uploading to S3 while model is being created #66

lekevicius opened this issue Feb 26, 2017 · 5 comments

Comments

@lekevicius
Copy link

lekevicius commented Feb 26, 2017

Apologies for using issues as a request for help, but hopefully someone can help me.

In the form, User model is created and avatar is uploaded at the same time. It means that Avatar is uploaded before User is created and has an id. I would like to give a photo UUID (rename from original filename my_photo.jpg to UUID.jpg) and persist that, instead of original file_name, like arc_ecto does by default.

What should I do to rename uploaded file to UUID and persist that to a database (instead of original filename)?

@andreapavoni
Copy link

Your question is a bit too generic because you didn't specified if you are trying to store files in dedicated folders and/or you're going to generate several versions of the image.

As a hint, you can do this:

  • generate a UUID in changeset and append into a dedicated field in your schema (say uuid), this will be saved within your model. Be sure to do this before calling cast_attachments, otherwise the scope will have uuid set to nil:
def changeset(struct, params \\ %{}) do
  struct
  |> cast(...)
  |> put_change(:uuid, UUID.uuid4())
  |> cast_attachments(...)
  |> ...
end
  • override the function filename in your definition file (aka uploader), I suppose you called it Avatar, like in the examples provided in README. Then something like this should work:
def filename(_version, {_file, scope}) do
  "#{scope.uuid}"
end

if you also want different versions, then modify the function like this:

def filename(version, {_file, scope}) do
  "{scope.uuid}_#{version}"
end

hope this helps ;-)

@lekevicius
Copy link
Author

Here's the final solution that satisfied me - works without adding additional field to schema, and instead renames uploaded filename and persists that to database:

Person model:

def changeset(struct, params \\ %{}) do
  params = if params["photo"] do
    %{ params | "photo" => %Plug.Upload{ params["photo"] | filename: UUID.uuid4() <> Path.extname(params["photo"].filename) } }
  else
    param
  end

  struct
  |> cast(params, [:first_name, :last_name])
  |> cast_attachments(params, [:photo])
end

Photo uploader:

def filename(version, {file, _scope}) do
  "#{Path.rootname(file.file_name)}_#{version}"
end

Works really well: all versions are saved, useless original filename is not persisted, instead it renames it to UUID and deals with that normally.

@andreapavoni
Copy link

Does it work across non-valid forms? It looks like your UUID generation happens every time you call changeset. So in case you have to re-submit the form, you'll get different filenames from the first submit.
Another pain point is the storage_dir: in my use case, I want to namespace each upload (and its versions as well) under a unique/dedicated folder, this can't be done by only renaming filenames.

That's why I've used an additional field on schema.

For completeness, here's how I've implemented filename and storage_dir in my uploader definition:

...
def storage_dir(_, {_file, scope}) do
  "uploads/images/#{scope.uuid}"
end

def filename(_version, {file, _scope}) do
  "#{Path.rootname(file.file_name)}"
end
...

as you can see, I don't care about filenames, but I care to store file(s) under a unique folder.

@andreyk-code
Copy link

Believe this is duplicate of #15
There are more example workarounds on that thread

@andreapavoni
Copy link

@andreyk-code they're related because that's the same problem, but they don't solve it as well.

btw, I've forked arc_ecto to use a different approach/workaround. it's not perfect, but it's working well in production on 2 apps.

Here's the fork in case you're interested: https://github.com/FunkyStudioHQ/arc_ecto_ng

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants