Changing waffle filename implementation on the fly

Hi, I’m using waffle to upload files a s3 bucket. It’s been working well by I’ve decided I wan’t to change what names are used for the files. So i implement the filename/2 as follows:

  # Override the persisted filenames:
  def filename(version, {file, scope}) do
    file_name = Path.basename(file.file_name, Path.extname(file.file_name))
    "#{file_name}_#{version}_#{:os.system_time()}"
  end

But now all my already uploaded files have incorrect references to them. Anyone know of a good way to do this, I guess I could just compare the files updated_at timestamp to see if it happend before or after the updated filename definition but I get the feeling I’m missing a more elegant solution.

Thanks in advance, any suggestions are appreciated :slight_smile:

Not sure there’s an elegant solution per se. Maybe you should make a script that retroactively changes the filenames of all files who don’t conform to the new format yet.

You might be interested in this issue on Github: Support dynamic file naming.

Had the same problem recently, you can also make the change of filename by tweaking the changeset (see https://github.com/elixir-waffle/waffle_ecto/issues/13) However, Waffle automatically added file extension and I gave up with this solution. Using the underlying ExAws.S3 is actually not hard and gives more flexibility if you don’t need advanced Waffle features.

2 Likes

Thanks for your suggestions!
After banging my head against this trying to to find a safe and effective solution i decided to solve the problem outside of waffle. So in my controllers I now have a function that goes through the attachments in the parameters and adjusts the filenames in the different %Plug.Upload{}.

  def unique_attachments(params, options \\ []) do
    defaults = [
      field: "attachments"
    ]

    options = defaults |> Keyword.merge(options) |> Enum.into(%{})

    attachments = Map.get(params, options.field, %{})

    Enum.reduce(attachments, %{}, fn {key, value}, acc ->
      case Map.get(value, "file", nil) do
        nil ->
          Map.put(acc, key, value)

        file ->
          filename = Map.fetch!(file, :filename)
          unique_filename = "#{:os.system_time()}_#{filename}"
          file = Map.put(file, :filename, unique_filename)
          value = Map.put(value, "file", file)
          Map.put(acc, key, value)
      end
    end)
  end

It’s not elegant but it works and it does not risk corrupting my old existing attachments, since it will only apply to new files. :slightly_smiling_face:

1 Like