Search code examples
ruby-on-railsrails-activestorage

How to test if a new file is sent with Active Storage in the model?


In my Rails model, I have this code to force the filename to be changed when uploading:

before_save :set_filename  
  def set_filename
    if file.attached?
      self.file.blob.update(filename: "#{new_file_name()}.#{self.file.blob.content_type.split('/')[1]}")
    end
  end

The problem is the filename is changed even if a new file is not sent in the form (when editing).

My attachement is simply named file:

# active storage
  has_one_attached :file

How to really test that a new file is attached when uploading ?

Thanks,

EDIT: more clarifications

I have a form with a file_field. I want to test if a new file is sent via the form, when I add or modify the object of the form.

My model is called Image and the attached file is called file.

class Image
  has_one_attached :file
end

I want to change the filename every time a new file is sent via the form, and not of course is the file_field stays empty.


Solution

  • You can use new_record? to check if file is new ie:

      def set_filename
        if file.attached? && file.new_record?
          self.file.blob.update(filename: "#{new_file_name()}.#{self.file.blob.content_type.split('/')[1]}")
        end
      end
    

    Alternatively, use before_create instead of before_save so that set_name only runs when uploading new file.

    Updated

    Interestingly, ActiveStorage handles blob change outside model hooks. Apparently, it doesn't even support validation right now. There's no way to verify blob has changed as its state is not persisted anywhere. If you peek into rails log, notice rails purge old blob as soon as a new one is added.

    Few options I can think of:

    1.Update filename in controller eg:

    original_name = params[:file].original_name
    params[:file].original_name = # your logic goes here
    

    2.Store blob file name in parent model and compare in before_save.

      def set_filename
        if file.attached? && file.blob.filename != self.old_filename
          self.file.blob.update(filename: "#{new_file_name()}.#{self.file.blob.content_type.split('/')[1]}")
        end
      end
    

    None of these solutions are ideal but hope they give you some ideas.