alex.bour
alex.bour

Reputation: 2964

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.

Upvotes: 5

Views: 6470

Answers (2)

Earl Jenkins
Earl Jenkins

Reputation: 4851

I have just solved this problem (or something very similar) using the blob record associated with the attachment.

Since the blob is an ActiveStorage::Blob, which is derived from ActiveRecord::Base, you can use the "dirty" methods on it to see if it has been changed. e.g.

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

Depending on where in the lifecycle set_filename is called from, there may only be a need to check one of has_changes_to_save? or saved_changes?. Since, in your example, you're calling this in before_save, you would only need file_blob.has_changes_to_save?

Upvotes: 1

kasperite
kasperite

Reputation: 2478

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.

Upvotes: 5

Related Questions