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

Generate random file name #85

Open
Linuus opened this issue Jun 28, 2016 · 5 comments
Open

Generate random file name #85

Linuus opened this issue Jun 28, 2016 · 5 comments

Comments

@Linuus
Copy link

Linuus commented Jun 28, 2016

Hi!

My model can have multiple images so I need the filenames to be unique, is there any way to do this? I have tried to do it in my changeset function but I can't make it work since cast_attachment uploads the image directly.

Any ideas?

@Linuus
Copy link
Author

Linuus commented Jun 28, 2016

What I ended up doing was this:

  def changeset(struct, params \\ %{}) do
    params = put_random_filename(params)

    struct
    |> cast_attachments(params, [:image])
    |> validate_required([:image])
  end

  defp put_random_filename(%{"image" => %Plug.Upload{filename: name} = image} = params) do
    image = %Plug.Upload{image | filename: random_filename(name)}
    %{params | "image" => image}
  end
  defp put_random_filename(params), do: params

  defp random_filename(name) do
    (:crypto.strong_rand_bytes(20) |> Base.url_encode64 |> binary_part(0, 20)) <> name
  end

... which doesn't seem very pretty :)

Any tips suggestions on how to make it better is much appreciated.

@mrluc
Copy link

mrluc commented Aug 2, 2016

I did something similar, and like you I also felt as though changeset functions were the only logical place for this.

I had a good number of models with this requirement, so I made a helper module with one publicly accessible function, prepend_filenames(params, [field | rest]), and then called it where you're calling it. The only difference is that I limited myself to prepending a unix timestamp, like DateTime.utc_now |> DateTime.to_unix |> Integer.to_string

I also had to add a bit of support for detecting when a file should be deleted due to an update, which also needed to be used from the changeset function:

  def delete_overwritten_files!( kind, entity, changeset ) do
    changeset.changes
    |> Enum.filter( fn({k,_v}) -> k in kind.arc_fields end )
    |> Enum.each( fn({k,_v}) ->
      arc_def = get_arc_definition(kind, k)
      delete_one_attachment( arc_def, entity, Map.get(entity,k) )
    end)
    changeset
  end

@jakecraige
Copy link

I'd be awesome to see this supported by Arc someday. I just attempted this since I wasn't going to have an ID to use and it took awhile to figure out it wouldn't work.

@makeitrein
Copy link

makeitrein commented Aug 13, 2018

Thx for the code @Linuus!

My first attempt at solving this problem was modifying the filename method in my arc definition file. As I soon discovered, filename is called not just on insertion of new files but on retrieval as well, making it a poor fit for a randomizer.

Further, relying on existing schema properties within the filename method works well for existing models, but breaks when working with new, dataless models.

Additionally, editing the filename method disrupts all prior file upload paths.

The code above seems to be the best pattern for preventing file collisions on both new and existing models from my (limited) research.

@Yamilquery
Copy link

Yamilquery commented Apr 8, 2019

Hey, I just need to implement something to delete overwritten files, something like @mrluc said. But I can't figure out how to achieve that goal, @mrluc could you put a more detailed example, please?

Thank you!

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

5 participants